Skip to content

Commit

Permalink
Support inheritance of __typename attribute (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
Cito committed Feb 21, 2020
1 parent 93b02bc commit f2535f4
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 6 deletions.
19 changes: 13 additions & 6 deletions src/graphql/execution/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,18 @@ def invalid_return_type_error(
)


def get_typename(value: Any) -> Optional[str]:
"""Get the `__typename` property of the given value."""
if isinstance(value, dict):
return value.get("__typename")
# need to de-mangle the attribute assumed to be "private" in Python
for cls in value.__class__.__mro__:
__typename = getattr(value, f"_{cls.__name__}__typename", None)
if __typename:
return __typename
return None


def default_type_resolver(
value: Any, info: GraphQLResolveInfo, abstract_type: GraphQLAbstractType
) -> AwaitableOrValue[Optional[Union[GraphQLObjectType, str]]]:
Expand All @@ -1107,12 +1119,7 @@ def default_type_resolver(
for the object being coerced, returning the first type that matches.
"""
# First, look for `__typename`.
type_name = (
value.get("__typename")
if isinstance(value, dict)
# need to de-mangle the attribute assumed to be "private" in Python
else getattr(value, f"_{value.__class__.__name__}__typename", None)
)
type_name = get_typename(value)
if isinstance(type_name, str):
return type_name

Expand Down
72 changes: 72 additions & 0 deletions tests/utilities/test_build_ast_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,46 @@ class RootValue:
== expected
)

def using_inheritance():
class Fruit:
__typename = "Fruit"

class Apple(Fruit):
__typename = "Apple"

class Delicious(Apple):
color = "golden or red"

class GoldenDelicious(Delicious):
color = "golden"

class RedDelicious(Delicious):
color = "red"

class GrannySmith(Apple):
color = "green"

class Banana(Fruit):
__typename = "Banana"
length = 5

class RootValue:
fruits = [GrannySmith(), RedDelicious(), GoldenDelicious(), Banana()]

assert graphql_sync(
schema=schema, source=source, root_value=RootValue()
) == (
{
"fruits": [
{"color": "green"},
{"color": "red"},
{"color": "golden"},
{"length": 5},
]
},
None,
)

def describe_specifying_interface_type_using_typename():
schema = build_schema(
"""
Expand Down Expand Up @@ -600,6 +640,38 @@ class RootValue:
== expected
)

def using_inheritance():
class Character:
__typename = "Character"

class Human(Character):
__typename = "Human"

class HanSolo(Human):
name = "Han Solo"
totalCredits = 10

class Droid(Character):
__typename = "Droid"

class RemoteControlled:
name = "R2"

class Mobile:
name = "D2"

class R2D2(RemoteControlled, Droid, Mobile):
name = "R2-D2"
primaryFunction = "Astromech"

class RootValue:
characters = [HanSolo(), R2D2()]

assert (
graphql_sync(schema=schema, source=source, root_value=RootValue())
== expected
)

def custom_scalar():
sdl = dedent(
"""
Expand Down

0 comments on commit f2535f4

Please sign in to comment.