22import collections .abc
33import datetime
44import decimal
5- import inspect
65import enum
6+ import inspect
77import sys
88import typing as T
99import uuid
10+ from typing import Type , get_origin
1011
12+ import graphene
1113from graphene import (
12- UUID ,
1314 Boolean ,
1415 Enum ,
1516 Field ,
1617 Float ,
18+ ID ,
1719 InputField ,
1820 Int ,
21+ JSONString ,
1922 List ,
2023 String ,
24+ UUID ,
2125 Union ,
2226)
23- import graphene
2427from graphene .types .base import BaseType
2528from graphene .types .datetime import Date , DateTime , Time
2629from pydantic import BaseModel
27- from pydantic .fields import ModelField
28- from pydantic . typing import evaluate_forwardref
30+ from pydantic .fields import FieldInfo
31+ from pydantic_core import PydanticUndefined
2932
30- from .registry import Registry
31- from .util import construct_union_class_name
33+ from .registry import Placeholder , Registry
34+ from .util import construct_union_class_name , evaluate_forward_ref
3235
33- from pydantic import fields
36+ PYTHON10 = sys .version_info >= (3 , 10 )
37+ if PYTHON10 :
38+ from types import UnionType
3439
3540GRAPHENE2 = graphene .VERSION [0 ] < 3
3641
37- SHAPE_SINGLETON = (fields .SHAPE_SINGLETON ,)
38- SHAPE_SEQUENTIAL = (
39- fields .SHAPE_LIST ,
40- fields .SHAPE_TUPLE ,
41- fields .SHAPE_TUPLE_ELLIPSIS ,
42- fields .SHAPE_SEQUENCE ,
43- fields .SHAPE_SET ,
44- )
45-
46- if hasattr (fields , "SHAPE_DICT" ):
47- SHAPE_MAPPING = T .cast (
48- T .Tuple , (fields .SHAPE_MAPPING , fields .SHAPE_DICT , fields .SHAPE_DEFAULTDICT )
49- )
50- else :
51- SHAPE_MAPPING = T .cast (T .Tuple , (fields .SHAPE_MAPPING ,))
42+ try :
43+ from bson import ObjectId
5244
45+ BSON_OBJECT_ID_SUPPORTED = True
46+ except ImportError :
47+ BSON_OBJECT_ID_SUPPORTED = False
5348
5449try :
5550 from graphene .types .decimal import Decimal as GrapheneDecimal
5954 # graphene 2.1.5+ is required for Decimals
6055 DECIMAL_SUPPORTED = False
6156
62-
6357NONE_TYPE = None .__class__ # need to do this because mypy complains about type(None)
6458
6559
@@ -80,7 +74,7 @@ def _get_field(root, _info):
8074
8175
8276def convert_pydantic_input_field (
83- field : ModelField ,
77+ field : FieldInfo ,
8478 registry : Registry ,
8579 parent_type : T .Type = None ,
8680 model : T .Type [BaseModel ] = None ,
@@ -90,26 +84,29 @@ def convert_pydantic_input_field(
9084 Convert a Pydantic model field into a Graphene type field that we can add
9185 to the generated Graphene data model type.
9286 """
93- declared_type = getattr (field , "type_ " , None )
87+ declared_type = getattr (field , "annotation " , None )
9488 field_kwargs .setdefault (
9589 "type" if GRAPHENE2 else "type_" ,
9690 convert_pydantic_type (
9791 declared_type , field , registry , parent_type = parent_type , model = model
9892 ),
9993 )
100- field_kwargs .setdefault ("required" , field .required )
101- field_kwargs .setdefault ("default_value" , field .default )
94+ field_kwargs .setdefault ("required" , field .is_required ())
95+ field_kwargs .setdefault (
96+ "default_value" , None if field .default is PydanticUndefined else field .default
97+ )
10298 # TODO: find a better way to get a field's description. Some ideas include:
10399 # - hunt down the description from the field's schema, or the schema
104100 # from the field's base model
105101 # - maybe even (Sphinx-style) parse attribute documentation
106- field_kwargs .setdefault ("description" , field .field_info . description )
102+ field_kwargs .setdefault ("description" , field .description )
107103
108104 return InputField (** field_kwargs )
109105
110106
111107def convert_pydantic_field (
112- field : ModelField ,
108+ name : str ,
109+ field : FieldInfo ,
113110 registry : Registry ,
114111 parent_type : T .Type = None ,
115112 model : T .Type [BaseModel ] = None ,
@@ -119,44 +116,67 @@ def convert_pydantic_field(
119116 Convert a Pydantic model field into a Graphene type field that we can add
120117 to the generated Graphene data model type.
121118 """
122- declared_type = getattr (field , "type_" , None )
119+ declared_type = getattr (field , "annotation" , None )
120+
121+ # Convert Python 10 UnionType to T.Union
122+ if PYTHON10 :
123+ is_union_type = (
124+ get_origin (declared_type ) is T .Union
125+ or get_origin (declared_type ) is UnionType
126+ )
127+ else :
128+ is_union_type = get_origin (declared_type ) is T .Union
129+
130+ if is_union_type :
131+ declared_type = T .Union [declared_type .__args__ ]
132+
123133 field_kwargs .setdefault (
124134 "type" if GRAPHENE2 else "type_" ,
125135 convert_pydantic_type (
126136 declared_type , field , registry , parent_type = parent_type , model = model
127137 ),
128138 )
129- field_kwargs .setdefault ("required" , not field .allow_none )
130- field_kwargs .setdefault ("default_value" , field .default )
131- if field .has_alias :
139+ field_kwargs .setdefault (
140+ "required" ,
141+ field .is_required ()
142+ or (
143+ type (field .default ) is not PydanticUndefined
144+ and getattr (declared_type , "_name" , "" ) != "Optional"
145+ and not is_union_type
146+ ),
147+ )
148+ field_kwargs .setdefault (
149+ "default_value" , None if field .default is PydanticUndefined else field .default
150+ )
151+ if field .alias :
132152 field_kwargs .setdefault ("name" , field .alias )
133153 # TODO: find a better way to get a field's description. Some ideas include:
134154 # - hunt down the description from the field's schema, or the schema
135155 # from the field's base model
136156 # - maybe even (Sphinx-style) parse attribute documentation
137- field_kwargs .setdefault ("description" , field .field_info . description )
157+ field_kwargs .setdefault ("description" , field .description )
138158
139159 # Handle Graphene 2 and 3
140160 field_type = field_kwargs .pop ("type" , field_kwargs .pop ("type_" , None ))
141161 if field_type is None :
142162 raise ValueError ("No field type could be determined." )
143163
144- resolver_function = getattr (parent_type , "resolve_" + field . name , None )
164+ resolver_function = getattr (parent_type , "resolve_" + name , None )
145165 if resolver_function and callable (resolver_function ):
146166 field_resolver = resolver_function
147167 else :
148- field_resolver = get_attr_resolver (field . name )
168+ field_resolver = get_attr_resolver (name )
149169
150170 return Field (field_type , resolver = field_resolver , ** field_kwargs )
151171
152172
153173def convert_pydantic_type (
154174 type_ : T .Type ,
155- field : ModelField ,
175+ field : FieldInfo ,
156176 registry : Registry ,
157177 parent_type : T .Type = None ,
158178 model : T .Type [BaseModel ] = None ,
159- ) -> BaseType : # noqa: C901
179+ ) -> T . Union [ Type [ T . Union [ BaseType , List ]], Placeholder ] : # noqa: C901
160180 """
161181 Convert a Pydantic type to a Graphene Field type, including not just the
162182 native Python type but any additional metadata (e.g. shape) that Pydantic
@@ -165,26 +185,30 @@ def convert_pydantic_type(
165185 graphene_type = find_graphene_type (
166186 type_ , field , registry , parent_type = parent_type , model = model
167187 )
168- if field .shape in SHAPE_SINGLETON :
169- return graphene_type
170- elif field .shape in SHAPE_SEQUENTIAL :
171- # TODO: _should_ Sets remain here?
172- return List (graphene_type )
173- elif field .shape in SHAPE_MAPPING :
188+ field_type = getattr (field .annotation , "__origin__" , None )
189+ if field_type == map : # SHAPE_MAPPING
174190 raise ConversionError ("Don't know how to handle mappings in Graphene." )
175191
192+ return graphene_type
193+
176194
177195def find_graphene_type (
178196 type_ : T .Type ,
179- field : ModelField ,
197+ field : FieldInfo ,
180198 registry : Registry ,
181199 parent_type : T .Type = None ,
182200 model : T .Type [BaseModel ] = None ,
183- ) -> BaseType : # noqa: C901
201+ ) -> T . Union [ Type [ T . Union [ BaseType , List ]], Placeholder ] : # noqa: C901
184202 """
185203 Map a native Python type to a Graphene-supported Field type, where possible,
186204 throwing an error if we don't know what to map it to.
187205 """
206+
207+ # Convert Python 10 UnionType to T.Union
208+ if PYTHON10 :
209+ if isinstance (type_ , UnionType ):
210+ type_ = T .Union [type_ .__args__ ]
211+
188212 if type_ == uuid .UUID :
189213 return UUID
190214 elif type_ in (str , bytes ):
@@ -199,6 +223,10 @@ def find_graphene_type(
199223 return Boolean
200224 elif type_ == float :
201225 return Float
226+ elif BSON_OBJECT_ID_SUPPORTED and type_ == ObjectId :
227+ return ID
228+ elif type_ == dict :
229+ return JSONString
202230 elif type_ == decimal .Decimal :
203231 return GrapheneDecimal if DECIMAL_SUPPORTED else Float
204232 elif type_ == int :
@@ -231,12 +259,13 @@ def find_graphene_type(
231259 if not sibling :
232260 raise ConversionError (
233261 "Don't know how to convert the Pydantic field "
234- f"{ field !r} ({ field .type_ } ), could not resolve "
262+ f"{ field !r} ({ field .annotation } ), could not resolve "
235263 "the forward reference. Did you call `resolve_placeholders()`? "
236264 "See the README for more on forward references."
237265 )
266+
238267 module_ns = sys .modules [sibling .__module__ ].__dict__
239- resolved = evaluate_forwardref (type_ , module_ns , None )
268+ resolved = evaluate_forward_ref (type_ , module_ns , None )
240269 # TODO: make this behavior optional. maybe this is a place for the TypeOptions to play a role?
241270 if registry :
242271 registry .add_placeholder_for_model (resolved )
@@ -265,20 +294,20 @@ def find_graphene_type(
265294 return List
266295 else :
267296 raise ConversionError (
268- f"Don't know how to convert the Pydantic field { field !r} ({ field .type_ } )"
297+ f"Don't know how to convert the Pydantic field { field !r} ({ field .annotation } )"
269298 )
270299
271300
272301def convert_generic_python_type (
273302 type_ : T .Type ,
274- field : ModelField ,
303+ field : FieldInfo ,
275304 registry : Registry ,
276305 parent_type : T .Type = None ,
277306 model : T .Type [BaseModel ] = None ,
278- ) -> BaseType : # noqa: C901
307+ ) -> T . Union [ Type [ T . Union [ BaseType , List ]], Placeholder ] : # noqa: C901
279308 """
280309 Convert annotated Python generic types into the most appropriate Graphene
281- Field type -- e.g. turn `typing.Union` into a Graphene Union.
310+ Field type -- e.g., turn `typing.Union` into a Graphene Union.
282311 """
283312 origin = type_ .__origin__
284313 if not origin : # pragma: no cover # this really should be impossible
@@ -321,14 +350,14 @@ def convert_generic_python_type(
321350 elif origin in (T .Dict , T .Mapping , collections .OrderedDict , dict ) or issubclass (
322351 origin , collections .abc .Mapping
323352 ):
324- raise ConversionError ("Don't know how to handle mappings in Graphene" )
353+ raise ConversionError ("Don't know how to handle mappings in Graphene. " )
325354 else :
326355 raise ConversionError (f"Don't know how to handle { type_ } (generic: { origin } )" )
327356
328357
329358def convert_union_type (
330359 type_ : T .Type ,
331- field : ModelField ,
360+ field : FieldInfo ,
332361 registry : Registry ,
333362 parent_type : T .Type = None ,
334363 model : T .Type [BaseModel ] = None ,
@@ -361,11 +390,11 @@ def convert_union_type(
361390
362391def convert_literal_type (
363392 type_ : T .Type ,
364- field : ModelField ,
393+ field : FieldInfo ,
365394 registry : Registry ,
366395 parent_type : T .Type = None ,
367396 model : T .Type [BaseModel ] = None ,
368- ):
397+ ) -> T . Union [ Type [ T . Union [ BaseType , List ]], Placeholder ] :
369398 """
370399 Convert an annotated Python Literal type into a Graphene Scalar or Union of Scalars.
371400 """
0 commit comments