Skip to content

Commit b732c1b

Browse files
committed
Support transformation of argument names (#41)
1 parent 6cfc9c7 commit b732c1b

File tree

9 files changed

+284
-25
lines changed

9 files changed

+284
-25
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ a query language for APIs created by Facebook.
1313
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
1414

1515
The current version 1.0.5 of GraphQL-core-next is up-to-date with GraphQL.js version
16-
14.3.1. All parts of the API are covered by an extensive test suite of currently 1792
16+
14.3.1. All parts of the API are covered by an extensive test suite of currently 1814
1717
unit tests.
1818

1919

graphql/execution/values.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ def get_argument_values(
138138
if not has_value and arg_def.default_value is not INVALID:
139139
# If no argument was provided where the definition has a default value,
140140
# use the default value.
141-
coerced_values[name] = arg_def.default_value
141+
# If an out name exists, we use that as the name (extension of GraphQL.js).
142+
coerced_values[arg_def.out_name or name] = arg_def.default_value
142143
elif (not has_value or is_null) and is_non_null_type(arg_type):
143144
# If no argument or a null value was provided to an argument with a non-null
144145
# type (required), produce a field error.
@@ -166,13 +167,15 @@ def get_argument_values(
166167
if isinstance(argument_node.value, NullValueNode):
167168
# If the explicit value `None` was provided, an entry in the coerced
168169
# values must exist as the value `None`.
169-
coerced_values[name] = None
170+
coerced_values[arg_def.out_name or name] = None
170171
elif isinstance(argument_node.value, VariableNode):
171172
variable_name = argument_node.value.name.value
172173
# Note: This Does no further checking that this variable is correct.
173174
# This assumes that this query has been validated and the variable
174175
# usage here is of the correct type.
175-
coerced_values[name] = variable_values[variable_name]
176+
coerced_values[arg_def.out_name or name] = variable_values[
177+
variable_name
178+
]
176179
else:
177180
value_node = argument_node.value
178181
coerced_value = value_from_ast(value_node, arg_type, variable_values)
@@ -185,7 +188,7 @@ def get_argument_values(
185188
f" has invalid value {print_ast(value_node)}.",
186189
argument_node.value,
187190
)
188-
coerced_values[name] = coerced_value
191+
coerced_values[arg_def.out_name or name] = coerced_value
189192
return coerced_values
190193

191194

graphql/type/definition.py

+33-9
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,11 @@ def __str__(self):
365365
def to_kwargs(self) -> Dict[str, Any]:
366366
return dict(
367367
**super().to_kwargs(),
368-
serialize=self.serialize,
369-
parse_value=self.parse_value,
370-
parse_literal=self.parse_literal,
368+
serialize=None if self.serialize is identity_func else self.serialize,
369+
parse_value=None if self.parse_value is identity_func else self.parse_value,
370+
parse_literal=None
371+
if self.parse_literal is value_from_ast_untyped
372+
else self.parse_literal,
371373
)
372374

373375

@@ -529,24 +531,29 @@ class GraphQLArgument:
529531
type: "GraphQLInputType"
530532
default_value: Any
531533
description: Optional[str]
534+
out_name: Optional[str] # for transforming names (extension of GraphQL.js)
532535
ast_node: Optional[InputValueDefinitionNode]
533536

534537
def __init__(
535538
self,
536539
type_: "GraphQLInputType",
537540
default_value: Any = INVALID,
538541
description: str = None,
542+
out_name: str = None,
539543
ast_node: InputValueDefinitionNode = None,
540544
) -> None:
541545
if not is_input_type(type_):
542546
raise TypeError(f"Argument type must be a GraphQL input type.")
543547
if description is not None and not isinstance(description, str):
544-
raise TypeError("The description must be a string.")
548+
raise TypeError("Argument description must be a string.")
549+
if out_name is not None and not isinstance(out_name, str):
550+
raise TypeError("Argument out name must be a string.")
545551
if ast_node and not isinstance(ast_node, InputValueDefinitionNode):
546552
raise TypeError("Argument AST node must be an InputValueDefinitionNode.")
547553
self.type = type_
548554
self.default_value = default_value
549555
self.description = description
556+
self.out_name = out_name
550557
self.ast_node = ast_node
551558

552559
def __eq__(self, other):
@@ -555,13 +562,15 @@ def __eq__(self, other):
555562
and self.type == other.type
556563
and self.default_value == other.default_value
557564
and self.description == other.description
565+
and self.out_name == other.out_name
558566
)
559567

560568
def to_kwargs(self) -> Dict[str, Any]:
561569
return dict(
562570
type_=self.type,
563571
default_value=self.default_value,
564572
description=self.description,
573+
out_name=self.out_name,
565574
ast_node=self.ast_node,
566575
)
567576

@@ -1119,8 +1128,7 @@ class GeoPoint(GraphQLInputObjectType):
11191128
converted to other types by specifying an `out_type` function or class.
11201129
"""
11211130

1122-
# Transforms values to different type (this is an extension of GraphQL.js).
1123-
out_type: GraphQLInputFieldOutType
1131+
out_type: GraphQLInputFieldOutType # transforms values (extension of GraphQL.js)
11241132
ast_node: Optional[InputObjectTypeDefinitionNode]
11251133
extension_ast_nodes: Optional[Tuple[InputObjectTypeExtensionNode]]
11261134

@@ -1156,7 +1164,13 @@ def __init__(
11561164
self.out_type = out_type or identity_func # type: ignore
11571165

11581166
def to_kwargs(self) -> Dict[str, Any]:
1159-
return dict(**super().to_kwargs(), fields=self.fields.copy())
1167+
return dict(
1168+
**super().to_kwargs(),
1169+
fields=self.fields.copy(),
1170+
out_type=None
1171+
if self.out_type is identity_func # type: ignore
1172+
else self.out_type, # type: ignore
1173+
)
11601174

11611175
@cached_property
11621176
def fields(self) -> GraphQLInputFieldMap:
@@ -1204,38 +1218,48 @@ class GraphQLInputField:
12041218
"""Definition of a GraphQL input field"""
12051219

12061220
type: "GraphQLInputType"
1207-
description: Optional[str]
12081221
default_value: Any
1222+
description: Optional[str]
1223+
out_name: Optional[str] # for transforming names (extension of GraphQL.js)
12091224
ast_node: Optional[InputValueDefinitionNode]
12101225

12111226
def __init__(
12121227
self,
12131228
type_: "GraphQLInputType",
1214-
description: str = None,
12151229
default_value: Any = INVALID,
1230+
description: str = None,
1231+
out_name: str = None,
12161232
ast_node: InputValueDefinitionNode = None,
12171233
) -> None:
12181234
if not is_input_type(type_):
12191235
raise TypeError(f"Input field type must be a GraphQL input type.")
1236+
if description is not None and not isinstance(description, str):
1237+
raise TypeError("Input field description must be a string.")
1238+
if out_name is not None and not isinstance(out_name, str):
1239+
raise TypeError("Input field out name must be a string.")
12201240
if ast_node and not isinstance(ast_node, InputValueDefinitionNode):
12211241
raise TypeError("Input field AST node must be an InputValueDefinitionNode.")
12221242
self.type = type_
12231243
self.default_value = default_value
12241244
self.description = description
1245+
self.out_name = out_name
12251246
self.ast_node = ast_node
12261247

12271248
def __eq__(self, other):
12281249
return self is other or (
12291250
isinstance(other, GraphQLInputField)
12301251
and self.type == other.type
1252+
and self.default_value == other.default_value
12311253
and self.description == other.description
1254+
and self.out_name == other.out_name
12321255
)
12331256

12341257
def to_kwargs(self) -> Dict[str, Any]:
12351258
return dict(
12361259
type_=self.type,
12371260
description=self.description,
12381261
default_value=self.default_value,
1262+
out_name=self.out_name,
12391263
ast_node=self.ast_node,
12401264
)
12411265

graphql/utilities/coerce_value.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,10 @@ def coerce_value(
139139
field_value = value.get(field_name, INVALID)
140140
if is_invalid(field_value):
141141
if not is_invalid(field.default_value):
142-
coerced_value_dict[field_name] = field.default_value
142+
# Use out name as name if it exists (extension of GraphQL.js).
143+
coerced_value_dict[
144+
field.out_name or field_name
145+
] = field.default_value
143146
elif is_non_null_type(field.type):
144147
errors = add(
145148
errors,
@@ -156,7 +159,9 @@ def coerce_value(
156159
if coerced_field.errors:
157160
errors = add(errors, *coerced_field.errors)
158161
else:
159-
coerced_value_dict[field_name] = coerced_field.value
162+
coerced_value_dict[
163+
field.out_name or field_name
164+
] = coerced_field.value
160165

161166
# Ensure every provided field is defined.
162167
for field_name in value:

graphql/utilities/value_from_ast.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,15 @@ def value_from_ast(
114114
field_node = field_nodes.get(field_name)
115115
if not field_node or is_missing_variable(field_node.value, variables):
116116
if field.default_value is not INVALID:
117-
coerced_obj[field_name] = field.default_value
117+
# Use out name as name if it exists (extension of GraphQL.js).
118+
coerced_obj[field.out_name or field_name] = field.default_value
118119
elif is_non_null_type(field.type):
119120
return INVALID
120121
continue
121122
field_value = value_from_ast(field_node.value, field.type, variables)
122123
if is_invalid(field_value):
123124
return INVALID
124-
coerced_obj[field_name] = field_value
125+
coerced_obj[field.out_name or field_name] = field_value
125126

126127
return type_.out_type(coerced_obj) # type: ignore
127128

tests/execution/test_resolve.py

+74-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
from json import dumps
2-
31
from graphql import graphql_sync
42
from graphql.type import (
53
GraphQLArgument,
64
GraphQLField,
5+
GraphQLInputField,
6+
GraphQLInputObjectType,
77
GraphQLInt,
88
GraphQLObjectType,
99
GraphQLSchema,
@@ -79,22 +79,89 @@ def uses_provided_resolve_function():
7979
"aStr": GraphQLArgument(GraphQLString),
8080
"aInt": GraphQLArgument(GraphQLInt),
8181
},
82-
resolve=lambda source, info, **args: dumps([source, args]),
82+
resolve=lambda source, info, **args: repr([source, args]),
8383
)
8484
)
8585

86-
assert graphql_sync(schema, "{ test }") == ({"test": "[null, {}]"}, None)
86+
assert graphql_sync(schema, "{ test }") == ({"test": "[None, {}]"}, None)
8787

8888
assert graphql_sync(schema, "{ test }", "Source!") == (
89-
{"test": '["Source!", {}]'},
89+
{"test": "['Source!', {}]"},
9090
None,
9191
)
9292

9393
assert graphql_sync(schema, '{ test(aStr: "String!") }', "Source!") == (
94-
{"test": '["Source!", {"aStr": "String!"}]'},
94+
{"test": "['Source!', {'aStr': 'String!'}]"},
9595
None,
9696
)
9797

9898
assert graphql_sync(
9999
schema, '{ test(aInt: -123, aStr: "String!") }', "Source!"
100-
) == ({"test": '["Source!", {"aStr": "String!", "aInt": -123}]'}, None)
100+
) == ({"test": "['Source!', {'aStr': 'String!', 'aInt': -123}]"}, None)
101+
102+
def transforms_arguments_using_out_names():
103+
# This is an extension of GraphQL.js.
104+
schema = _test_schema(
105+
GraphQLField(
106+
GraphQLString,
107+
args={
108+
"aStr": GraphQLArgument(GraphQLString, out_name="a_str"),
109+
"aInt": GraphQLArgument(GraphQLInt, out_name="a_int"),
110+
},
111+
resolve=lambda source, info, **args: repr([source, args]),
112+
)
113+
)
114+
115+
assert graphql_sync(schema, "{ test }") == ({"test": "[None, {}]"}, None)
116+
117+
assert graphql_sync(schema, "{ test }", "Source!") == (
118+
{"test": "['Source!', {}]"},
119+
None,
120+
)
121+
122+
assert graphql_sync(schema, '{ test(aStr: "String!") }', "Source!") == (
123+
{"test": "['Source!', {'a_str': 'String!'}]"},
124+
None,
125+
)
126+
127+
assert graphql_sync(
128+
schema, '{ test(aInt: -123, aStr: "String!") }', "Source!"
129+
) == ({"test": "['Source!', {'a_str': 'String!', 'a_int': -123}]"}, None)
130+
131+
def transforms_arguments_with_inputs_using_out_names():
132+
# This is an extension of GraphQL.js.
133+
TestInputObject = GraphQLInputObjectType(
134+
"TestInputObjectType",
135+
lambda: {
136+
"inputOne": GraphQLInputField(GraphQLString, out_name="input_one"),
137+
"inputRecursive": GraphQLInputField(
138+
TestInputObject, out_name="input_recursive"
139+
),
140+
},
141+
)
142+
143+
schema = _test_schema(
144+
GraphQLField(
145+
GraphQLString,
146+
args={"aInput": GraphQLArgument(TestInputObject, out_name="a_input")},
147+
resolve=lambda source, info, **args: repr([source, args]),
148+
)
149+
)
150+
151+
assert graphql_sync(schema, "{ test }") == ({"test": "[None, {}]"}, None)
152+
153+
assert graphql_sync(
154+
schema, '{ test(aInput: {inputOne: "String!"}) }', "Source!"
155+
) == ({"test": "['Source!', {'a_input': {'input_one': 'String!'}}]"}, None)
156+
157+
assert graphql_sync(
158+
schema,
159+
'{ test(aInput: {inputRecursive: {inputOne: "SourceRecursive!"}}) }',
160+
"Source!",
161+
) == (
162+
{
163+
"test": "['Source!',"
164+
" {'a_input': {'input_recursive': {'input_one': 'SourceRecursive!'}}}]"
165+
},
166+
None,
167+
)

0 commit comments

Comments
 (0)