Fix Optional[Shape] unwrapping and add comprehensive shapes stress tests

_extract_shape_class now handles `Shape | None` (Union types) by checking
isinstance(hint, types.UnionType) and iterating args for Shape subclasses.
This fixes nullable FK detection — any `editor: AuthorShape | None` field
is now correctly recognized as a nested shape.

48 stress tests covering:
- 5-level deep nesting (Publisher → Author → Book → Chapter → Section)
- Two FKs to same model (author + editor)
- Slug PK (Tag), UUID PK (Section)
- M2M relationships (Book.tags)
- Nullable FKs returning None
- Empty strings, zero integers, false booleans (truthiness traps)
- 100-record smoke test
- Query efficiency (assertNumQueries)
- All diff operations with deep nesting

Known gap documented: self-referential forward refs (CategoryShape)
crash get_type_hints() at __init_subclass__ time. Needs deferred resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 02:56:11 -04:00
parent 5a56d7a4a5
commit 625d8cf9b9
3 changed files with 582 additions and 332 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
from typing import Any, ClassVar, Generic, TypeVar, get_type_hints
import types
from typing import Any, ClassVar, Generic, TypeVar, Union, get_type_hints
from pydantic import BaseModel
@@ -15,6 +16,7 @@ def _extract_shape_class(hint) -> type[Shape] | None:
origin = getattr(hint, "__origin__", None)
args = getattr(hint, "__args__", ())
# list[SomeShape]
if (
origin is list
and args
@@ -22,8 +24,19 @@ def _extract_shape_class(hint) -> type[Shape] | None:
and issubclass(args[0], Shape)
):
return args[0]
# SomeShape (bare)
if isinstance(hint, type) and issubclass(hint, Shape) and hint is not Shape:
return hint
# SomeShape | None (Union/Optional)
if origin is Union or isinstance(hint, types.UnionType):
for arg in args:
if arg is type(None):
continue
if isinstance(arg, type) and issubclass(arg, Shape) and arg is not Shape:
return arg
return None