Skip to content

Commit b35f3b0

Browse files
authored
Merge branch 'main' into support-async
2 parents 45fb299 + 54372b4 commit b35f3b0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1086
-203
lines changed

.github/workflows/deploy.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ on:
66
- 'v*'
77

88
jobs:
9-
build:
9+
lint:
10+
uses: ./.github/workflows/lint.yml
11+
tests:
12+
uses: ./.github/workflows/tests.yml
13+
release:
1014
runs-on: ubuntu-latest
15+
needs: [lint, tests]
1116

1217
steps:
1318
- uses: actions/checkout@v3

.github/workflows/lint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches: ["main"]
66
pull_request:
7+
workflow_call:
78

89
jobs:
910
build:

.github/workflows/tests.yml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,25 @@ on:
44
push:
55
branches: ["main"]
66
pull_request:
7+
workflow_call:
78

89
jobs:
910
build:
1011
runs-on: ubuntu-latest
1112
strategy:
1213
max-parallel: 4
1314
matrix:
14-
django: ["3.2", "4.1", "4.2"]
15-
python-version: ["3.8", "3.9", "3.10"]
16-
include:
17-
- django: "4.1"
18-
python-version: "3.11"
19-
- django: "4.2"
15+
django: ["3.2", "4.2", "5.0"]
16+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
17+
exclude:
18+
- django: "3.2"
2019
python-version: "3.11"
20+
- django: "3.2"
21+
python-version: "3.12"
22+
- django: "5.0"
23+
python-version: "3.8"
24+
- django: "5.0"
25+
python-version: "3.9"
2126
steps:
2227
- uses: actions/checkout@v3
2328
- name: Set up Python ${{ matrix.python-version }}

.pre-commit-config.yaml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ default_language_version:
22
python: python3.11
33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v4.4.0
5+
rev: v4.5.0
66
hooks:
77
- id: check-merge-conflict
88
- id: check-json
@@ -15,12 +15,9 @@ repos:
1515
- --autofix
1616
- id: trailing-whitespace
1717
exclude: README.md
18-
- repo: https://github.com/psf/black
19-
rev: 23.7.0
20-
hooks:
21-
- id: black
2218
- repo: https://github.com/astral-sh/ruff-pre-commit
23-
rev: v0.0.283
19+
rev: v0.1.2
2420
hooks:
2521
- id: ruff
2622
args: [--fix, --exit-non-zero-on-fix, --show-fixes]
23+
- id: ruff-format

.ruff.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ignore = [
1313
"B017", # pytest.raises(Exception) should be considered evil
1414
"B028", # warnings.warn called without an explicit stacklevel keyword argument
1515
"B904", # check for raise statements in exception handlers that lack a from clause
16+
"W191", # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
1617
]
1718

