Skip to content

fix: support pydantic 2.13 by invalidating cached model schema#1694

Merged
collerek merged 3 commits into
masterfrom
fix/pydantic-213-defer-build
May 25, 2026
Merged

fix: support pydantic 2.13 by invalidating cached model schema#1694
collerek merged 3 commits into
masterfrom
fix/pydantic-213-defer-build

Conversation

@collerek
Copy link
Copy Markdown
Collaborator

Summary

  • Sets __pydantic_complete__ = False at the end of ormar's metaclass for non-abstract / non-proxy / non-forward-ref models. Invalidates the cached pydantic core schema so the next access recompiles afresh — picking up the reverse-relation fields that expand_reverse_relationships added to related models moments earlier.
  • Makes the two wrap-serializer fallbacks (forward FK/M2M in metaclass.get_serializer, reverse list in add_field_serializer_for_reverse_relations) defensive against the broader value types (QuerysetProxy, dead weakproxy) that pydantic 2.13 now dispatches them with.
  • Relaxes the upper bound: pydantic = "^2.11.9"">=2.11.9,<3.0.0". Unblocks build(deps): bump pydantic from 2.12.5 to 2.13.4 #1660.

Root cause

Pydantic 2.13 changed how TypeAdapter(Annotated[T, FieldInfo(...)]) — exactly what FastAPI builds in fastapi/_compat.py::ModelField.__post_init__ for response serialization — resolves nested schemas. 2.12 walked the live model's fields; 2.13 uses the cached child snapshot. Verified with a pure-pydantic repro: same code, identical for TypeAdapter(T).dump_python(...), but the Annotated[T, FieldInfo(...)] wrapping diverges between versions.

The consequence in ormar: expand_reverse_relationships adds items to Category.model_fields after Item.__pydantic_core_schema__ has already been compiled. Standalone Category.model_dump() still includes items, but Item.model_dump() (and FastAPI's serializer) drop it from the nested category — because Item's schema holds a stale snapshot.

Invalidating Item.__pydantic_complete__ so pydantic lazily recompiles avoids the regression without the schema-divergence side effects that an eager model_rebuild(force=True) triggered (FastAPI splitting the model into Category-Input / Category-Output, wrap serializer hitting QuerysetProxy values it can't handle, etc.).

Test plan

  • tests/test_fastapi/test_fastapi_usage.py::test_read_main passes under pydantic 2.13.4 (was the only failing test on master).
  • Full suite (653 tests + 25 skipped) passes under pydantic 2.11.9, 2.12.5, and 2.13.4.
  • make pre-commit clean (ruff format/check + mypy strict).
  • make coverage at 100.00% on the 2.11.9 floor. The one pydantic-2.13-only branch (hasattr(value, "ormar_config") fallback for QuerysetProxy) is marked # pragma: no cover with a comment, mirroring the existing dialect-specific pragma pattern.

Pydantic 2.13 changed how `TypeAdapter(Annotated[T, FieldInfo(...)])` —
FastAPI's response-serializer pattern — resolves nested schemas: it now
uses the cached child snapshot instead of walking the live model. As a
result, reverse-relation fields that ormar's metaclass adds to related
models in `expand_reverse_relationships` were invisible from nested
dumps under pydantic 2.13.

Invalidate the parent's cached schema by setting
`__pydantic_complete__ = False` at the end of the ormar metaclass, so
the next access recompiles afresh without the stale snapshot. Make the
wrap-serializer fallbacks (forward + reverse relation) defensive
against the broader value types (QuerysetProxy, dead weakproxy) that
pydantic 2.13 now dispatches them with.

Relax the pydantic upper bound so 2.13.x is accepted.
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 24, 2026

Merging this PR will improve performance by 46.98%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 97 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
WallTime test_saving_models_individually_with_related_models[10] 87.5 ms 59.5 ms +46.98%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing fix/pydantic-213-defer-build (a23f777) with master (9cd037e)1

Open in CodSpeed

Footnotes

  1. No successful run was found on master (0e31776) during the generation of this report, so 9cd037e was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

collerek added 2 commits May 25, 2026 10:46
Ormar relies on pydantic internals, so the upper bound is capped at
<2.14 and must be bumped manually after verifying compatibility with
each new release. Refreshes the lock content-hash; resolved versions
are unchanged (pydantic stays at 2.12.5).
@collerek collerek enabled auto-merge (squash) May 25, 2026 09:07
@collerek collerek disabled auto-merge May 25, 2026 09:07
@collerek collerek merged commit 1271803 into master May 25, 2026
11 checks passed
@collerek collerek deleted the fix/pydantic-213-defer-build branch May 25, 2026 09:24
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