Skip to content

Commit 987a56f

Browse files
authored
👌 Defer ORM Pydantic model rebuilding (#7507)
Avoid rebuilding generated ORM Pydantic models during class setup. Leave validator and schema construction to Pydantic's deferred build machinery until models are validated, serialized, or used for schema generation. Add focused regression coverage to ensure generated node model setup does not force model rebuilds while explicit model use still works.
1 parent df7fdcd commit 987a56f

5 files changed

Lines changed: 30 additions & 14 deletions

File tree

‎src/aiida/orm/entities.py‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,6 @@ def optionalize(annotation: Any) -> Any:
415415
)
416416
model.__qualname__ = f'{cast(Any, cls).__name__}.Model'
417417
model.model_config = deepcopy(cls.ReadModel.model_config)
418-
model.model_rebuild(force=True)
419418

420419
cls._COMPAT_MODEL = model
421420

@@ -602,8 +601,6 @@ def copy_model_field(field: pdt.fields.FieldInfo) -> tuple[Any, pdt.fields.Field
602601
WriteModel.__pydantic_decorators__.field_validators = validators
603602
WriteModel.model_config = deepcopy(model_cls.model_config)
604603

605-
WriteModel.model_rebuild(force=True)
606-
607604
return WriteModel
608605

609606
if 'WriteModel' not in cls.__dict__:

‎src/aiida/orm/nodes/data/code/abstract.py‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,6 @@ def _patch_cli_model(cls):
227227
)
228228
CliModel.__qualname__ = f'{cls.__name__}.CliModel'
229229
CliModel.model_config['arbitrary_types_allowed'] = True
230-
CliModel.model_rebuild(force=True)
231230
cls._CliModel = CliModel
232231

233232
@abc.abstractmethod

‎src/aiida/orm/nodes/node.py‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,6 @@ def merge_field(
455455
)
456456
model.__qualname__ = f'{cast(Any, cls).__name__}.Model'
457457
model.model_config = deepcopy(cls.ReadModel.model_config)
458-
model.model_rebuild(force=True)
459458

460459
cls._COMPAT_MODEL = model
461460

@@ -1177,7 +1176,6 @@ def _patch_attributes_model(cls):
11771176
)
11781177
AttributesModel.__qualname__ = f'{cls.__name__}.AttributesModel'
11791178
cls.AttributesModel = AttributesModel # type: ignore[misc]
1180-
cls.AttributesModel.model_rebuild(force=True)
11811179

11821180
@classmethod
11831181
def _get_patched_node_type_field(cls):
@@ -1236,7 +1234,6 @@ def _patch_read_model(cls):
12361234
),
12371235
)
12381236
ReadModel.__qualname__ = f'{cls.__name__}.ReadModel'
1239-
ReadModel.model_rebuild(force=True)
12401237

12411238
cls.ReadModel = ReadModel # type: ignore[misc]
12421239

@@ -1265,7 +1262,6 @@ def _patch_constructor_model(cls):
12651262
),
12661263
)
12671264
ConstructorModel.__qualname__ = f'{cls.__name__}.ConstructorModel'
1268-
ConstructorModel.model_rebuild(force=True)
12691265

12701266
cls._ConstructorModel = ConstructorModel
12711267

‎src/aiida/orm/pydantic.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ def _as_minimal_model(cls: type[OrmModel]) -> type[OrmModel]:
8686
field.default_factory = None
8787
MinimalModel.model_fields[key] = field
8888

89-
MinimalModel.model_rebuild(force=True)
90-
9189
# Make subsequent calls idempotent for this specific class and the derived model
9290
cls._AIIDA_MINIMAL_MODEL = MinimalModel # type: ignore[attr-defined]
9391
MinimalModel._AIIDA_MINIMAL_MODEL = MinimalModel # type: ignore[attr-defined]

‎tests/orm/models/test_models.py‎

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from aiida import orm
1414
from aiida.common.datastructures import StashMode
1515
from aiida.common.exceptions import UnsupportedSchemaError
16+
from aiida.orm.pydantic import OrmModel
1617

1718
orm_to_test = (
1819
orm.AuthInfo,
@@ -517,6 +518,31 @@ def test_minimal_model_idempotency():
517518
assert RepeatedDynamicModel is DynamicModel
518519

519520

521+
def test_generated_orm_model_setup_defers_pydantic_rebuild(monkeypatch):
522+
"""Test generated ORM models are not rebuilt eagerly during class setup."""
523+
rebuilt: list[type[OrmModel]] = []
524+
525+
def model_rebuild(cls, *args, **kwargs):
526+
rebuilt.append(cls)
527+
return True
528+
529+
with monkeypatch.context() as context:
530+
context.setattr(OrmModel, 'model_rebuild', classmethod(model_rebuild))
531+
532+
class TestData(orm.Data):
533+
class AttributesModel(orm.Data.AttributesModel):
534+
value: int
535+
536+
class ConstructorArgsModel(OrmModel):
537+
value: int
538+
539+
assert rebuilt == []
540+
541+
model = TestData.WriteModel(node_type=TestData.class_node_type, attributes={'value': '1'})
542+
assert model.attributes.value == 1
543+
assert TestData.ReadModel.model_json_schema()['title'] == 'TestDataReadModel'
544+
545+
520546
@pytest.mark.parametrize(
521547
'required_arguments',
522548
orm_to_test,
@@ -527,10 +553,10 @@ def test_model_overrides(required_arguments: RequiredEntityArguments):
527553
name = cls.__name__
528554

529555
assert cls.ReadModel.__qualname__ == f'{name}.ReadModel'
530-
assert cls.ReadModel.model_config.get('title') == f'{name}ReadModel'
556+
assert cls.ReadModel.model_json_schema()['title'] == f'{name}ReadModel'
531557

532558
assert cls.WriteModel.__qualname__ == f'{name}.WriteModel'
533-
assert cls.WriteModel.model_config.get('title') == f'{name}WriteModel'
559+
assert cls.WriteModel.model_json_schema()['title'] == f'{name}WriteModel'
534560

535561

536562
def _clean_and_sort(dictionary: dict) -> dict:
@@ -616,11 +642,11 @@ def test_node_attributes_model_overrides(required_arguments: RequiredNodeArgumen
616642
AttributesModel = cls.ReadModel.model_fields['attributes'].annotation # noqa: N806
617643
assert AttributesModel is cls.AttributesModel
618644
assert AttributesModel.__qualname__ == f'{name}.AttributesModel'
619-
assert AttributesModel.model_config.get('title') == f'{name}AttributesModel'
645+
assert AttributesModel.model_json_schema()['title'] == f'{name}AttributesModel'
620646

621647
AttributesWriteModel = cls.WriteModel.model_fields['attributes'].annotation # noqa: N806
622648
assert AttributesWriteModel.__qualname__ == f'{name}.AttributesWriteModel'
623-
assert AttributesWriteModel.model_config.get('title') == f'{name}AttributesWriteModel'
649+
assert AttributesWriteModel.model_json_schema()['title'] == f'{name}AttributesWriteModel'
624650

625651

626652
def _validate_value(value):

0 commit comments

Comments
 (0)