Skip to content

Commit e03a5c8

Browse files
committed
feat: added support for v2
deprecated: pydantic v1
1 parent 1fdf001 commit e03a5c8

File tree

10 files changed

+288
-214
lines changed

10 files changed

+288
-214
lines changed

graphene_pydantic/converters.py

Lines changed: 78 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,26 @@
22
import collections.abc
33
import datetime
44
import decimal
5-
import inspect
65
import enum
6+
import inspect
77
import sys
88
import typing as T
99
import uuid
10-
from bson import ObjectId
1110

12-
from graphene import (
13-
UUID,
14-
Boolean,
15-
Enum,
16-
Field,
17-
Float,
18-
InputField,
19-
Int,
20-
List,
21-
String,
22-
Union,
23-
JSONString,
24-
ID
25-
)
2611
import graphene
12+
from bson import ObjectId
13+
from graphene import (Boolean, Enum, Field, Float, ID, InputField, Int, JSONString, List, String, UUID, Union)
2714
from graphene.types.base import BaseType
2815
from graphene.types.datetime import Date, DateTime, Time
2916
from pydantic import BaseModel
30-
from pydantic.fields import ModelField
31-
from pydantic.typing import evaluate_forwardref
17+
from pydantic.fields import FieldInfo
18+
from pydantic_core import PydanticUndefined
3219

3320
from .registry import Registry
3421
from .util import construct_union_class_name
3522

36-
from pydantic import fields
37-
3823
GRAPHENE2 = graphene.VERSION[0] < 3
3924

40-
SHAPE_SINGLETON = (fields.SHAPE_SINGLETON,)
41-
SHAPE_SEQUENTIAL = (
42-
fields.SHAPE_LIST,
43-
fields.SHAPE_TUPLE,
44-
fields.SHAPE_TUPLE_ELLIPSIS,
45-
fields.SHAPE_SEQUENCE,
46-
fields.SHAPE_SET,
47-
)
48-
49-
if hasattr(fields, "SHAPE_DICT"):
50-
SHAPE_MAPPING = T.cast(
51-
T.Tuple, (fields.SHAPE_MAPPING, fields.SHAPE_DICT, fields.SHAPE_DEFAULTDICT)
52-
)
53-
else:
54-
SHAPE_MAPPING = T.cast(T.Tuple, (fields.SHAPE_MAPPING,))
55-
56-
5725
try:
5826
from graphene.types.decimal import Decimal as GrapheneDecimal
5927

@@ -62,7 +30,6 @@
6230
# graphene 2.1.5+ is required for Decimals
6331
DECIMAL_SUPPORTED = False
6432

65-
6633
NONE_TYPE = None.__class__ # need to do this because mypy complains about type(None)
6734

6835

@@ -83,82 +50,89 @@ def _get_field(root, _info):
8350

8451

8552
def convert_pydantic_input_field(
86-
field: ModelField,
87-
registry: Registry,
88-
parent_type: T.Type = None,
89-
model: T.Type[BaseModel] = None,
90-
**field_kwargs,
53+
field: FieldInfo,
54+
registry: Registry,
55+
parent_type: T.Type = None,
56+
model: T.Type[BaseModel] = None,
57+
**field_kwargs,
9158
) -> InputField:
9259
"""
9360
Convert a Pydantic model field into a Graphene type field that we can add
9461
to the generated Graphene data model type.
9562
"""
96-
declared_type = getattr(field, "type_", None)
63+
declared_type = getattr(field, "annotation", None)
9764
field_kwargs.setdefault(
9865
"type" if GRAPHENE2 else "type_",
9966
convert_pydantic_type(
10067
declared_type, field, registry, parent_type=parent_type, model=model
10168
),
10269
)
103-
field_kwargs.setdefault("required", field.required)
104-
field_kwargs.setdefault("default_value", field.default)
70+
field_kwargs.setdefault("required", field.is_required())
71+
field_kwargs.setdefault("default_value", None if PydanticUndefined else field.default)
10572
# TODO: find a better way to get a field's description. Some ideas include:
10673
# - hunt down the description from the field's schema, or the schema
10774
# from the field's base model
10875
# - maybe even (Sphinx-style) parse attribute documentation
109-
field_kwargs.setdefault("description", field.field_info.description)
76+
field_kwargs.setdefault("description", field.description)
11077

11178
return InputField(**field_kwargs)
11279

11380

