123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251 |
- from asyncio import ensure_future, gather
- from collections.abc import Mapping
- from inspect import isawaitable
- from typing import (
- Any,
- AsyncIterable,
- Awaitable,
- Callable,
- Dict,
- Iterable,
- List,
- Optional,
- Union,
- Tuple,
- Type,
- cast,
- )
- try:
- from typing import TypedDict
- except ImportError: # Python < 3.8
- from typing_extensions import TypedDict
- from ..error import GraphQLError, GraphQLFormattedError, located_error
- from ..language import (
- DocumentNode,
- FieldNode,
- FragmentDefinitionNode,
- OperationDefinitionNode,
- OperationType,
- )
- from ..pyutils import (
- inspect,
- is_awaitable as default_is_awaitable,
- is_iterable,
- AwaitableOrValue,
- Path,
- Undefined,
- )
- from ..type import (
- GraphQLAbstractType,
- GraphQLField,
- GraphQLLeafType,
- GraphQLList,
- GraphQLNonNull,
- GraphQLObjectType,
- GraphQLOutputType,
- GraphQLSchema,
- GraphQLFieldResolver,
- GraphQLTypeResolver,
- GraphQLResolveInfo,
- SchemaMetaFieldDef,
- TypeMetaFieldDef,
- TypeNameMetaFieldDef,
- assert_valid_schema,
- is_abstract_type,
- is_leaf_type,
- is_list_type,
- is_non_null_type,
- is_object_type,
- )
- from .collect_fields import collect_fields, collect_sub_fields
- from .middleware import MiddlewareManager
- from .values import get_argument_values, get_variable_values
- __all__ = [
- "assert_valid_execution_arguments",
- "default_field_resolver",
- "default_type_resolver",
- "execute",
- "execute_sync",
- "get_field_def",
- "ExecutionResult",
- "ExecutionContext",
- "FormattedExecutionResult",
- "Middleware",
- ]
- # Terminology
- #
- # "Definitions" are the generic name for top-level statements in the document.
- # Examples of this include:
- # 1) Operations (such as a query)
- # 2) Fragments
- #
- # "Operations" are a generic name for requests in the document.
- # Examples of this include:
- # 1) query,
- # 2) mutation
- #
- # "Selections" are the definitions that can appear legally and at
- # single level of the query. These include:
- # 1) field references e.g "a"
- # 2) fragment "spreads" e.g. "...c"
- # 3) inline fragment "spreads" e.g. "...on Type { a }"
- class FormattedExecutionResult(TypedDict, total=False):
- """Formatted execution result"""
- errors: List[GraphQLFormattedError]
- data: Optional[Dict[str, Any]]
- extensions: Dict[str, Any]
- class ExecutionResult:
- """The result of GraphQL execution.
- - ``data`` is the result of a successful execution of the query.
- - ``errors`` is included when any errors occurred as a non-empty list.
- - ``extensions`` is reserved for adding non-standard properties.
- """
- __slots__ = "data", "errors", "extensions"
- data: Optional[Dict[str, Any]]
- errors: Optional[List[GraphQLError]]
- extensions: Optional[Dict[str, Any]]
- def __init__(
- self,
- data: Optional[Dict[str, Any]] = None,
- errors: Optional[List[GraphQLError]] = None,
- extensions: Optional[Dict[str, Any]] = None,
- ):
- self.data = data
- self.errors = errors
- self.extensions = extensions
- def __repr__(self) -> str:
- name = self.__class__.__name__
- ext = "" if self.extensions is None else f", extensions={self.extensions}"
- return f"{name}(data={self.data!r}, errors={self.errors!r}{ext})"
- def __iter__(self) -> Iterable[Any]:
- return iter((self.data, self.errors))
- @property
- def formatted(self) -> FormattedExecutionResult:
- """Get execution result formatted according to the specification."""
- formatted: FormattedExecutionResult = {"data": self.data}
- if self.errors is not None:
- formatted["errors"] = [error.formatted for error in self.errors]
- if self.extensions is not None:
- formatted["extensions"] = self.extensions
- return formatted
- def __eq__(self, other: Any) -> bool:
- if isinstance(other, dict):
- if "extensions" not in other:
- return other == dict(data=self.data, errors=self.errors)
- return other == dict(
- data=self.data, errors=self.errors, extensions=self.extensions
- )
- if isinstance(other, tuple):
- if len(other) == 2:
- return other == (self.data, self.errors)
- return other == (self.data, self.errors, self.extensions)
- return (
- isinstance(other, self.__class__)
- and other.data == self.data
- and other.errors == self.errors
- and other.extensions == self.extensions
- )
- def __ne__(self, other: Any) -> bool:
- return not self == other
- Middleware = Optional[Union[Tuple, List, MiddlewareManager]]
- class ExecutionContext:
- """Data that must be available at all points during query execution.
- Namely, schema of the type system that is currently executing, and the fragments
- defined in the query document.
- """
- schema: GraphQLSchema
- fragments: Dict[str, FragmentDefinitionNode]
- root_value: Any
- context_value: Any
- operation: OperationDefinitionNode
- variable_values: Dict[str, Any]
- field_resolver: GraphQLFieldResolver
- type_resolver: GraphQLTypeResolver
- subscribe_field_resolver: GraphQLFieldResolver
- errors: List[GraphQLError]
- middleware_manager: Optional[MiddlewareManager]
- is_awaitable = staticmethod(default_is_awaitable)
- def __init__(
- self,
- schema: GraphQLSchema,
- fragments: Dict[str, FragmentDefinitionNode],
- root_value: Any,
- context_value: Any,
- operation: OperationDefinitionNode,
- variable_values: Dict[str, Any],
- field_resolver: GraphQLFieldResolver,
- type_resolver: GraphQLTypeResolver,
- subscribe_field_resolver: GraphQLFieldResolver,
- errors: List[GraphQLError],
- middleware_manager: Optional[MiddlewareManager],
- is_awaitable: Optional[Callable[[Any], bool]],
- ) -> None:
- self.schema = schema
- self.fragments = fragments
- self.root_value = root_value
- self.context_value = context_value
- self.operation = operation
- self.variable_values = variable_values
- self.field_resolver = field_resolver # type: ignore
- self.type_resolver = type_resolver # type: ignore
- self.subscribe_field_resolver = subscribe_field_resolver # type: ignore
- self.errors = errors
- self.middleware_manager = middleware_manager
- if is_awaitable:
- self.is_awaitable = is_awaitable
- self._subfields_cache: Dict[Tuple, Dict[str, List[FieldNode]]] = {}
- @classmethod
- def build(
- cls,
- schema: GraphQLSchema,
- document: DocumentNode,
- root_value: Any = None,
- context_value: Any = None,
- raw_variable_values: Optional[Dict[str, Any]] = None,
- operation_name: Optional[str] = None,
- field_resolver: Optional[GraphQLFieldResolver] = None,
- type_resolver: Optional[GraphQLTypeResolver] = None,
- subscribe_field_resolver: Optional[GraphQLFieldResolver] = None,
- middleware: Optional[Middleware] = None,
- is_awaitable: Optional[Callable[[Any], bool]] = None,
- ) -> Union[List[GraphQLError], "ExecutionContext"]:
- """Build an execution context
- Constructs a ExecutionContext object from the arguments passed to execute, which
- we will pass throughout the other execution methods.
- Throws a GraphQLError if a valid execution context cannot be created.
- For internal use only.
- """
- operation: Optional[OperationDefinitionNode] = None
- fragments: Dict[str, FragmentDefinitionNode] = {}
- middleware_manager: Optional[MiddlewareManager] = None
- if middleware is not None:
- if isinstance(middleware, (list, tuple)):
- middleware_manager = MiddlewareManager(*middleware)
- elif isinstance(middleware, MiddlewareManager):
- middleware_manager = middleware
- else:
- raise TypeError(
- "Middleware must be passed as a list or tuple of functions"
- " or objects, or as a single MiddlewareManager object."
- f" Got {inspect(middleware)} instead."
- )
- for definition in document.definitions:
- if isinstance(definition, OperationDefinitionNode):
- if operation_name is None:
- if operation:
- return [
- GraphQLError(
- "Must provide operation name"
- " if query contains multiple operations."
- )
- ]
- operation = definition
- elif definition.name and definition.name.value == operation_name:
- operation = definition
- elif isinstance(definition, FragmentDefinitionNode):
- fragments[definition.name.value] = definition
- if not operation:
- if operation_name is not None:
- return [GraphQLError(f"Unknown operation named '{operation_name}'.")]
- return [GraphQLError("Must provide an operation.")]
- coerced_variable_values = get_variable_values(
- schema,
- operation.variable_definitions or (),
- raw_variable_values or {},
- max_errors=50,
- )
- if isinstance(coerced_variable_values, list):
- return coerced_variable_values # errors
- return cls(
- schema,
- fragments,
- root_value,
- context_value,
- operation,
- coerced_variable_values, # coerced values
- field_resolver or default_field_resolver,
- type_resolver or default_type_resolver,
- subscribe_field_resolver or default_field_resolver,
- [],
- middleware_manager,
- is_awaitable,
- )
- @staticmethod
- def build_response(
- data: Optional[Dict[str, Any]], errors: List[GraphQLError]
- ) -> ExecutionResult:
- """Build response.
- Given a completed execution context and data, build the (data, errors) response
- defined by the "Response" section of the GraphQL spec.
- """
- if not errors:
- return ExecutionResult(data, None)
- # Sort the error list in order to make it deterministic, since we might have
- # been using parallel execution.
- errors.sort(
- key=lambda error: (error.locations or [], error.path or [], error.message)
- )
- return ExecutionResult(data, errors)
- def execute_operation(
- self, operation: OperationDefinitionNode, root_value: Any
- ) -> Optional[AwaitableOrValue[Any]]:
- """Execute an operation.
- Implements the "Executing operations" section of the spec.
- """
- root_type = self.schema.get_root_type(operation.operation)
- if root_type is None:
- raise GraphQLError(
- "Schema is not configured to execute"
- f" {operation.operation.value} operation.",
- operation,
- )
- root_fields = collect_fields(
- self.schema,
- self.fragments,
- self.variable_values,
- root_type,
- operation.selection_set,
- )
- path = None
- return (
- self.execute_fields_serially
- if operation.operation == OperationType.MUTATION
- else self.execute_fields
- )(root_type, root_value, path, root_fields)
- def execute_fields_serially(
- self,
- parent_type: GraphQLObjectType,
- source_value: Any,
- path: Optional[Path],
- fields: Dict[str, List[FieldNode]],
- ) -> AwaitableOrValue[Dict[str, Any]]:
- """Execute the given fields serially.
- Implements the "Executing selection sets" section of the spec
- for fields that must be executed serially.
- """
- results: AwaitableOrValue[Dict[str, Any]] = {}
- is_awaitable = self.is_awaitable
- for response_name, field_nodes in fields.items():
- field_path = Path(path, response_name, parent_type.name)
- result = self.execute_field(
- parent_type, source_value, field_nodes, field_path
- )
- if result is Undefined:
- continue
- if is_awaitable(results):
- # noinspection PyShadowingNames
- async def await_and_set_result(
- results: Awaitable[Dict[str, Any]],
- response_name: str,
- result: AwaitableOrValue[Any],
- ) -> Dict[str, Any]:
- awaited_results = await results
- awaited_results[response_name] = (
- await result if is_awaitable(result) else result
- )
- return awaited_results
- results = await_and_set_result(
- cast(Awaitable, results), response_name, result
- )
- elif is_awaitable(result):
- # noinspection PyShadowingNames
- async def set_result(
- results: Dict[str, Any],
- response_name: str,
- result: Awaitable,
- ) -> Dict[str, Any]:
- results[response_name] = await result
- return results
- results = set_result(
- cast(Dict[str, Any], results), response_name, result
- )
- else:
- cast(Dict[str, Any], results)[response_name] = result
- return results
- def execute_fields(
- self,
- parent_type: GraphQLObjectType,
- source_value: Any,
- path: Optional[Path],
- fields: Dict[str, List[FieldNode]],
- ) -> AwaitableOrValue[Dict[str, Any]]:
- """Execute the given fields concurrently.
- Implements the "Executing selection sets" section of the spec
- for fields that may be executed in parallel.
- """
- results = {}
- is_awaitable = self.is_awaitable
- awaitable_fields: List[str] = []
- append_awaitable = awaitable_fields.append
- for response_name, field_nodes in fields.items():
- field_path = Path(path, response_name, parent_type.name)
- result = self.execute_field(
- parent_type, source_value, field_nodes, field_path
- )
- if result is not Undefined:
- results[response_name] = result
- if is_awaitable(result):
- append_awaitable(response_name)
- # If there are no coroutines, we can just return the object
- if not awaitable_fields:
- return results
- # Otherwise, results is a map from field name to the result of resolving that
- # field, which is possibly a coroutine object. Return a coroutine object that
- # will yield this same map, but with any coroutines awaited in parallel and
- # replaced with the values they yielded.
- async def get_results() -> Dict[str, Any]:
- results.update(
- zip(
- awaitable_fields,
- await gather(*(results[field] for field in awaitable_fields)),
- )
- )
- return results
- return get_results()
- def build_resolve_info(
- self,
- field_def: GraphQLField,
- field_nodes: List[FieldNode],
- parent_type: GraphQLObjectType,
- path: Path,
- ) -> GraphQLResolveInfo:
- """Build the GraphQLResolveInfo object.
- For internal use only."""
- # The resolve function's first argument is a collection of information about
- # the current execution state.
- return GraphQLResolveInfo(
- field_nodes[0].name.value,
- field_nodes,
- field_def.type,
- parent_type,
- path,
- self.schema,
- self.fragments,
- self.root_value,
- self.operation,
- self.variable_values,
- self.context_value,
- self.is_awaitable,
- )
- def execute_field(
- self,
- parent_type: GraphQLObjectType,
- source: Any,
- field_nodes: List[FieldNode],
- path: Path,
- ) -> AwaitableOrValue[Any]:
- """Resolve the field on the given source object.
- Implements the "Executing fields" section of the spec.
- In particular, this method figures out the value that the field returns by
- calling its resolve function, then calls complete_value to await coroutine
- objects, serialize scalars, or execute the sub-selection-set for objects.
- """
- field_def = get_field_def(self.schema, parent_type, field_nodes[0])
- if not field_def:
- return Undefined
- return_type = field_def.type
- resolve_fn = field_def.resolve or self.field_resolver
- if self.middleware_manager:
- resolve_fn = self.middleware_manager.get_field_resolver(resolve_fn)
- info = self.build_resolve_info(field_def, field_nodes, parent_type, path)
- # Get the resolve function, regardless of if its result is normal or abrupt
- # (error).
- try:
- # Build a dictionary of arguments from the field.arguments AST, using the
- # variables scope to fulfill any variable references.
- args = get_argument_values(field_def, field_nodes[0], self.variable_values)
- # Note that contrary to the JavaScript implementation, we pass the context
- # value as part of the resolve info.
- result = resolve_fn(source, info, **args)
- if self.is_awaitable(result):
- # noinspection PyShadowingNames
- async def await_result() -> Any:
- try:
- completed = self.complete_value(
- return_type, field_nodes, info, path, await result
- )
- if self.is_awaitable(completed):
- return await completed
- return completed
- except Exception as raw_error:
- error = located_error(raw_error, field_nodes, path.as_list())
- self.handle_field_error(error, return_type)
- return None
- return await_result()
- completed = self.complete_value(
- return_type, field_nodes, info, path, result
- )
- if self.is_awaitable(completed):
- # noinspection PyShadowingNames
- async def await_completed() -> Any:
- try:
- return await completed
- except Exception as raw_error:
- error = located_error(raw_error, field_nodes, path.as_list())
- self.handle_field_error(error, return_type)
- return None
- return await_completed()
- return completed
- except Exception as raw_error:
- error = located_error(raw_error, field_nodes, path.as_list())
- self.handle_field_error(error, return_type)
- return None
- def handle_field_error(
- self,
- error: GraphQLError,
- return_type: GraphQLOutputType,
- ) -> None:
- # If the field type is non-nullable, then it is resolved without any protection
- # from errors, however it still properly locates the error.
- if is_non_null_type(return_type):
- raise error
- # Otherwise, error protection is applied, logging the error and resolving a
- # null value for this field if one is encountered.
- self.errors.append(error)
- return None
- def complete_value(
- self,
- return_type: GraphQLOutputType,
- field_nodes: List[FieldNode],
- info: GraphQLResolveInfo,
- path: Path,
- result: Any,
- ) -> AwaitableOrValue[Any]:
- """Complete a value.
- Implements the instructions for completeValue as defined in the
- "Value completion" section of the spec.
- If the field type is Non-Null, then this recursively completes the value
- for the inner type. It throws a field error if that completion returns null,
- as per the "Nullability" section of the spec.
- If the field type is a List, then this recursively completes the value
- for the inner type on each item in the list.
- If the field type is a Scalar or Enum, ensures the completed value is a legal
- value of the type by calling the ``serialize`` method of GraphQL type
- definition.
- If the field is an abstract type, determine the runtime type of the value and
- then complete based on that type.
- Otherwise, the field type expects a sub-selection set, and will complete the
- value by evaluating all sub-selections.
- """
- # If result is an Exception, throw a located error.
- if isinstance(result, Exception):
- raise result
- # If field type is NonNull, complete for inner type, and throw field error if
- # result is null.
- if is_non_null_type(return_type):
- completed = self.complete_value(
- cast(GraphQLNonNull, return_type).of_type,
- field_nodes,
- info,
- path,
- result,
- )
- if completed is None:
- raise TypeError(
- "Cannot return null for non-nullable field"
- f" {info.parent_type.name}.{info.field_name}."
- )
- return completed
- # If result value is null or undefined then return null.
- if result is None or result is Undefined:
- return None
- # If field type is List, complete each item in the list with inner type
- if is_list_type(return_type):
- return self.complete_list_value(
- cast(GraphQLList, return_type), field_nodes, info, path, result
- )
- # If field type is a leaf type, Scalar or Enum, serialize to a valid value,
- # returning null if serialization is not possible.
- if is_leaf_type(return_type):
- return self.complete_leaf_value(cast(GraphQLLeafType, return_type), result)
- # If field type is an abstract type, Interface or Union, determine the runtime
- # Object type and complete for that type.
- if is_abstract_type(return_type):
- return self.complete_abstract_value(
- cast(GraphQLAbstractType, return_type), field_nodes, info, path, result
- )
- # If field type is Object, execute and complete all sub-selections.
- if is_object_type(return_type):
- return self.complete_object_value(
- cast(GraphQLObjectType, return_type), field_nodes, info, path, result
- )
- # Not reachable. All possible output types have been considered.
- raise TypeError( # pragma: no cover
- "Cannot complete value of unexpected output type:"
- f" '{inspect(return_type)}'."
- )
- def complete_list_value(
- self,
- return_type: GraphQLList[GraphQLOutputType],
- field_nodes: List[FieldNode],
- info: GraphQLResolveInfo,
- path: Path,
- result: Union[AsyncIterable[Any], Iterable[Any]],
- ) -> AwaitableOrValue[List[Any]]:
- """Complete a list value.
- Complete a list value by completing each item in the list with the inner type.
- """
- if not is_iterable(result):
- # experimental: allow async iterables
- if isinstance(result, AsyncIterable):
- # noinspection PyShadowingNames
- async def async_iterable_to_list(
- async_result: AsyncIterable[Any],
- ) -> Any:
- sync_result = [item async for item in async_result]
- return self.complete_list_value(
- return_type, field_nodes, info, path, sync_result
- )
- return async_iterable_to_list(result)
- raise GraphQLError(
- "Expected Iterable, but did not find one for field"
- f" '{info.parent_type.name}.{info.field_name}'."
- )
- result = cast(Iterable[Any], result)
- # This is specified as a simple map, however we're optimizing the path where
- # the list contains no coroutine objects by avoiding creating another coroutine
- # object.
- item_type = return_type.of_type
- is_awaitable = self.is_awaitable
- awaitable_indices: List[int] = []
- append_awaitable = awaitable_indices.append
- completed_results: List[Any] = []
- append_result = completed_results.append
- for index, item in enumerate(result):
- # No need to modify the info object containing the path, since from here on
- # it is not ever accessed by resolver functions.
- item_path = path.add_key(index, None)
- completed_item: AwaitableOrValue[Any]
- if is_awaitable(item):
- # noinspection PyShadowingNames
- async def await_completed(item: Any, item_path: Path) -> Any:
- try:
- completed = self.complete_value(
- item_type, field_nodes, info, item_path, await item
- )
- if is_awaitable(completed):
- return await completed
- return completed
- except Exception as raw_error:
- error = located_error(
- raw_error, field_nodes, item_path.as_list()
- )
- self.handle_field_error(error, item_type)
- return None
- completed_item = await_completed(item, item_path)
- else:
- try:
- completed_item = self.complete_value(
- item_type, field_nodes, info, item_path, item
- )
- if is_awaitable(completed_item):
- # noinspection PyShadowingNames
- async def await_completed(item: Any, item_path: Path) -> Any:
- try:
- return await item
- except Exception as raw_error:
- error = located_error(
- raw_error, field_nodes, item_path.as_list()
- )
- self.handle_field_error(error, item_type)
- return None
- completed_item = await_completed(completed_item, item_path)
- except Exception as raw_error:
- error = located_error(raw_error, field_nodes, item_path.as_list())
- self.handle_field_error(error, item_type)
- completed_item = None
- if is_awaitable(completed_item):
- append_awaitable(index)
- append_result(completed_item)
- if not awaitable_indices:
- return completed_results
- # noinspection PyShadowingNames
- async def get_completed_results() -> List[Any]:
- for index, result in zip(
- awaitable_indices,
- await gather(
- *(completed_results[index] for index in awaitable_indices)
- ),
- ):
- completed_results[index] = result
- return completed_results
- return get_completed_results()
- @staticmethod
- def complete_leaf_value(return_type: GraphQLLeafType, result: Any) -> Any:
- """Complete a leaf value.
- Complete a Scalar or Enum by serializing to a valid value, returning null if
- serialization is not possible.
- """
- serialized_result = return_type.serialize(result)
- if serialized_result is Undefined or serialized_result is None:
- raise TypeError(
- f"Expected `{inspect(return_type)}.serialize({inspect(result)})`"
- f" to return non-nullable value, returned: {inspect(serialized_result)}"
- )
- return serialized_result
- def complete_abstract_value(
- self,
- return_type: GraphQLAbstractType,
- field_nodes: List[FieldNode],
- info: GraphQLResolveInfo,
- path: Path,
- result: Any,
- ) -> AwaitableOrValue[Any]:
- """Complete an abstract value.
- Complete a value of an abstract type by determining the runtime object type of
- that value, then complete the value for that type.
- """
- resolve_type_fn = return_type.resolve_type or self.type_resolver
- runtime_type = resolve_type_fn(result, info, return_type) # type: ignore
- if self.is_awaitable(runtime_type):
- runtime_type = cast(Awaitable, runtime_type)
- async def await_complete_object_value() -> Any:
- value = self.complete_object_value(
- self.ensure_valid_runtime_type(
- await runtime_type, # type: ignore
- return_type,
- field_nodes,
- info,
- result,
- ),
- field_nodes,
- info,
- path,
- result,
- )
- if self.is_awaitable(value):
- return await value # type: ignore
- return value # pragma: no cover
- return await_complete_object_value()
- runtime_type = cast(Optional[str], runtime_type)
- return self.complete_object_value(
- self.ensure_valid_runtime_type(
- runtime_type, return_type, field_nodes, info, result
- ),
- field_nodes,
- info,
- path,
- result,
- )
- def ensure_valid_runtime_type(
- self,
- runtime_type_name: Any,
- return_type: GraphQLAbstractType,
- field_nodes: List[FieldNode],
- info: GraphQLResolveInfo,
- result: Any,
- ) -> GraphQLObjectType:
- if runtime_type_name is None:
- raise GraphQLError(
- f"Abstract type '{return_type.name}' must resolve"
- " to an Object type at runtime"
- f" for field '{info.parent_type.name}.{info.field_name}'."
- f" Either the '{return_type.name}' type should provide"
- " a 'resolve_type' function or each possible type should provide"
- " an 'is_type_of' function.",
- field_nodes,
- )
- if is_object_type(runtime_type_name): # pragma: no cover
- raise GraphQLError(
- "Support for returning GraphQLObjectType from resolve_type was"
- " removed in GraphQL-core 3.2, please return type name instead."
- )
- if not isinstance(runtime_type_name, str):
- raise GraphQLError(
- f"Abstract type '{return_type.name}' must resolve"
- " to an Object type at runtime"
- f" for field '{info.parent_type.name}.{info.field_name}' with value"
- f" {inspect(result)}, received '{inspect(runtime_type_name)}'.",
- field_nodes,
- )
- runtime_type = self.schema.get_type(runtime_type_name)
- if runtime_type is None:
- raise GraphQLError(
- f"Abstract type '{return_type.name}' was resolved to a type"
- f" '{runtime_type_name}' that does not exist inside the schema.",
- field_nodes,
- )
- if not is_object_type(runtime_type):
- raise GraphQLError(
- f"Abstract type '{return_type.name}' was resolved"
- f" to a non-object type '{runtime_type_name}'.",
- field_nodes,
- )
- runtime_type = cast(GraphQLObjectType, runtime_type)
- if not self.schema.is_sub_type(return_type, runtime_type):
- raise GraphQLError(
- f"Runtime Object type '{runtime_type.name}' is not a possible"
- f" type for '{return_type.name}'.",
- field_nodes,
- )
- return runtime_type
- def complete_object_value(
- self,
- return_type: GraphQLObjectType,
- field_nodes: List[FieldNode],
- info: GraphQLResolveInfo,
- path: Path,
- result: Any,
- ) -> AwaitableOrValue[Dict[str, Any]]:
- """Complete an Object value by executing all sub-selections."""
- # Collect sub-fields to execute to complete this value.
- sub_field_nodes = self.collect_subfields(return_type, field_nodes)
- # If there is an `is_type_of()` predicate function, call it with the current
- # result. If `is_type_of()` returns False, then raise an error rather than
- # continuing execution.
- if return_type.is_type_of:
- is_type_of = return_type.is_type_of(result, info)
- if self.is_awaitable(is_type_of):
- async def execute_subfields_async() -> Dict[str, Any]:
- if not await is_type_of: # type: ignore
- raise invalid_return_type_error(
- return_type, result, field_nodes
- )
- return self.execute_fields(
- return_type, result, path, sub_field_nodes
- ) # type: ignore
- return execute_subfields_async()
- if not is_type_of:
- raise invalid_return_type_error(return_type, result, field_nodes)
- return self.execute_fields(return_type, result, path, sub_field_nodes)
- def collect_subfields(
- self, return_type: GraphQLObjectType, field_nodes: List[FieldNode]
- ) -> Dict[str, List[FieldNode]]:
- """Collect subfields.
- A cached collection of relevant subfields with regard to the return type is
- kept in the execution context as ``_subfields_cache``. This ensures the
- subfields are not repeatedly calculated, which saves overhead when resolving
- lists of values.
- """
- cache = self._subfields_cache
- # We cannot use the field_nodes themselves as key for the cache, since they
- # are not hashable as a list. We also do not want to use the field_nodes
- # themselves (converted to a tuple) as keys, since hashing them is slow.
- # Therefore we use the ids of the field_nodes as keys. Note that we do not
- # use the id of the list, since we want to hit the cache for all lists of
- # the same nodes, not only for the same list of nodes. Also, the list id may
- # even be reused, in which case we would get wrong results from the cache.
- key = (
- (return_type, id(field_nodes[0]))
- if len(field_nodes) == 1 # optimize most frequent case
- else tuple((return_type, *map(id, field_nodes)))
- )
- sub_field_nodes = cache.get(key)
- if sub_field_nodes is None:
- sub_field_nodes = collect_sub_fields(
- self.schema,
- self.fragments,
- self.variable_values,
- return_type,
- field_nodes,
- )
- cache[key] = sub_field_nodes
- return sub_field_nodes
- def execute(
- schema: GraphQLSchema,
- document: DocumentNode,
- root_value: Any = None,
- context_value: Any = None,
- variable_values: Optional[Dict[str, Any]] = None,
- operation_name: Optional[str] = None,
- field_resolver: Optional[GraphQLFieldResolver] = None,
- type_resolver: Optional[GraphQLTypeResolver] = None,
- subscribe_field_resolver: Optional[GraphQLFieldResolver] = None,
- middleware: Optional[Middleware] = None,
- execution_context_class: Optional[Type["ExecutionContext"]] = None,
- is_awaitable: Optional[Callable[[Any], bool]] = None,
- ) -> AwaitableOrValue[ExecutionResult]:
- """Execute a GraphQL operation.
- Implements the "Executing requests" section of the GraphQL specification.
- Returns an ExecutionResult (if all encountered resolvers are synchronous),
- or a coroutine object eventually yielding an ExecutionResult.
- If the arguments to this function do not result in a legal execution context,
- a GraphQLError will be thrown immediately explaining the invalid input.
- """
- # If arguments are missing or incorrect, throw an error.
- assert_valid_execution_arguments(schema, document, variable_values)
- if execution_context_class is None:
- execution_context_class = ExecutionContext
- # If a valid execution context cannot be created due to incorrect arguments,
- # a "Response" with only errors is returned.
- exe_context = execution_context_class.build(
- schema,
- document,
- root_value,
- context_value,
- variable_values,
- operation_name,
- field_resolver,
- type_resolver,
- subscribe_field_resolver,
- middleware,
- is_awaitable,
- )
- # Return early errors if execution context failed.
- if isinstance(exe_context, list):
- return ExecutionResult(data=None, errors=exe_context)
- # Return a possible coroutine object that will eventually yield the data described
- # by the "Response" section of the GraphQL specification.
- #
- # If errors are encountered while executing a GraphQL field, only that field and
- # its descendants will be omitted, and sibling fields will still be executed. An
- # execution which encounters errors will still result in a coroutine object that
- # can be executed without errors.
- #
- # Errors from sub-fields of a NonNull type may propagate to the top level,
- # at which point we still log the error and null the parent field, which
- # in this case is the entire response.
- errors = exe_context.errors
- build_response = exe_context.build_response
- try:
- operation = exe_context.operation
- result = exe_context.execute_operation(operation, root_value)
- if exe_context.is_awaitable(result):
- # noinspection PyShadowingNames
- async def await_result() -> Any:
- try:
- return build_response(await result, errors) # type: ignore
- except GraphQLError as error:
- errors.append(error)
- return build_response(None, errors)
- return await_result()
- except GraphQLError as error:
- errors.append(error)
- return build_response(None, errors)
- else:
- return build_response(result, errors) # type: ignore
- def assume_not_awaitable(_value: Any) -> bool:
- """Replacement for isawaitable if everything is assumed to be synchronous."""
- return False
- def execute_sync(
- schema: GraphQLSchema,
- document: DocumentNode,
- root_value: Any = None,
- context_value: Any = None,
- variable_values: Optional[Dict[str, Any]] = None,
- operation_name: Optional[str] = None,
- field_resolver: Optional[GraphQLFieldResolver] = None,
- type_resolver: Optional[GraphQLTypeResolver] = None,
- middleware: Optional[Middleware] = None,
- execution_context_class: Optional[Type["ExecutionContext"]] = None,
- check_sync: bool = False,
- ) -> ExecutionResult:
- """Execute a GraphQL operation synchronously.
- Also implements the "Executing requests" section of the GraphQL specification.
- However, it guarantees to complete synchronously (or throw an error) assuming
- that all field resolvers are also synchronous.
- Set check_sync to True to still run checks that no awaitable values are returned.
- """
- is_awaitable = (
- check_sync
- if callable(check_sync)
- else (None if check_sync else assume_not_awaitable)
- )
- result = execute(
- schema,
- document,
- root_value,
- context_value,
- variable_values,
- operation_name,
- field_resolver,
- type_resolver,
- None,
- middleware,
- execution_context_class,
- is_awaitable,
- )
- # Assert that the execution was synchronous.
- if isawaitable(result):
- ensure_future(cast(Awaitable[ExecutionResult], result)).cancel()
- raise RuntimeError("GraphQL execution failed to complete synchronously.")
- return cast(ExecutionResult, result)
- def assert_valid_execution_arguments(
- schema: GraphQLSchema,
- document: DocumentNode,
- raw_variable_values: Optional[Dict[str, Any]] = None,
- ) -> None:
- """Check that the arguments are acceptable.
- Essential assertions before executing to provide developer feedback for improper use
- of the GraphQL library.
- For internal use only.
- """
- if not document:
- raise TypeError("Must provide document.")
- # If the schema used for execution is invalid, throw an error.
- assert_valid_schema(schema)
- # Variables, if provided, must be a dictionary.
- if not (raw_variable_values is None or isinstance(raw_variable_values, dict)):
- raise TypeError(
- "Variable values must be provided as a dictionary"
- " with variable names as keys. Perhaps look to see"
- " if an unparsed JSON string was provided."
- )
- def get_field_def(
- schema: GraphQLSchema, parent_type: GraphQLObjectType, field_node: FieldNode
- ) -> GraphQLField:
- """Get field definition.
- This method looks up the field on the given type definition. It has special casing
- for the three introspection fields, ``__schema``, ``__type`, and ``__typename``.
- ``__typename`` is special because it can always be queried as a field, even in
- situations where no other fields are allowed, like on a Union. ``__schema`` and
- ``__type`` could get automatically added to the query type, but that would require
- mutating type definitions, which would cause issues.
- For internal use only.
- """
- field_name = field_node.name.value
- if field_name == "__schema" and schema.query_type == parent_type:
- return SchemaMetaFieldDef
- elif field_name == "__type" and schema.query_type == parent_type:
- return TypeMetaFieldDef
- elif field_name == "__typename":
- return TypeNameMetaFieldDef
- return parent_type.fields.get(field_name)
- def invalid_return_type_error(
- return_type: GraphQLObjectType, result: Any, field_nodes: List[FieldNode]
- ) -> GraphQLError:
- """Create a GraphQLError for an invalid return type."""
- return GraphQLError(
- f"Expected value of type '{return_type.name}' but got: {inspect(result)}.",
- field_nodes,
- )
- def get_typename(value: Any) -> Optional[str]:
- """Get the ``__typename`` property of the given value."""
- if isinstance(value, Mapping):
- return value.get("__typename")
- # need to de-mangle the attribute assumed to be "private" in Python
- for cls in value.__class__.__mro__:
- __typename = getattr(value, f"_{cls.__name__}__typename", None)
- if __typename:
- return __typename
- return None
- def default_type_resolver(
- value: Any, info: GraphQLResolveInfo, abstract_type: GraphQLAbstractType
- ) -> AwaitableOrValue[Optional[str]]:
- """Default type resolver function.
- If a resolve_type function is not given, then a default resolve behavior is used
- which attempts two strategies:
- First, See if the provided value has a ``__typename`` field defined, if so, use that
- value as name of the resolved type.
- Otherwise, test each possible type for the abstract type by calling
- :meth:`~graphql.type.GraphQLObjectType.is_type_of` for the object
- being coerced, returning the first type that matches.
- """
- # First, look for `__typename`.
- type_name = get_typename(value)
- if isinstance(type_name, str):
- return type_name
- # Otherwise, test each possible type.
- possible_types = info.schema.get_possible_types(abstract_type)
- is_awaitable = info.is_awaitable
- awaitable_is_type_of_results: List[Awaitable] = []
- append_awaitable_results = awaitable_is_type_of_results.append
- awaitable_types: List[GraphQLObjectType] = []
- append_awaitable_types = awaitable_types.append
- for type_ in possible_types:
- if type_.is_type_of:
- is_type_of_result = type_.is_type_of(value, info)
- if is_awaitable(is_type_of_result):
- append_awaitable_results(cast(Awaitable, is_type_of_result))
- append_awaitable_types(type_)
- elif is_type_of_result:
- return type_.name
- if awaitable_is_type_of_results:
- # noinspection PyShadowingNames
- async def get_type() -> Optional[str]:
- is_type_of_results = await gather(*awaitable_is_type_of_results)
- for is_type_of_result, type_ in zip(is_type_of_results, awaitable_types):
- if is_type_of_result:
- return type_.name
- return None
- return get_type()
- return None
- def default_field_resolver(source: Any, info: GraphQLResolveInfo, **args: Any) -> Any:
- """Default field resolver.
- If a resolve function is not given, then a default resolve behavior is used which
- takes the property of the source object of the same name as the field and returns
- it as the result, or if it's a function, returns the result of calling that function
- while passing along args and context.
- For dictionaries, the field names are used as keys, for all other objects they are
- used as attribute names.
- """
- # Ensure source is a value for which property access is acceptable.
- field_name = info.field_name
- value = (
- source.get(field_name)
- if isinstance(source, Mapping)
- else getattr(source, field_name, None)
- )
- if callable(value):
- return value(info, **args)
- return value
|