From 546fc2844aab73a2f85482aca43d4483104ac9af Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:39:41 +0100 Subject: [PATCH 1/5] Extract completeListValue funciton from CompleteValue Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/ab774705cf57fd21faf58ce33c360c21d13fe607 --- graphql/execution/executor.py | 36 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 92aec6b3..625a780e 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -251,21 +251,7 @@ def complete_value(self, ctx, return_type, field_asts, info, result): # If field type is List, complete each item in the list with the inner type if isinstance(return_type, GraphQLList): - assert isinstance(result, collections.Iterable), \ - ('User Error: expected iterable, but did not find one' + - 'for field {}.{}').format(info.parent_type, info.field_name) - - item_type = return_type.of_type - completed_results = [] - contains_deferred = False - for item in result: - completed_item = self.complete_value_catching_error(ctx, item_type, field_asts, info, item) - if not contains_deferred and isinstance(completed_item, Deferred): - contains_deferred = True - - completed_results.append(completed_item) - - return DeferredList(completed_results) if contains_deferred else completed_results + return self.complete_list_value(ctx, return_type, field_asts, info, result) # If field type is Scalar or Enum, serialize to a valid value, returning null if coercion is not possible. if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)): @@ -312,6 +298,26 @@ def complete_value(self, ctx, return_type, field_asts, info, result): return self._execute_fields(ctx, runtime_type, result, subfield_asts) + def complete_list_value(self, ctx, return_type, field_asts, info, result): + """ + Complete a list value by completing each item in the list with the inner type + """ + assert isinstance(result, collections.Iterable), \ + ('User Error: expected iterable, but did not find one' + + 'for field {}.{}').format(info.parent_type, info.field_name) + + item_type = return_type.of_type + completed_results = [] + contains_deferred = False + for item in result: + completed_item = self.complete_value_catching_error(ctx, item_type, field_asts, info, item) + if not contains_deferred and isinstance(completed_item, Deferred): + contains_deferred = True + + completed_results.append(completed_item) + + return DeferredList(completed_results) if contains_deferred else completed_results + def resolve_or_error(self, resolve_fn, source, args, info): curried_resolve_fn = functools.partial(resolve_fn, source, args, info) From 6cf4407ef6bff7f8ddff2d2f40bc24558a3d9e88 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:44:28 +0100 Subject: [PATCH 2/5] Extract completeLeafValue from CompleteValue Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/4c5904c69e61fe2bc62cbbbe007016c2d82e24f2 --- graphql/execution/executor.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 625a780e..0c20e9a2 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -255,12 +255,7 @@ def complete_value(self, ctx, return_type, field_asts, info, result): # If field type is Scalar or Enum, serialize to a valid value, returning null if coercion is not possible. if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)): - serialized_result = return_type.serialize(result) - - if serialized_result is None: - return None - - return serialized_result + return self.complete_leaf_value(ctx, return_type, field_asts, info, result) runtime_type = None @@ -318,6 +313,17 @@ def complete_list_value(self, ctx, return_type, field_asts, info, result): return DeferredList(completed_results) if contains_deferred else completed_results + def complete_leaf_value(self, ctx, return_type, field_asts, info, result): + """ + 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 None: + return None + + return serialized_result + def resolve_or_error(self, resolve_fn, source, args, info): curried_resolve_fn = functools.partial(resolve_fn, source, args, info) From 3b51194320fe6e256d97ab133d5edc8b87990329 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:50:23 +0100 Subject: [PATCH 3/5] Extract completeObjectValue from CompleteValue Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/5a136489f59c934dd9183caac8607c3415a7be07 --- graphql/execution/executor.py | 50 ++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 0c20e9a2..3a6959a1 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -257,13 +257,13 @@ def complete_value(self, ctx, return_type, field_asts, info, result): if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)): return self.complete_leaf_value(ctx, return_type, field_asts, info, result) - runtime_type = None + if isinstance(return_type, GraphQLObjectType): + return self.complete_object_value(ctx, return_type, field_asts, info, result) # Field type must be Object, Interface or Union and expect sub-selections. - if isinstance(return_type, GraphQLObjectType): - runtime_type = return_type + runtime_type = None - elif isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): + if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): runtime_type = return_type.resolve_type(result, info) if runtime_type and not return_type.is_possible_type(runtime_type): raise GraphQLError( @@ -274,24 +274,7 @@ def complete_value(self, ctx, return_type, field_asts, info, result): if not runtime_type: return None - if runtime_type.is_type_of and not runtime_type.is_type_of(result, info): - raise GraphQLError( - u'Expected value of type "{}" but got {}.'.format(return_type, type(result).__name__), - field_asts - ) - - # Collect sub-fields to execute to complete this value. - subfield_asts = DefaultOrderedDict(list) if self._enforce_strict_ordering else collections.defaultdict(list) - visited_fragment_names = set() - for field_ast in field_asts: - selection_set = field_ast.selection_set - if selection_set: - subfield_asts = collect_fields( - ctx, runtime_type, selection_set, - subfield_asts, visited_fragment_names - ) - - return self._execute_fields(ctx, runtime_type, result, subfield_asts) + return self.complete_object_value(ctx, runtime_type, field_asts, info, result) def complete_list_value(self, ctx, return_type, field_asts, info, result): """ @@ -324,6 +307,29 @@ def complete_leaf_value(self, ctx, return_type, field_asts, info, result): return serialized_result + def complete_object_value(self, ctx, return_type, field_asts, info, result): + """ + Complete an Object value by evaluating all sub-selections. + """ + if return_type.is_type_of and not return_type.is_type_of(result, info): + raise GraphQLError( + u'Expected value of type "{}" but got {}.'.format(return_type, type(result).__name__), + field_asts + ) + + # Collect sub-fields to execute to complete this value. + subfield_asts = DefaultOrderedDict(list) if self._enforce_strict_ordering else collections.defaultdict(list) + visited_fragment_names = set() + for field_ast in field_asts: + selection_set = field_ast.selection_set + if selection_set: + subfield_asts = collect_fields( + ctx, return_type, selection_set, + subfield_asts, visited_fragment_names + ) + + return self._execute_fields(ctx, return_type, result, subfield_asts) + def resolve_or_error(self, resolve_fn, source, args, info): curried_resolve_fn = functools.partial(resolve_fn, source, args, info) From a033eaeb20e34d8936f8a2e2c9ff5b544362cfe2 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:54:50 +0100 Subject: [PATCH 4/5] Extract completeAbstractValue from CompleteValue Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/d484f3186a7d9933257a73a7f1240e9abe68895d --- graphql/execution/executor.py | 39 +++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 3a6959a1..1797547e 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -214,6 +214,8 @@ def complete_value(self, ctx, return_type, field_asts, info, result): 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. """ @@ -260,21 +262,11 @@ def complete_value(self, ctx, return_type, field_asts, info, result): if isinstance(return_type, GraphQLObjectType): return self.complete_object_value(ctx, return_type, field_asts, info, result) - # Field type must be Object, Interface or Union and expect sub-selections. - runtime_type = None - if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): - runtime_type = return_type.resolve_type(result, info) - if runtime_type and not return_type.is_possible_type(runtime_type): - raise GraphQLError( - u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), - field_asts - ) - - if not runtime_type: - return None + return self.complete_abstract_value(ctx, return_type, field_asts, info, result) - return self.complete_object_value(ctx, runtime_type, field_asts, info, result) + # Not reachable + return None def complete_list_value(self, ctx, return_type, field_asts, info, result): """ @@ -330,6 +322,27 @@ def complete_object_value(self, ctx, return_type, field_asts, info, result): return self._execute_fields(ctx, return_type, result, subfield_asts) + def complete_abstract_value(self, ctx, return_type, field_asts, info, result): + """ + Complete an value of an abstract type by determining the runtime type of that value, then completing based + on that type. + """ + # Field type must be Object, Interface or Union and expect sub-selections. + runtime_type = None + + if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): + runtime_type = return_type.resolve_type(result, info) + if runtime_type and not return_type.is_possible_type(runtime_type): + raise GraphQLError( + u'Runtime Object type "{}" is not a possible type for "{}".'.format(runtime_type, return_type), + field_asts + ) + + if not runtime_type: + return None + + return self.complete_object_value(ctx, runtime_type, field_asts, info, result) + def resolve_or_error(self, resolve_fn, source, args, info): curried_resolve_fn = functools.partial(resolve_fn, source, args, info) From 968d605551b7d5bfd532d2d17f00309ee675ef40 Mon Sep 17 00:00:00 2001 From: Sam Cooke Date: Thu, 21 Apr 2016 11:56:33 +0100 Subject: [PATCH 5/5] Add invariant for unreachable condition Related GraphQL-js commit: https://github.com/graphql/graphql-js/commit/7a15a3f17452fadf2cc7d4f43fd41a7c24b38c8a --- graphql/execution/executor.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 1797547e..b34188eb 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -265,8 +265,7 @@ def complete_value(self, ctx, return_type, field_asts, info, result): if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)): return self.complete_abstract_value(ctx, return_type, field_asts, info, result) - # Not reachable - return None + assert False, u'Cannot complete value of unexpected type "{}"'.format(return_type) def complete_list_value(self, ctx, return_type, field_asts, info, result): """