1
+ import sys
1
2
import collections
2
- from collections import abc
3
+ import collections . abc
3
4
import typing as T
4
5
import uuid
5
6
import datetime
6
7
import decimal
7
8
import enum
8
9
10
+ from pydantic import BaseModel , fields
11
+
9
12
from graphene import Field , Boolean , Enum , Float , Int , List , String , UUID , Union
10
13
from graphene .types .base import BaseType
14
+ from graphene .types .datetime import Date , Time , DateTime
11
15
12
16
try :
13
17
from graphene .types .decimal import Decimal as GrapheneDecimal
17
21
# graphene 2.1.5+ is required for Decimals
18
22
DECIMAL_SUPPORTED = False
19
23
20
- from graphene .types .datetime import Date , Time , DateTime
21
- from pydantic import fields
22
-
23
24
from .registry import Registry
24
25
from .util import construct_union_class_name
25
26
@@ -44,15 +45,22 @@ def _get_field(root, _info):
44
45
45
46
46
47
def convert_pydantic_field (
47
- field : fields .Field , registry : Registry , ** field_kwargs
48
+ field : fields .Field ,
49
+ registry : Registry ,
50
+ parent_type : T .Type = None ,
51
+ model : T .Type [BaseModel ] = None ,
52
+ ** field_kwargs ,
48
53
) -> Field :
49
54
"""
50
55
Convert a Pydantic model field into a Graphene type field that we can add
51
56
to the generated Graphene data model type.
52
57
"""
53
58
declared_type = getattr (field , "type_" , None )
54
59
field_kwargs .setdefault (
55
- "type" , convert_pydantic_type (declared_type , field , registry )
60
+ "type" ,
61
+ convert_pydantic_type (
62
+ declared_type , field , registry , parent_type = parent_type , model = model
63
+ ),
56
64
)
57
65
field_kwargs .setdefault ("required" , field .required )
58
66
field_kwargs .setdefault ("default_value" , field .default )
@@ -66,14 +74,20 @@ def convert_pydantic_field(
66
74
67
75
68
76
def convert_pydantic_type (
69
- type_ : T .Type , field : fields .Field , registry : Registry = None
77
+ type_ : T .Type ,
78
+ field : fields .Field ,
79
+ registry : Registry = None ,
80
+ parent_type : T .Type = None ,
81
+ model : T .Type [BaseModel ] = None ,
70
82
) -> BaseType : # noqa: C901
71
83
"""
72
84
Convert a Pydantic type to a Graphene Field type, including not just the
73
85
native Python type but any additional metadata (e.g. shape) that Pydantic
74
86
knows about.
75
87
"""
76
- graphene_type = find_graphene_type (type_ , field , registry )
88
+ graphene_type = find_graphene_type (
89
+ type_ , field , registry , parent_type = parent_type , model = model
90
+ )
77
91
if field .shape == fields .Shape .SINGLETON :
78
92
return graphene_type
79
93
elif field .shape in (
@@ -90,7 +104,11 @@ def convert_pydantic_type(
90
104
91
105
92
106
def find_graphene_type (
93
- type_ : T .Type , field : fields .Field , registry : Registry = None
107
+ type_ : T .Type ,
108
+ field : fields .Field ,
109
+ registry : Registry = None ,
110
+ parent_type : T .Type = None ,
111
+ model : T .Type [BaseModel ] = None ,
94
112
) -> BaseType : # noqa: C901
95
113
"""
96
114
Map a native Python type to a Graphene-supported Field type, where possible,
@@ -114,25 +132,56 @@ def find_graphene_type(
114
132
return GrapheneDecimal if DECIMAL_SUPPORTED else Float
115
133
elif type_ == int :
116
134
return Int
117
- # NOTE: this has to come before any `issubclass()` checks, because annotated
118
- # generic types aren't valid arguments to `issubclass`
119
- elif hasattr (type_ , "__origin__" ):
120
- return convert_generic_python_type (type_ , field , registry )
121
135
elif type_ in (tuple , list , set ):
122
136
# TODO: do Sets really belong here?
123
137
return List
124
- elif issubclass (type_ , enum .Enum ):
125
- return Enum .from_enum (type_ )
126
138
elif registry and registry .get_type_for_model (type_ ):
127
139
return registry .get_type_for_model (type_ )
140
+ elif registry and isinstance (type_ , BaseModel ):
141
+ # If it's a Pydantic model that hasn't yet been wrapped with a ObjectType,
142
+ # we can put a placeholder in and request that `resolve_placeholders()`
143
+ # be called to update it.
144
+ registry .add_placeholder_for_model (type_ )
145
+ # NOTE: this has to come before any `issubclass()` checks, because annotated
146
+ # generic types aren't valid arguments to `issubclass`
147
+ elif hasattr (type_ , "__origin__" ):
148
+ return convert_generic_python_type (
149
+ type_ , field , registry , parent_type = parent_type , model = model
150
+ )
151
+ elif isinstance (type_ , T .ForwardRef ):
152
+ # A special case! We have to do a little hackery to try and resolve
153
+ # the type that this points to, by trying to reference a "sibling" type
154
+ # to where this was defined so we can get access to that namespace...
155
+ sibling = model or parent_type
156
+ if not sibling :
157
+ raise ConversionError (
158
+ "Don't know how to convert the Pydantic field "
159
+ f"{ field !r} ({ field .type_ } ), could not resolve "
160
+ "the forward reference. Did you call `resolve_placeholders()`? "
161
+ "See the README for more on forward references."
162
+ )
163
+ module_ns = sys .modules [sibling .__module__ ].__dict__
164
+ resolved = type_ ._evaluate (module_ns , None )
165
+ # TODO: make this behavior optional. maybe this is a place for the TypeOptions to play a role?
166
+ if registry :
167
+ registry .add_placeholder_for_model (resolved )
168
+ return find_graphene_type (
169
+ resolved , field , registry , parent_type = parent_type , model = model
170
+ )
171
+ elif issubclass (type_ , enum .Enum ):
172
+ return Enum .from_enum (type_ )
128
173
else :
129
174
raise ConversionError (
130
175
f"Don't know how to convert the Pydantic field { field !r} ({ field .type_ } )"
131
176
)
132
177
133
178
134
179
def convert_generic_python_type (
135
- type_ : T .Type , field : fields .Field , registry : Registry = None
180
+ type_ : T .Type ,
181
+ field : fields .Field ,
182
+ registry : Registry = None ,
183
+ parent_type : T .Type = None ,
184
+ model : T .Type [BaseModel ] = None ,
136
185
) -> BaseType : # noqa: C901
137
186
"""
138
187
Convert annotated Python generic types into the most appropriate Graphene
@@ -146,7 +195,9 @@ def convert_generic_python_type(
146
195
# decide whether the origin type is a subtype of, say, T.Iterable since typical
147
196
# Python functions like `isinstance()` don't work
148
197
if origin == T .Union :
149
- return convert_union_type (type_ , field , registry )
198
+ return convert_union_type (
199
+ type_ , field , registry , parent_type = parent_type , model = model
200
+ )
150
201
elif origin in (
151
202
T .Tuple ,
152
203
T .List ,
@@ -155,7 +206,7 @@ def convert_generic_python_type(
155
206
T .Iterable ,
156
207
list ,
157
208
set ,
158
- ) or issubclass (origin , abc .Sequence ):
209
+ ) or issubclass (origin , collections . abc .Sequence ):
159
210
# TODO: find a better way of divining that the origin is sequence-like
160
211
inner_types = getattr (type_ , "__args__" , [])
161
212
if not inner_types : # pragma: no cover # this really should be impossible
@@ -165,16 +216,26 @@ def convert_generic_python_type(
165
216
# Of course, we can only return a homogeneous type here, so we pick the
166
217
# first of the wrapped types
167
218
inner_type = inner_types [0 ]
168
- return List (find_graphene_type (inner_type , field , registry ))
219
+ return List (
220
+ find_graphene_type (
221
+ inner_type , field , registry , parent_type = parent_type , model = model
222
+ )
223
+ )
169
224
elif origin in (T .Dict , T .Mapping , collections .OrderedDict , dict ) or issubclass (
170
- origin , abc .Mapping
225
+ origin , collections . abc .Mapping
171
226
):
172
227
raise ConversionError ("Don't know how to handle mappings in Graphene" )
173
228
else :
174
229
raise ConversionError (f"Don't know how to handle { type_ } (generic: { origin } )" )
175
230
176
231
177
- def convert_union_type (type_ : T .Type , field : fields .Field , registry : Registry = None ):
232
+ def convert_union_type (
233
+ type_ : T .Type ,
234
+ field : fields .Field ,
235
+ registry : Registry = None ,
236
+ parent_type : T .Type = None ,
237
+ model : T .Type [BaseModel ] = None ,
238
+ ):
178
239
"""
179
240
Convert an annotated Python Union type into a Graphene Union.
180
241
"""
@@ -184,12 +245,17 @@ def convert_union_type(type_: T.Type, field: fields.Field, registry: Registry =
184
245
# typing.Union[None, T] -- we can return the Graphene type for T directly
185
246
# since Pydantic will have already parsed it as optional
186
247
native_type = next (x for x in inner_types if x != NONE_TYPE ) # noqa: E721
187
- graphene_type = find_graphene_type (native_type , field , registry )
248
+ graphene_type = find_graphene_type (
249
+ native_type , field , registry , parent_type = parent_type , model = model
250
+ )
188
251
return graphene_type
189
252
190
253
# Otherwise, we use a little metaprogramming -- create our own unique
191
254
# subclass of graphene.Union that knows its constituent Graphene types
192
- parent_types = tuple (find_graphene_type (x , field , registry ) for x in inner_types )
255
+ parent_types = tuple (
256
+ find_graphene_type (x , field , registry , parent_type = parent_type , model = model )
257
+ for x in inner_types
258
+ )
193
259
internal_meta_cls = type ("Meta" , (), {"types" : parent_types })
194
260
195
261
union_cls = type (
0 commit comments