Skip to content

Commit 4fd37e0

Browse files
committed
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.
1 parent c6d44b6 commit 4fd37e0

File tree

2 files changed

+41
-9
lines changed

2 files changed

+41
-9
lines changed

ninja/schema.py

+18-9
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,30 @@ def __getattr__(self, key: str) -> Any:
6666
if resolver:
6767
value = resolver(getter=self)
6868
else:
69+
resolve = False
6970
if isinstance(self._obj, dict):
70-
if key not in self._obj:
71+
if key in self._obj:
72+
value = self._obj[key]
73+
elif '.' in key and not key.startswith('_'):
74+
# Only do template lookup if necessary, which will also work around
75+
# Variable.resolve() returning default value of defaultdict's
76+
resolve = True
77+
else:
7178
raise AttributeError(key)
72-
value = self._obj[key]
7379
else:
7480
try:
7581
value = getattr(self._obj, key)
7682
except AttributeError:
77-
try:
78-
# value = attrgetter(key)(self._obj)
79-
value = Variable(key).resolve(self._obj)
80-
# TODO: Variable(key) __init__ is actually slower than
81-
# Variable.resolve - so it better be cached
82-
except VariableDoesNotExist as e:
83-
raise AttributeError(key) from e
83+
resolve = True
84+
85+
if resolve and not key.startswith('_'):
86+
try:
87+
# value = attrgetter(key)(self._obj)
88+
value = Variable(key).resolve(self._obj)
89+
# TODO: Variable(key) __init__ is actually slower than
90+
# Variable.resolve - so it better be cached
91+
except VariableDoesNotExist as e:
92+
raise AttributeError(key) from e
8493
return self._convert_result(value)
8594

8695
# def get(self, key: Any, default: Any = None) -> Any:

tests/test_schema.py

+23
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ class UserSchema(Schema):
6868
avatar: Optional[str] = None
6969

7070

71+
class BossSchema(Schema):
72+
name: str
73+
title: str
74+
75+
7176
class UserWithBossSchema(UserSchema):
7277
boss: Optional[str] = Field(None, alias="boss.name")
7378
has_boss: bool
@@ -78,6 +83,11 @@ def resolve_has_boss(obj):
7883
return bool(obj.boss)
7984

8085

86+
class NestedUserWithBossSchema(UserSchema):
87+
boss: Optional[BossSchema] = None
88+
boss_name: Optional[str] = Field(None, alias="boss.name")
89+
90+
8191
class UserWithInitialsSchema(UserWithBossSchema):
8292
initials: str
8393

@@ -144,6 +154,19 @@ def test_with_boss_schema():
144154
}
145155

146156

157+
def test_boss_schema_as_nested_dict():
158+
user = User()
159+
schema = NestedUserWithBossSchema.from_orm(user)
160+
161+
result1 = schema.dict(by_alias=True)
162+
result1_no_boss_name = result1.copy()
163+
result1_no_boss_name.pop('boss.name')
164+
165+
result2 = NestedUserWithBossSchema.from_orm(result1_no_boss_name).dict(by_alias=True)
166+
167+
assert result1 == result2
168+
169+
147170
SKIP_NON_STATIC_RESOLVES = True
148171

149172

0 commit comments

Comments
 (0)