Skip to content

Add Pydantic models for configuration validation#510

Draft
jan-janssen wants to merge 12 commits intomainfrom
add-pydantic-validation-8214308980248924915
Draft

Add Pydantic models for configuration validation#510
jan-janssen wants to merge 12 commits intomainfrom
add-pydantic-validation-8214308980248924915

Conversation

@jan-janssen
Copy link
Copy Markdown
Member

@jan-janssen jan-janssen commented Mar 23, 2026

This change introduces Pydantic models to validate the configuration files (typically queue.yaml) used by pysqa.

Key changes:

  • QueueModel defines the schema for individual queue configurations, including optional fields for resource limits and submission scripts.
  • ConfigModel validates the top-level configuration structure.
  • QueueAdapterWithConfig now validates its input configuration upon initialization.
  • The ConfigDict(extra='allow') is used to maintain backward compatibility with custom template variables and adapter-specific settings.
  • Added pydantic as a core dependency in pyproject.toml.
  • Improved robustness of template loading by checking for the existence of the script field.
  • Comprehensive tests added to ensure validation works as expected and handles edge cases like missing fields or incorrect types.

PR created automatically by Jules for task 8214308980248924915 started by @jan-janssen

Summary by CodeRabbit

  • Chores

    • Pinned Pydantic in project and CI environment definitions.
  • New Features

    • Added optional runtime configuration validation that normalizes configs while retaining unknown/extra fields.
  • Bug Fixes

    • Template loading now skips absent/null script entries to avoid unintended file access.
  • Tests

    • Added unit tests covering validation behavior, error cases, and preservation of extra fields.

- Introduced `QueueModel` and `ConfigModel` in `pysqa/base/config.py` using Pydantic v2.
- Integrated validation into `QueueAdapterWithConfig` initialization.
- Added `pydantic>=2.0` to project dependencies.
- Updated `_load_templates` to handle queues without a submission script (e.g., remote queues).
- Added unit tests for configuration validation.

Co-authored-by: jan-janssen <3854739+jan-janssen@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Pydantic-based config models and a validate_config function, integrates validation into QueueAdapterWithConfig (fallback passthrough if Pydantic is unavailable), avoids reading template files when script is null, adds unit tests for validation behavior, and updates optional dependency / CI envs to include Pydantic.

Changes

Cohort / File(s) Summary
Dependency & CI envs
pyproject.toml, .ci_support/environment.yml, .ci_support/environment-docs.yml, .ci_support/environment-old.yml
Added Pydantic to optional dependencies and Conda/CI environment YAMLs (pinned versions).
Config models
src/pysqa/base/models.py
New Pydantic models: QueueModel, ConfigModel, and validate_config(config: dict) -> dict that validate/normalize config and allow extra fields.
Config integration
src/pysqa/base/config.py
Import validate_config with ImportError fallback; QueueAdapterWithConfig now stores validated config (self._config) and reads queue_type from it; _load_templates skips file reads when script is null.
Tests
tests/unit/base/test_config.py, tests/unit/base/test_models.py
Added tests covering missing queues, invalid field types, preservation of extra fields, and behavior when Pydantic is unavailable.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Caller
  participant QA as QueueAdapterWithConfig
  participant Validator as validate_config
  participant FS as FileSystem

  Caller->>QA: instantiate with raw config
  QA->>Validator: call validate_config(config)
  alt pydantic available
    Validator-->>QA: validated/normalized config
  else pydantic missing
    Validator-->>QA: passthrough config
  end
  QA->>QA: set self._config and determine queue_type
  loop per queue entry
    QA->>FS: if queue.script != null then open/read file
    FS-->>QA: file content
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled through the config vine,
Pydantic pruned each curious line,
Null scripts kept the burrow fine,
Extras stayed where they align,
Hop—validated queues, and carrots shine!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding Pydantic models for configuration validation, which is the primary objective reflected across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-pydantic-validation-8214308980248924915

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
pyproject.toml (1)

34-39: Inconsistent version pinning strategy.

