Skip to content

Migrate to pydantic v2 - #769

Merged
amrit110 merged 1 commit into
mainfrom
migrate-pydantic-v2
Aug 4, 2026
Merged

Migrate to pydantic v2#769
amrit110 merged 1 commit into
mainfrom
migrate-pydantic-v2

Conversation

@amrit110

@amrit110 amrit110 commented Aug 4, 2026

Copy link
Copy Markdown
Member

Problem

The report/model_card system used pydantic v1-only APIs throughout: Config-class inheritance, root_validator/validator, .dict()/.json()/.parse_obj()/.parse_file(), __fields__/ModelField introspection, and Field(unique_items=...). This blocked upgrading pydantic past <2, which in turn was one of the pinned dependencies with known vulnerabilities. An earlier attempt at this (#642) was an incomplete, auto-generated stub — closed as superseded by this PR.

Changes

  • base.py: replace Config classes with ConfigDict; replace the root_validator-based composition check with model_validator(mode="before"); capture the composable_with/list_factory class arguments via __init_subclass__ instead of pydantic v1's implicit Config-kwarg merging. Simplify add_field/update_field: pydantic v2's extra="allow" natively tracks and (de)serializes extra attributes, so manual ModelField/FieldInfo construction is no longer needed. One v1 behavior is explicitly preserved: v1 silently coerced an arbitrary BaseModel instance into a differently-typed nested model via dict(); v2 requires an explicit dict, so mismatched instances are dumped to dicts before being appended (a test relies on this).
  • fields.py/model_card.py: root_validator/validatormodel_validator/field_validator; drop the no-longer-supported unique_items Field kwarg (it was never enforced past initial construction anyway, per the existing code comments). License's SPDX validator now mutates self directly (model_validator(mode="after")) instead of a values dict, bypassing the validated __setattr__ to avoid infinite recursion (since validate_assignment would otherwise re-run the same "after" validator).
  • report.py/utils.py: .dict()/.json()/.parse_obj()/.parse_file()/ModelCard.validate()model_dump()/model_dump_json()/model_validate()/model_validate_json(); __fields__/.type_model_fields + a small Optional/List-unwrapping helper, since FieldInfo has no v1-style .type_.
  • deploy/report/api/main.py: same validator/Field(min_items=...) updates for its own (separate) pydantic model.
  • tests: AnyUrl is no longer a str subclass in pydantic v2, so compare str(url) instead of url directly.

Validation

Full local pre-commit run --all-files (ruff, ruff-format, mypy, doctest, nbstripout, pytest) passes across the whole repo, including the full tests/cyclops/report/ suite (50 tests).

🤖 Generated with Claude Code

The report/model_card system used pydantic v1-only APIs throughout:
Config-class inheritance, root_validator/validator, .dict()/.json()/
.parse_obj()/.parse_file(), __fields__/ModelField introspection, and
Field(unique_items=...). None of this works under pydantic v2.

- base.py: replace Config classes with ConfigDict; replace the
  root_validator-based composition check with model_validator(mode=
  "before"); capture the composable_with/list_factory class arguments
  via __init_subclass__ instead of pydantic v1's implicit Config-kwarg
  merging. Simplify add_field/update_field: pydantic v2's extra="allow"
  natively tracks and (de)serializes extra attributes, so the manual
  ModelField/FieldInfo construction is no longer needed. Preserve one
  v1 behavior update_field relied on: pydantic v1 silently coerced an
  arbitrary BaseModel instance into a differently-typed nested model
  via dict(); v2 requires an explicit dict, so mismatched instances are
  dumped to dicts before being appended.
- fields.py/model_card.py: root_validator/validator -> model_validator/
  field_validator; drop the no-longer-supported unique_items Field
  kwarg (never enforced past initial construction anyway); License's
  spdx validator now mutates self directly (model_validator(mode=
  "after")) instead of a values dict, bypassing the validated
  __setattr__ to avoid infinite recursion since validate_assignment
  would otherwise re-run the same "after" validator.
- report.py/utils.py: .dict()/.json()/.parse_obj()/.parse_file()/
  ModelCard.validate() -> model_dump()/model_dump_json()/
  model_validate()/model_validate_json(); __fields__/.type_ ->
  model_fields + a small Optional/List-unwrapping helper, since
  FieldInfo has no v1-style .type_.
- deploy/report/api/main.py: same validator/Field(min_items=...)
  updates for its own (separate) pydantic model.
- tests: AnyUrl is no longer a str subclass in pydantic v2, so
  compare str(url) instead of url directly.

Full pre-commit suite (ruff, mypy, doctest, nbstripout, pytest) passes
across the whole repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@amrit110 amrit110 mentioned this pull request Aug 4, 2026
@amrit110
amrit110 merged commit 88ff4e0 into main Aug 4, 2026
9 checks passed
@amrit110
amrit110 deleted the migrate-pydantic-v2 branch August 4, 2026 17:49
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.

1 participant