Fix self-referential shapes (CategoryShape)
Set field-only _spec before building the full spec with nested shapes. Self-references resolve because cls._spec already exists when the generator encounters shape._spec where shape is cls. Also: pass cls into get_type_hints localns so forward ref strings like list["CategoryShape"] resolve to the class being defined. 49 shapes tests, 0 skipped. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,7 @@ class Shape(BaseModel, Generic[_M]):
|
|||||||
cls._nested = {}
|
cls._nested = {}
|
||||||
cls._pk_field = model._meta.pk.name if model._meta.pk else "id"
|
cls._pk_field = model._meta.pk.name if model._meta.pk else "id"
|
||||||
|
|
||||||
hints = get_type_hints(cls, include_extras=False) or cls.__annotations__
|
hints = get_type_hints(cls, include_extras=False, localns={cls.__name__: cls}) or cls.__annotations__
|
||||||
field_names = []
|
field_names = []
|
||||||
|
|
||||||
for name, hint in hints.items():
|
for name, hint in hints.items():
|
||||||
@@ -78,6 +78,11 @@ class Shape(BaseModel, Generic[_M]):
|
|||||||
field_names.append(name)
|
field_names.append(name)
|
||||||
|
|
||||||
cls._field_names = field_names
|
cls._field_names = field_names
|
||||||
|
|
||||||
|
# Set field-only spec first so self-references can find it
|
||||||
|
cls._spec = [*field_names]
|
||||||
|
|
||||||
|
# Now rebuild with nested — self-refs resolve because cls._spec exists
|
||||||
cls._spec = [
|
cls._spec = [
|
||||||
*field_names,
|
*field_names,
|
||||||
*({name: shape._spec} for name, shape in cls._nested.items()),
|
*({name: shape._spec} for name, shape in cls._nested.items()),
|
||||||
|
|||||||
@@ -105,13 +105,10 @@ class BookWithEditorShape(Shape[Book]):
|
|||||||
editor: FlatAuthorShape | None = None
|
editor: FlatAuthorShape | None = None
|
||||||
|
|
||||||
|
|
||||||
# CategoryShape is commented out — self-referential forward refs crash
|
class CategoryShape(Shape[Category]):
|
||||||
# get_type_hints() at __init_subclass__ time. Known gap.
|
id: int | None = None
|
||||||
#
|
name: str
|
||||||
# class CategoryShape(Shape[Category]):
|
children: list["CategoryShape"] = []
|
||||||
# id: int | None = None
|
|
||||||
# name: str
|
|
||||||
# children: list["CategoryShape"] = []
|
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -169,11 +166,9 @@ class TestShapeClassCreation(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_self_referential_shape(self):
|
def test_self_referential_shape(self):
|
||||||
"""CategoryShape.children references itself.
|
"""CategoryShape.children references itself."""
|
||||||
Currently crashes at class definition time — known gap."""
|
self.assertIn("children", CategoryShape._nested)
|
||||||
self.skipTest(
|
self.assertIs(CategoryShape._nested["children"], CategoryShape)
|
||||||
"Self-referential forward ref crashes get_type_hints() at __init_subclass__ time"
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_multiple_shapes_same_model_independent(self):
|
def test_multiple_shapes_same_model_independent(self):
|
||||||
self.assertLess(len(FlatBookShape._field_names), len(BookDetailShape._field_names))
|
self.assertLess(len(FlatBookShape._field_names), len(BookDetailShape._field_names))
|
||||||
|
|||||||
Reference in New Issue
Block a user