diff --git a/graphql/__init__.py b/graphql/__init__.py index 20f5c995..434b5a75 100644 --- a/graphql/__init__.py +++ b/graphql/__init__.py @@ -205,7 +205,7 @@ set_default_backend, ) -VERSION = (2, 1, 0, 'rc', 1) +VERSION = (2, 1, 0, 'rc', 2) __version__ = get_version(VERSION) diff --git a/graphql/backend/quiver_cloud.py b/graphql/backend/quiver_cloud.py index f760c5fc..d8ac8233 100644 --- a/graphql/backend/quiver_cloud.py +++ b/graphql/backend/quiver_cloud.py @@ -6,12 +6,12 @@ "You can install it using: pip install requests" ) -from six import urlparse - from ..utils.schema_printer import print_schema from .base import GraphQLBackend from .compiled import GraphQLCompiledDocument +from six.moves.urllib.parse import urlparse + GRAPHQL_QUERY = """ mutation($schemaDsl: String!, $query: String!) { diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 55c32e54..a209dc8b 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -1,9 +1,16 @@ # We keep the following imports to preserve compatibility -from .utils import (ExecutionContext, SubscriberExecutionContext, - collect_fields, default_resolve_fn, - does_fragment_condition_match, get_field_def, - get_field_entry_key, get_operation_root_type, - should_include_node) +from .utils import ( + ExecutionContext, + SubscriberExecutionContext, + get_operation_root_type, + collect_fields, + should_include_node, + does_fragment_condition_match, + get_field_entry_key, + default_resolve_fn, + get_field_def +) +from ..error.format_error import format_error as default_format_error class ExecutionResult(object): @@ -33,6 +40,19 @@ def __eq__(self, other): ) ) + def to_dict(self, format_error=None, dict_class=dict): + if format_error is None: + format_error = default_format_error + + response = dict_class() + if self.errors: + response['errors'] = [format_error(e) for e in self.errors] + + if not self.invalid: + response['data'] = self.data + + return response + class ResolveInfo(object): __slots__ = ('field_name', 'field_asts', 'return_type', 'parent_type', diff --git a/graphql/execution/executors/asyncio.py b/graphql/execution/executors/asyncio.py index 541e6cf6..13f19028 100644 --- a/graphql/execution/executors/asyncio.py +++ b/graphql/execution/executors/asyncio.py @@ -62,5 +62,5 @@ def execute(self, fn, *args, **kwargs): self.futures.append(future) return Promise.resolve(future) elif isasyncgen(result): - return asyncgen_to_observable(result) + return asyncgen_to_observable(result, loop=self.loop) return result diff --git a/graphql/execution/executors/asyncio_utils.py b/graphql/execution/executors/asyncio_utils.py index d5e0464c..b327cec6 100644 --- a/graphql/execution/executors/asyncio_utils.py +++ b/graphql/execution/executors/asyncio_utils.py @@ -1,4 +1,4 @@ -from asyncio import ensure_future +from asyncio import ensure_future, wait, CancelledError from inspect import isasyncgen from rx import AnonymousObserver, Observable @@ -7,6 +7,7 @@ ObserverBase) from rx.core.anonymousobserver import AnonymousObserver from rx.core.autodetachobserver import AutoDetachObserver +from rx import AnonymousObservable # class AsyncgenDisposable(Disposable): @@ -102,27 +103,16 @@ def set_disposable(scheduler=None, value=None): else: auto_detach_observer.disposable = fix_subscriber(subscriber) - # Subscribe needs to set up the trampoline before for subscribing. - # Actually, the first call to Subscribe creates the trampoline so - # that it may assign its disposable before any observer executes - # OnNext over the CurrentThreadScheduler. This enables single- - # threaded cancellation - # https://social.msdn.microsoft.com/Forums/en-US/eb82f593-9684-4e27- - # 97b9-8b8886da5c33/whats-the-rationale-behind-how-currentthreadsche - # dulerschedulerequired-behaves?forum=rx - if current_thread_scheduler.schedule_required(): - current_thread_scheduler.schedule(set_disposable) - else: - set_disposable() + def dispose(): + async def await_task(): + await task - # Hide the identity of the auto detach observer - return Disposable.create(auto_detach_observer.dispose) + task.cancel() + ensure_future(await_task(), loop=loop) + return dispose -def asyncgen_to_observable(asyncgen): - def emit(observer): - ensure_future(iterate_asyncgen(asyncgen, observer)) - return AsyncgenObservable(emit, asyncgen) + return AnonymousObservable(emit) async def iterate_asyncgen(asyncgen, observer): @@ -130,5 +120,7 @@ async def iterate_asyncgen(asyncgen, observer): async for item in asyncgen: observer.on_next(item) observer.on_completed() + except CancelledError: + pass except Exception as e: observer.on_error(e) diff --git a/graphql/execution/middleware.py b/graphql/execution/middleware.py index 7a0ad386..ce81da47 100644 --- a/graphql/execution/middleware.py +++ b/graphql/execution/middleware.py @@ -10,9 +10,9 @@ class MiddlewareManager(object): __slots__ = "middlewares", "wrap_in_promise", "_middleware_resolvers", "_cached_resolvers" - def __init__(self, *middlewares, wrap_in_promise=True): + def __init__(self, *middlewares, **kwargs): self.middlewares = middlewares - self.wrap_in_promise = wrap_in_promise + self.wrap_in_promise = kwargs.get('wrap_in_promise', True) self._middleware_resolvers = list(get_middleware_resolvers(middlewares)) if middlewares else [] self._cached_resolvers = {}