From 8f028e606bfad4841400e9c8c97523c8fc1c1ca4 Mon Sep 17 00:00:00 2001 From: tangkikodo Date: Tue, 30 Jun 2026 12:32:51 +0800 Subject: [PATCH] release: bump version to 5.10.5 Changelog entry includes the full history of the get_class_field_annotations fix (#302): the 3-year-old latent bug from e637541 (2023-04-06), the 2026-05-14 copy_dataloader_kls refactor (ba8762e) that activated it, why cls.__annotations__.keys() is insufficient, and the side effects on _context detection and DataLoader's own annotated fields. Co-Authored-By: Claude Opus 4.7 --- docs/changelog.md | 13 ++++++++++++- pyproject.toml | 2 +- uv.lock | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index f2111385..e0edfaf9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,5 @@ --- -description: "Release-by-release changelog for pydantic-resolve, following semver — major for breaking changes, minor for new features, patch for bug fixes. Most recent: 5.10.3." +description: "Release-by-release changelog for pydantic-resolve, following semver — major for breaking changes, minor for new features, patch for bug fixes. Most recent: 5.10.5." --- # Changelog @@ -10,6 +10,17 @@ description: "Release-by-release changelog for pydantic-resolve, following semve ## 5.10 +### 5.10.5 (2026-6-30) + +- fix: + - **`copy_dataloader_kls` copies inherit the parent loader's annotated fields (#302)**: `get_class_field_annotations` in `pydantic_resolve/utils/types.py` now walks `cls.__mro__` and merges `__annotations__` from every class in the chain, instead of reading only `cls.__dict__['__annotations__']`. Without this fix, `copy_dataloader_kls('Foo', ParentLoader)` produces a `class NewLoader(ParentLoader): pass` whose own `__dict__['__annotations__']` is empty, so `_create_loader_instance` could no longer see fields like `user_id: int` / `permission: str` declared on the parent — the `setattr` injection loop ran zero times and the loader instance later raised `AttributeError: 'Foo' object has no attribute 'user_id'` from inside `batch_load_fn`. Direct subclasses of `DataLoader` (the common case) were unaffected because their own `__dict__` carried the annotations. + + **Bug history** — the `cls.__dict__.get('__annotations__')` lookup was a latent bug from `e637541` (2023-04-06, "global filter"), present for 3+ years. It was masked until `ba8762e` (2026-05-14, "Replace type() factory with inheritance in empty loaders and `copy_dataloader_kls`") changed `copy_dataloader_kls` from `type(name, loader_kls.__bases__, dict(loader_kls.__dict__))` to the standard `class NewLoader(loader_kls): pass`. The old `type()` factory shallow-copied the parent's `__dict__` into the new class — including `__annotations__` — so the lookup accidentally worked. The refactor correctly fixed the original state-isolation bug (#265/#266: copies sharing mutable class attributes via shallow `__dict__`), but in doing so dropped the `__annotations__` copy and activated this latent issue. Symptom window: ~6 weeks (2026-05-14 → 2026-06-30). + + **Why not `cls.__annotations__.keys()`** — Python's attribute lookup returns the *first* `__annotations__` found in the MRO, not a merge. So `class Child(Parent): extra: str` would yield only `{'extra': str}` and drop the parent's fields. The explicit `reversed(cls.__mro__)` walk merges annotations across all levels (subclass overrides win on key collision). + + **Side effects** — (1) `analysis.py`'s `_is_loader_context_required` uses the same function, so copies of `_context`-aware loaders are now correctly recognized as needing context injection (previously silently skipped). (2) Walking the full MRO also surfaces `aiodataloader.DataLoader`'s own annotated fields (`batch`, `cache`, `max_batch_size`); all of these have class-level defaults, so `_create_loader_instance`'s existing `if has_default and field not in param_config: continue` branch skips them unchanged. (3) Two existing tests in `tests/utils/test_types.py` and `tests/utils/test_utils.py` had been encoding the old "only-this-class" behavior with explicit comments; both are updated to assert the merged-MRO semantics, and a new `test_copy_dataloader_kls_inherits_parent_annotations` regression covers the reported scenario. + ### 5.10.4 (2026-6-25) - fix: diff --git a/pyproject.toml b/pyproject.toml index eb2c544f..c4229e9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pydantic-resolve" -version = "5.10.4" +version = "5.10.5" description = "Entity-First Architecture framework for Python: define business entities, declare relationships, and let the framework assemble your data." readme = "README.md" requires-python = ">=3.10" diff --git a/uv.lock b/uv.lock index 983e9e52..44b291b6 100644 --- a/uv.lock +++ b/uv.lock @@ -1560,7 +1560,7 @@ wheels = [ [[package]] name = "pydantic-resolve" -version = "5.10.4" +version = "5.10.5" source = { editable = "." } dependencies = [ { name = "aiodataloader" },