11481
def convert_pydantic_field(
115-
field: ModelField,
116-
registry: Registry,
117-
parent_type: T.Type = None,
118-
model: T.Type[BaseModel] = None,
119-
**field_kwargs,
82+
name: str,
83+
field: FieldInfo,
84+
registry: Registry,
85+
parent_type: T.Type = None,
86+
model: T.Type[BaseModel] = None,
87+
**field_kwargs,
12088
) -> Field:
12189
"""
12290
Convert a Pydantic model field into a Graphene type field that we can add
12391
to the generated Graphene data model type.
12492
"""
125-
declared_type = getattr(field, "type_", None)
93+
declared_type = getattr(field, "annotation", None)
12694
field_kwargs.setdefault(
12795
"type" if GRAPHENE2 else "type_",
12896
convert_pydantic_type(
12997
declared_type, field, registry, parent_type=parent_type, model=model
13098
),
13199
)
132-
field_kwargs.setdefault("required", not field.allow_none)
133-
field_kwargs.setdefault("default_value", field.default)
134-
if field.has_alias:
100+
field_kwargs.setdefault(
101+
"required",
102+
field.is_required() or (
103+
type(field.default) is not PydanticUndefined and
104+
getattr(declared_type, '_name', '') != 'Optional'
105+
)
106+
)
107+
field_kwargs.setdefault("default_value", None if type(field.default) is PydanticUndefined else field.default)
108+
if field.alias:
135109
field_kwargs.setdefault("name", field.alias)
136110
# TODO: find a better way to get a field's description. Some ideas include:
137111
# - hunt down the description from the field's schema, or the schema
138112
# from the field's base model
139113
# - maybe even (Sphinx-style) parse attribute documentation
140-
field_kwargs.setdefault("description", field.field_info.description)
114+
field_kwargs.setdefault("description", field.description)
141115

142116
# Handle Graphene 2 and 3
143117
field_type = field_kwargs.pop("type", field_kwargs.pop("type_", None))
144118
if field_type is None:
145119
raise ValueError("No field type could be determined.")
146120

147-
resolver_function = getattr(parent_type, "resolve_" + field.name, None)
121+
resolver_function = getattr(parent_type, "resolve_" + name, None)
148122
if resolver_function and callable(resolver_function):
149123
field_resolver = resolver_function
150124
else:
151-
field_resolver = get_attr_resolver(field.name)
125+
field_resolver = get_attr_resolver(name)
152126

153127
return Field(field_type, resolver=field_resolver, **field_kwargs)
154128

155129

