From 4fd37e0be3e3326fb144e00f710d3ad5a8d9210b Mon Sep 17 00:00:00 2001 From: John Bergvall Date: Fri, 24 May 2024 14:41:09 +0200 Subject: [PATCH] Restore support to do template-syntax lookups for nested dicts. This patch skips the lookup for regular "single" key-access to not return default value from defaultdicts. Nested defaultdicts will however return the default value instead of "missing" due to how Django's Variable.resolve(key) works. --- ninja/schema.py | 27 ++++++++++++++++++--------- tests/test_schema.py | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/ninja/schema.py b/ninja/schema.py index 55a60a84c..d131655e7 100644 --- a/ninja/schema.py +++ b/ninja/schema.py @@ -66,21 +66,30 @@ def __getattr__(self, key: str) -> Any: if resolver: value = resolver(getter=self) else: + resolve = False if isinstance(self._obj, dict): - if key not in self._obj: + if key in self._obj: + value = self._obj[key] + elif '.' in key and not key.startswith('_'): + # Only do template lookup if necessary, which will also work around + # Variable.resolve() returning default value of defaultdict's + resolve = True + else: raise AttributeError(key) - value = self._obj[key] else: try: value = getattr(self._obj, key) except AttributeError: - try: - # value = attrgetter(key)(self._obj) - value = Variable(key).resolve(self._obj) - # TODO: Variable(key) __init__ is actually slower than - # Variable.resolve - so it better be cached - except VariableDoesNotExist as e: - raise AttributeError(key) from e + resolve = True + + if resolve and not key.startswith('_'): + try: + # value = attrgetter(key)(self._obj) + value = Variable(key).resolve(self._obj) + # TODO: Variable(key) __init__ is actually slower than + # Variable.resolve - so it better be cached + except VariableDoesNotExist as e: + raise AttributeError(key) from e return self._convert_result(value) # def get(self, key: Any, default: Any = None) -> Any: diff --git a/tests/test_schema.py b/tests/test_schema.py index 8f92d5201..f36b626cf 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -68,6 +68,11 @@ class UserSchema(Schema): avatar: Optional[str] = None +class BossSchema(Schema): + name: str + title: str + + class UserWithBossSchema(UserSchema): boss: Optional[str] = Field(None, alias="boss.name") has_boss: bool @@ -78,6 +83,11 @@ def resolve_has_boss(obj): return bool(obj.boss) +class NestedUserWithBossSchema(UserSchema): + boss: Optional[BossSchema] = None + boss_name: Optional[str] = Field(None, alias="boss.name") + + class UserWithInitialsSchema(UserWithBossSchema): initials: str @@ -144,6 +154,19 @@ def test_with_boss_schema(): } +def test_boss_schema_as_nested_dict(): + user = User() + schema = NestedUserWithBossSchema.from_orm(user) + + result1 = schema.dict(by_alias=True) + result1_no_boss_name = result1.copy() + result1_no_boss_name.pop('boss.name') + + result2 = NestedUserWithBossSchema.from_orm(result1_no_boss_name).dict(by_alias=True) + + assert result1 == result2 + + SKIP_NON_STATIC_RESOLVES = True