Skip to content

Commit ea79cc3

Browse files
committed
Allow providing custom default type resolver
Replicates graphql/graphql-js@fd308ce
1 parent 1c45dec commit ea79cc3

File tree

7 files changed

+75
-16
lines changed

7 files changed

+75
-16
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ a query language for APIs created by Facebook.
1313
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
1414

1515
The current version 1.0.2 of GraphQL-core-next is up-to-date with GraphQL.js version
16-
14.1.1. All parts of the API are covered by an extensive test suite of currently 1724
16+
14.1.1. All parts of the API are covered by an extensive test suite of currently 1725
1717
unit tests.
1818

1919

graphql/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@
257257
from .execution import (
258258
execute,
259259
default_field_resolver,
260+
default_type_resolver,
260261
response_path_as_list,
261262
get_directive_values,
262263
# Types
@@ -558,6 +559,7 @@
558559
"InputObjectTypeExtensionNode",
559560
"execute",
560561
"default_field_resolver",
562+
"default_type_resolver",
561563
"response_path_as_list",
562564
"get_directive_values",
563565
"ExecutionContext",

graphql/execution/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from .execute import (
88
execute,
99
default_field_resolver,
10+
default_type_resolver,
1011
response_path_as_list,
1112
ExecutionContext,
1213
ExecutionResult,
@@ -18,6 +19,7 @@
1819
__all__ = [
1920
"execute",
2021
"default_field_resolver",
22+
"default_type_resolver",
2123
"response_path_as_list",
2224
"ExecutionContext",
2325
"ExecutionResult",

graphql/execution/execute.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
GraphQLSchema,
4242
GraphQLSkipDirective,
4343
GraphQLFieldResolver,
44+
GraphQLTypeResolver,
4445
GraphQLResolveInfo,
4546
ResponsePath,
4647
SchemaMetaFieldDef,
@@ -59,6 +60,7 @@
5960
"add_path",
6061
"assert_valid_execution_arguments",
6162
"default_field_resolver",
63+
"default_type_resolver",
6264
"execute",
6365
"get_field_def",
6466
"response_path_as_list",
@@ -111,6 +113,7 @@ def execute(
111113
variable_values: Dict[str, Any] = None,
112114
operation_name: str = None,
113115
field_resolver: GraphQLFieldResolver = None,
116+
type_resolver: GraphQLTypeResolver = None,
114117
middleware: Middleware = None,
115118
execution_context_class: Type["ExecutionContext"] = None,
116119
) -> MaybeAwaitable[ExecutionResult]:
@@ -140,6 +143,7 @@ def execute(
140143
variable_values,
141144
operation_name,
142145
field_resolver,
146+
type_resolver,
143147
middleware,
144148
)
145149

@@ -173,6 +177,7 @@ class ExecutionContext:
173177
operation: OperationDefinitionNode
174178
variable_values: Dict[str, Any]
175179
field_resolver: GraphQLFieldResolver
180+
type_resolver: GraphQLTypeResolver
176181
middleware_manager: Optional[MiddlewareManager]
177182
errors: List[GraphQLError]
178183

@@ -185,6 +190,7 @@ def __init__(
185190
operation: OperationDefinitionNode,
186191
variable_values: Dict[str, Any],
187192
field_resolver: GraphQLFieldResolver,
193+
type_resolver: GraphQLTypeResolver,
188194
middleware_manager: Optional[MiddlewareManager],
189195
errors: List[GraphQLError],
190196
) -> None:
@@ -195,6 +201,7 @@ def __init__(
195201
self.operation = operation
196202
self.variable_values = variable_values
197203
self.field_resolver = field_resolver # type: ignore
204+
self.type_resolver = type_resolver # type: ignore
198205
self.middleware_manager = middleware_manager
199206
self.errors = errors
200207
self._subfields_cache: Dict[
@@ -211,6 +218,7 @@ def build(
211218
raw_variable_values: Dict[str, Any] = None,
212219
operation_name: str = None,
213220
field_resolver: GraphQLFieldResolver = None,
221+
type_resolver: GraphQLTypeResolver = None,
214222
middleware: Middleware = None,
215223
) -> Union[List[GraphQLError], "ExecutionContext"]:
216224
"""Build an execution context
@@ -290,6 +298,7 @@ def build(
290298
operation,
291299
variable_values,
292300
field_resolver or default_field_resolver,
301+
type_resolver or default_type_resolver,
293302
middleware_manager,
294303
errors,
295304
)
@@ -390,6 +399,7 @@ async def await_and_set_result(results, response_name, result):
390399
)
391400
return awaited_results
392401

402+
# noinspection PyTypeChecker
393403
results = await_and_set_result(
394404
cast(Awaitable, results), response_name, result
395405
)
@@ -399,6 +409,7 @@ async def set_result(results, response_name, result):
399409
results[response_name] = await result
400410
return results
401411

412+
# noinspection PyTypeChecker
402413
results = set_result(results, response_name, result)
403414
else:
404415
results[response_name] = result
@@ -860,8 +871,8 @@ def complete_abstract_value(
860871
Complete a value of an abstract type by determining the runtime object type of
861872
that value, then complete the value for that type.
862873
"""
863-
resolve_type_fn = return_type.resolve_type or default_resolve_type_fn
864-
runtime_type = resolve_type_fn(result, info, return_type)
874+
resolve_type_fn = return_type.resolve_type or self.type_resolver
875+
runtime_type = resolve_type_fn(result, info, return_type) # type: ignore
865876

866877
if isawaitable(runtime_type):
867878

@@ -1086,12 +1097,12 @@ def invalid_return_type_error(
10861097
)
10871098

10881099

1089-
def default_resolve_type_fn(
1100+
def default_type_resolver(
10901101
value: Any, info: GraphQLResolveInfo, abstract_type: GraphQLAbstractType
10911102
) -> MaybeAwaitable[Optional[Union[GraphQLObjectType, str]]]:
10921103
"""Default type resolver function.
10931104
1094-
If a resolveType function is not given, then a default resolve behavior is used
1105+
If a resolve_type function is not given, then a default resolve behavior is used
10951106
which attempts two strategies:
10961107
10971108
First, See if the provided value has a `__typename` field defined, if so, use that
@@ -1100,7 +1111,6 @@ def default_resolve_type_fn(
11001111
Otherwise, test each possible type for the abstract type by calling `is_type_of`
11011112
for the object being coerced, returning the first type that matches.
11021113
"""
1103-
11041114
# First, look for `__typename`.
11051115
type_name = (
11061116
value.get("__typename")

graphql/graphql.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
from asyncio import ensure_future
22
from inspect import isawaitable
3-
from typing import Any, Awaitable, Callable, Dict, Union, Type, cast
3+
from typing import Any, Awaitable, Dict, Union, Type, cast
44

55
from .error import GraphQLError
66
from .execution import execute, ExecutionResult, ExecutionContext, Middleware
77
from .language import parse, Source
88
from .pyutils import MaybeAwaitable
9-
from .type import GraphQLSchema, validate_schema
9+
from .type import (
10+
GraphQLFieldResolver,
11+
GraphQLSchema,
12+
GraphQLTypeResolver,
13+
validate_schema,
14+
)
1015

1116
__all__ = ["graphql", "graphql_sync"]
1217

@@ -18,7 +23,8 @@ async def graphql(
1823
context_value: Any = None,
1924
variable_values: Dict[str, Any] = None,
2025
operation_name: str = None,
21-
field_resolver: Callable = None,
26+
field_resolver: GraphQLFieldResolver = None,
27+
type_resolver: GraphQLTypeResolver = None,
2228
middleware: Middleware = None,
2329
execution_context_class: Type[ExecutionContext] = ExecutionContext,
2430
) -> ExecutionResult:
@@ -55,6 +61,10 @@ async def graphql(
5561
A resolver function to use when one is not provided by the schema.
5662
If not provided, the default field resolver is used (which looks for a value
5763
or method on the source value with the field's name).
64+
:arg type_resolver:
65+
A type resolver function to use when none is provided by the schema.
66+
If not provided, the default type resolver is used (which looks for a
67+
`__typename` field or alternatively calls the `isTypeOf` method).
5868
:arg middleware:
5969
The middleware to wrap the resolvers with
6070
:arg execution_context_class:
@@ -69,6 +79,7 @@ async def graphql(
6979
variable_values,
7080
operation_name,
7181
field_resolver,
82+
type_resolver,
7283
middleware,
7384
execution_context_class,
7485
)
@@ -86,7 +97,8 @@ def graphql_sync(
8697
context_value: Any = None,
8798
variable_values: Dict[str, Any] = None,
8899
operation_name: str = None,
89-
field_resolver: Callable = None,
100+
field_resolver: GraphQLFieldResolver = None,
101+
type_resolver: GraphQLTypeResolver = None,
90102
middleware: Middleware = None,
91103
execution_context_class: Type[ExecutionContext] = ExecutionContext,
92104
) -> ExecutionResult:
@@ -105,6 +117,7 @@ def graphql_sync(
105117
variable_values,
106118
operation_name,
107119
field_resolver,
120+
type_resolver,
108121
middleware,
109122
execution_context_class,
110123
)
@@ -125,6 +138,7 @@ def graphql_impl(
125138
variable_values,
126139
operation_name,
127140
field_resolver,
141+
type_resolver,
128142
middleware,
129143
execution_context_class,
130144
) -> MaybeAwaitable[ExecutionResult]:
@@ -159,6 +173,7 @@ def graphql_impl(
159173
variable_values,
160174
operation_name,
161175
field_resolver,
176+
type_resolver,
162177
middleware,
163178
execution_context_class,
164179
)

graphql/utilities/build_ast_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def get_operation_types(
173173
return op_types
174174

175175

176-
def default_type_resolver(type_name: str) -> NoReturn:
176+
def default_type_resolver(type_name: str, *_args) -> NoReturn:
177177
"""Type resolver that always throws an error."""
178178
raise TypeError(f"Type '{type_name}' not found in document.")
179179

tests/execution/test_executor.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@
88
from graphql.language import parse, OperationDefinitionNode, FieldNode
99
from graphql.pyutils import inspect
1010
from graphql.type import (
11-
GraphQLSchema,
12-
GraphQLObjectType,
13-
GraphQLString,
14-
GraphQLField,
1511
GraphQLArgument,
12+
GraphQLBoolean,
13+
GraphQLField,
1614
GraphQLInt,
15+
GraphQLInterfaceType,
1716
GraphQLList,
1817
GraphQLNonNull,
19-
GraphQLBoolean,
18+
GraphQLObjectType,
2019
GraphQLResolveInfo,
20+
GraphQLSchema,
21+
GraphQLString,
2122
ResponsePath,
2223
)
2324

@@ -854,3 +855,32 @@ def field_resolver(source_, info):
854855

855856
result = execute(schema, document, field_resolver=field_resolver)
856857
assert result == ({"foo": "foo"}, None)
858+
859+
def uses_a_custom_type_resolver():
860+
document = parse("{ foo { bar } }")
861+
862+
foo_interface = GraphQLInterfaceType(
863+
"FooInterface", {"bar": GraphQLField(GraphQLString)}
864+
)
865+
866+
foo_object = GraphQLObjectType(
867+
"FooObject", {"bar": GraphQLField(GraphQLString)}, [foo_interface]
868+
)
869+
870+
schema = GraphQLSchema(
871+
GraphQLObjectType("Query", {"foo": GraphQLField(foo_interface)}),
872+
types=[foo_object],
873+
)
874+
875+
def type_resolver(_source, info, abstract_type):
876+
# Resolver should be able to figure out all possible types on its own
877+
nonlocal possible_types
878+
possible_types = info.schema.get_possible_types(abstract_type)
879+
return "FooObject"
880+
881+
possible_types = None
882+
root_value = {"foo": {"bar": "bar"}}
883+
result = execute(schema, document, root_value, type_resolver=type_resolver)
884+
885+
assert result == ({"foo": {"bar": "bar"}}, None)
886+
assert possible_types == [foo_object]

0 commit comments

Comments
 (0)