Skip to content

Commit 701a6f7

Browse files
committed
Improved undefined variables usage
1 parent 7c956aa commit 701a6f7

File tree

13 files changed

+135
-98
lines changed

13 files changed

+135
-98
lines changed

graphql/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@
112112
# Un-modifiers
113113
get_nullable_type,
114114
get_named_type,
115+
116+
# Undefined const
117+
Undefined
115118
)
116119

117120
# Parse and operate on GraphQL language source files.
@@ -284,4 +287,5 @@
284287
'type_from_ast',
285288
'value_from_ast',
286289
'get_version',
290+
'Undefined',
287291
)

graphql/execution/base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from ..error import GraphQLError
55
from ..language import ast
66
from ..pyutils.default_ordered_dict import DefaultOrderedDict
7-
from ..type.definition import Undefined, GraphQLInterfaceType, GraphQLUnionType
7+
from ..type.definition import GraphQLInterfaceType, GraphQLUnionType
88
from ..type.directives import GraphQLIncludeDirective, GraphQLSkipDirective
99
from ..type.introspection import (SchemaMetaFieldDef, TypeMetaFieldDef,
1010
TypeNameMetaFieldDef)
@@ -75,7 +75,6 @@ def get_field_resolver(self, field_resolver):
7575
def get_argument_values(self, field_def, field_ast):
7676
k = field_def, field_ast
7777
result = self.argument_values_cache.get(k)
78-
7978
if not result:
8079
result = self.argument_values_cache[k] = get_argument_values(field_def.args, field_ast.arguments,
8180
self.variable_values)

