Skip to content

Commit 1007a46

Browse files
committed
Merge branch 'features/middleware' into features/next
2 parents e7e24bf + 51b2c58 commit 1007a46

File tree

5 files changed

+115
-7
lines changed

5 files changed

+115
-7
lines changed

graphql/execution/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"""
2121
from .executor import execute
2222
from .base import ExecutionResult
23+
from .middleware import middlewares, MiddlewareManager
2324

2425

25-
__all__ = ['execute', 'ExecutionResult']
26+
__all__ = ['execute', 'ExecutionResult', 'MiddlewareManager', 'middlewares']

graphql/execution/base.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ class ExecutionContext(object):
1818
and the fragments defined in the query document"""
1919

2020
__slots__ = 'schema', 'fragments', 'root_value', 'operation', 'variable_values', 'errors', 'context_value', \
21-
'argument_values_cache', 'executor'
21+
'argument_values_cache', 'executor', 'middleware_manager'
2222

23-
def __init__(self, schema, document_ast, root_value, context_value, variable_values, operation_name, executor):
23+
def __init__(self, schema, document_ast, root_value, context_value, variable_values, operation_name, executor, middleware_manager):
2424
"""Constructs a ExecutionContext object from the arguments passed
2525
to execute, which we will pass throughout the other execution
2626
methods."""
@@ -63,6 +63,12 @@ def __init__(self, schema, document_ast, root_value, context_value, variable_val
6363
self.context_value = context_value
6464
self.argument_values_cache = {}
6565
self.executor = executor
66+
self.middleware_manager = middleware_manager
67+
68+
def get_field_resolver(self, field_resolver):
69+
if not self.middleware_manager:
70+
return field_resolver
71+
return self.middleware_manager.get_field_resolver(field_resolver)
6672

6773
def get_argument_values(self, field_def, field_ast):
6874
k = field_def, field_ast

graphql/execution/executor.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,24 @@
1414
collect_fields, default_resolve_fn, get_field_def,
1515
get_operation_root_type)
1616
from .executors.sync import SyncExecutor
17+
from .middleware import MiddlewareManager
1718

1819
logger = logging.getLogger(__name__)
1920

2021

2122
def execute(schema, document_ast, root_value=None, context_value=None,
2223
variable_values=None, operation_name=None, executor=None,
23-
return_promise=False):
24+
return_promise=False, middlewares=None):
2425
assert schema, 'Must provide schema'
2526
assert isinstance(schema, GraphQLSchema), (
2627
'Schema must be an instance of GraphQLSchema. Also ensure that there are ' +
2728
'not multiple versions of GraphQL installed in your node_modules directory.'
2829
)
30+
if middlewares:
31+
assert isinstance(middlewares, MiddlewareManager), (
32+
'middlewares have to be an instance'
33+
' of MiddlewareManager. Received "{}".'.format(middlewares)
34+
)
2935

3036
if executor is None:
3137
executor = SyncExecutor()
@@ -37,7 +43,8 @@ def execute(schema, document_ast, root_value=None, context_value=None,
3743
context_value,
3844
variable_values,
3945
operation_name,
40-
executor
46+
executor,
47+
middlewares
4148
)
4249

4350
def executor(resolve, reject):
@@ -132,6 +139,9 @@ def resolve_field(exe_context, parent_type, source, field_asts):
132139
return_type = field_def.type
133140
resolve_fn = field_def.resolver or default_resolve_fn
134141

142+
# We wrap the resolve_fn from the middleware
143+
resolve_fn_middleware = exe_context.get_field_resolver(resolve_fn)
144+
135145
# Build a dict of arguments from the field.arguments AST, using the variables scope to
136146
# fulfill any variable references.
137147
args = exe_context.get_argument_values(field_def, field_ast)
@@ -156,7 +166,7 @@ def resolve_field(exe_context, parent_type, source, field_asts):
156166
)
157167

