-
-
Notifications
You must be signed in to change notification settings - Fork 138
/
Copy pathast_from_value.py
130 lines (110 loc) · 4.45 KB
/
ast_from_value.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import re
from typing import Any, Iterable, List, Mapping, Optional, cast
from ..language import (
BooleanValueNode,
EnumValueNode,
FloatValueNode,
IntValueNode,
ListValueNode,
NameNode,
NullValueNode,
ObjectFieldNode,
ObjectValueNode,
StringValueNode,
ValueNode,
)
from ..pyutils import inspect, is_nullish, is_invalid
from ..type import (
GraphQLID,
GraphQLInputType,
GraphQLInputObjectType,
GraphQLList,
GraphQLNonNull,
is_enum_type,
is_input_object_type,
is_leaf_type,
is_list_type,
is_non_null_type,
)
__all__ = ["ast_from_value"]
_re_integer_string = re.compile("^-?(?:0|[1-9][0-9]*)$")
def ast_from_value(value: Any, type_: GraphQLInputType) -> Optional[ValueNode]:
"""Produce a GraphQL Value AST given a Python value.
A GraphQL type must be provided, which will be used to interpret different Python
values.
| JSON Value | GraphQL Value |
| ------------- | -------------------- |
| Object | Input Object |
| Array | List |
| Boolean | Boolean |
| String | String / Enum Value |
| Number | Int / Float |
| Mixed | Enum Value |
| null | NullValue |
"""
if is_non_null_type(type_):
type_ = cast(GraphQLNonNull, type_)
ast_value = ast_from_value(value, type_.of_type)
if isinstance(ast_value, NullValueNode):
return None
return ast_value
# only explicit None, not INVALID or NaN
if value is None:
return NullValueNode()
# INVALID or NaN
if is_invalid(value):
return None
# Convert Python list to GraphQL list. If the GraphQLType is a list, but the value
# is not a list, convert the value using the list's item type.
if is_list_type(type_):
type_ = cast(GraphQLList, type_)
item_type = type_.of_type
if isinstance(value, Iterable) and not isinstance(value, str):
value_nodes = [
ast_from_value(item, item_type) for item in value # type: ignore
]
return ListValueNode(values=value_nodes)
return ast_from_value(value, item_type) # type: ignore
# Populate the fields of the input object by creating ASTs from each value in the
# Python dict according to the fields in the input type.
if is_input_object_type(type_):
if value is None or not isinstance(value, Mapping):
return None
type_ = cast(GraphQLInputObjectType, type_)
field_nodes: List[ObjectFieldNode] = []
append_node = field_nodes.append
for field_name, field in type_.fields.items():
if field_name in value:
field_value = ast_from_value(value[field_name], field.type)
if field_value:
append_node(
ObjectFieldNode(
name=NameNode(value=field_name), value=field_value
)
)
return ObjectValueNode(fields=field_nodes)
if is_leaf_type(type_):
# Since value is an internally represented value, it must be serialized to an
# externally represented value before converting into an AST.
serialized = type_.serialize(value) # type: ignore
if is_nullish(serialized):
return None
# Others serialize based on their corresponding Python scalar types.
if isinstance(serialized, bool):
return BooleanValueNode(value=serialized)
# Python ints and floats correspond nicely to Int and Float values.
if isinstance(serialized, int):
return IntValueNode(value=f"{serialized:d}")
if isinstance(serialized, float):
return FloatValueNode(value=f"{serialized:g}")
if isinstance(serialized, str):
# Enum types use Enum literals.
if is_enum_type(type_):
return EnumValueNode(value=serialized)
# ID types can use Int literals.
if type_ is GraphQLID and _re_integer_string.match(serialized):
return IntValueNode(value=serialized)
return StringValueNode(value=serialized)
raise TypeError(f"Cannot convert value to AST: {inspect(serialized)}")
# Not reachable. All possible input types have been considered.
raise TypeError(f"Unexpected input type: '{inspect(type_)}'.") # pragma: no cover