graphql/execution/executor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
from ..error import GraphQLError, GraphQLLocatedError
1010
from ..pyutils.default_ordered_dict import DefaultOrderedDict
1111
from ..pyutils.ordereddict import OrderedDict
12-
from ..type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList,
12+
from ..type import (Undefined, GraphQLEnumType, GraphQLInterfaceType, GraphQLList,
1313
GraphQLNonNull, GraphQLObjectType, GraphQLScalarType,
1414
GraphQLSchema, GraphQLUnionType)
15-
from .base import (ExecutionContext, ExecutionResult, ResolveInfo, Undefined,
15+
from .base import (ExecutionContext, ExecutionResult, ResolveInfo,
1616
collect_fields, default_resolve_fn, get_field_def,
1717
get_operation_root_type)
1818
from .executors.sync import SyncExecutor

graphql/execution/experimental/fragment.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
from ...pyutils.cached_property import cached_property
66
from ...pyutils.default_ordered_dict import DefaultOrderedDict
7-
from ...type import (GraphQLInterfaceType, GraphQLList, GraphQLNonNull,
7+
from ...type import (Undefined, GraphQLInterfaceType, GraphQLList, GraphQLNonNull,
88
GraphQLObjectType, GraphQLUnionType)
9-
from ..base import ResolveInfo, Undefined, collect_fields, get_field_def
9+
from ..base import ResolveInfo, collect_fields, get_field_def
1010
from ..values import get_argument_values
1111
from ...error import GraphQLError
1212
try:

graphql/execution/experimental/tests/test_variables.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,9 +413,10 @@ def test_passes_along_null_for_non_nullable_inputs_if_explcitly_set_in_the_query
413413
'''
414414

415415
check(doc, {
416-
'data': {
417-
'fieldWithNonNullableStringInput': None
418-
}
416+
'errors': [{
417+
'message': 'Argument "input" of required type String!" was not provided.'
418+
}],
419+
'data': None
419420
})
420421

421422

graphql/execution/tests/test_variables.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,9 +413,10 @@ def test_passes_along_null_for_non_nullable_inputs_if_explcitly_set_in_the_query
413413
'''
414414

415415
check(doc, {
416-
'data': {
417-
'fieldWithNonNullableStringInput': None
418-
}
416+
'errors': [{
417+
'message': 'Argument "input" of required type String!" was not provided.'
418+
}],
419+
'data': None
419420
})
420421

421422

graphql/execution/values.py

Lines changed: 79 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
from six import string_types
55

66
from ..error import GraphQLError
7+
from ..language import ast
78
from ..language.printer import print_ast
8-
from ..type import (GraphQLEnumType, GraphQLInputObjectType, GraphQLList,
9+
from ..type import (Undefined, GraphQLEnumType, GraphQLInputObjectType, GraphQLList,
910
GraphQLNonNull, GraphQLScalarType, is_input_type)
1011
from ..utils.is_valid_value import is_valid_value
1112
from ..utils.type_from_ast import type_from_ast
@@ -23,8 +24,43 @@ def get_variable_values(schema, definition_asts, inputs):
2324
values = {}
2425
for def_ast in definition_asts:
2526
var_name = def_ast.variable.name.value
26-
value = get_variable_value(schema, def_ast, inputs.get(var_name))
27-
values[var_name] = value
27+
var_type = type_from_ast(schema, def_ast.type)
28+
value = inputs.get(var_name, Undefined)
29+
30+
if not is_input_type(var_type):
31+
raise GraphQLError(
32+
'Variable "${var_name}" expected value of type "{var_type}" which cannot be used as an input type.'.format(
33+
var_name=var_name,
34+
var_type=print_ast(def_ast.type),
35+
),
36+
[def_ast]
37+
)
38+
elif value is Undefined or value is None:
39+
if def_ast.default_value is not None:
40+
values[var_name] = value_from_ast(def_ast.default_value, var_type)
41+
if isinstance(var_type, GraphQLNonNull):
42+
raise GraphQLError(
43+
'Variable "${var_name}" of required type "{var_type}" was not provided.'.format(
44+
var_name=var_name, var_type=var_type
45+
), [def_ast]
46+
)
47+
else:
48+
errors = is_valid_value(value, var_type)
49+
if errors:
50+
message = u'\n' + u'\n'.join(errors)
51+
raise GraphQLError(
52+
'Variable "${}" got invalid value {}.{}'.format(
53+
var_name,
54+
json.dumps(value, sort_keys=True),
55+
message
56+
),
57+
[def_ast]
58+
)
59+
coerced_value = coerce_value(var_type, value)
60+
if coerced_value is Undefined:
61+
raise Exception('Should have reported error.')
62+
63+
values[var_name] = coerced_value
2864

2965
return values
3066

@@ -42,72 +78,51 @@ def get_argument_values(arg_defs, arg_asts, variables=None):
4278

4379
result = {}
4480
for name, arg_def in arg_defs.items():
45-
value_ast = arg_ast_map.get(name)
81+
arg_type = arg_def.type
82+
value_ast = arg_ast_map.get(name, Undefined)
83+
if not value_ast:
84+
if arg_def.default_value is not Undefined:
85+
result[arg_def.out_name or name] = arg_def.default_value
86+
continue
87+
elif isinstance(arg_type, GraphQLNonNull):
88+
raise GraphQLError('Argument "{name}" of required type {arg_type}" was not provided.'.format(
89+
name=name,
90+
arg_type=arg_type
91+
), arg_asts)
92+
elif isinstance(value_ast.value, ast.Variable):
93+
variable_name = value_ast.value.name.value
94+
variable_value = variables.get(variable_name, Undefined)
95+
if variables and variable_value is not Undefined:
96+
result[arg_def.out_name or name] = variable_value
97+
elif arg_def.default_value is not Undefined:
98+
result[arg_def.out_name or name] = arg_def.default_value
99+
elif isinstance(arg_type, GraphQLNonNull):
100+
raise GraphQLError('Argument "{name}" of required type {arg_type}" provided the variable "${variable_name}" which was not provided'.format(
101+
name=name,
102+
arg_type=arg_type,
103+
variable_name=variable_name
104+
), arg_asts)
105+
continue
106+
46107
if value_ast:
47108
value_ast = value_ast.value
48109

49110
value = value_from_ast(
50111
value_ast,
51-
arg_def.type,
112+
arg_type,
52113
variables
53114
)
54-
55-
if value is None:
115+
if value is Undefined:
56116
value = arg_def.default_value
57117

58-
if value is not None:
118+
if value is not Undefined:
59119
# We use out_name as the output name for the
60120
# dict if exists
61121
result[arg_def.out_name or name] = value
62122

63123
return result
64124

65125

66-
def get_variable_value(schema, definition_ast, input):
67-
"""Given a variable definition, and any value of input, return a value which adheres to the variable definition,
68-
or throw an error."""
69-
type = type_from_ast(schema, definition_ast.type)
70-
variable = definition_ast.variable
71-
72-
if not type or not is_input_type(type):
73-
raise GraphQLError(
74-
'Variable "${}" expected value of type "{}" which cannot be used as an input type.'.format(
75-
variable.name.value,
76-
print_ast(definition_ast.type),
77-
),
78-
[definition_ast]
79-
)
80-
81-
input_type = type
82-
errors = is_valid_value(input, input_type)
83-
if not errors:
84-
if input is None:
85-
default_value = definition_ast.default_value
86-
if default_value:
87-
return value_from_ast(default_value, input_type)
88-
89-
return coerce_value(input_type, input)
90-
91-
if input is None:
92-
raise GraphQLError(
93-
'Variable "${}" of required type "{}" was not provided.'.format(
94-
variable.name.value,
95-
print_ast(definition_ast.type)
96-
),
97-
[definition_ast]
98-
)
99-
100-
message = (u'\n' + u'\n'.join(errors)) if errors else u''
101-
raise GraphQLError(
102-
'Variable "${}" got invalid value {}.{}'.format(
103-
variable.name.value,
104-
json.dumps(input, sort_keys=True),
105-
message
106-
),
107-
[definition_ast]
108-
)
109-
110-
111126
def coerce_value(type, value):
112127
"""Given a type and any value, return a runtime value coerced to match the type."""
113128
if isinstance(type, GraphQLNonNull):
@@ -116,6 +131,9 @@ def coerce_value(type, value):
116131
# We only call this function after calling isValidValue.
117132
return coerce_value(type.of_type, value)
118133

134+
if value is Undefined:
135+
return Undefined
136+
119137
if value is None:
120138
return None
121139

@@ -130,11 +148,11 @@ def coerce_value(type, value):
130148
fields = type.fields
131149
obj = {}
132150
for field_name, field in fields.items():
133-
field_value = coerce_value(field.type, value.get(field_name))
134-
if field_value is None:
151+
field_value = coerce_value(field.type, value.get(field_name, Undefined))
152+
if field_value is Undefined:
135153
field_value = field.default_value
136154

137-
if field_value is not None:
155+
if field_value is not Undefined:
138156
# We use out_name as the output name for the
139157
# dict if exists
140158
obj[field.out_name or field_name] = field_value
@@ -144,4 +162,8 @@ def coerce_value(type, value):
144162
assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), \
145163
'Must be input type'
146164

147-
return type.parse_value(value)
165+
parsed = type.parse_value(value)
166+
if parsed is None:
167+
return Undefined
168+
169+
return parsed

graphql/language/ast.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# This is autogenerated code. DO NOT change this manually.
22
# Run scripts/generate_ast.py to generate this file.
3+
from ..utils.undefined import Undefined
34

45

56
class Node(object):
@@ -94,7 +95,7 @@ class VariableDefinition(Node):
9495
__slots__ = ('loc', 'variable', 'type', 'default_value',)
9596
_fields = ('variable', 'type', 'default_value',)
9697

97-
def __init__(self, variable, type, default_value=None, loc=None):
98+
def __init__(self, variable, type, default_value=Undefined, loc=None):
9899
self.loc = loc
99100
self.variable = variable
100101
self.type = type
@@ -1005,7 +1006,7 @@ class InputValueDefinition(Node):
10051006
__slots__ = ('loc', 'name', 'type', 'default_value', 'directives')
10061007
_fields = ('name', 'type', 'default_value',)
10071008

1008-
def __init__(self, name, type, default_value=None, loc=None,
1009+
def __init__(self, name, type, default_value=Undefined, loc=None,
10091010
directives=None):
10101011
self.loc = loc
10111012
self.name = name

graphql/type/definition.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,7 @@
55
from ..pyutils.cached_property import cached_property
66
from ..pyutils.ordereddict import OrderedDict
77
from ..utils.assert_valid_name import assert_valid_name
8-
9-
10-
class _Undefined(object):
11-
def __bool__(self):
12-
return False
13-
14-
__nonzero__ = __bool__
15-
16-
17-
Undefined = _Undefined()
8+
from ..utils.undefined import Undefined
189

1910

2011
def is_type(type):
@@ -275,7 +266,7 @@ def __hash__(self):
275266
class GraphQLArgument(object):
276267
__slots__ = 'type', 'default_value', 'description', 'out_name'
277268

278-
def __init__(self, type, default_value=None, description=None, out_name=None):
269+
def __init__(self, type, default_value=Undefined, description=None, out_name=None):
279270
self.type = type
280271
self.default_value = default_value
281272
self.description = description
@@ -548,7 +539,7 @@ def _define_field_map(self):
548539
class GraphQLInputObjectField(object):
549540
__slots__ = 'type', 'default_value', 'description', 'out_name'
550541

551-
def __init__(self, type, default_value=None, description=None, out_name=None):
542+
def __init__(self, type, default_value=Undefined, description=None, out_name=None):
552543
self.type = type
553544
self.default_value = default_value
554545
self.description = description

graphql/type/introspection.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from ..language.printer import print_ast
44
from ..utils.ast_from_value import ast_from_value
5-
from .definition import (GraphQLArgument, GraphQLEnumType, GraphQLEnumValue,
5+
from .definition import (Undefined, GraphQLArgument, GraphQLEnumType, GraphQLEnumValue,
66
GraphQLField, GraphQLInputObjectType,
77
GraphQLInterfaceType, GraphQLList, GraphQLNonNull,
88
GraphQLObjectType, GraphQLScalarType,
@@ -345,7 +345,7 @@ def input_fields(type, *_):
345345
('defaultValue', GraphQLField(
346346
type=GraphQLString,
347347
resolver=lambda input_val, *_:
348-
None if input_val.default_value is None
348+
None if input_val.default_value is Undefined
349349
else print_ast(ast_from_value(input_val.default_value, input_val))
350350
))
351351
]))

0 commit comments

Comments
 (0)