diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 7929cd5a..7ba88529 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -103,11 +103,12 @@ class ExecutionResult(object): query, `errors` is null if no errors occurred, and is a non-empty array if an error occurred.""" - __slots__ = 'data', 'errors', 'invalid' + __slots__ = 'data', 'errors', 'invalid', 'variable_values' - def __init__(self, data=None, errors=None, invalid=False): + def __init__(self, data=None, errors=None, invalid=False, variable_values=None): self.data = data self.errors = errors + self.variable_values = variable_values if invalid: assert data is None diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 202ba981..d4ab6d1d 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -6,6 +6,7 @@ from six import string_types from promise import Promise, promise_for_dict, is_thenable +from ..type.directives import GraphQLExportDirective from ..error import GraphQLError, GraphQLLocatedError from ..pyutils.default_ordered_dict import DefaultOrderedDict from ..pyutils.ordereddict import OrderedDict @@ -72,7 +73,7 @@ def on_rejected(error): def on_resolve(data): if not context.errors: - return ExecutionResult(data=data) + return ExecutionResult(data=data, variable_values=context.variable_values) return ExecutionResult(data=data, errors=context.errors) promise = Promise(executor).catch(on_rejected).then(on_resolve) @@ -133,6 +134,13 @@ def execute_fields(exe_context, parent_type, source_value, fields): for response_name, field_asts in fields.items(): result = resolve_field(exe_context, parent_type, source_value, field_asts) + directives = field_asts[0].directives + if directives: + for directive in directives: + if directive.name.value == GraphQLExportDirective.name: + variable_name = directive.arguments[0].value.value + exe_context.variable_values.setdefault(variable_name, []).append(result) + if result is Undefined: continue diff --git a/graphql/execution/tests/test_directives.py b/graphql/execution/tests/test_directives.py index 059a8050..06f781a5 100644 --- a/graphql/execution/tests/test_directives.py +++ b/graphql/execution/tests/test_directives.py @@ -35,6 +35,13 @@ def test_if_true_includes_scalar(): assert result.data == {'a': 'a', 'b': 'b'} +def test_export(): + result = execute_test_query('{ a, b @export(as: "ids") }') + assert not result.errors + assert result.data == {'a': 'a', 'b': 'b'} + assert result.variable_values == {'ids': ['b']} + + def test_if_false_omits_on_scalar(): result = execute_test_query('{ a, b @include(if: false) }') assert not result.errors diff --git a/graphql/execution/tests/test_nonnull.py b/graphql/execution/tests/test_nonnull.py index 65d7f098..1cbaa1ab 100644 --- a/graphql/execution/tests/test_nonnull.py +++ b/graphql/execution/tests/test_nonnull.py @@ -117,7 +117,6 @@ def test_nulls_a_nullable_field_that_throws_sync(): sync } ''' - check(doc, ThrowingData(), { 'data': {'sync': None}, 'errors': [{'locations': [{'column': 13, 'line': 3}], 'message': str(sync_error)}] diff --git a/graphql/type/directives.py b/graphql/type/directives.py index 06c920de..060e3e1d 100644 --- a/graphql/type/directives.py +++ b/graphql/type/directives.py @@ -103,6 +103,21 @@ def __init__(self, name, description=None, args=None, locations=None): ] ) +"""Used to export variable values as the result of a query (for batching).""" +GraphQLExportDirective = GraphQLDirective( + name='export', + description='Exports variable values from the results of the query.', + args={ + 'as': GraphQLArgument( + type=GraphQLNonNull(GraphQLString), + description='Variable name used for export.', + ), + }, + locations=[ + DirectiveLocation.FIELD + ] +) + """Constant string used for default reason for a deprecation.""" DEFAULT_DEPRECATION_REASON = 'No longer supported'