Skip to content

Commit 9ee1620

Browse files
committed
Merge branch 'main' into feature/feat-conveters
# Conflicts: # graphene_pydantic/converters.py # poetry.lock
2 parents b8dce09 + 88faade commit 9ee1620

File tree

9 files changed

+615
-604
lines changed

9 files changed

+615
-604
lines changed

.github/workflows/tests.yaml

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,24 @@ jobs:
66
name: lint
77
runs-on: ubuntu-latest
88
steps:
9-
- uses: actions/checkout@master
10-
- uses: actions/setup-python@v2
11-
with:
12-
python-version: 3.8
13-
- uses: pre-commit/action@v2.0.0
14-
pytest:
9+
- uses: actions/checkout@v3
10+
- uses: actions/setup-python@v4
11+
with:
12+
python-version: "3.10"
13+
- uses: pre-commit/action@v3.0.0
14+
tests:
1515
name: pytest
1616
runs-on: ubuntu-latest
1717
strategy:
1818
fail-fast: false
1919
matrix:
20-
python-version: ["3.7", "3.8", "3.9", "3.10"]
20+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
2121
os: [ubuntu-latest, macos-latest, windows-latest]
2222
steps:
23-
- uses: actions/checkout@master
24-
- uses: actions/setup-python@v2
23+
- uses: actions/checkout@v3
24+
- uses: actions/setup-python@v4
2525
with:
2626
python-version: ${{ matrix.python-version }}
27-
- name: Ensure poetry
28-
uses: abatilo/actions-poetry@v2.1.2
29-
- name: Install dependencies
30-
run: poetry install
31-
- name: Run matrix of tests with Tox
32-
run: poetry run tox
27+
- uses: abatilo/actions-poetry@v2
28+
- run: poetry install
29+
- run: poetry run nox --non-interactive

.pre-commit-config.yaml

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
1-
default_language_version:
2-
python: python3.8
31
fail_fast: true
42
repos:
5-
- repo: git://github.com/pre-commit/pre-commit-hooks
6-
rev: v2.1.0
3+
- repo: https://github.com/pre-commit/pre-commit-hooks
4+
rev: v3.0.0
75
hooks:
8-
- id: check-merge-conflict
9-
- id: check-yaml
10-
- id: debug-statements
11-
- id: end-of-file-fixer
12-
exclude: ^docs/.*$
13-
- id: trailing-whitespace
14-
exclude: README.md
15-
- id: flake8
6+
- id: check-merge-conflict
7+
- id: check-yaml
8+
- id: debug-statements
9+
- id: end-of-file-fixer
10+
exclude: ^docs/.*$
11+
- id: trailing-whitespace
12+
exclude: README.md
13+
- repo: https://github.com/pycqa/flake8
14+
rev: "6.1.0"
15+
hooks:
16+
- id: flake8
1617
- repo: https://github.com/pre-commit/mirrors-mypy
17-
rev: v0.812
18+
rev: v1.4.1
1819
hooks:
1920
- id: mypy
2021
args: [--ignore-missing-imports, --no-strict-optional]
21-
- repo: https://github.com/python/black
22-
rev: 20.8b1
22+
- repo: https://github.com/psf/black
23+
rev: 23.7.0
2324
hooks:
2425
- id: black

CONTRIBUTING.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
# Contributing Guide
22

33
You will need:
4-
- Python 3.6 or higher
4+
- Python 3.7 or higher
55

66
## Getting started
77

8-
To get your development environment set up, run:
8+
To get your development environment set up, create and activate a virtual
9+
environment, and install poetry:
10+
11+
```
12+
pipx install poetry
13+
# or with conda
14+
conda install poetry
15+
```
16+
17+
Then install dependencies with poetry:
918

1019
```sh
11-
pip install -e .[dev]
20+
poetry install
1221
```
1322

14-
in an activated virtual environment. This will install the repo version of
23+
This will install the repo version of
1524
`graphene-pydantic` and then install the development dependencies. Once that
1625
has completed, you can start developing.
1726

graphene_pydantic/converters.py

Lines changed: 81 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
JSONString,
2424
ID
2525
)
26-
26+
import graphene
2727
from graphene.types.base import BaseType
2828
from graphene.types.datetime import Date, DateTime, Time
2929
from pydantic import BaseModel
@@ -35,6 +35,8 @@
3535

