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:
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user