158168
executor = exe_context.executor
159-
result = resolve_or_error(resolve_fn, source, args, context, info, executor)
169+
result = resolve_or_error(resolve_fn_middleware, source, args, context, info, executor)
160170

161171
return complete_value_catching_error(
162172
exe_context,

graphql/execution/middleware.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import inspect
2+
from functools import partial
3+
from itertools import chain
4+
5+
from promise import Promise
6+
7+
MIDDLEWARE_RESOLVER_FUNCTION = 'resolve'
8+
9+
10+
class MiddlewareManager(object):
11+
12+
def __init__(self, *middlewares):
13+
self.middlewares = middlewares
14+
self._middleware_resolvers = get_middleware_resolvers(middlewares)
15+
self._cached_resolvers = {}
16+
17+
def get_field_resolver(self, field_resolver):
18+
if field_resolver not in self._cached_resolvers:
19+
self._cached_resolvers[field_resolver] = middleware_chain(field_resolver, self._middleware_resolvers)
20+
21+
return self._cached_resolvers[field_resolver]
22+
23+
24+
middlewares = MiddlewareManager
25+
26+
27+
def get_middleware_resolvers(middlewares):
28+
for middleware in middlewares:
29+
# If the middleware is a function instead of a class
30+
if inspect.isfunction(middleware):
31+
yield middleware
32+
if not hasattr(middleware, MIDDLEWARE_RESOLVER_FUNCTION):
33+
continue
34+
yield getattr(middleware, MIDDLEWARE_RESOLVER_FUNCTION)
35+
36+
37+
def middleware_chain(func, middlewares):
38+
if not middlewares:
39+
return func
40+
middlewares = chain((func, make_it_promise), middlewares)
41+
last_func = None
42+
for middleware in middlewares:
43+
last_func = partial(middleware, last_func) if last_func else middleware
44+
45+
return last_func
46+
47+
48+
def make_it_promise(next, *a, **b):
49+
return Promise.resolve(next(*a, **b))

graphql/execution/tests/test_executor.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from pytest import raises
44

55
from graphql.error import GraphQLError
6-
from graphql.execution import execute
6+
from graphql.execution import execute, MiddlewareManager
77
from graphql.language.parser import parse
88
from graphql.type import (GraphQLArgument, GraphQLBoolean, GraphQLField,
99
GraphQLInt, GraphQLList, GraphQLObjectType,
@@ -561,3 +561,45 @@ def resolver(*_):
561561

562562
execute(schema, query)
563563
logger.exception.assert_called_with("An error occurred while resolving field Query.foo")
564+
565+
566+
def test_middleware():
567+
doc = '''{
568+
ok
569+
}'''
570+
571+
class Data(object):
572+
573+
def ok(self):
574+
return 'ok'
575+
576+
doc_ast = parse(doc)
577+
578+
Type = GraphQLObjectType('Type', {
579+
'ok': GraphQLField(GraphQLString),
580+
})
581+
middlewares = MiddlewareManager(lambda *_: None)
582+
result = execute(GraphQLSchema(Type), doc_ast, Data(), middlewares=middlewares)
583+
assert result.data == {'ok': None}
584+
585+
586+
def test_middleware_wrong():
587+
doc = '''{
588+
ok
589+
}'''
590+
591+
class Data(object):
592+
593+
def ok(self):
594+
return 'ok'
595+
596+
doc_ast = parse(doc)
597+
598+
Type = GraphQLObjectType('Type', {
599+
'ok': GraphQLField(GraphQLString),
600+
})
601+
middlewares = [None]
602+
with raises(AssertionError) as excinfo:
603+
execute(GraphQLSchema(Type), doc_ast, Data(), middlewares=middlewares)
604+
605+
assert 'middlewares have to be an instance of MiddlewareManager. Received "[None]".' == str(excinfo.value)

0 commit comments

Comments
 (0)