1+ import sys
12import collections
2- from collections import abc
3+ import collections . abc
34import typing as T
45import uuid
56import datetime
67import decimal
78import enum
89
10+ from pydantic import BaseModel , fields
11+
912from graphene import Field , Boolean , Enum , Float , Int , List , String , UUID , Union
1013from graphene .types .base import BaseType
14+ from graphene .types .datetime import Date , Time , DateTime
1115
1216try :
1317 from graphene .types .decimal import Decimal as GrapheneDecimal
1721 # graphene 2.1.5+ is required for Decimals
1822 DECIMAL_SUPPORTED = False
1923
20- from graphene .types .datetime import Date , Time , DateTime
21- from pydantic import fields
22-
2324from .registry import Registry
2425from .util import construct_union_class_name
2526
@@ -44,15 +45,22 @@ def _get_field(root, _info):
4445
4546
4647def 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 ,
4853) -> Field :
4954 """
5055 Convert a Pydantic model field into a Graphene type field that we can add
5156 to the generated Graphene data model type.
5257 """
5358 declared_type = getattr (field , "type_" , None )
5459 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+ ),
5664 )
5765 field_kwargs .setdefault ("required" , field .required )
5866 field_kwargs .setdefault ("default_value" , field .default )
@@ -66,14 +74,20 @@ def convert_pydantic_field(
6674
6775
6876def 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 ,
7082) -> BaseType : # noqa: C901
7183 """
7284 Convert a Pydantic type to a Graphene Field type, including not just the
7385 native Python type but any additional metadata (e.g. shape) that Pydantic
7486 knows about.
7587 """
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+ )
7791 if field .shape == fields .Shape .SINGLETON :
7892 return graphene_type
7993 elif field .shape in (
@@ -90,7 +104,11 @@ def convert_pydantic_type(
90104
91105
92106def 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 ,
94112) -> BaseType : # noqa: C901
95113 """
96114 Map a native Python type to a Graphene-supported Field type, where possible,
@@ -114,25 +132,56 @@ def find_graphene_type(
114132 return GrapheneDecimal if DECIMAL_SUPPORTED else Float
115133 elif type_ == int :
116134 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 )
121135 elif type_ in (tuple , list , set ):
122136 # TODO: do Sets really belong here?
123137 return List
124- elif issubclass (type_ , enum .Enum ):
125- return Enum .from_enum (type_ )
126138 elif registry and registry .get_type_for_model (type_ ):
127139 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_ )
128173 else :
129174 raise ConversionError (
130175 f"Don't know how to convert the Pydantic field { field !r} ({ field .type_ } )"
131176 )
132177
133178
134179def 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 ,
136185) -> BaseType : # noqa: C901
137186 """
138187 Convert annotated Python generic types into the most appropriate Graphene
@@ -146,7 +195,9 @@ def convert_generic_python_type(
146195 # decide whether the origin type is a subtype of, say, T.Iterable since typical
147196 # Python functions like `isinstance()` don't work
148197 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+ )
150201 elif origin in (
151202 T .Tuple ,
152203 T .List ,
@@ -155,7 +206,7 @@ def convert_generic_python_type(
155206 T .Iterable ,
156207 list ,
157208 set ,
158- ) or issubclass (origin , abc .Sequence ):
209+ ) or issubclass (origin , collections . abc .Sequence ):
159210 # TODO: find a better way of divining that the origin is sequence-like
160211 inner_types = getattr (type_ , "__args__" , [])
161212 if not inner_types : # pragma: no cover # this really should be impossible
@@ -165,16 +216,26 @@ def convert_generic_python_type(
165216 # Of course, we can only return a homogeneous type here, so we pick the
166217 # first of the wrapped types
167218 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+ )
169224 elif origin in (T .Dict , T .Mapping , collections .OrderedDict , dict ) or issubclass (
170- origin , abc .Mapping
225+ origin , collections . abc .Mapping
171226 ):
172227 raise ConversionError ("Don't know how to handle mappings in Graphene" )
173228 else :
174229 raise ConversionError (f"Don't know how to handle { type_ } (generic: { origin } )" )
175230
176231
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+ ):
178239 """
179240 Convert an annotated Python Union type into a Graphene Union.
180241 """
@@ -184,12 +245,17 @@ def convert_union_type(type_: T.Type, field: fields.Field, registry: Registry =
184245 # typing.Union[None, T] -- we can return the Graphene type for T directly
185246 # since Pydantic will have already parsed it as optional
186247 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+ )
188251 return graphene_type
189252
190253 # Otherwise, we use a little metaprogramming -- create our own unique
191254 # 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+ )
193259 internal_meta_cls = type ("Meta" , (), {"types" : parent_types })
194260
195261 union_cls = type (
0 commit comments