Skip to content

Commit f2535f4

Browse files
committed
Support inheritance of __typename attribute (#25)
1 parent 93b02bc commit f2535f4

File tree

2 files changed

+85
-6
lines changed

2 files changed

+85
-6
lines changed

src/graphql/execution/execute.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,18 @@ def invalid_return_type_error(
10921092
)
10931093

10941094

1095+
def get_typename(value: Any) -> Optional[str]:
1096+
"""Get the `__typename` property of the given value."""
1097+
if isinstance(value, dict):
1098+
return value.get("__typename")
1099+
# need to de-mangle the attribute assumed to be "private" in Python
1100+
for cls in value.__class__.__mro__:
1101+
__typename = getattr(value, f"_{cls.__name__}__typename", None)
1102+
if __typename:
1103+
return __typename
1104+
return None
1105+
1106+
10951107
def default_type_resolver(
10961108
value: Any, info: GraphQLResolveInfo, abstract_type: GraphQLAbstractType
10971109
) -> AwaitableOrValue[Optional[Union[GraphQLObjectType, str]]]:
@@ -1107,12 +1119,7 @@ def default_type_resolver(
11071119
for the object being coerced, returning the first type that matches.
11081120
"""
11091121
# First, look for `__typename`.
1110-
type_name = (
1111-
value.get("__typename")
1112-
if isinstance(value, dict)
1113-
# need to de-mangle the attribute assumed to be "private" in Python
1114-
else getattr(value, f"_{value.__class__.__name__}__typename", None)
1115-
)
1122+
type_name = get_typename(value)
11161123
if isinstance(type_name, str):
11171124
return type_name
11181125

tests/utilities/test_build_ast_schema.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,46 @@ class RootValue:
517517
== expected
518518
)
519519

520+
def using_inheritance():
521+
class Fruit:
522+
__typename = "Fruit"
523+
524+
class Apple(Fruit):
525+
__typename = "Apple"
526+
527+
class Delicious(Apple):
528+
color = "golden or red"
529+
530+
class GoldenDelicious(Delicious):
531+
color = "golden"
532+
533+
class RedDelicious(Delicious):
534+
color = "red"
535+
536+
class GrannySmith(Apple):
537+
color = "green"
538+
539+
class Banana(Fruit):
540+
__typename = "Banana"
541+
length = 5
542+
543+
class RootValue:
544+
fruits = [GrannySmith(), RedDelicious(), GoldenDelicious(), Banana()]
545+
546+
assert graphql_sync(
547+
schema=schema, source=source, root_value=RootValue()
548+
) == (
549+
{
550+
"fruits": [
551+
{"color": "green"},
552+
{"color": "red"},
553+
{"color": "golden"},
554+
{"length": 5},
555+
]
556+
},
557+
None,
558+
)
559+
520560
def describe_specifying_interface_type_using_typename():
521561
schema = build_schema(
522562
"""
@@ -600,6 +640,38 @@ class RootValue:
600640
== expected
601641
)
602642

643+
def using_inheritance():
644+
class Character:
645+
__typename = "Character"
646+
647+
class Human(Character):
648+
__typename = "Human"
649+
650+
class HanSolo(Human):
651+
name = "Han Solo"
652+
totalCredits = 10
653+
654+
class Droid(Character):
655+
__typename = "Droid"
656+
657+
class RemoteControlled:
658+
name = "R2"
659+
660+
class Mobile:
661+
name = "D2"
662+
663+
class R2D2(RemoteControlled, Droid, Mobile):
664+
name = "R2-D2"
665+
primaryFunction = "Astromech"
666+
667+
class RootValue:
668+
characters = [HanSolo(), R2D2()]
669+
670+
assert (
671+
graphql_sync(schema=schema, source=source, root_value=RootValue())
672+
== expected
673+
)
674+
603675
def custom_scalar():
604676
sdl = dedent(
605677
"""

0 commit comments

Comments
 (0)