From fcb885b5f4c34d01510b00356031d1e2b25ec042 Mon Sep 17 00:00:00 2001 From: Markus Padourek Date: Thu, 13 Oct 2016 16:36:14 +0100 Subject: [PATCH] Support returning of promise-like objects. --- graphql/execution/executor.py | 8 ++-- graphql/execution/tests/test_resolve.py | 57 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 2cfd65e9..015b8a76 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -3,7 +3,7 @@ import logging import sys -from promise import Promise, promise_for_dict, promisify +from promise import Promise, promise_for_dict, promisify, is_thenable from ..error import GraphQLError, GraphQLLocatedError from ..pyutils.default_ordered_dict import DefaultOrderedDict @@ -100,7 +100,7 @@ def execute_field_callback(results, response_name): if result is Undefined: return results - if is_promise(result): + if is_thenable(result): def collect_result(resolved_result): results[response_name] = resolved_result return results @@ -206,7 +206,7 @@ def complete_value_catching_error(exe_context, return_type, field_asts, info, re # resolving a null value for this field if one is encountered. try: completed = complete_value(exe_context, return_type, field_asts, info, result) - if is_promise(completed): + if is_thenable(completed): def handle_error(error): exe_context.errors.append(error) return Promise.fulfilled(None) @@ -240,7 +240,7 @@ def complete_value(exe_context, return_type, field_asts, info, result): """ # If field type is NonNull, complete for inner type, and throw field error if result is null. - if is_promise(result): + if is_thenable(result): return promisify(result).then( lambda resolved: complete_value( exe_context, diff --git a/graphql/execution/tests/test_resolve.py b/graphql/execution/tests/test_resolve.py index 05851f0a..42b96899 100644 --- a/graphql/execution/tests/test_resolve.py +++ b/graphql/execution/tests/test_resolve.py @@ -6,6 +6,35 @@ GraphQLInputObjectField, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLSchema, GraphQLString) +from promise import Promise + +class CustomPromise(object): + def __init__(self, fn=None, promise=None): + self._promise = promise or Promise(fn) + + def get(self, _=None): + raise NotImplementedError("Blocking for results not allowed. Use 'then' if you want to " + "work with the result.") + + def then(self, success=None, failure=None): + return self.__class__(promise=self._promise.then(success, failure)) + + def __getattr__(self, item): + return getattr(self._promise, item) + + @classmethod + def fulfilled(cls, x): + p = cls() + p.fulfill(x) + return p + + resolve = fulfilled + + @classmethod + def rejected(cls, reason): + p = cls() + p.reject(reason) + return p def _test_schema(test_field): @@ -73,6 +102,34 @@ def resolver(source, args, *_): ] +def test_handles_resolved_promises(): + def resolver(source, args, *_): + return Promise.resolve('foo') + + schema = _test_schema(GraphQLField( + GraphQLString, + resolver=resolver + )) + + result = graphql(schema, '{ test }', None) + assert not result.errors + assert result.data == {'test': 'foo'} + + +def test_handles_resolved_custom_promises(): + def resolver(source, args, *_): + return CustomPromise.resolve('custom_foo') + + schema = _test_schema(GraphQLField( + GraphQLString, + resolver=resolver + )) + + result = graphql(schema, '{ test }', None) + assert not result.errors + assert result.data == {'test': 'custom_foo'} + + def test_maps_argument_out_names_well(): def resolver(source, args, *_): return json.dumps([source, args], separators=(',', ':'))