1819
exclude = [
@@ -29,5 +30,4 @@ target-version = "py38"
2930
[isort]
3031
known-first-party = ["graphene", "graphene-django"]
3132
known-local-folder = ["cookbook"]
32-
force-wrap-aliases = true
3333
combine-as-imports = true

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ tests:
1414

1515
.PHONY: format ## Format code
1616
format:
17-
black graphene_django examples setup.py
17+
ruff format graphene_django examples setup.py
1818

1919
.PHONY: lint ## Lint code
2020
lint:

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ For more advanced use, check out the Relay tutorial.
3333
authorization
3434
debug
3535
introspection
36+
validation
3637
testing
3738
settings

docs/settings.rst

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Graphene-Django can be customised using settings. This page explains each settin
66
Usage
77
-----
88

9-
Add settings to your Django project by creating a Dictonary with name ``GRAPHENE`` in the project's ``settings.py``:
9+
Add settings to your Django project by creating a Dictionary with name ``GRAPHENE`` in the project's ``settings.py``:
1010

1111
.. code:: python
1212
@@ -142,6 +142,15 @@ Default: ``False``
142142
# ]
143143
144144
145+
``DJANGO_CHOICE_FIELD_ENUM_CONVERT``
146+
--------------------------------------
147+
148+
When set to ``True`` Django choice fields are automatically converted into Enum types.
149+
150+
Can be disabled globally by setting it to ``False``.
151+
152+
Default: ``True``
153+
145154
``DJANGO_CHOICE_FIELD_ENUM_V2_NAMING``
146155
--------------------------------------
147156

@@ -197,9 +206,6 @@ Set to ``False`` if you want to disable GraphiQL headers editor tab for some rea
197206

198207
This setting is passed to ``headerEditorEnabled`` GraphiQL options, for details refer to GraphiQLDocs_.
199208

200-
.. _GraphiQLDocs: https://github.com/graphql/graphiql/tree/main/packages/graphiql#options
201-
202-
203209
Default: ``True``
204210

205211
.. code:: python
@@ -230,8 +236,6 @@ Set to ``True`` if you want to persist GraphiQL headers after refreshing the pag
230236

231237
This setting is passed to ``shouldPersistHeaders`` GraphiQL options, for details refer to GraphiQLDocs_.
232238

233-
.. _GraphiQLDocs: https://github.com/graphql/graphiql/tree/main/packages/graphiql#options
234-
235239

236240
Default: ``False``
237241

@@ -240,3 +244,48 @@ Default: ``False``
240244
GRAPHENE = {
241245
'GRAPHIQL_SHOULD_PERSIST_HEADERS': False,
242246
}
247+
248+
249+
``GRAPHIQL_INPUT_VALUE_DEPRECATION``
250+
------------------------------------
251+
252+
Set to ``True`` if you want GraphiQL to show any deprecated fields on input object types' docs.
253+
254+
For example, having this schema:
255+
256+
.. code:: python
257+
258+
class MyMutationInputType(graphene.InputObjectType):
259+
old_field = graphene.String(deprecation_reason="You should now use 'newField' instead.")
260+
new_field = graphene.String()
261+
262+
class MyMutation(graphene.Mutation):
263+
class Arguments:
264+
input = types.MyMutationInputType()
265+
266+
GraphiQL will add a ``Show Deprecated Fields`` button to toggle information display on ``oldField`` and its deprecation
267+
reason. Otherwise, you would get neither a button nor any information at all on ``oldField``.
268+
269+
This setting is passed to ``inputValueDeprecation`` GraphiQL options, for details refer to GraphiQLDocs_.
270+
271+
Default: ``False``
272+
273+
.. code:: python
274+
275+
GRAPHENE = {
276+
'GRAPHIQL_INPUT_VALUE_DEPRECATION': False,
277+
}
278+
279+
280+
.. _GraphiQLDocs: https://graphiql-test.netlify.app/typedoc/modules/graphiql_react#graphiqlprovider-2
281+
282+
283+
``MAX_VALIDATION_ERRORS``
284+
------------------------------------
285+
286+
In case ``validation_rules`` are provided to ``GraphQLView``, if this is set to a non-negative ``int`` value,
287+
``graphql.validation.validate`` will stop validation after this number of errors has been reached.
288+
If not set or set to ``None``, the maximum number of errors will follow ``graphql.validation.validate`` default
289+
*i.e.* 100.
290+
291+
Default: ``None``

docs/validation.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Query Validation
2+
================
3+
4+
Graphene-Django supports query validation by allowing passing a list of validation rules (subclasses of `ValidationRule <https://github.com/graphql-python/graphql-core/blob/v3.2.3/src/graphql/validation/rules/__init__.py>`_ from graphql-core) to the ``validation_rules`` option in ``GraphQLView``.
5+
6+
.. code:: python
7+
8+
from django.urls import path
9+
from graphene.validation import DisableIntrospection
10+
from graphene_django.views import GraphQLView
11+
12+
urlpatterns = [
13+
path("graphql", GraphQLView.as_view(validation_rules=(DisableIntrospection,))),
14+
]
15+
16+
or
17+
18+
.. code:: python
19+
20+
from django.urls import path
21+
from graphene.validation import DisableIntrospection
22+
from graphene_django.views import GraphQLView
23+
24+
class View(GraphQLView):
25+
validation_rules = (DisableIntrospection,)
26+
27+
urlpatterns = [
28+
path("graphql", View.as_view()),
29+
]

examples/cookbook/dummy_data.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@
231231
"fields": {
232232
"category": 3,
233233
"name": "Newt",
234-
"notes": "Braised and Confuesd"
234+
"notes": "Braised and Confused"
235235
},
236236
"model": "ingredients.ingredient",
237237
"pk": 5

examples/cookbook/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
graphene>=2.1,<3
22
graphene-django>=2.1,<3
33
graphql-core>=2.1,<3
4-
django==3.1.14
4+
django==3.2.24
55
django-filter>=2

examples/starwars/data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def initialize():
2828

2929
# Yeah, technically it's Corellian. But it flew in the service of the rebels,
3030
# so for the purposes of this demo it's a rebel ship.
31-
falcon = Ship(id="4", name="Millenium Falcon", faction=rebels)
31+
falcon = Ship(id="4", name="Millennium Falcon", faction=rebels)
3232
falcon.save()
3333

3434
homeOne = Ship(id="5", name="Home One", faction=rebels)

examples/starwars/models.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ def __str__(self):
2424

2525

2626
class Ship(models.Model):
27+
class Meta:
28+
ordering = ["pk"]
29+
2730
name = models.CharField(max_length=50)
2831
faction = models.ForeignKey(Faction, on_delete=models.CASCADE, related_name="ships")
2932

examples/starwars/tests/test_mutation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def test_mutations():
4040
{"node": {"id": "U2hpcDox", "name": "X-Wing"}},
4141
{"node": {"id": "U2hpcDoy", "name": "Y-Wing"}},
4242
{"node": {"id": "U2hpcDoz", "name": "A-Wing"}},
43-
{"node": {"id": "U2hpcDo0", "name": "Millenium Falcon"}},
43+
{"node": {"id": "U2hpcDo0", "name": "Millennium Falcon"}},
4444
{"node": {"id": "U2hpcDo1", "name": "Home One"}},
4545
{"node": {"id": "U2hpcDo5", "name": "Peter"}},
4646
]