Other runtime dependencies (jinja2, pandas, pyyaml) use exact version pinning (==), while pydantic>=2.0 uses a minimum version constraint. This is acceptable if intentional (to allow Pydantic v2 minor/patch updates), but consider documenting this choice or aligning with the existing pinning strategy for reproducibility.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` around lines 34 - 39, The dependencies list currently mixes
exact pins and a minimum constraint: update the entry for "pydantic>=2.0" to
match the project's pinning strategy or document the exception; specifically
either change the dependency in the dependencies array to an exact version
(e.g., "pydantic==2.x.y") to match jinja2/pandas/pyyaml, or add a brief comment
in the pyproject.toml (or project README) next to the dependencies list
explaining why pydantic is intentionally left as "pydantic>=2.0" to allow
minor/patch upgrades for the Pydantic package.
src/pysqa/base/config.py (2)

28-37: Consider validating queue_type against allowed values.

The queue_type field accepts any string, but valid values are constrained to "SGE", "TORQUE", "SLURM", "LSF", "MOAB", "FLUX", and "REMOTE" (per set_queue_adapter in queueadapter.py). Using Literal would catch invalid queue types earlier with a clearer error message.

💡 Suggested improvement
-from pydantic import BaseModel, ConfigDict
+from typing import Literal
+from pydantic import BaseModel, ConfigDict

+QUEUE_TYPES = Literal["SGE", "TORQUE", "SLURM", "LSF", "MOAB", "FLUX", "REMOTE"]

 class ConfigModel(BaseModel):
     model_config = ConfigDict(extra="allow")
-    queue_type: str
+    queue_type: QUEUE_TYPES
     queue_primary: Optional[str] = None
     queues: dict[str, QueueModel]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pysqa/base/config.py` around lines 28 - 37, The ConfigModel currently
allows any string for queue_type; change queue_type to a constrained literal
type to validate allowed values early (e.g., use typing.Literal or
typing_extensions.Literal and set queue_type:
Literal["SGE","TORQUE","SLURM","LSF","MOAB","FLUX","REMOTE"]). Update the
import(s) as needed and ensure ConfigModel (in src/pysqa/base/config.py) uses
this Literal so invalid values are caught by Pydantic; this aligns with the
allowed queue types enforced by set_queue_adapter in queueadapter.py.

338-350: Minor: Redundant key check after Pydantic validation.

After model_dump(), the "script" key will always exist in queue_dict (defaulting to None if not provided in the original config). The "script" in queue_dict check is now redundant and can be simplified.

✨ Simplified condition
 for queue_dict in queue_lst_dict.values():
-    if "script" in queue_dict and queue_dict["script"] is not None:
+    if queue_dict.get("script") is not None:
         with open(os.path.join(directory, queue_dict["script"])) as f:

Note: The current code is functionally correct and provides defensive coding if this method is ever called with unvalidated data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pysqa/base/config.py` around lines 338 - 350, The check for the presence
of the "script" key is redundant after Pydantic's model_dump(); update the loop
over queue_lst_dict so you only test for a non-None script value (e.g., replace
the current if "script" in queue_dict and queue_dict["script"] is not None: with
a single check for queue_dict["script"] is not None), then proceed to open
os.path.join(directory, queue_dict["script"]) and compile the Template and
handle TemplateSyntaxError as before (references: queue_lst_dict,
queue_dict["script"], Template, TemplateSyntaxError, model_dump()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pysqa/base/config.py`:
- Around line 103-106: The constructor currently calls ConfigModel(**config)
which raises pydantic.ValidationError on invalid input; wrap that call in a
try/except that catches pydantic.ValidationError and re-raises a ValueError
(including the original error message) before continuing to set self._config and
call super().__init__; reference the call to ConfigModel and the surrounding
code that assigns self._config and calls super().__init__ so the conversion
happens before queue_type is read.

---

Nitpick comments:
In `@pyproject.toml`:
- Around line 34-39: The dependencies list currently mixes exact pins and a
minimum constraint: update the entry for "pydantic>=2.0" to match the project's
pinning strategy or document the exception; specifically either change the
dependency in the dependencies array to an exact version (e.g.,
"pydantic==2.x.y") to match jinja2/pandas/pyyaml, or add a brief comment in the
pyproject.toml (or project README) next to the dependencies list explaining why
pydantic is intentionally left as "pydantic>=2.0" to allow minor/patch upgrades
for the Pydantic package.