3636
from pydantic import fields
3737

38+
GRAPHENE2 = graphene.VERSION[0] < 3
39+
3840
SHAPE_SINGLETON = (fields.SHAPE_SINGLETON,)
3941
SHAPE_SEQUENTIAL = (
4042
fields.SHAPE_LIST,
@@ -51,6 +53,7 @@
5153
else:
5254
SHAPE_MAPPING = T.cast(T.Tuple, (fields.SHAPE_MAPPING,))
5355

56+
5457
try:
5558
from graphene.types.decimal import Decimal as GrapheneDecimal
5659

@@ -59,6 +62,7 @@
5962
# graphene 2.1.5+ is required for Decimals
6063
DECIMAL_SUPPORTED = False
6164

65+
6266
NONE_TYPE = None.__class__ # need to do this because mypy complains about type(None)
6367

6468

@@ -79,19 +83,19 @@ def _get_field(root, _info):
7983

8084

8185
def convert_pydantic_input_field(
82-
field: ModelField,
83-
registry: Registry,
84-
parent_type: T.Type = None,
85-
model: T.Type[BaseModel] = None,
86-
**field_kwargs,
86+
field: ModelField,
87+
registry: Registry,
88+
parent_type: T.Type = None,
89+
model: T.Type[BaseModel] = None,
90+
**field_kwargs,
8791
) -> InputField:
8892
"""
8993
Convert a Pydantic model field into a Graphene type field that we can add
9094
to the generated Graphene data model type.
9195
"""
9296
declared_type = getattr(field, "type_", None)
9397
field_kwargs.setdefault(
94-
"type_",
98+
"type" if GRAPHENE2 else "type_",
9599
convert_pydantic_type(
96100
declared_type, field, registry, parent_type=parent_type, model=model
97101
),
@@ -108,25 +112,27 @@ def convert_pydantic_input_field(
108112

109113

110114
def convert_pydantic_field(
111-
field: ModelField,
112-
registry: Registry,
113-
parent_type: T.Type = None,
114-
model: T.Type[BaseModel] = None,
115-
**field_kwargs,
115+
field: ModelField,
116+
registry: Registry,
117+
parent_type: T.Type = None,
118+
model: T.Type[BaseModel] = None,
119+
**field_kwargs,
116120
) -> Field:
117121
"""
118122
Convert a Pydantic model field into a Graphene type field that we can add
119123
to the generated Graphene data model type.
120124
"""
121125
declared_type = getattr(field, "type_", None)
122126
field_kwargs.setdefault(
123-
"type",
127+
"type" if GRAPHENE2 else "type_",
124128
convert_pydantic_type(
125129
declared_type, field, registry, parent_type=parent_type, model=model
126130
),
127131
)
128-
field_kwargs.setdefault("required", field.required)
132+
field_kwargs.setdefault("required", not field.allow_none)
129133
field_kwargs.setdefault("default_value", field.default)
134+
if field.has_alias:
135+
field_kwargs.setdefault("name", field.alias)
130136
# TODO: find a better way to get a field's description. Some ideas include:
131137
# - hunt down the description from the field's schema, or the schema
132138
# from the field's base model
@@ -138,15 +144,21 @@ def convert_pydantic_field(
138144
if field_type is None:
139145
raise ValueError("No field type could be determined.")
140146

141-
return Field(field_type, resolver=get_attr_resolver(field.name), **field_kwargs)
147+
resolver_function = getattr(parent_type, "resolve_" + field.name, None)
148+
if resolver_function and callable(resolver_function):
149+
field_resolver = resolver_function
150+
else:
151+
field_resolver = get_attr_resolver(field.name)
152+
153+
return Field(field_type, resolver=field_resolver, **field_kwargs)
142154

143155

144156
def convert_pydantic_type(
145-
type_: T.Type,
146-
field: ModelField,
147-
registry: Registry,
148-
parent_type: T.Type = None,
149-
model: T.Type[BaseModel] = None,
157+
type_: T.Type,
158+
field: ModelField,
159+
registry: Registry,
160+
parent_type: T.Type = None,
161+
model: T.Type[BaseModel] = None,
150162
) -> BaseType: # noqa: C901
151163
"""
152164
Convert a Pydantic type to a Graphene Field type, including not just the
@@ -166,11 +178,11 @@ def convert_pydantic_type(
166178

167179

168180
def find_graphene_type(
169-
type_: T.Type,
170-
field: ModelField,
171-
registry: Registry,
172-
parent_type: T.Type = None,
173-
model: T.Type[BaseModel] = None,
181+
type_: T.Type,
182+
field: ModelField,
183+
registry: Registry,
184+
parent_type: T.Type = None,
185+
model: T.Type[BaseModel] = None,
174186
) -> BaseType: # noqa: C901
175187
"""
176188
Map a native Python type to a Graphene-supported Field type, where possible,
@@ -204,8 +216,8 @@ def find_graphene_type(
204216
elif registry and registry.get_type_for_model(type_):
205217
return registry.get_type_for_model(type_)
206218
elif registry and (
207-
isinstance(type_, BaseModel)
208-
or (inspect.isclass(type_) and issubclass(type_, BaseModel))
219+
isinstance(type_, BaseModel)
220+
or (inspect.isclass(type_) and issubclass(type_, BaseModel))
209221
):
210222
# If it's a Pydantic model that hasn't yet been wrapped with a ObjectType,
211223
# we can put a placeholder in and request that `resolve_placeholders()`
@@ -240,20 +252,36 @@ def find_graphene_type(
240252
)
241253
elif issubclass(type_, enum.Enum):
242254
return Enum.from_enum(type_)
243-
elif issubclass(type_, str):
255+
elif issubclass(type_, (str, bytes)):
244256
return String
257+
elif issubclass(type_, datetime.datetime):
258+
return DateTime
259+
elif issubclass(type_, datetime.date):
260+
return Date
261+
elif issubclass(type_, datetime.time):
262+
return Time
263+
elif issubclass(type_, bool):
264+
return Boolean
265+
elif issubclass(type_, float):
266+
return Float
267+
elif issubclass(type_, decimal.Decimal):
268+
return GrapheneDecimal if DECIMAL_SUPPORTED else Float
269+
elif issubclass(type_, int):
270+
return Int
271+
elif issubclass(type_, (tuple, list, set)):
272+
return List
245273
else:
246274
raise ConversionError(
247275
f"Don't know how to convert the Pydantic field {field!r} ({field.type_})"
248276
)
249277

250278

251279
def convert_generic_python_type(
252-
type_: T.Type,
253-
field: ModelField,
254-
registry: Registry,
255-
parent_type: T.Type = None,
256-
model: T.Type[BaseModel] = None,
280+
type_: T.Type,
281+
field: ModelField,
282+
registry: Registry,
283+
parent_type: T.Type = None,
284+
model: T.Type[BaseModel] = None,
257285
) -> BaseType: # noqa: C901
258286
"""
259287
Convert annotated Python generic types into the most appropriate Graphene
@@ -274,19 +302,15 @@ def convert_generic_python_type(
274302
return convert_literal_type(
275303
type_, field, registry, parent_type=parent_type, model=model
276304
)
277-
elif (
278-
origin
279-
in (
280-
T.Tuple,
281-
T.List,
282-
T.Set,
283-
T.Collection,
284-
T.Iterable,
285-
list,
286-
set,
287-
)
288-
or issubclass(origin, collections.abc.Sequence)
289-
):
305+
elif origin in (
306+
T.Tuple,
307+
T.List,
308+
T.Set,
309+
T.Collection,
310+
T.Iterable,
311+
list,
312+
set,
313+
) or issubclass(origin, collections.abc.Sequence):
290314
# TODO: find a better way of divining that the origin is sequence-like
291315
inner_types = getattr(type_, "__args__", [])
292316
if not inner_types: # pragma: no cover # this really should be impossible
@@ -302,19 +326,19 @@ def convert_generic_python_type(
302326
)
303327
)
304328
elif origin in (T.Dict, T.Mapping, collections.OrderedDict, dict) or issubclass(
305-
origin, collections.abc.Mapping
329+
origin, collections.abc.Mapping
306330
):
307331
raise ConversionError("Don't know how to handle mappings in Graphene")
308332
else:
309333
raise ConversionError(f"Don't know how to handle {type_} (generic: {origin})")
310334

311335

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

344368

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

0 commit comments

Comments
 (0)