Skip to content

Commit 3b7e98d

Browse files
edan-bainglassagoscinski
authored andcommitted
Implement a cache for QB fields construction #6869
Cherry-picked at 2025-05-09 22:06 This aims to avoid reconstruction of fields from parent models during MRO walks.
1 parent 9d2664c commit 3b7e98d

File tree

1 file changed

+17
-5
lines changed

1 file changed

+17
-5
lines changed

src/aiida/orm/fields.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,8 @@ def _dict(self):
396396
class EntityFieldMeta(ABCMeta):
397397
"""A metaclass for entity fields, which adds a `fields` class attribute."""
398398

399+
_model_fields_cache: t.Dict[t.Type[BaseModel], QbFields] = {}
400+
399401
def __init__(cls, name, bases, classdict):
400402
super().__init__(name, bases, classdict)
401403

@@ -404,12 +406,17 @@ def __init__(cls, name, bases, classdict):
404406
if current_fields is not None and not isinstance(current_fields, QbFields):
405407
raise ValueError(f"class '{cls}' already has a `fields` attribute set")
406408

407-
fields = {}
408-
409409
# If the class has an attribute ``Model`` that is a subclass of :class:`pydantic.BaseModel`, parse the model
410410
# fields to build up the ``fields`` class attribute, which is used to allow specifying ``QueryBuilder`` filters
411411
# programmatically.
412412
if hasattr(cls, 'Model') and issubclass(cls.Model, BaseModel):
413+
# If the class has a ``Model``, check if we have already parsed it and cached
414+
# the result. Use the cached result if available.
415+
cache = cls._model_fields_cache
416+
if cls.Model in cache:
417+
cls.fields = cache[cls.Model]
418+
return
419+
413420
# If the class itself directly specifies the ``Model`` attribute, check that it is valid. Here, the check
414421
# ``cls.__dict__`` is used instead of ``hasattr`` as the former only returns true if the class itself
415422
# defines the attribute and does not just inherit it from a base class. In that case, this check will
@@ -461,17 +468,22 @@ def __init__(cls, name, bases, classdict):
461468
f'`class Model({", ".join(sorted(bases))}):`'
462469
)
463470

464-
for key, field in cls.Model.model_fields.items():
465-
fields[key] = add_field(
471+
fields = {
472+
key: add_field(
466473
key,
467474
alias=get_metadata(field, 'alias', None),
468475
dtype=field.annotation,
469476
doc=field.description,
470477
is_attribute=get_metadata(field, 'is_attribute', False),
471478
is_subscriptable=get_metadata(field, 'is_subscriptable', False),
472479
)
480+
for key, field in cls.Model.model_fields.items()
481+
}
473482

474-
cls.fields = QbFields({key: fields[key] for key in sorted(fields)})
483+
cls.fields = QbFields({key: fields[key] for key in sorted(fields)})
484+
cls._model_fields_cache[cls.Model] = cls.fields
485+
else:
486+
cls.fields = QbFields()
475487

476488

477489
class QbFieldArguments(t.TypedDict):

0 commit comments

Comments
 (0)