Skip to content

Commit

Permalink
Restore support to do template-syntax lookups for nested dicts.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
johnbergvall committed May 24, 2024
1 parent c6d44b6 commit ab110e6
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 9 deletions.
27 changes: 18 additions & 9 deletions ninja/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
25 changes: 25 additions & 0 deletions tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -144,6 +154,21 @@ 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


Expand Down

0 comments on commit ab110e6

Please sign in to comment.