156130
def convert_pydantic_type(
157-
type_: T.Type,
158-
field: ModelField,
159-
registry: Registry,
160-
parent_type: T.Type = None,
161-
model: T.Type[BaseModel] = None,
131+
type_: T.Type,
132+
field: FieldInfo,
133+
registry: Registry,
134+
parent_type: T.Type = None,
135+
model: T.Type[BaseModel] = None,
162136
) -> BaseType: # noqa: C901
163137
"""
164138
Convert a Pydantic type to a Graphene Field type, including not just the
@@ -168,21 +142,23 @@ def convert_pydantic_type(
168142
graphene_type = find_graphene_type(
169143
type_, field, registry, parent_type=parent_type, model=model
170144
)
171-
if field.shape in SHAPE_SINGLETON:
172-
return graphene_type
173-
elif field.shape in SHAPE_SEQUENTIAL:
174-
# TODO: _should_ Sets remain here?
145+
field_type = getattr(field.annotation, '__origin__', None)
146+
147+
# TODO: _should_ Sets remain here?
148+
if field_type in [list, set, tuple]: # SHAPE_SEQUENTIAL
175149
return List(graphene_type)
176-
elif field.shape in SHAPE_MAPPING:
150+
if field_type == map: # SHAPE_MAPPING
177151
raise ConversionError("Don't know how to handle mappings in Graphene.")
178152

153+
return graphene_type
154+
179155

180156
def find_graphene_type(
181-
type_: T.Type,
182-
field: ModelField,
183-
registry: Registry,
184-
parent_type: T.Type = None,
185-
model: T.Type[BaseModel] = None,
157+
type_: T.Type,
158+
field: FieldInfo,
159+
registry: Registry,
160+
parent_type: T.Type = None,
161+
model: T.Type[BaseModel] = None,
186162
) -> BaseType: # noqa: C901
187163
"""
188164
Map a native Python type to a Graphene-supported Field type, where possible,
@@ -216,8 +192,8 @@ def find_graphene_type(
216192
elif registry and registry.get_type_for_model(type_):
217193
return registry.get_type_for_model(type_)
218194
elif registry and (
219-
isinstance(type_, BaseModel)
220-
or (inspect.isclass(type_) and issubclass(type_, BaseModel))
195+
isinstance(type_, BaseModel)
196+
or (inspect.isclass(type_) and issubclass(type_, BaseModel))
221197
):
222198
# If it's a Pydantic model that hasn't yet been wrapped with a ObjectType,
223199
# we can put a placeholder in and request that `resolve_placeholders()`
@@ -238,12 +214,12 @@ def find_graphene_type(
238214
if not sibling:
239215
raise ConversionError(
240216
"Don't know how to convert the Pydantic field "
241-
f"{field!r} ({field.type_}), could not resolve "
217+
f"{field!r} ({field.annotation}), could not resolve "
242218
"the forward reference. Did you call `resolve_placeholders()`? "
243219
"See the README for more on forward references."
244220
)
245221
module_ns = sys.modules[sibling.__module__].__dict__
246-
resolved = evaluate_forwardref(type_, module_ns, None)
222+
resolved = T.cast(T.Any, type_)._evaluate(type_, module_ns, None)
247223
# TODO: make this behavior optional. maybe this is a place for the TypeOptions to play a role?
248224
if registry:
249225
registry.add_placeholder_for_model(resolved)
@@ -277,11 +253,11 @@ def find_graphene_type(
277253

278254

279255
def convert_generic_python_type(
280-
type_: T.Type,
281-
field: ModelField,
282-
registry: Registry,
283-
parent_type: T.Type = None,
284-
model: T.Type[BaseModel] = None,
256+
type_: T.Type,
257+
field: FieldInfo,
258+
registry: Registry,
259+
parent_type: T.Type = None,
260+
model: T.Type[BaseModel] = None,
285261
) -> BaseType: # noqa: C901
286262
"""
287263
Convert annotated Python generic types into the most appropriate Graphene
@@ -303,13 +279,13 @@ def convert_generic_python_type(
303279
type_, field, registry, parent_type=parent_type, model=model
304280
)
305281
elif origin in (
306-
T.Tuple,
307-
T.List,
308-
T.Set,
309-
T.Collection,
310-
T.Iterable,
311-
list,
312-
set,
282+
T.Tuple,
283+
T.List,
284+
T.Set,
285+
T.Collection,
286+
T.Iterable,
287+
list,
288+
set,
313289
) or issubclass(origin, collections.abc.Sequence):
314290
# TODO: find a better way of divining that the origin is sequence-like
315291
inner_types = getattr(type_, "__args__", [])
@@ -326,19 +302,19 @@ def convert_generic_python_type(
326302
)
327303
)
328304
elif origin in (T.Dict, T.Mapping, collections.OrderedDict, dict) or issubclass(
329-
origin, collections.abc.Mapping
305+
origin, collections.abc.Mapping
330306
):
331-
raise ConversionError("Don't know how to handle mappings in Graphene")
307+
raise ConversionError("Don't know how to handle mappings in Graphene.")
332308
else:
333309
raise ConversionError(f"Don't know how to handle {type_} (generic: {origin})")
334310

335311

336312
def convert_union_type(
337-
type_: T.Type,
338-
field: ModelField,
339-
registry: Registry,
340-
parent_type: T.Type = None,
341-
model: T.Type[BaseModel] = None,
313+
type_: T.Type,
314+
field: FieldInfo,
315+
registry: Registry,
316+
parent_type: T.Type = None,
317+
model: T.Type[BaseModel] = None,
342318
):
343319
"""
344320
Convert an annotated Python Union type into a Graphene Union.
@@ -367,11 +343,11 @@ def convert_union_type(
367343

368344

369345
def convert_literal_type(
370-
type_: T.Type,
371-
field: ModelField,
372-
registry: Registry,
373-
parent_type: T.Type = None,
374-
model: T.Type[BaseModel] = None,
346+
type_: T.Type,
347+
field: FieldInfo,
348+
registry: Registry,
349+
parent_type: T.Type = None,
350+
model: T.Type[BaseModel] = None,
375351
):
376352
"""
377353
Convert an annotated Python Literal type into a Graphene Scalar or Union of Scalars.

0 commit comments

Comments
 (0)