graphene_django/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from .types import DjangoObjectType
33
from .utils import bypass_get_queryset
44

5-
__version__ = "3.1.5"
5+
__version__ = "3.2.0"
66

77
__all__ = [
88
"__version__",

graphene_django/compat.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import sys
2+
from pathlib import PurePath
3+
14
# For backwards compatibility, we import JSONField to have it available for import via
25
# this compat module (https://github.com/graphql-python/graphene-django/issues/1428).
36
# Django's JSONField is available in Django 3.2+ (the minimum version we support)
@@ -19,4 +22,23 @@ def __init__(self, *args, **kwargs):
1922
RangeField,
2023
)
2124
except ImportError:
22-
IntegerRangeField, ArrayField, HStoreField, RangeField = (MissingType,) * 4
25+
IntegerRangeField, HStoreField, RangeField = (MissingType,) * 3
26+
27+
# For unit tests we fake ArrayField using JSONFields
28+
if any(
29+
PurePath(sys.argv[0]).match(p)
30+
for p in [
31+
"**/pytest",
32+
"**/py.test",
33+
"**/pytest/__main__.py",
34+
]
35+
):
36+
37+
class ArrayField(JSONField):
38+
def __init__(self, *args, **kwargs):
39+
if len(args) > 0:
40+
self.base_field = args[0]
41+
super().__init__(**kwargs)
42+
43+
else:
44+
ArrayField = MissingType

graphene_django/converter.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,17 @@ def convert_choice_field_to_enum(field, name=None):
133133

134134

135135
def convert_django_field_with_choices(
136-
field, registry=None, convert_choices_to_enum=True
136+
field, registry=None, convert_choices_to_enum=None
137137
):
138138
if registry is not None:
139139
converted = registry.get_converted_field(field)
140140
if converted:
141141
return converted
142142
choices = getattr(field, "choices", None)
143+
if convert_choices_to_enum is None:
144+
convert_choices_to_enum = bool(
145+
graphene_settings.DJANGO_CHOICE_FIELD_ENUM_CONVERT
146+
)
143147
if choices and convert_choices_to_enum:
144148
EnumCls = convert_choice_field_to_enum(field)
145149
required = not (field.blank or field.null)

graphene_django/fields.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,20 @@
2020

2121
class DjangoListField(Field):
2222
def __init__(self, _type, *args, **kwargs):
23-
from .types import DjangoObjectType
24-
2523
if isinstance(_type, NonNull):
2624
_type = _type.of_type
2725

2826
# Django would never return a Set of None vvvvvvv
2927
super().__init__(List(NonNull(_type)), *args, **kwargs)
3028

29+
@property
30+
def type(self):
31+
from .types import DjangoObjectType
32+
3133
assert issubclass(
3234
self._underlying_type, DjangoObjectType
33-
), "DjangoListField only accepts DjangoObjectType types"
35+
), "DjangoListField only accepts DjangoObjectType types as underlying type"
36+
return super().type
3437

3538
@property
3639
def _underlying_type(self):
@@ -123,13 +126,19 @@ def type(self):
123126
non_null = True
124127
assert issubclass(
125128
_type, DjangoObjectType
126-
), "DjangoConnectionField only accepts DjangoObjectType types"
129+
), "DjangoConnectionField only accepts DjangoObjectType types as underlying type"
127130
assert _type._meta.connection, "The type {} doesn't have a connection".format(
128131
_type.__name__
129132
)
130133
connection_type = _type._meta.connection
131134
if non_null:
132135
return NonNull(connection_type)
136+
# Since Relay Connections require to have a predictible ordering for pagination,
137+
# check on init that the Django model provided has a default ordering declared.
138+
model = connection_type._meta.node._meta.model
139+
assert (
140+
len(getattr(model._meta, "ordering", [])) > 0
141+
), f"Django model {model._meta.app_label}.{model.__name__} has to have a default ordering to be used in a Connection."
133142
return connection_type
134143

135144
@property
@@ -219,7 +228,7 @@ def connection_resolver(
219228
enforce_first_or_last,
220229
root,
221230
info,
222-
**args
231+
**args,
223232
):
224233
first = args.get("first")
225234
last = args.get("last")

graphene_django/filter/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __init__(
3737
extra_filter_meta=None,
3838
filterset_class=None,
3939
*args,
40-
**kwargs
40+
**kwargs,
4141
):
4242
self._fields = fields
4343
self._provided_filterset_class = filterset_class

0 commit comments

Comments
 (0)