1
1
import logging
2
+ import re
2
3
from abc import ABC , abstractmethod
4
+ from math import isfinite
3
5
from typing import Any , Dict , Iterable , List , Mapping , Optional , Tuple , Union , cast
4
6
5
7
from graphql import (
6
8
ArgumentNode ,
9
+ BooleanValueNode ,
7
10
DocumentNode ,
11
+ EnumValueNode ,
8
12
FieldNode ,
13
+ FloatValueNode ,
9
14
FragmentDefinitionNode ,
10
15
FragmentSpreadNode ,
11
16
GraphQLArgument ,
17
+ GraphQLError ,
12
18
GraphQLField ,
19
+ GraphQLID ,
13
20
GraphQLInputObjectType ,
14
21
GraphQLInputType ,
15
22
GraphQLInterfaceType ,
20
27
GraphQLSchema ,
21
28
GraphQLWrappingType ,
22
29
InlineFragmentNode ,
30
+ IntValueNode ,
23
31
ListTypeNode ,
24
32
ListValueNode ,
25
33
NamedTypeNode ,
31
39
OperationDefinitionNode ,
32
40
OperationType ,
33
41
SelectionSetNode ,
42
+ StringValueNode ,
34
43
TypeNode ,
35
44
Undefined ,
36
45
ValueNode ,
37
46
VariableDefinitionNode ,
38
47
VariableNode ,
39
48
assert_named_type ,
49
+ is_enum_type ,
40
50
is_input_object_type ,
51
+ is_leaf_type ,
41
52
is_list_type ,
42
53
is_non_null_type ,
43
54
is_wrapping_type ,
44
55
print_ast ,
45
56
)
46
- from graphql .pyutils import FrozenList
47
- from graphql .utilities import ast_from_value as default_ast_from_value
57
+ from graphql .pyutils import FrozenList , inspect
48
58
49
59
from .utils import to_camel_case
50
60
51
61
log = logging .getLogger (__name__ )
52
62
63
+ _re_integer_string = re .compile ("^-?(?:0|[1-9][0-9]*)$" )
64
+
65
+
66
+ def ast_from_serialized_value_untyped (serialized : Any ) -> Optional [ValueNode ]:
67
+ """Given a serialized value, try our best to produce an AST.
68
+
69
+ Anything ressembling an array (instance of Mapping) will be converted
70
+ to an ObjectFieldNode.
71
+
72
+ Anything ressembling a list (instance of Iterable - except str)
73
+ will be converted to a ListNode.
74
+
75
+ In some cases, a custom scalar can be serialized differently in the query
76
+ than in the variables. In that case, this function will not work."""
77
+
78
+ if serialized is None or serialized is Undefined :
79
+ return NullValueNode ()
80
+
81
+ if isinstance (serialized , Mapping ):
82
+ field_items = (
83
+ (key , ast_from_serialized_value_untyped (value ))
84
+ for key , value in serialized .items ()
85
+ )
86
+ field_nodes = (
87
+ ObjectFieldNode (name = NameNode (value = field_name ), value = field_value )
88
+ for field_name , field_value in field_items
89
+ if field_value
90
+ )
91
+ return ObjectValueNode (fields = FrozenList (field_nodes ))
92
+
93
+ if isinstance (serialized , Iterable ) and not isinstance (serialized , str ):
94
+ maybe_nodes = (ast_from_serialized_value_untyped (item ) for item in serialized )
95
+ nodes = filter (None , maybe_nodes )
96
+ return ListValueNode (values = FrozenList (nodes ))
97
+
98
+ if isinstance (serialized , bool ):
99
+ return BooleanValueNode (value = serialized )
100
+
101
+ if isinstance (serialized , int ):
102
+ return IntValueNode (value = f"{ serialized :d} " )
103
+
104
+ if isinstance (serialized , float ) and isfinite (serialized ):
105
+ return FloatValueNode (value = f"{ serialized :g} " )
106
+
107
+ if isinstance (serialized , str ):
108
+ return StringValueNode (value = serialized )
109
+
110
+ raise TypeError (f"Cannot convert value to AST: { inspect (serialized )} ." )
111
+
53
112
54
113
def ast_from_value (value : Any , type_ : GraphQLInputType ) -> Optional [ValueNode ]:
55
114
"""
@@ -60,15 +119,21 @@ def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]:
60
119
VariableNode when value is a DSLVariable
61
120
62
121
Produce a GraphQL Value AST given a Python object.
122
+
123
+ Raises a GraphQLError instead of returning None if we receive an Undefined
124
+ of if we receive a Null value for a Non-Null type.
63
125
"""
64
126
if isinstance (value , DSLVariable ):
65
127
return value .set_type (type_ ).ast_variable
66
128
67
129
if is_non_null_type (type_ ):
68
130
type_ = cast (GraphQLNonNull , type_ )
69
- ast_value = ast_from_value (value , type_ .of_type )
131
+ inner_type = type_ .of_type
132
+ ast_value = ast_from_value (value , inner_type )
70
133
if isinstance (ast_value , NullValueNode ):
71
- return None
134
+ raise GraphQLError (
135
+ "Received Null value for a Non-Null type " f"{ inspect (inner_type )} ."
136
+ )
72
137
return ast_value
73
138
74
139
# only explicit None, not Undefined or NaN
@@ -77,7 +142,7 @@ def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]:
77
142
78
143
# undefined
79
144
if value is Undefined :
80
- return None
145
+ raise GraphQLError ( f"Received Undefined value for type { inspect ( type_ ) } ." )
81
146
82
147
# Convert Python list to GraphQL list. If the GraphQLType is a list, but the value
83
148
# is not a list, convert the value using the list's item type.
@@ -108,7 +173,32 @@ def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]:
108
173
)
109
174
return ObjectValueNode (fields = FrozenList (field_nodes ))
110
175
111
- return default_ast_from_value (value , type_ )
176
+ if is_leaf_type (type_ ):
177
+ # Since value is an internally represented value, it must be serialized to an
178
+ # externally represented value before converting into an AST.
179
+ serialized = type_ .serialize (value ) # type: ignore
180
+
181
+ # if the serialized value is a string, then we should use the
182
+ # type to determine if it is an enum, an ID or a normal string
183
+ if isinstance (serialized , str ):
184
+ # Enum types use Enum literals.
185
+ if is_enum_type (type_ ):
186
+ return EnumValueNode (value = serialized )
187
+
188
+ # ID types can use Int literals.
189
+ if type_ is GraphQLID and _re_integer_string .match (serialized ):
190
+ return IntValueNode (value = serialized )
191
+
192
+ return StringValueNode (value = serialized )
193
+
194
+ # Some custom scalars will serialize to dicts or lists
195
+ # Providing here a default conversion to AST using our best judgment
196
+ # until graphql-js issue #1817 is solved
197
+ # https://github.com/graphql/graphql-js/issues/1817
198
+ return ast_from_serialized_value_untyped (serialized )
199
+
200
+ # Not reachable. All possible input types have been considered.
201
+ raise TypeError (f"Unexpected input type: { inspect (type_ )} ." )
112
202
113
203
114
204
def dsl_gql (
0 commit comments