In `@src/pysqa/base/config.py`:
- Around line 28-37: The ConfigModel currently allows any string for queue_type;
change queue_type to a constrained literal type to validate allowed values early
(e.g., use typing.Literal or typing_extensions.Literal and set queue_type:
Literal["SGE","TORQUE","SLURM","LSF","MOAB","FLUX","REMOTE"]). Update the
import(s) as needed and ensure ConfigModel (in src/pysqa/base/config.py) uses
this Literal so invalid values are caught by Pydantic; this aligns with the
allowed queue types enforced by set_queue_adapter in queueadapter.py.
- Around line 338-350: The check for the presence of the "script" key is
redundant after Pydantic's model_dump(); update the loop over queue_lst_dict so
you only test for a non-None script value (e.g., replace the current if "script"
in queue_dict and queue_dict["script"] is not None: with a single check for
queue_dict["script"] is not None), then proceed to open os.path.join(directory,
queue_dict["script"]) and compile the Template and handle TemplateSyntaxError as
before (references: queue_lst_dict, queue_dict["script"], Template,
TemplateSyntaxError, model_dump()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9676030-bfd4-4101-a875-249e1a5cd7f0

📥 Commits

Reviewing files that changed from the base of the PR and between 63ace34 and 443d16a.

📒 Files selected for processing (3)
  • pyproject.toml
  • src/pysqa/base/config.py
  • tests/unit/base/test_config.py

Comment on lines 103 to 106
self._config = ConfigModel(**config).model_dump()
super().__init__(
queue_type=config["queue_type"], execute_command=execute_command
queue_type=self._config["queue_type"], execute_command=execute_command
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Pydantic ValidationError is not converted to ValueError.

The unit tests expect ValueError for validation failures, but ConfigModel(**config) raises pydantic.ValidationError. Either wrap the validation in a try/except block to convert the exception, or update the tests to catch ValidationError.

🔧 Option 1: Wrap validation to raise ValueError
+from pydantic import BaseModel, ConfigDict, ValidationError
 ...
 def __init__(
     self,
     config: dict,
     directory: str = "~/.queues",
     execute_command: Callable = execute_command,
 ):
-    self._config = ConfigModel(**config).model_dump()
+    try:
+        self._config = ConfigModel(**config).model_dump()
+    except ValidationError as e:
+        raise ValueError(f"Invalid configuration: {e}") from e
     super().__init__(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self._config = ConfigModel(**config).model_dump()
super().__init__(
queue_type=config["queue_type"], execute_command=execute_command
queue_type=self._config["queue_type"], execute_command=execute_command
)
try:
self._config = ConfigModel(**config).model_dump()
except ValidationError as e:
raise ValueError(f"Invalid configuration: {e}") from e
super().__init__(
queue_type=self._config["queue_type"], execute_command=execute_command
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pysqa/base/config.py` around lines 103 - 106, The constructor currently
calls ConfigModel(**config) which raises pydantic.ValidationError on invalid
input; wrap that call in a try/except that catches pydantic.ValidationError and
re-raises a ValueError (including the original error message) before continuing
to set self._config and call super().__init__; reference the call to ConfigModel
and the surrounding code that assigns self._config and calls super().__init__ so
the conversion happens before queue_type is read.

Copilot AI review requested due to automatic review settings March 27, 2026 18:06
@codecov
Copy link
Copy Markdown

codecov bot commented Mar 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.48%. Comparing base (d81baef) to head (7399922).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #510      +/-   ##
==========================================
+ Coverage   92.20%   92.48%   +0.27%     
==========================================
  Files          19       20       +1     
  Lines        1026     1064      +38     
==========================================
+ Hits          946      984      +38     
  Misses         80       80              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Pydantic-based configuration validation for pysqa queue configuration dictionaries (commonly loaded from queue.yaml) and integrates validation into QueueAdapterWithConfig initialization.

Changes:

  • Added QueueModel / ConfigModel plus a validate_config() helper to validate and normalize config dictionaries.
  • Updated QueueAdapterWithConfig to validate configs at construction time and made template loading resilient to script: null.
  • Added unit tests covering missing required fields, type errors, and allowance of extra fields; updated dependency/environment pinning for Pydantic.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/unit/base/test_config.py Adds tests asserting validation failures and extra-field passthrough.
src/pysqa/base/models.py New Pydantic models + validate_config() helper for config validation.
src/pysqa/base/config.py Calls validate_config() during adapter init; avoids loading templates when script is None.
pyproject.toml Adds a Pydantic dependency entry (currently as an optional extra).
.ci_support/environment.yml Pins Pydantic in CI environment.
.ci_support/environment-old.yml Pins a different Pydantic version than other env files.
.ci_support/environment-docs.yml Pins Pydantic for docs environment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 46 to 54
[project.optional-dependencies]
sge = ["defusedxml==0.7.1"]
remote = [
"paramiko==4.0.0",
"tqdm==4.67.3",
]
twofactor = ["pyauthenticator==0.3.0"]
pydantic = ["pydantic==2.12.5"]

Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pydantic is only added as an optional dependency here, but the runtime code now conditionally validates configs with Pydantic. This means validation silently won’t run for a default install, contradicting the PR description and making behavior environment-dependent. Consider moving pydantic to [project].dependencies (core) or, if it must remain optional, ensure the adapter raises a clear error when validation is unavailable rather than relying on silent fallback.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +17
except ImportError:

def validate_config(config: dict) -> dict:
return config
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching a broad ImportError here will also hide unrelated import problems inside pysqa.base.models (e.g., a coding error) and will silently disable validation when pydantic is missing. If validation is expected to be available, import unconditionally; otherwise catch ModuleNotFoundError specifically for pydantic and raise a clear message telling users to install the extra.

Suggested change
except ImportError:
def validate_config(config: dict) -> dict:
return config
except ModuleNotFoundError as e:
# Handle the optional dependency on pydantic explicitly; re-raise other import errors.
if e.name == "pydantic":
def validate_config(config: dict) -> dict:
raise ModuleNotFoundError(
"Configuration validation requires 'pydantic'. "
"Install the appropriate extra (e.g. 'pysqa[validation]') to enable validation."
) from e
else:
raise

Copilot uses AI. Check for mistakes.
cores_min: Optional[int] = None
cores_max: Optional[int] = None
run_time_max: Optional[int] = None
memory_max: Optional[Union[int, str]] = None
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

memory_max previously supported float values (see check_queue_parameters(... memory_max: Optional[Union[int, float, str]])). Restricting this field to int | str can break existing YAML configs that specify non-integer numeric memory limits. Consider allowing float as well (or documenting/validating conversion rules explicitly).

Suggested change
memory_max: Optional[Union[int, str]] = None
memory_max: Optional[Union[int, float, str]] = None

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +37
ssh_continous_connection: bool = True
ssh_delete_file_on_remote: bool = False
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defaults for ssh_continous_connection and ssh_delete_file_on_remote don’t match the existing runtime defaults in RemoteQueueAdapter (currently defaults to False and True respectively via config.get(...)). Because validate_config() uses model_dump() without excluding defaults, these values can be injected into qa.config even when the user didn’t specify them, creating confusing/incorrect apparent configuration. Align the defaults with current behavior and/or exclude defaults in model_dump().

Suggested change
ssh_continous_connection: bool = True
ssh_delete_file_on_remote: bool = False
ssh_continous_connection: bool = False
ssh_delete_file_on_remote: bool = True

Copilot uses AI. Check for mistakes.
Returns:
dict: The validated configuration dictionary.
"""
return ConfigModel(**config).model_dump()
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

validate_config() returns model_dump() with default settings, which includes fields set to None/defaults (e.g., queue entries get script: None). This changes the shape of config compared to what was loaded from YAML and is what necessitated extra is not None checks elsewhere. Consider using model_dump(exclude_none=True, exclude_defaults=True) (or exclude_unset=True) to preserve the original config structure while still validating/coercing types.

Suggested change
return ConfigModel(**config).model_dump()
return ConfigModel(**config).model_dump(exclude_none=True, exclude_defaults=True)

Copilot uses AI. Check for mistakes.
- jinja2 =2.11.3
- paramiko =2.7.1
- tqdm =4.66.1
- pydantic =2.5.3
Copy link

Copilot AI Mar 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI environment files pin pydantic to different versions (2.5.3 here vs 2.12.5 in the main/doc environments and in pyproject.toml). This can lead to inconsistent validation behavior and CI results across environments. Align the pinned versions (or use a compatible range) across all environment files.

Suggested change
- pydantic =2.5.3
- pydantic =2.12.5

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/pysqa/base/models.py (1)

41-51: ⚠️ Potential issue | 🟠 Major

Normalize Pydantic errors to ValueError at the validation boundary.

ConfigModel(**config) raises pydantic.ValidationError; this changes the public failure mode for invalid configs. Converting here keeps caller behavior stable.

Proposed fix
-from pydantic import BaseModel, ConfigDict
+from pydantic import BaseModel, ConfigDict, ValidationError
...
 def validate_config(config: dict) -> dict:
@@
-    return ConfigModel(**config).model_dump()
+    try:
+        return ConfigModel(**config).model_dump()
+    except ValidationError as error:
+        raise ValueError(f"Invalid configuration: {error}") from error
#!/bin/bash
# Verify current exception expectations and usage sites.
rg -nP "raises\\((ValueError|ValidationError)\\)|assertRaises\\((ValueError|ValidationError)\\)" -C2
rg -nP "\\bvalidate_config\\(|\\bQueueAdapterWithConfig\\(" -C2
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pysqa/base/models.py` around lines 41 - 51, validate_config currently
lets pydantic.ValidationError escape the validation boundary; wrap the
ConfigModel(**config) construction in a try/except that catches
pydantic.ValidationError and re-raises a ValueError (including the original
error message and use "from" to preserve exception chaining) so callers
observing ValueError continue to work; reference validate_config and ConfigModel
and ensure pydantic.ValidationError is imported where needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pysqa/base/config.py`:
- Around line 12-17: The current broad ImportError catch around "from
pysqa.base.models import validate_config" suppresses all import errors and hides
missing-pydantic issues; instead, if the import raises because pydantic is not
present, raise a clear error advising to install pydantic; for other import
failures re-raise the original exception. Update the except block that defines
the fallback validate_config to inspect the caught ImportError (or
ModuleNotFoundError) and if the missing module is "pydantic" raise a descriptive
RuntimeError/ImportError explaining pydantic is required for validate_config
(mentioning validate_config and pysqa.base.models), otherwise re-raise the
original exception so unrelated import problems are not silently ignored.

---

Duplicate comments:
In `@src/pysqa/base/models.py`:
- Around line 41-51: validate_config currently lets pydantic.ValidationError
escape the validation boundary; wrap the ConfigModel(**config) construction in a
try/except that catches pydantic.ValidationError and re-raises a ValueError
(including the original error message and use "from" to preserve exception
chaining) so callers observing ValueError continue to work; reference
validate_config and ConfigModel and ensure pydantic.ValidationError is imported
where needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 184b282e-26ad-4572-a79b-216b34ab6b88

📥 Commits

Reviewing files that changed from the base of the PR and between 443d16a and 98867e5.

📒 Files selected for processing (6)
  • .ci_support/environment-docs.yml
  • .ci_support/environment-old.yml
  • .ci_support/environment.yml
  • pyproject.toml
  • src/pysqa/base/config.py
  • src/pysqa/base/models.py
✅ Files skipped from review due to trivial changes (3)
  • .ci_support/environment.yml
  • .ci_support/environment-old.yml
  • .ci_support/environment-docs.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml

Comment on lines +12 to +17
try:
from pysqa.base.models import validate_config
except ImportError:

def validate_config(config: dict) -> dict:
return config
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify dependency declaration and locate all validation fallback/import patterns.
fd 'pyproject.toml' --exec sed -n '1,260p' {}
rg -nP "pydantic|from pysqa\\.base\\.models import validate_config|except ImportError|except ModuleNotFoundError" -C2

Repository: pyiron/pysqa

Length of output: 5952


🏁 Script executed:

# Find all usages of validate_config to understand where it's called
rg -n "validate_config" -B2 -A2

# Check if pydantic is conditionally required or truly optional
rg -n "pydantic" --type python

# Look at the actual validation function in models.py
cat -n src/pysqa/base/models.py

Repository: pyiron/pysqa

Length of output: 2792


🏁 Script executed:

# Search for any documentation or comments about optional pydantic
rg -n "optional|pydantic" -i --type-list | head -5
cat README.md | head -100

# Look for any conditional imports or environment checks related to pydantic
rg -n "pydantic" -B3 -A3

# Check if config is accessed with square bracket notation after validate_config
sed -n '80,90p' src/pysqa/base/config.py

Repository: pyiron/pysqa

Length of output: 10580


🏁 Script executed:

# Look at test_config.py to see if it handles missing pydantic
cat -n tests/unit/base/test_config.py | head -50

# Check if there are any skip decorators or conditional imports in test_config
rg -n "skip|ImportError|pydantic" tests/unit/base/test_config.py -B2 -A2

Repository: pyiron/pysqa

Length of output: 2038


Replace broad ImportError catch with specific error handling for missing pydantic.

Catching ImportError without specificity silently disables config validation when pydantic is unavailable, deferring failures to KeyError at line 87 instead of providing a clear validation/installation error. The tests (test_pydantic_validation_missing_queues, test_pydantic_validation_wrong_type) demonstrate that validation is core functionality and pydantic is not truly optional despite being listed in optional-dependencies.

Proposed fix
 try:
     from pysqa.base.models import validate_config
-except ImportError:
-
-    def validate_config(config: dict) -> dict:
-        return config
+except ModuleNotFoundError as exc:
+    if exc.name == "pydantic":
+        raise RuntimeError(
+            "pydantic is required for configuration validation."
+        ) from exc
+    raise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pysqa/base/config.py` around lines 12 - 17, The current broad ImportError
catch around "from pysqa.base.models import validate_config" suppresses all
import errors and hides missing-pydantic issues; instead, if the import raises
because pydantic is not present, raise a clear error advising to install
pydantic; for other import failures re-raise the original exception. Update the
except block that defines the fallback validate_config to inspect the caught
ImportError (or ModuleNotFoundError) and if the missing module is "pydantic"
raise a descriptive RuntimeError/ImportError explaining pydantic is required for
validate_config (mentioning validate_config and pysqa.base.models), otherwise
re-raise the original exception so unrelated import problems are not silently
ignored.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/base/test_config.py`:
- Around line 37-45: The test currently attempts to delete attribute-like keys
from sys.modules so the fallback path in pysqa.base.config is never exercised;
replace those deletes with sys.modules.pop calls for the actual module names
(e.g., sys.modules.pop('pysqa.base.models', None) and
sys.modules.pop('pysqa.base.config', None)) and then re-import the config module
using importlib.import_module('pysqa.base.config') to force re-execution so
QueueAdapterWithConfig and validate_config are loaded from the fallback path;
keep the patch.dict('sys.modules', {'pydantic': None}) context but perform the
pops and importlib.import_module inside that context before doing "from
pysqa.base.config import QueueAdapterWithConfig".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f907b20e-f24c-4a3a-b9c2-5e7d02ea314e

📥 Commits

Reviewing files that changed from the base of the PR and between a5b23a0 and 4c9b071.

📒 Files selected for processing (1)
  • tests/unit/base/test_config.py

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/base/test_models.py`:
- Around line 8-22: The test attempts to simulate the no-Pydantic path
incorrectly by deleting a function-name key from sys.modules and not ensuring
pysqa.base.config is re-imported; update the test to remove the actual module
entry ('pysqa.base.config') from sys.modules (not
'pysqa.base.models.validate_config'), set 'pydantic' to None in
patch.dict('sys.modules', {'pydantic': None}), then import (or importlib.reload)
pysqa.base.config and construct QueueAdapterWithConfig to force the fallback
branch; reference the QueueAdapterWithConfig class and the pysqa.base.config
module when locating the code to change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 67f33b28-87c4-496f-81d4-81bfedcee6f3

📥 Commits

Reviewing files that changed from the base of the PR and between 4c9b071 and b4d01c5.

📒 Files selected for processing (2)
  • tests/unit/base/test_config.py
  • tests/unit/base/test_models.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/base/test_config.py

@jan-janssen jan-janssen marked this pull request as draft March 27, 2026 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants