Repository: mirumee/ariadne

Issue:
set_alias support for nested attributes
I have a use case where the parent resolver returns something like this:

`Person(name="John", state=PersonState(married=False))`

To implement `person { married }`, you would need to create a new resolver returning `parent.state.married`, and in a large schema, there are many of these trivial resolvers.

It would be great if `set_alias` supported nested attributes, so one could do `person.set_alias("married", "state.married")`, maybe using `operator.attrgetter` instead of `getattr`.

Discussion:
dacevedo12: There seems to be a flaky unrelated test
DamianCzajkowski: Hi @dacevedo12 those failing tests are fixed on the main, try to merge it and it should be fine :D
dacevedo12: Indeed, thanks. CI is now green
DamianCzajkowski: LGTM! Please add a changelog entry, and I'll merge it. :D
dacevedo12: Changelog entry added 🚀 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bbcf59f..21981d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,8 +7,9 @@
 - Replaced hardcoded HTTP statuses with `HTTPStatus` from the `http` stdlib module.
 - Added `include_cookies` option to the `ExplorerApollo`.
 - Fixed typing on `extract_data_from_request` method.
-- Fixed tests websockets after starlette update
+- Fixed tests websockets after starlette update.
 - Added `share_enabled` param to `ExplorerPlayground` to enable share playground feature.
+- Added support for nested attribute resolution in alias resolvers.
 
 
 ## 0.23 (2024-03-18)
diff --git a/ariadne/resolvers.py b/ariadne/resolvers.py
index 21a3014..7a43341 100644
--- a/ariadne/resolvers.py
+++ b/ariadne/resolvers.py
@@ -79,9 +79,16 @@ snake_case_fallback_resolvers = SnakeCaseFallbackResolversSetter()
 
 
 def resolve_parent_field(parent: Any, field_name: str) -> Any:
-    if isinstance(parent, Mapping):
-        return parent.get(field_name)
-    return getattr(parent, field_name, None)
+    value = parent
+    for name in field_name.split("."):
+        if isinstance(value, Mapping):
+            value = value.get(name)
+        else:
+            value = getattr(value, name, None)
+
+        if value is None:
+            break
+    return value
 
 
 def resolve_to(attr_name: str) -> Resolver:


Test patch:
diff --git a/tests/test_default_resolvers.py b/tests/test_default_resolvers.py
index 6efa5d2..58b24d9 100644
--- a/tests/test_default_resolvers.py
+++ b/tests/test_default_resolvers.py
@@ -14,6 +14,15 @@ def test_alias_resolver_supports_callable_return_value():
     assert alias_resolver(obj, None)
 
 
+def test_alias_resolver_supports_nested_name():
+    parent_mapping = {"nested": {"hello": "world"}}
+    parent_object = Mock(nested=Mock(hello="world"))
+
+    alias_resolver = resolve_to("nested.hello")
+    assert alias_resolver(parent_mapping, None) == "world"
+    assert alias_resolver(parent_object, None) == "world"
+
+
 def test_alias_resolver_passess_field_args_to_callable_return_value():
     def callable_resolver(*_, test):
         return test