From 701a6f7d1052ecfc1d6b005a47361bd1647566b2 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 01:06:21 -0700 Subject: [PATCH 1/9] Improved undefined variables usage --- graphql/__init__.py | 4 + graphql/execution/base.py | 3 +- graphql/execution/executor.py | 4 +- graphql/execution/experimental/fragment.py | 4 +- .../experimental/tests/test_variables.py | 7 +- graphql/execution/tests/test_variables.py | 7 +- graphql/execution/values.py | 136 ++++++++++-------- graphql/language/ast.py | 5 +- graphql/type/definition.py | 15 +- graphql/type/introspection.py | 4 +- graphql/utils/schema_printer.py | 4 +- graphql/utils/undefined.py | 11 ++ graphql/utils/value_from_ast.py | 29 ++-- 13 files changed, 135 insertions(+), 98 deletions(-) create mode 100644 graphql/utils/undefined.py diff --git a/graphql/__init__.py b/graphql/__init__.py index d53457d4..ac8c4025 100644 --- a/graphql/__init__.py +++ b/graphql/__init__.py @@ -112,6 +112,9 @@ # Un-modifiers get_nullable_type, get_named_type, + + # Undefined const + Undefined ) # Parse and operate on GraphQL language source files. @@ -284,4 +287,5 @@ 'type_from_ast', 'value_from_ast', 'get_version', + 'Undefined', ) diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 04b3629d..ca23fed9 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -4,7 +4,7 @@ from ..error import GraphQLError from ..language import ast from ..pyutils.default_ordered_dict import DefaultOrderedDict -from ..type.definition import Undefined, GraphQLInterfaceType, GraphQLUnionType +from ..type.definition import GraphQLInterfaceType, GraphQLUnionType from ..type.directives import GraphQLIncludeDirective, GraphQLSkipDirective from ..type.introspection import (SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef) @@ -75,7 +75,6 @@ def get_field_resolver(self, field_resolver): def get_argument_values(self, field_def, field_ast): k = field_def, field_ast result = self.argument_values_cache.get(k) - if not result: result = self.argument_values_cache[k] = get_argument_values(field_def.args, field_ast.arguments, self.variable_values) diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 797c2fd7..0e567255 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -9,10 +9,10 @@ from ..error import GraphQLError, GraphQLLocatedError from ..pyutils.default_ordered_dict import DefaultOrderedDict from ..pyutils.ordereddict import OrderedDict -from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList, +from ..type import (Undefined, GraphQLEnumType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLUnionType) -from .base import (ExecutionContext, ExecutionResult, ResolveInfo, Undefined, +from .base import (ExecutionContext, ExecutionResult, ResolveInfo, collect_fields, default_resolve_fn, get_field_def, get_operation_root_type) from .executors.sync import SyncExecutor diff --git a/graphql/execution/experimental/fragment.py b/graphql/execution/experimental/fragment.py index 427acbaf..41108eac 100644 --- a/graphql/execution/experimental/fragment.py +++ b/graphql/execution/experimental/fragment.py @@ -4,9 +4,9 @@ from ...pyutils.cached_property import cached_property from ...pyutils.default_ordered_dict import DefaultOrderedDict -from ...type import (GraphQLInterfaceType, GraphQLList, GraphQLNonNull, +from ...type import (Undefined, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLUnionType) -from ..base import ResolveInfo, Undefined, collect_fields, get_field_def +from ..base import ResolveInfo, collect_fields, get_field_def from ..values import get_argument_values from ...error import GraphQLError try: diff --git a/graphql/execution/experimental/tests/test_variables.py b/graphql/execution/experimental/tests/test_variables.py index 83e91a22..0bafa260 100644 --- a/graphql/execution/experimental/tests/test_variables.py +++ b/graphql/execution/experimental/tests/test_variables.py @@ -413,9 +413,10 @@ def test_passes_along_null_for_non_nullable_inputs_if_explcitly_set_in_the_query ''' check(doc, { - 'data': { - 'fieldWithNonNullableStringInput': None - } + 'errors': [{ + 'message': 'Argument "input" of required type String!" was not provided.' + }], + 'data': None }) diff --git a/graphql/execution/tests/test_variables.py b/graphql/execution/tests/test_variables.py index 83e91a22..0bafa260 100644 --- a/graphql/execution/tests/test_variables.py +++ b/graphql/execution/tests/test_variables.py @@ -413,9 +413,10 @@ def test_passes_along_null_for_non_nullable_inputs_if_explcitly_set_in_the_query ''' check(doc, { - 'data': { - 'fieldWithNonNullableStringInput': None - } + 'errors': [{ + 'message': 'Argument "input" of required type String!" was not provided.' + }], + 'data': None }) diff --git a/graphql/execution/values.py b/graphql/execution/values.py index 56008463..e42bba04 100644 --- a/graphql/execution/values.py +++ b/graphql/execution/values.py @@ -4,8 +4,9 @@ from six import string_types from ..error import GraphQLError +from ..language import ast from ..language.printer import print_ast -from ..type import (GraphQLEnumType, GraphQLInputObjectType, GraphQLList, +from ..type import (Undefined, GraphQLEnumType, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLScalarType, is_input_type) from ..utils.is_valid_value import is_valid_value from ..utils.type_from_ast import type_from_ast @@ -23,8 +24,43 @@ def get_variable_values(schema, definition_asts, inputs): values = {} for def_ast in definition_asts: var_name = def_ast.variable.name.value - value = get_variable_value(schema, def_ast, inputs.get(var_name)) - values[var_name] = value + var_type = type_from_ast(schema, def_ast.type) + value = inputs.get(var_name, Undefined) + + if not is_input_type(var_type): + raise GraphQLError( + 'Variable "${var_name}" expected value of type "{var_type}" which cannot be used as an input type.'.format( + var_name=var_name, + var_type=print_ast(def_ast.type), + ), + [def_ast] + ) + elif value is Undefined or value is None: + if def_ast.default_value is not None: + values[var_name] = value_from_ast(def_ast.default_value, var_type) + if isinstance(var_type, GraphQLNonNull): + raise GraphQLError( + 'Variable "${var_name}" of required type "{var_type}" was not provided.'.format( + var_name=var_name, var_type=var_type + ), [def_ast] + ) + else: + errors = is_valid_value(value, var_type) + if errors: + message = u'\n' + u'\n'.join(errors) + raise GraphQLError( + 'Variable "${}" got invalid value {}.{}'.format( + var_name, + json.dumps(value, sort_keys=True), + message + ), + [def_ast] + ) + coerced_value = coerce_value(var_type, value) + if coerced_value is Undefined: + raise Exception('Should have reported error.') + + values[var_name] = coerced_value return values @@ -42,20 +78,44 @@ def get_argument_values(arg_defs, arg_asts, variables=None): result = {} for name, arg_def in arg_defs.items(): - value_ast = arg_ast_map.get(name) + arg_type = arg_def.type + value_ast = arg_ast_map.get(name, Undefined) + if not value_ast: + if arg_def.default_value is not Undefined: + result[arg_def.out_name or name] = arg_def.default_value + continue + elif isinstance(arg_type, GraphQLNonNull): + raise GraphQLError('Argument "{name}" of required type {arg_type}" was not provided.'.format( + name=name, + arg_type=arg_type + ), arg_asts) + elif isinstance(value_ast.value, ast.Variable): + variable_name = value_ast.value.name.value + variable_value = variables.get(variable_name, Undefined) + if variables and variable_value is not Undefined: + result[arg_def.out_name or name] = variable_value + elif arg_def.default_value is not Undefined: + result[arg_def.out_name or name] = arg_def.default_value + elif isinstance(arg_type, GraphQLNonNull): + raise GraphQLError('Argument "{name}" of required type {arg_type}" provided the variable "${variable_name}" which was not provided'.format( + name=name, + arg_type=arg_type, + variable_name=variable_name + ), arg_asts) + continue + if value_ast: value_ast = value_ast.value value = value_from_ast( value_ast, - arg_def.type, + arg_type, variables ) - - if value is None: + if value is Undefined: value = arg_def.default_value - if value is not None: + if value is not Undefined: # We use out_name as the output name for the # dict if exists result[arg_def.out_name or name] = value @@ -63,51 +123,6 @@ def get_argument_values(arg_defs, arg_asts, variables=None): return result -def get_variable_value(schema, definition_ast, input): - """Given a variable definition, and any value of input, return a value which adheres to the variable definition, - or throw an error.""" - type = type_from_ast(schema, definition_ast.type) - variable = definition_ast.variable - - if not type or not is_input_type(type): - raise GraphQLError( - 'Variable "${}" expected value of type "{}" which cannot be used as an input type.'.format( - variable.name.value, - print_ast(definition_ast.type), - ), - [definition_ast] - ) - - input_type = type - errors = is_valid_value(input, input_type) - if not errors: - if input is None: - default_value = definition_ast.default_value - if default_value: - return value_from_ast(default_value, input_type) - - return coerce_value(input_type, input) - - if input is None: - raise GraphQLError( - 'Variable "${}" of required type "{}" was not provided.'.format( - variable.name.value, - print_ast(definition_ast.type) - ), - [definition_ast] - ) - - message = (u'\n' + u'\n'.join(errors)) if errors else u'' - raise GraphQLError( - 'Variable "${}" got invalid value {}.{}'.format( - variable.name.value, - json.dumps(input, sort_keys=True), - message - ), - [definition_ast] - ) - - def coerce_value(type, value): """Given a type and any value, return a runtime value coerced to match the type.""" if isinstance(type, GraphQLNonNull): @@ -116,6 +131,9 @@ def coerce_value(type, value): # We only call this function after calling isValidValue. return coerce_value(type.of_type, value) + if value is Undefined: + return Undefined + if value is None: return None @@ -130,11 +148,11 @@ def coerce_value(type, value): fields = type.fields obj = {} for field_name, field in fields.items(): - field_value = coerce_value(field.type, value.get(field_name)) - if field_value is None: + field_value = coerce_value(field.type, value.get(field_name, Undefined)) + if field_value is Undefined: field_value = field.default_value - if field_value is not None: + if field_value is not Undefined: # We use out_name as the output name for the # dict if exists obj[field.out_name or field_name] = field_value @@ -144,4 +162,8 @@ def coerce_value(type, value): assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), \ 'Must be input type' - return type.parse_value(value) + parsed = type.parse_value(value) + if parsed is None: + return Undefined + + return parsed diff --git a/graphql/language/ast.py b/graphql/language/ast.py index 6fffae84..363cb137 100644 --- a/graphql/language/ast.py +++ b/graphql/language/ast.py @@ -1,5 +1,6 @@ # This is autogenerated code. DO NOT change this manually. # Run scripts/generate_ast.py to generate this file. +from ..utils.undefined import Undefined class Node(object): @@ -94,7 +95,7 @@ class VariableDefinition(Node): __slots__ = ('loc', 'variable', 'type', 'default_value',) _fields = ('variable', 'type', 'default_value',) - def __init__(self, variable, type, default_value=None, loc=None): + def __init__(self, variable, type, default_value=Undefined, loc=None): self.loc = loc self.variable = variable self.type = type @@ -1005,7 +1006,7 @@ class InputValueDefinition(Node): __slots__ = ('loc', 'name', 'type', 'default_value', 'directives') _fields = ('name', 'type', 'default_value',) - def __init__(self, name, type, default_value=None, loc=None, + def __init__(self, name, type, default_value=Undefined, loc=None, directives=None): self.loc = loc self.name = name diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 24cee3b1..44209073 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -5,16 +5,7 @@ from ..pyutils.cached_property import cached_property from ..pyutils.ordereddict import OrderedDict from ..utils.assert_valid_name import assert_valid_name - - -class _Undefined(object): - def __bool__(self): - return False - - __nonzero__ = __bool__ - - -Undefined = _Undefined() +from ..utils.undefined import Undefined def is_type(type): @@ -275,7 +266,7 @@ def __hash__(self): class GraphQLArgument(object): __slots__ = 'type', 'default_value', 'description', 'out_name' - def __init__(self, type, default_value=None, description=None, out_name=None): + def __init__(self, type, default_value=Undefined, description=None, out_name=None): self.type = type self.default_value = default_value self.description = description @@ -548,7 +539,7 @@ def _define_field_map(self): class GraphQLInputObjectField(object): __slots__ = 'type', 'default_value', 'description', 'out_name' - def __init__(self, type, default_value=None, description=None, out_name=None): + def __init__(self, type, default_value=Undefined, description=None, out_name=None): self.type = type self.default_value = default_value self.description = description diff --git a/graphql/type/introspection.py b/graphql/type/introspection.py index b2732fc8..79c2cf25 100644 --- a/graphql/type/introspection.py +++ b/graphql/type/introspection.py @@ -2,7 +2,7 @@ from ..language.printer import print_ast from ..utils.ast_from_value import ast_from_value -from .definition import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, +from .definition import (Undefined, GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, @@ -345,7 +345,7 @@ def input_fields(type, *_): ('defaultValue', GraphQLField( type=GraphQLString, resolver=lambda input_val, *_: - None if input_val.default_value is None + None if input_val.default_value is Undefined else print_ast(ast_from_value(input_val.default_value, input_val)) )) ])) diff --git a/graphql/utils/schema_printer.py b/graphql/utils/schema_printer.py index 168a17ec..7aac7d5d 100644 --- a/graphql/utils/schema_printer.py +++ b/graphql/utils/schema_printer.py @@ -1,5 +1,5 @@ from ..language.printer import print_ast -from ..type.definition import (GraphQLEnumType, GraphQLInputObjectType, +from ..type.definition import (Undefined, GraphQLEnumType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType) from ..type.directives import DEFAULT_DEPRECATION_REASON @@ -153,7 +153,7 @@ def _print_args(field_or_directives): def _print_input_value(name, arg): - if arg.default_value is not None: + if arg.default_value not in (Undefined, None): default_value = ' = ' + print_ast(ast_from_value(arg.default_value, arg.type)) else: default_value = '' diff --git a/graphql/utils/undefined.py b/graphql/utils/undefined.py new file mode 100644 index 00000000..00e47835 --- /dev/null +++ b/graphql/utils/undefined.py @@ -0,0 +1,11 @@ +class _Undefined(object): + def __bool__(self): + return False + + __nonzero__ = __bool__ + + def __repr__(self): + return 'Undefined' + + +Undefined = _Undefined() diff --git a/graphql/utils/value_from_ast.py b/graphql/utils/value_from_ast.py index ff7486be..c1ad5ad9 100644 --- a/graphql/utils/value_from_ast.py +++ b/graphql/utils/value_from_ast.py @@ -1,5 +1,5 @@ from ..language import ast -from ..type import (GraphQLEnumType, GraphQLInputObjectType, GraphQLList, +from ..type import (Undefined, GraphQLEnumType, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLScalarType) @@ -11,17 +11,20 @@ def value_from_ast(value_ast, type, variables=None): # We're assuming that this query has been validated and the value used here is of the correct type. return value_from_ast(value_ast, type.of_type, variables) - if not value_ast: + if value_ast is Undefined: + return Undefined + + if value_ast is None: return None if isinstance(value_ast, ast.Variable): variable_name = value_ast.name.value - if not variables or variable_name not in variables: - return None + if not variables: + return Undefined # Note: we're not doing any checking that this variable is correct. We're assuming that this query # has been validated and the variable usage here is of the correct type. - return variables[variable_name] + return variables.get(variable_name, Undefined) if isinstance(type, GraphQLList): item_type = type.of_type @@ -35,7 +38,7 @@ def value_from_ast(value_ast, type, variables=None): if isinstance(type, GraphQLInputObjectType): fields = type.fields if not isinstance(value_ast, ast.ObjectValue): - return None + return Undefined field_asts = {} @@ -44,8 +47,8 @@ def value_from_ast(value_ast, type, variables=None): obj = {} for field_name, field in fields.items(): - field_ast = field_asts.get(field_name) - field_value_ast = None + field_ast = field_asts.get(field_name, Undefined) + field_value_ast = Undefined if field_ast: field_value_ast = field_ast.value @@ -53,10 +56,10 @@ def value_from_ast(value_ast, type, variables=None): field_value = value_from_ast( field_value_ast, field.type, variables ) - if field_value is None: + if field_value is Undefined: field_value = field.default_value - if field_value is not None: + if field_value is not Undefined: # We use out_name as the output name for the # dict if exists obj[field.out_name or field_name] = field_value @@ -66,4 +69,8 @@ def value_from_ast(value_ast, type, variables=None): assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), \ 'Must be input type' - return type.parse_literal(value_ast) + value = type.parse_literal(value_ast) + if value is None: + return Undefined + + return value From 1cba47be975012b2b27ce57c6f82e2a3ee4ca693 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 01:50:06 -0700 Subject: [PATCH 2/9] Added dynamic input container --- graphql/execution/values.py | 2 +- graphql/type/definition.py | 3 +++ graphql/utils/value_from_ast.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/graphql/execution/values.py b/graphql/execution/values.py index e42bba04..4b672b2e 100644 --- a/graphql/execution/values.py +++ b/graphql/execution/values.py @@ -157,7 +157,7 @@ def coerce_value(type, value): # dict if exists obj[field.out_name or field_name] = field_value - return obj + return type.create_container(obj) assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), \ 'Must be input type' diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 44209073..c452b14f 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -514,6 +514,9 @@ def __init__(self, name, fields, description=None): self._fields = fields + def create_container(self, data): + return dict(data) + @cached_property def fields(self): return self._define_field_map() diff --git a/graphql/utils/value_from_ast.py b/graphql/utils/value_from_ast.py index c1ad5ad9..6a1c350e 100644 --- a/graphql/utils/value_from_ast.py +++ b/graphql/utils/value_from_ast.py @@ -64,7 +64,7 @@ def value_from_ast(value_ast, type, variables=None): # dict if exists obj[field.out_name or field_name] = field_value - return obj + return type.create_container(obj) assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), \ 'Must be input type' From 36a5bae5c21502a37858ba1c698ef3af31d0bcce Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 02:08:41 -0700 Subject: [PATCH 3/9] Added test cases for custom object input containers --- graphql/execution/tests/test_variables.py | 30 ++++++++++++++++++++++- graphql/type/definition.py | 9 ++++--- graphql/utils/value_from_ast.py | 2 +- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/graphql/execution/tests/test_variables.py b/graphql/execution/tests/test_variables.py index 0bafa260..36d0ba1a 100644 --- a/graphql/execution/tests/test_variables.py +++ b/graphql/execution/tests/test_variables.py @@ -6,7 +6,7 @@ from graphql.error import GraphQLError, format_error from graphql.execution import execute from graphql.language.parser import parse -from graphql.type import (GraphQLArgument, GraphQLField, +from graphql.type import (GraphQLArgument, GraphQLField, GraphQLBoolean, GraphQLInputObjectField, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLString) @@ -18,6 +18,11 @@ parse_literal=lambda v: 'DeserializedValue' if v.value == 'SerializedValue' else None ) + +class my_special_dict(dict): + pass + + TestInputObject = GraphQLInputObjectType('TestInputObject', OrderedDict([ ('a', GraphQLInputObjectField(GraphQLString)), ('b', GraphQLInputObjectField(GraphQLList(GraphQLString))), @@ -25,6 +30,11 @@ ('d', GraphQLInputObjectField(TestComplexScalar)) ])) + +TestCustomInputObject = GraphQLInputObjectType('TestCustomInputObject', OrderedDict([ + ('a', GraphQLInputObjectField(GraphQLString)), +]), container_type=my_special_dict) + stringify = lambda obj: json.dumps(obj, sort_keys=True) @@ -47,6 +57,10 @@ def input_to_json(obj, args, context, info): GraphQLString, args={'input': GraphQLArgument(TestInputObject)}, resolver=input_to_json), + 'fieldWithCustomObjectInput': GraphQLField( + GraphQLBoolean, + args={'input': GraphQLArgument(TestCustomInputObject)}, + resolver=lambda root, args, context, info: isinstance(args.get('input'), my_special_dict)), 'fieldWithNullableStringInput': GraphQLField( GraphQLString, args={'input': GraphQLArgument(GraphQLString)}, @@ -420,6 +434,20 @@ def test_passes_along_null_for_non_nullable_inputs_if_explcitly_set_in_the_query }) +def test_uses_objectinput_container(): + doc = ''' + { + fieldWithCustomObjectInput(input: {a: "b"}) + } + ''' + + check(doc, { + 'data': { + 'fieldWithCustomObjectInput': True + } + }) + + def test_allows_lists_to_be_null(): doc = ''' query q($input: [String]) { diff --git a/graphql/type/definition.py b/graphql/type/definition.py index c452b14f..765b7ae3 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -507,15 +507,18 @@ class GeoPoint(GraphQLInputObjectType): default_value=0) } """ - def __init__(self, name, fields, description=None): + def __init__(self, name, fields, description=None, container_type=None): assert name, 'Type must be named.' self.name = name self.description = description - + if container_type is None: + container_type = dict + assert callable(container_type), "container_type must be callable" + self.container_type = container_type self._fields = fields def create_container(self, data): - return dict(data) + return self.container_type(data) @cached_property def fields(self): diff --git a/graphql/utils/value_from_ast.py b/graphql/utils/value_from_ast.py index 6a1c350e..4ea33859 100644 --- a/graphql/utils/value_from_ast.py +++ b/graphql/utils/value_from_ast.py @@ -64,7 +64,7 @@ def value_from_ast(value_ast, type, variables=None): # dict if exists obj[field.out_name or field_name] = field_value - return type.create_container(obj) + return type.create_container(obj) assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), \ 'Must be input type' From cf714ec85cdb1d1709c118f172f259149a4e06a6 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 12:07:13 -0700 Subject: [PATCH 4/9] Simplify Undefined --- graphql/execution/values.py | 52 ++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/graphql/execution/values.py b/graphql/execution/values.py index 4b672b2e..51055a1b 100644 --- a/graphql/execution/values.py +++ b/graphql/execution/values.py @@ -25,7 +25,7 @@ def get_variable_values(schema, definition_asts, inputs): for def_ast in definition_asts: var_name = def_ast.variable.name.value var_type = type_from_ast(schema, def_ast.type) - value = inputs.get(var_name, Undefined) + value = inputs.get(var_name) if not is_input_type(var_type): raise GraphQLError( @@ -35,7 +35,7 @@ def get_variable_values(schema, definition_asts, inputs): ), [def_ast] ) - elif value is Undefined or value is None: + elif value is None: if def_ast.default_value is not None: values[var_name] = value_from_ast(def_ast.default_value, var_type) if isinstance(var_type, GraphQLNonNull): @@ -57,7 +57,7 @@ def get_variable_values(schema, definition_asts, inputs): [def_ast] ) coerced_value = coerce_value(var_type, value) - if coerced_value is Undefined: + if coerced_value is None: raise Exception('Should have reported error.') values[var_name] = coerced_value @@ -79,8 +79,8 @@ def get_argument_values(arg_defs, arg_asts, variables=None): result = {} for name, arg_def in arg_defs.items(): arg_type = arg_def.type - value_ast = arg_ast_map.get(name, Undefined) - if not value_ast: + value_ast = arg_ast_map.get(name) + if name not in arg_ast_map: if arg_def.default_value is not Undefined: result[arg_def.out_name or name] = arg_def.default_value continue @@ -92,7 +92,7 @@ def get_argument_values(arg_defs, arg_asts, variables=None): elif isinstance(value_ast.value, ast.Variable): variable_name = value_ast.value.name.value variable_value = variables.get(variable_name, Undefined) - if variables and variable_value is not Undefined: + if variables and variable_name in variables: result[arg_def.out_name or name] = variable_value elif arg_def.default_value is not Undefined: result[arg_def.out_name or name] = arg_def.default_value @@ -104,21 +104,22 @@ def get_argument_values(arg_defs, arg_asts, variables=None): ), arg_asts) continue - if value_ast: + else: value_ast = value_ast.value - value = value_from_ast( - value_ast, - arg_type, - variables - ) - if value is Undefined: - value = arg_def.default_value - - if value is not Undefined: - # We use out_name as the output name for the - # dict if exists - result[arg_def.out_name or name] = value + value = value_from_ast( + value_ast, + arg_type, + variables + ) + if value is Undefined: + if arg_def.default_value is not Undefined: + value = arg_def.default_value + result[arg_def.out_name or name] = value + else: + # We use out_name as the output name for the + # dict if exists + result[arg_def.out_name or name] = value return result @@ -148,13 +149,12 @@ def coerce_value(type, value): fields = type.fields obj = {} for field_name, field in fields.items(): - field_value = coerce_value(field.type, value.get(field_name, Undefined)) - if field_value is Undefined: - field_value = field.default_value - - if field_value is not Undefined: - # We use out_name as the output name for the - # dict if exists + if field_name not in value: + if field.default_value is not Undefined: + field_value = field.default_value + obj[field.out_name or field_name] = field_value + else: + field_value = coerce_value(field.type, value.get(field_name)) obj[field.out_name or field_name] = field_value return type.create_container(obj) From 460632f6d38627fc98218d80d97dbbfdf09a1322 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 12:31:22 -0700 Subject: [PATCH 5/9] Improved default_value --- graphql/execution/values.py | 8 ++++---- graphql/type/definition.py | 4 ++-- graphql/type/introspection.py | 4 ++-- graphql/utils/value_from_ast.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/graphql/execution/values.py b/graphql/execution/values.py index 51055a1b..4869647c 100644 --- a/graphql/execution/values.py +++ b/graphql/execution/values.py @@ -81,7 +81,7 @@ def get_argument_values(arg_defs, arg_asts, variables=None): arg_type = arg_def.type value_ast = arg_ast_map.get(name) if name not in arg_ast_map: - if arg_def.default_value is not Undefined: + if arg_def.default_value is not None: result[arg_def.out_name or name] = arg_def.default_value continue elif isinstance(arg_type, GraphQLNonNull): @@ -94,7 +94,7 @@ def get_argument_values(arg_defs, arg_asts, variables=None): variable_value = variables.get(variable_name, Undefined) if variables and variable_name in variables: result[arg_def.out_name or name] = variable_value - elif arg_def.default_value is not Undefined: + elif arg_def.default_value is not None: result[arg_def.out_name or name] = arg_def.default_value elif isinstance(arg_type, GraphQLNonNull): raise GraphQLError('Argument "{name}" of required type {arg_type}" provided the variable "${variable_name}" which was not provided'.format( @@ -113,7 +113,7 @@ def get_argument_values(arg_defs, arg_asts, variables=None): variables ) if value is Undefined: - if arg_def.default_value is not Undefined: + if arg_def.default_value is not None: value = arg_def.default_value result[arg_def.out_name or name] = value else: @@ -150,7 +150,7 @@ def coerce_value(type, value): obj = {} for field_name, field in fields.items(): if field_name not in value: - if field.default_value is not Undefined: + if field.default_value is not None: field_value = field.default_value obj[field.out_name or field_name] = field_value else: diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 765b7ae3..352a2b5e 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -266,7 +266,7 @@ def __hash__(self): class GraphQLArgument(object): __slots__ = 'type', 'default_value', 'description', 'out_name' - def __init__(self, type, default_value=Undefined, description=None, out_name=None): + def __init__(self, type, default_value=None, description=None, out_name=None): self.type = type self.default_value = default_value self.description = description @@ -545,7 +545,7 @@ def _define_field_map(self): class GraphQLInputObjectField(object): __slots__ = 'type', 'default_value', 'description', 'out_name' - def __init__(self, type, default_value=Undefined, description=None, out_name=None): + def __init__(self, type, default_value=None, description=None, out_name=None): self.type = type self.default_value = default_value self.description = description diff --git a/graphql/type/introspection.py b/graphql/type/introspection.py index 79c2cf25..b2732fc8 100644 --- a/graphql/type/introspection.py +++ b/graphql/type/introspection.py @@ -2,7 +2,7 @@ from ..language.printer import print_ast from ..utils.ast_from_value import ast_from_value -from .definition import (Undefined, GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, +from .definition import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue, GraphQLField, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, @@ -345,7 +345,7 @@ def input_fields(type, *_): ('defaultValue', GraphQLField( type=GraphQLString, resolver=lambda input_val, *_: - None if input_val.default_value is Undefined + None if input_val.default_value is None else print_ast(ast_from_value(input_val.default_value, input_val)) )) ])) diff --git a/graphql/utils/value_from_ast.py b/graphql/utils/value_from_ast.py index 4ea33859..e4654d6c 100644 --- a/graphql/utils/value_from_ast.py +++ b/graphql/utils/value_from_ast.py @@ -56,7 +56,7 @@ def value_from_ast(value_ast, type, variables=None): field_value = value_from_ast( field_value_ast, field.type, variables ) - if field_value is Undefined: + if field_value is Undefined and field.default_value is not None: field_value = field.default_value if field_value is not Undefined: From 2b17fa2d88a1aac15276cbbe70eb1933cf779f1c Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 13:37:16 -0700 Subject: [PATCH 6/9] Fixed undefined imports --- graphql/__init__.py | 6 +++--- graphql/execution/executor.py | 3 ++- graphql/execution/experimental/fragment.py | 3 ++- graphql/execution/values.py | 3 ++- graphql/type/__init__.py | 3 +-- graphql/type/definition.py | 1 - graphql/utils/base.py | 5 +++++ graphql/utils/schema_printer.py | 4 ++-- graphql/utils/value_from_ast.py | 3 ++- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/graphql/__init__.py b/graphql/__init__.py index ac8c4025..a01711ba 100644 --- a/graphql/__init__.py +++ b/graphql/__init__.py @@ -112,9 +112,6 @@ # Un-modifiers get_nullable_type, get_named_type, - - # Undefined const - Undefined ) # Parse and operate on GraphQL language source files. @@ -205,6 +202,9 @@ # Asserts a string is a valid GraphQL name. assert_valid_name, + + # Undefined const + Undefined, ) __all__ = ( diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 0e567255..699ba8e7 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -9,7 +9,8 @@ from ..error import GraphQLError, GraphQLLocatedError from ..pyutils.default_ordered_dict import DefaultOrderedDict from ..pyutils.ordereddict import OrderedDict -from ..type import (Undefined, GraphQLEnumType, GraphQLInterfaceType, GraphQLList, +from ..utils.undefined import Undefined +from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLScalarType, GraphQLSchema, GraphQLUnionType) from .base import (ExecutionContext, ExecutionResult, ResolveInfo, diff --git a/graphql/execution/experimental/fragment.py b/graphql/execution/experimental/fragment.py index 41108eac..d6b75120 100644 --- a/graphql/execution/experimental/fragment.py +++ b/graphql/execution/experimental/fragment.py @@ -4,7 +4,8 @@ from ...pyutils.cached_property import cached_property from ...pyutils.default_ordered_dict import DefaultOrderedDict -from ...type import (Undefined, GraphQLInterfaceType, GraphQLList, GraphQLNonNull, +from ...utils.undefined import Undefined +from ...type import (GraphQLInterfaceType, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLUnionType) from ..base import ResolveInfo, collect_fields, get_field_def from ..values import get_argument_values diff --git a/graphql/execution/values.py b/graphql/execution/values.py index 4869647c..13e841bf 100644 --- a/graphql/execution/values.py +++ b/graphql/execution/values.py @@ -6,11 +6,12 @@ from ..error import GraphQLError from ..language import ast from ..language.printer import print_ast -from ..type import (Undefined, GraphQLEnumType, GraphQLInputObjectType, GraphQLList, +from ..type import (GraphQLEnumType, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLScalarType, is_input_type) from ..utils.is_valid_value import is_valid_value from ..utils.type_from_ast import type_from_ast from ..utils.value_from_ast import value_from_ast +from ..utils.undefined import Undefined __all__ = ['get_variable_values', 'get_argument_values'] diff --git a/graphql/type/__init__.py b/graphql/type/__init__.py index 6f53635d..153c1b5e 100644 --- a/graphql/type/__init__.py +++ b/graphql/type/__init__.py @@ -19,8 +19,7 @@ is_leaf_type, is_type, get_nullable_type, - is_output_type, - Undefined + is_output_type ) from .directives import ( # "Enum" of Directive locations diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 352a2b5e..e593fb23 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -5,7 +5,6 @@ from ..pyutils.cached_property import cached_property from ..pyutils.ordereddict import OrderedDict from ..utils.assert_valid_name import assert_valid_name -from ..utils.undefined import Undefined def is_type(type): diff --git a/graphql/utils/base.py b/graphql/utils/base.py index 5e895853..54a10346 100644 --- a/graphql/utils/base.py +++ b/graphql/utils/base.py @@ -53,6 +53,10 @@ # Asserts that a string is a valid GraphQL name from .assert_valid_name import assert_valid_name +# Undefined const +from .undefined import Undefined + + __all__ = [ 'introspection_query', 'get_operation_ast', @@ -72,4 +76,5 @@ 'is_equal_type', 'is_type_sub_type_of', 'assert_valid_name', + 'Undefined', ] diff --git a/graphql/utils/schema_printer.py b/graphql/utils/schema_printer.py index 7aac7d5d..168a17ec 100644 --- a/graphql/utils/schema_printer.py +++ b/graphql/utils/schema_printer.py @@ -1,5 +1,5 @@ from ..language.printer import print_ast -from ..type.definition import (Undefined, GraphQLEnumType, GraphQLInputObjectType, +from ..type.definition import (GraphQLEnumType, GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType, GraphQLScalarType, GraphQLUnionType) from ..type.directives import DEFAULT_DEPRECATION_REASON @@ -153,7 +153,7 @@ def _print_args(field_or_directives): def _print_input_value(name, arg): - if arg.default_value not in (Undefined, None): + if arg.default_value is not None: default_value = ' = ' + print_ast(ast_from_value(arg.default_value, arg.type)) else: default_value = '' diff --git a/graphql/utils/value_from_ast.py b/graphql/utils/value_from_ast.py index e4654d6c..3da5d4c0 100644 --- a/graphql/utils/value_from_ast.py +++ b/graphql/utils/value_from_ast.py @@ -1,6 +1,7 @@ from ..language import ast -from ..type import (Undefined, GraphQLEnumType, GraphQLInputObjectType, GraphQLList, +from ..type import (GraphQLEnumType, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLScalarType) +from .undefined import Undefined def value_from_ast(value_ast, type, variables=None): From cf672bc73228573a00f7770f515f714ed55e4a78 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 13:40:07 -0700 Subject: [PATCH 7/9] Removed Unnecessary default_value=Undefined in ast --- graphql/language/ast.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/graphql/language/ast.py b/graphql/language/ast.py index 363cb137..6fffae84 100644 --- a/graphql/language/ast.py +++ b/graphql/language/ast.py @@ -1,6 +1,5 @@ # This is autogenerated code. DO NOT change this manually. # Run scripts/generate_ast.py to generate this file. -from ..utils.undefined import Undefined class Node(object): @@ -95,7 +94,7 @@ class VariableDefinition(Node): __slots__ = ('loc', 'variable', 'type', 'default_value',) _fields = ('variable', 'type', 'default_value',) - def __init__(self, variable, type, default_value=Undefined, loc=None): + def __init__(self, variable, type, default_value=None, loc=None): self.loc = loc self.variable = variable self.type = type @@ -1006,7 +1005,7 @@ class InputValueDefinition(Node): __slots__ = ('loc', 'name', 'type', 'default_value', 'directives') _fields = ('name', 'type', 'default_value',) - def __init__(self, name, type, default_value=Undefined, loc=None, + def __init__(self, name, type, default_value=None, loc=None, directives=None): self.loc = loc self.name = name From aca5711a7b93ca8020211906840f14b98529534a Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 13:53:01 -0700 Subject: [PATCH 8/9] Removed unnecessary undefined vars --- graphql/execution/values.py | 14 +++--------- graphql/utils/value_from_ast.py | 39 ++++++++++++++------------------- 2 files changed, 19 insertions(+), 34 deletions(-) diff --git a/graphql/execution/values.py b/graphql/execution/values.py index 13e841bf..252ba1f3 100644 --- a/graphql/execution/values.py +++ b/graphql/execution/values.py @@ -11,7 +11,6 @@ from ..utils.is_valid_value import is_valid_value from ..utils.type_from_ast import type_from_ast from ..utils.value_from_ast import value_from_ast -from ..utils.undefined import Undefined __all__ = ['get_variable_values', 'get_argument_values'] @@ -92,7 +91,7 @@ def get_argument_values(arg_defs, arg_asts, variables=None): ), arg_asts) elif isinstance(value_ast.value, ast.Variable): variable_name = value_ast.value.name.value - variable_value = variables.get(variable_name, Undefined) + variable_value = variables.get(variable_name) if variables and variable_name in variables: result[arg_def.out_name or name] = variable_value elif arg_def.default_value is not None: @@ -113,7 +112,7 @@ def get_argument_values(arg_defs, arg_asts, variables=None): arg_type, variables ) - if value is Undefined: + if value is None: if arg_def.default_value is not None: value = arg_def.default_value result[arg_def.out_name or name] = value @@ -133,9 +132,6 @@ def coerce_value(type, value): # We only call this function after calling isValidValue. return coerce_value(type.of_type, value) - if value is Undefined: - return Undefined - if value is None: return None @@ -163,8 +159,4 @@ def coerce_value(type, value): assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), \ 'Must be input type' - parsed = type.parse_value(value) - if parsed is None: - return Undefined - - return parsed + return type.parse_value(value) diff --git a/graphql/utils/value_from_ast.py b/graphql/utils/value_from_ast.py index 3da5d4c0..55026680 100644 --- a/graphql/utils/value_from_ast.py +++ b/graphql/utils/value_from_ast.py @@ -1,7 +1,6 @@ from ..language import ast from ..type import (GraphQLEnumType, GraphQLInputObjectType, GraphQLList, GraphQLNonNull, GraphQLScalarType) -from .undefined import Undefined def value_from_ast(value_ast, type, variables=None): @@ -12,20 +11,17 @@ def value_from_ast(value_ast, type, variables=None): # We're assuming that this query has been validated and the value used here is of the correct type. return value_from_ast(value_ast, type.of_type, variables) - if value_ast is Undefined: - return Undefined - if value_ast is None: return None if isinstance(value_ast, ast.Variable): variable_name = value_ast.name.value - if not variables: - return Undefined + if not variables or variable_name not in variables: + return None # Note: we're not doing any checking that this variable is correct. We're assuming that this query # has been validated and the variable usage here is of the correct type. - return variables.get(variable_name, Undefined) + return variables.get(variable_name) if isinstance(type, GraphQLList): item_type = type.of_type @@ -39,7 +35,7 @@ def value_from_ast(value_ast, type, variables=None): if isinstance(type, GraphQLInputObjectType): fields = type.fields if not isinstance(value_ast, ast.ObjectValue): - return Undefined + return None field_asts = {} @@ -48,30 +44,27 @@ def value_from_ast(value_ast, type, variables=None): obj = {} for field_name, field in fields.items(): - field_ast = field_asts.get(field_name, Undefined) - field_value_ast = Undefined + if field_name not in field_asts: + if field.default_value is not None: + # We use out_name as the output name for the + # dict if exists + obj[field.out_name or field_name] = field.default_value - if field_ast: - field_value_ast = field_ast.value + continue + field_ast = field_asts.get(field_name) + field_value_ast = field_ast.value field_value = value_from_ast( field_value_ast, field.type, variables ) - if field_value is Undefined and field.default_value is not None: - field_value = field.default_value - if field_value is not Undefined: - # We use out_name as the output name for the - # dict if exists - obj[field.out_name or field_name] = field_value + # We use out_name as the output name for the + # dict if exists + obj[field.out_name or field_name] = field_value return type.create_container(obj) assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), \ 'Must be input type' - value = type.parse_literal(value_ast) - if value is None: - return Undefined - - return value + return type.parse_literal(value_ast) From 427305c1ede81975097fd97ac8c65ee30acac79f Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sun, 23 Jul 2017 13:59:48 -0700 Subject: [PATCH 9/9] Remove support for Python 3.3. Added support for 3.6 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f80f2790..b2d8c50c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,9 @@ language: python sudo: false python: - 2.7 -- 3.3 - 3.4 - 3.5 +- 3.6 - pypy before_install: - |