Compare commits

..

No commits in common. "main" and "v3.2.1" have entirely different histories.
main ... v3.2.1

28 changed files with 200 additions and 373 deletions

View File

@ -15,9 +15,9 @@ jobs:
needs: [lint, tests] needs: [lint, tests]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set up Python 3.11 - name: Set up Python 3.11
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: '3.11' python-version: '3.11'
- name: Build wheel and source tarball - name: Build wheel and source tarball

View File

@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set up Python 3.11 - name: Set up Python 3.11
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: '3.11' python-version: '3.11'
- name: Install dependencies - name: Install dependencies

View File

@ -12,7 +12,7 @@ jobs:
strategy: strategy:
max-parallel: 4 max-parallel: 4
matrix: matrix:
django: ["3.2", "4.2", "5.0", "5.1", "5.2"] django: ["3.2", "4.2", "5.0"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
exclude: exclude:
- django: "3.2" - django: "3.2"
@ -23,18 +23,10 @@ jobs:
python-version: "3.8" python-version: "3.8"
- django: "5.0" - django: "5.0"
python-version: "3.9" python-version: "3.9"
- django: "5.1"
python-version: "3.8"
- django: "5.1"
python-version: "3.9"
- django: "5.2"
python-version: "3.8"
- django: "5.2"
python-version: "3.9"
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Install dependencies - name: Install dependencies

View File

@ -25,6 +25,7 @@ target-version = "py38"
[per-file-ignores] [per-file-ignores]
# Ignore unused imports (F401) in these files # Ignore unused imports (F401) in these files
"__init__.py" = ["F401"] "__init__.py" = ["F401"]
"graphene_django/compat.py" = ["F401"]
[isort] [isort]
known-first-party = ["graphene", "graphene-django"] known-first-party = ["graphene", "graphene-django"]

View File

@ -1,5 +1,5 @@
graphene>=2.1,<3 graphene>=2.1,<3
graphene-django>=2.1,<3 graphene-django>=2.1,<3
graphql-core>=2.1,<3 graphql-core>=2.1,<3
django==4.2.18 django==3.2.24
django-filter>=2 django-filter>=2

View File

@ -28,5 +28,3 @@ TEMPLATES = [
GRAPHENE = {"SCHEMA": "graphene_django.tests.schema_view.schema"} GRAPHENE = {"SCHEMA": "graphene_django.tests.schema_view.schema"}
ROOT_URLCONF = "graphene_django.tests.urls" ROOT_URLCONF = "graphene_django.tests.urls"
USE_TZ = True

View File

@ -24,6 +24,9 @@ class Faction(models.Model):
class Ship(models.Model): class Ship(models.Model):
class Meta:
ordering = ["pk"]
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
faction = models.ForeignKey(Faction, on_delete=models.CASCADE, related_name="ships") faction = models.ForeignKey(Faction, on_delete=models.CASCADE, related_name="ships")

View File

@ -1,5 +1,5 @@
import graphene import graphene
from graphene import Schema, relay from graphene import Schema, relay, resolve_only_args
from graphene_django import DjangoConnectionField, DjangoObjectType from graphene_django import DjangoConnectionField, DjangoObjectType
from .data import create_ship, get_empire, get_faction, get_rebels, get_ship, get_ships from .data import create_ship, get_empire, get_faction, get_rebels, get_ship, get_ships
@ -62,13 +62,16 @@ class Query(graphene.ObjectType):
node = relay.Node.Field() node = relay.Node.Field()
ships = DjangoConnectionField(Ship, description="All the ships.") ships = DjangoConnectionField(Ship, description="All the ships.")
def resolve_ships(self, info): @resolve_only_args
def resolve_ships(self):
return get_ships() return get_ships()
def resolve_rebels(self, info): @resolve_only_args
def resolve_rebels(self):
return get_rebels() return get_rebels()
def resolve_empire(self, info): @resolve_only_args
def resolve_empire(self):
return get_empire() return get_empire()

View File

@ -2,7 +2,7 @@ from .fields import DjangoConnectionField, DjangoListField
from .types import DjangoObjectType from .types import DjangoObjectType
from .utils import bypass_get_queryset from .utils import bypass_get_queryset
__version__ = "3.2.3" __version__ = "3.2.1"
__all__ = [ __all__ = [
"__version__", "__version__",

View File

@ -1,11 +1,10 @@
import sys import sys
from collections.abc import Callable
from pathlib import PurePath from pathlib import PurePath
# For backwards compatibility, we import JSONField to have it available for import via # For backwards compatibility, we import JSONField to have it available for import via
# this compat module (https://github.com/graphql-python/graphene-django/issues/1428). # this compat module (https://github.com/graphql-python/graphene-django/issues/1428).
# Django's JSONField is available in Django 3.2+ (the minimum version we support) # Django's JSONField is available in Django 3.2+ (the minimum version we support)
from django.db.models import Choices, JSONField from django.db.models import JSONField
class MissingType: class MissingType:
@ -43,23 +42,3 @@ except ImportError:
else: else:
ArrayField = MissingType ArrayField = MissingType
try:
from django.utils.choices import normalize_choices
except ImportError:
def normalize_choices(choices):
if isinstance(choices, type) and issubclass(choices, Choices):
choices = choices.choices
if isinstance(choices, Callable):
choices = choices()
# In restframework==3.15.0, choices are not passed
# as OrderedDict anymore, so it's safer to check
# for a dict
if isinstance(choices, dict):
choices = choices.items()
return choices

View File

@ -1,4 +1,5 @@
import inspect import inspect
from collections.abc import Callable
from functools import partial, singledispatch, wraps from functools import partial, singledispatch, wraps
from django.db import models from django.db import models
@ -36,7 +37,7 @@ except ImportError:
from graphql import assert_valid_name as assert_name from graphql import assert_valid_name as assert_name
from graphql.pyutils import register_description from graphql.pyutils import register_description
from .compat import ArrayField, HStoreField, RangeField, normalize_choices from .compat import ArrayField, HStoreField, RangeField
from .fields import DjangoConnectionField, DjangoListField from .fields import DjangoConnectionField, DjangoListField
from .settings import graphene_settings from .settings import graphene_settings
from .utils.str_converters import to_const from .utils.str_converters import to_const
@ -60,24 +61,6 @@ class BlankValueField(Field):
return blank_field_wrapper(resolver) return blank_field_wrapper(resolver)
class EnumValueField(BlankValueField):
def wrap_resolve(self, parent_resolver):
resolver = super().wrap_resolve(parent_resolver)
# create custom resolver
def enum_field_wrapper(func):
@wraps(func)
def wrapped_resolver(*args, **kwargs):
return_value = func(*args, **kwargs)
if isinstance(return_value, models.Choices):
return_value = return_value.value
return return_value
return wrapped_resolver
return enum_field_wrapper(resolver)
def convert_choice_name(name): def convert_choice_name(name):
name = to_const(force_str(name)) name = to_const(force_str(name))
try: try:
@ -89,7 +72,15 @@ def convert_choice_name(name):
def get_choices(choices): def get_choices(choices):
converted_names = [] converted_names = []
choices = normalize_choices(choices) if isinstance(choices, Callable):
choices = choices()
# In restframework==3.15.0, choices are not passed
# as OrderedDict anymore, so it's safer to check
# for a dict
if isinstance(choices, dict):
choices = choices.items()
for value, help_text in choices: for value, help_text in choices:
if isinstance(help_text, (tuple, list)): if isinstance(help_text, (tuple, list)):
yield from get_choices(help_text) yield from get_choices(help_text)
@ -166,7 +157,7 @@ def convert_django_field_with_choices(
converted = EnumCls( converted = EnumCls(
description=get_django_field_description(field), required=required description=get_django_field_description(field), required=required
).mount_as(EnumValueField) ).mount_as(BlankValueField)
else: else:
converted = convert_django_field(field, registry) converted = convert_django_field(field, registry)
if registry is not None: if registry is not None:
@ -199,13 +190,19 @@ def convert_field_to_string(field, registry=None):
) )
@convert_django_field.register(models.AutoField)
@convert_django_field.register(models.BigAutoField) @convert_django_field.register(models.BigAutoField)
@convert_django_field.register(models.SmallAutoField) @convert_django_field.register(models.AutoField)
def convert_field_to_id(field, registry=None): def convert_field_to_id(field, registry=None):
return ID(description=get_django_field_description(field), required=not field.null) return ID(description=get_django_field_description(field), required=not field.null)
if hasattr(models, "SmallAutoField"):
@convert_django_field.register(models.SmallAutoField)
def convert_field_small_to_id(field, registry=None):
return convert_field_to_id(field, registry)
@convert_django_field.register(models.UUIDField) @convert_django_field.register(models.UUIDField)
def convert_field_to_uuid(field, registry=None): def convert_field_to_uuid(field, registry=None):
return UUID( return UUID(

View File

@ -101,13 +101,19 @@ class DjangoConnectionField(ConnectionField):
non_null = True non_null = True
assert issubclass( assert issubclass(
_type, DjangoObjectType _type, DjangoObjectType
), "DjangoConnectionField only accepts DjangoObjectType types" ), "DjangoConnectionField only accepts DjangoObjectType types as underlying type"
assert _type._meta.connection, "The type {} doesn't have a connection".format( assert _type._meta.connection, "The type {} doesn't have a connection".format(
_type.__name__ _type.__name__
) )
connection_type = _type._meta.connection connection_type = _type._meta.connection
if non_null: if non_null:
return NonNull(connection_type) return NonNull(connection_type)
# Since Relay Connections require to have a predictible ordering for pagination,
# check on init that the Django model provided has a default ordering declared.
model = connection_type._meta.node._meta.model
assert (
len(getattr(model._meta, "ordering", [])) > 0
), f"Django model {model._meta.app_label}.{model.__name__} has to have a default ordering to be used in a Connection."
return connection_type return connection_type
@property @property
@ -247,7 +253,7 @@ class DjangoConnectionField(ConnectionField):
def wrap_resolve(self, parent_resolver): def wrap_resolve(self, parent_resolver):
return partial( return partial(
self.connection_resolver, self.connection_resolver,
self.resolver or parent_resolver, parent_resolver,
self.connection_type, self.connection_type,
self.get_manager(), self.get_manager(),
self.get_queryset_resolver(), self.get_queryset_resolver(),

View File

@ -26,6 +26,9 @@ else:
class Event(models.Model): class Event(models.Model):
class Meta:
ordering = ["pk"]
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
tags = ArrayField(models.CharField(max_length=50)) tags = ArrayField(models.CharField(max_length=50))
tag_ids = ArrayField(models.IntegerField()) tag_ids = ArrayField(models.IntegerField())

View File

@ -1,4 +1,4 @@
from django import VERSION as DJANGO_VERSION, forms from django import forms
from pytest import raises from pytest import raises
from graphene import ( from graphene import (
@ -19,16 +19,12 @@ from graphene import (
from ..converter import convert_form_field from ..converter import convert_form_field
def assert_conversion(django_field, graphene_field, *args, **kwargs): def assert_conversion(django_field, graphene_field, *args):
# Arrange field = django_field(*args, help_text="Custom Help Text")
help_text = kwargs.setdefault("help_text", "Custom Help Text")
field = django_field(*args, **kwargs)
# Act
graphene_type = convert_form_field(field) graphene_type = convert_form_field(field)
# Assert
assert isinstance(graphene_type, graphene_field) assert isinstance(graphene_type, graphene_field)
field = graphene_type.Field() field = graphene_type.Field()
assert field.description == help_text assert field.description == "Custom Help Text"
return field return field
@ -63,12 +59,7 @@ def test_should_slug_convert_string():
def test_should_url_convert_string(): def test_should_url_convert_string():
kwargs = {} assert_conversion(forms.URLField, String)
if DJANGO_VERSION >= (5, 0):
# silence RemovedInDjango60Warning
kwargs["assume_scheme"] = "https"
assert_conversion(forms.URLField, String, **kwargs)
def test_should_choice_convert_string(): def test_should_choice_convert_string():
@ -84,7 +75,8 @@ def test_should_regex_convert_string():
def test_should_uuid_convert_string(): def test_should_uuid_convert_string():
assert_conversion(forms.UUIDField, UUID) if hasattr(forms, "UUIDField"):
assert_conversion(forms.UUIDField, UUID)
def test_should_integer_convert_int(): def test_should_integer_convert_int():

View File

@ -3,7 +3,7 @@ from graphene import ID
from graphene.types.inputobjecttype import InputObjectType from graphene.types.inputobjecttype import InputObjectType
from graphene.utils.str_converters import to_camel_case from graphene.utils.str_converters import to_camel_case
from ..converter import EnumValueField from ..converter import BlankValueField
from ..types import ErrorType # noqa Import ErrorType for backwards compatibility from ..types import ErrorType # noqa Import ErrorType for backwards compatibility
from .mutation import fields_for_form from .mutation import fields_for_form
@ -57,10 +57,11 @@ class DjangoFormInputObjectType(InputObjectType):
if ( if (
object_type object_type
and name in object_type._meta.fields and name in object_type._meta.fields
and isinstance(object_type._meta.fields[name], EnumValueField) and isinstance(object_type._meta.fields[name], BlankValueField)
): ):
# Field type EnumValueField here means that field # Field type BlankValueField here means that field
# with choices have been converted to enum # with choices have been converted to enum
# (BlankValueField is using only for that task ?)
setattr(cls, name, cls.get_enum_cnv_cls_instance(name, object_type)) setattr(cls, name, cls.get_enum_cnv_cls_instance(name, object_type))
elif ( elif (
object_type object_type

View File

@ -96,7 +96,8 @@ def test_should_regex_convert_string():
def test_should_uuid_convert_string(): def test_should_uuid_convert_string():
assert_conversion(serializers.UUIDField, graphene.String) if hasattr(serializers, "UUIDField"):
assert_conversion(serializers.UUIDField, graphene.String)
def test_should_model_convert_field(): def test_should_model_convert_field():

View File

@ -1,39 +1,13 @@
import django
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
CHOICES = ((1, "this"), (2, _("that"))) CHOICES = ((1, "this"), (2, _("that")))
def get_choices_as_class(choices_class):
if django.VERSION >= (5, 0):
return choices_class
else:
return choices_class.choices
def get_choices_as_callable(choices_class):
if django.VERSION >= (5, 0):
def choices():
return choices_class.choices
return choices
else:
return choices_class.choices
class TypedIntChoice(models.IntegerChoices):
CHOICE_THIS = 1
CHOICE_THAT = 2
class TypedStrChoice(models.TextChoices):
CHOICE_THIS = "this"
CHOICE_THAT = "that"
class Person(models.Model): class Person(models.Model):
class Meta:
ordering = ["pk"]
name = models.CharField(max_length=30) name = models.CharField(max_length=30)
parent = models.ForeignKey( parent = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True, related_name="children" "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
@ -41,6 +15,9 @@ class Person(models.Model):
class Pet(models.Model): class Pet(models.Model):
class Meta:
ordering = ["pk"]
name = models.CharField(max_length=30) name = models.CharField(max_length=30)
age = models.PositiveIntegerField() age = models.PositiveIntegerField()
owner = models.ForeignKey( owner = models.ForeignKey(
@ -60,6 +37,9 @@ class FilmDetails(models.Model):
class Film(models.Model): class Film(models.Model):
class Meta:
ordering = ["pk"]
genre = models.CharField( genre = models.CharField(
max_length=2, max_length=2,
help_text="Genre", help_text="Genre",
@ -75,26 +55,14 @@ class DoeReporterManager(models.Manager):
class Reporter(models.Model): class Reporter(models.Model):
class Meta:
ordering = ["pk"]
first_name = models.CharField(max_length=30) first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
email = models.EmailField() email = models.EmailField()
pets = models.ManyToManyField("self") pets = models.ManyToManyField("self")
a_choice = models.IntegerField(choices=CHOICES, null=True, blank=True) a_choice = models.IntegerField(choices=CHOICES, null=True, blank=True)
typed_choice = models.IntegerField(
choices=TypedIntChoice.choices,
null=True,
blank=True,
)
class_choice = models.IntegerField(
choices=get_choices_as_class(TypedIntChoice),
null=True,
blank=True,
)
callable_choice = models.IntegerField(
choices=get_choices_as_callable(TypedStrChoice),
null=True,
blank=True,
)
objects = models.Manager() objects = models.Manager()
doe_objects = DoeReporterManager() doe_objects = DoeReporterManager()
fans = models.ManyToManyField(Person) fans = models.ManyToManyField(Person)

View File

@ -25,7 +25,7 @@ from ..converter import (
) )
from ..registry import Registry from ..registry import Registry
from ..types import DjangoObjectType from ..types import DjangoObjectType
from .models import Article, Film, FilmDetails, Reporter, TypedIntChoice, TypedStrChoice from .models import Article, Film, FilmDetails, Reporter
# from graphene.core.types.custom_scalars import DateTime, Time, JSONString # from graphene.core.types.custom_scalars import DateTime, Time, JSONString
@ -53,8 +53,9 @@ def assert_conversion(django_field, graphene_field, *args, **kwargs):
def test_should_unknown_django_field_raise_exception(): def test_should_unknown_django_field_raise_exception():
with raises(Exception, match="Don't know how to convert the Django field"): with raises(Exception) as excinfo:
convert_django_field(None) convert_django_field(None)
assert "Don't know how to convert the Django field" in str(excinfo.value)
def test_should_date_time_convert_string(): def test_should_date_time_convert_string():
@ -114,7 +115,8 @@ def test_should_big_auto_convert_id():
def test_should_small_auto_convert_id(): def test_should_small_auto_convert_id():
assert_conversion(models.SmallAutoField, graphene.ID, primary_key=True) if hasattr(models, "SmallAutoField"):
assert_conversion(models.SmallAutoField, graphene.ID, primary_key=True)
def test_should_uuid_convert_id(): def test_should_uuid_convert_id():
@ -164,14 +166,14 @@ def test_field_with_choices_convert_enum():
help_text="Language", choices=(("es", "Spanish"), ("en", "English")) help_text="Language", choices=(("es", "Spanish"), ("en", "English"))
) )
class ChoicesModel(models.Model): class TranslatedModel(models.Model):
language = field language = field
class Meta: class Meta:
app_label = "test" app_label = "test"
graphene_type = convert_django_field_with_choices(field).type.of_type graphene_type = convert_django_field_with_choices(field).type.of_type
assert graphene_type._meta.name == "TestChoicesModelLanguageChoices" assert graphene_type._meta.name == "TestTranslatedModelLanguageChoices"
assert graphene_type._meta.enum.__members__["ES"].value == "es" assert graphene_type._meta.enum.__members__["ES"].value == "es"
assert graphene_type._meta.enum.__members__["ES"].description == "Spanish" assert graphene_type._meta.enum.__members__["ES"].description == "Spanish"
assert graphene_type._meta.enum.__members__["EN"].value == "en" assert graphene_type._meta.enum.__members__["EN"].value == "en"
@ -184,14 +186,14 @@ def test_field_with_callable_choices_convert_enum():
field = models.CharField(help_text="Language", choices=get_choices) field = models.CharField(help_text="Language", choices=get_choices)
class CallableChoicesModel(models.Model): class TranslatedModel(models.Model):
language = field language = field
class Meta: class Meta:
app_label = "test" app_label = "test"
graphene_type = convert_django_field_with_choices(field).type.of_type graphene_type = convert_django_field_with_choices(field).type.of_type
assert graphene_type._meta.name == "TestCallableChoicesModelLanguageChoices" assert graphene_type._meta.name == "TestTranslatedModelLanguageChoices"
assert graphene_type._meta.enum.__members__["ES"].value == "es" assert graphene_type._meta.enum.__members__["ES"].value == "es"
assert graphene_type._meta.enum.__members__["ES"].description == "Spanish" assert graphene_type._meta.enum.__members__["ES"].description == "Spanish"
assert graphene_type._meta.enum.__members__["EN"].value == "en" assert graphene_type._meta.enum.__members__["EN"].value == "en"
@ -441,102 +443,35 @@ def test_choice_enum_blank_value():
class ReporterType(DjangoObjectType): class ReporterType(DjangoObjectType):
class Meta: class Meta:
model = Reporter model = Reporter
fields = ("callable_choice",) fields = (
"first_name",
"a_choice",
)
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
reporter = graphene.Field(ReporterType) reporter = graphene.Field(ReporterType)
def resolve_reporter(root, info): def resolve_reporter(root, info):
# return a model instance with blank choice field value return Reporter.objects.first()
return Reporter(callable_choice="")
schema = graphene.Schema(query=Query) schema = graphene.Schema(query=Query)
# Create model with empty choice option
Reporter.objects.create(
first_name="Bridget", last_name="Jones", email="bridget@example.com"
)
result = schema.execute( result = schema.execute(
""" """
query { query {
reporter { reporter {
callableChoice firstName
aChoice
} }
} }
""" """
) )
assert not result.errors assert not result.errors
assert result.data == { assert result.data == {
"reporter": {"callableChoice": None}, "reporter": {"firstName": "Bridget", "aChoice": None},
} }
def test_typed_choice_value():
"""Test that typed choices fields are resolved correctly to the enum values"""
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
fields = ("typed_choice", "class_choice", "callable_choice")
class Query(graphene.ObjectType):
reporter = graphene.Field(ReporterType)
def resolve_reporter(root, info):
# assign choice values to the fields instead of their str or int values
return Reporter(
typed_choice=TypedIntChoice.CHOICE_THIS,
class_choice=TypedIntChoice.CHOICE_THAT,
callable_choice=TypedStrChoice.CHOICE_THIS,
)
class CreateReporter(graphene.Mutation):
reporter = graphene.Field(ReporterType)
def mutate(root, info, **kwargs):
return CreateReporter(
reporter=Reporter(
typed_choice=TypedIntChoice.CHOICE_THIS,
class_choice=TypedIntChoice.CHOICE_THAT,
callable_choice=TypedStrChoice.CHOICE_THIS,
),
)
class Mutation(graphene.ObjectType):
create_reporter = CreateReporter.Field()
schema = graphene.Schema(query=Query, mutation=Mutation)
reporter_fragment = """
fragment reporter on ReporterType {
typedChoice
classChoice
callableChoice
}
"""
expected_reporter = {
"typedChoice": "A_1",
"classChoice": "A_2",
"callableChoice": "THIS",
}
result = schema.execute(
reporter_fragment
+ """
query {
reporter { ...reporter }
}
"""
)
assert not result.errors
assert result.data["reporter"] == expected_reporter
result = schema.execute(
reporter_fragment
+ """
mutation {
createReporter {
reporter { ...reporter }
}
}
"""
)
assert not result.errors
assert result.data["createReporter"]["reporter"] == expected_reporter

View File

@ -2,11 +2,12 @@ import datetime
import re import re
import pytest import pytest
from django.db.models import Count, Prefetch from django.db.models import Count, Model, Prefetch
from graphene import List, NonNull, ObjectType, Schema, String from graphene import List, NonNull, ObjectType, Schema, String
from graphene.relay import Node
from ..fields import DjangoListField from ..fields import DjangoConnectionField, DjangoListField
from ..types import DjangoObjectType from ..types import DjangoObjectType
from .models import ( from .models import (
Article as ArticleModel, Article as ArticleModel,
@ -716,3 +717,34 @@ class TestDjangoListField:
r'SELECT .* FROM "tests_film" INNER JOIN "tests_film_reporters" .* LEFT OUTER JOIN "tests_filmdetails"', r'SELECT .* FROM "tests_film" INNER JOIN "tests_film_reporters" .* LEFT OUTER JOIN "tests_filmdetails"',
captured.captured_queries[1]["sql"], captured.captured_queries[1]["sql"],
) )
class TestDjangoConnectionField:
def test_model_ordering_assertion(self):
class Chaos(Model):
class Meta:
app_label = "test"
class ChaosType(DjangoObjectType):
class Meta:
model = Chaos
interfaces = (Node,)
class Query(ObjectType):
chaos = DjangoConnectionField(ChaosType)
with pytest.raises(
TypeError,
match=r"Django model test\.Chaos has to have a default ordering to be used in a Connection\.",
):
Schema(query=Query)
def test_only_django_object_types(self):
class Query(ObjectType):
something = DjangoConnectionField(String)
with pytest.raises(
TypeError,
match="DjangoConnectionField only accepts DjangoObjectType types as underlying type",
):
Schema(query=Query)

View File

@ -26,7 +26,6 @@ class TestShouldCallGetQuerySetOnForeignKey:
class ReporterType(DjangoObjectType): class ReporterType(DjangoObjectType):
class Meta: class Meta:
model = Reporter model = Reporter
fields = "__all__"
@classmethod @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info):
@ -37,7 +36,6 @@ class TestShouldCallGetQuerySetOnForeignKey:
class ArticleType(DjangoObjectType): class ArticleType(DjangoObjectType):
class Meta: class Meta:
model = Article model = Article
fields = "__all__"
@classmethod @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info):
@ -202,7 +200,6 @@ class TestShouldCallGetQuerySetOnForeignKeyNode:
class ReporterType(DjangoObjectType): class ReporterType(DjangoObjectType):
class Meta: class Meta:
model = Reporter model = Reporter
fields = "__all__"
interfaces = (Node,) interfaces = (Node,)
@classmethod @classmethod
@ -214,7 +211,6 @@ class TestShouldCallGetQuerySetOnForeignKeyNode:
class ArticleType(DjangoObjectType): class ArticleType(DjangoObjectType):
class Meta: class Meta:
model = Article model = Article
fields = "__all__"
interfaces = (Node,) interfaces = (Node,)
@classmethod @classmethod
@ -374,7 +370,6 @@ class TestShouldCallGetQuerySetOnOneToOne:
class FilmDetailsType(DjangoObjectType): class FilmDetailsType(DjangoObjectType):
class Meta: class Meta:
model = FilmDetails model = FilmDetails
fields = "__all__"
@classmethod @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info):
@ -385,7 +380,6 @@ class TestShouldCallGetQuerySetOnOneToOne:
class FilmType(DjangoObjectType): class FilmType(DjangoObjectType):
class Meta: class Meta:
model = Film model = Film
fields = "__all__"
@classmethod @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info):

View File

@ -1,6 +1,5 @@
import base64 import base64
import datetime import datetime
from unittest.mock import ANY, Mock
import pytest import pytest
from django.db import models from django.db import models
@ -2001,62 +2000,14 @@ def test_connection_should_succeed_if_last_higher_than_number_of_objects():
assert result.data == expected assert result.data == expected
def test_connection_should_call_resolver_function():
resolver_mock = Mock(
name="resolver",
return_value=[
Reporter(first_name="Some", last_name="One"),
Reporter(first_name="John", last_name="Doe"),
],
)
class ReporterType(DjangoObjectType):
class Meta:
model = Reporter
fields = "__all__"
interfaces = [Node]
class Query(graphene.ObjectType):
reporters = DjangoConnectionField(ReporterType, resolver=resolver_mock)
schema = graphene.Schema(query=Query)
result = schema.execute(
"""
query {
reporters {
edges {
node {
firstName
lastName
}
}
}
}
"""
)
resolver_mock.assert_called_once_with(None, ANY)
assert not result.errors
assert result.data == {
"reporters": {
"edges": [
{"node": {"firstName": "Some", "lastName": "One"}},
{"node": {"firstName": "John", "lastName": "Doe"}},
],
},
}
def test_should_query_nullable_foreign_key(): def test_should_query_nullable_foreign_key():
class PetType(DjangoObjectType): class PetType(DjangoObjectType):
class Meta: class Meta:
model = Pet model = Pet
fields = "__all__"
class PersonType(DjangoObjectType): class PersonType(DjangoObjectType):
class Meta: class Meta:
model = Person model = Person
fields = "__all__"
class Query(graphene.ObjectType): class Query(graphene.ObjectType):
pet = graphene.Field(PetType, name=graphene.String(required=True)) pet = graphene.Field(PetType, name=graphene.String(required=True))
@ -2071,8 +2022,10 @@ def test_should_query_nullable_foreign_key():
schema = graphene.Schema(query=Query) schema = graphene.Schema(query=Query)
person = Person.objects.create(name="Jane") person = Person.objects.create(name="Jane")
Pet.objects.create(name="Stray dog", age=1) [
Pet.objects.create(name="Jane's dog", owner=person, age=1) Pet.objects.create(name="Stray dog", age=1),
Pet.objects.create(name="Jane's dog", owner=person, age=1),
]
query_pet = """ query_pet = """
query getPet($name: String!) { query getPet($name: String!) {
@ -2115,7 +2068,6 @@ def test_should_query_nullable_one_to_one_relation_with_custom_resolver():
class FilmType(DjangoObjectType): class FilmType(DjangoObjectType):
class Meta: class Meta:
model = Film model = Film
fields = "__all__"
@classmethod @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info):
@ -2124,7 +2076,6 @@ def test_should_query_nullable_one_to_one_relation_with_custom_resolver():
class FilmDetailsType(DjangoObjectType): class FilmDetailsType(DjangoObjectType):
class Meta: class Meta:
model = FilmDetails model = FilmDetails
fields = "__all__"
@classmethod @classmethod
def get_queryset(cls, queryset, info): def get_queryset(cls, queryset, info):

View File

@ -40,9 +40,6 @@ def test_should_map_fields_correctly():
"email", "email",
"pets", "pets",
"a_choice", "a_choice",
"typed_choice",
"class_choice",
"callable_choice",
"fans", "fans",
"reporter_type", "reporter_type",
] ]

View File

@ -77,9 +77,6 @@ def test_django_objecttype_map_correct_fields():
"email", "email",
"pets", "pets",
"a_choice", "a_choice",
"typed_choice",
"class_choice",
"callable_choice",
"fans", "fans",
"reporter_type", "reporter_type",
] ]
@ -189,9 +186,6 @@ def test_schema_representation():
email: String! email: String!
pets: [Reporter!]! pets: [Reporter!]!
aChoice: TestsReporterAChoiceChoices aChoice: TestsReporterAChoiceChoices
typedChoice: TestsReporterTypedChoiceChoices
classChoice: TestsReporterClassChoiceChoices
callableChoice: TestsReporterCallableChoiceChoices
reporterType: TestsReporterReporterTypeChoices reporterType: TestsReporterReporterTypeChoices
articles(offset: Int, before: String, after: String, first: Int, last: Int): ArticleConnection! articles(offset: Int, before: String, after: String, first: Int, last: Int): ArticleConnection!
} }
@ -205,33 +199,6 @@ def test_schema_representation():
A_2 A_2
} }
\"""An enumeration.\"""
enum TestsReporterTypedChoiceChoices {
\"""Choice This\"""
A_1
\"""Choice That\"""
A_2
}
\"""An enumeration.\"""
enum TestsReporterClassChoiceChoices {
\"""Choice This\"""
A_1
\"""Choice That\"""
A_2
}
\"""An enumeration.\"""
enum TestsReporterCallableChoiceChoices {
\"""Choice This\"""
THIS
\"""Choice That\"""
THAT
}
\"""An enumeration.\""" \"""An enumeration.\"""
enum TestsReporterReporterTypeChoices { enum TestsReporterReporterTypeChoices {
\"""Regular\""" \"""Regular\"""

View File

@ -1,5 +1,4 @@
import json import json
from http import HTTPStatus
from unittest.mock import patch from unittest.mock import patch
import pytest import pytest
@ -38,7 +37,7 @@ def jl(**kwargs):
def test_graphiql_is_enabled(client): def test_graphiql_is_enabled(client):
response = client.get(url_string(), HTTP_ACCEPT="text/html") response = client.get(url_string(), HTTP_ACCEPT="text/html")
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response["Content-Type"].split(";")[0] == "text/html" assert response["Content-Type"].split(";")[0] == "text/html"
@ -47,7 +46,7 @@ def test_qfactor_graphiql(client):
url_string(query="{test}"), url_string(query="{test}"),
HTTP_ACCEPT="application/json;q=0.8, text/html;q=0.9", HTTP_ACCEPT="application/json;q=0.8, text/html;q=0.9",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response["Content-Type"].split(";")[0] == "text/html" assert response["Content-Type"].split(";")[0] == "text/html"
@ -56,7 +55,7 @@ def test_qfactor_json(client):
url_string(query="{test}"), url_string(query="{test}"),
HTTP_ACCEPT="text/html;q=0.8, application/json;q=0.9", HTTP_ACCEPT="text/html;q=0.8, application/json;q=0.9",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response["Content-Type"].split(";")[0] == "application/json" assert response["Content-Type"].split(";")[0] == "application/json"
assert response_json(response) == {"data": {"test": "Hello World"}} assert response_json(response) == {"data": {"test": "Hello World"}}
@ -64,7 +63,7 @@ def test_qfactor_json(client):
def test_allows_get_with_query_param(client): def test_allows_get_with_query_param(client):
response = client.get(url_string(query="{test}")) response = client.get(url_string(query="{test}"))
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello World"}} assert response_json(response) == {"data": {"test": "Hello World"}}
@ -76,7 +75,7 @@ def test_allows_get_with_variable_values(client):
) )
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello Dolly"}} assert response_json(response) == {"data": {"test": "Hello Dolly"}}
@ -95,7 +94,7 @@ def test_allows_get_with_operation_name(client):
) )
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == { assert response_json(response) == {
"data": {"test": "Hello World", "shared": "Hello Everyone"} "data": {"test": "Hello World", "shared": "Hello Everyone"}
} }
@ -104,7 +103,7 @@ def test_allows_get_with_operation_name(client):
def test_reports_validation_errors(client): def test_reports_validation_errors(client):
response = client.get(url_string(query="{ test, unknownOne, unknownTwo }")) response = client.get(url_string(query="{ test, unknownOne, unknownTwo }"))
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [ "errors": [
{ {
@ -129,7 +128,7 @@ def test_errors_when_missing_operation_name(client):
) )
) )
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [ "errors": [
{ {
@ -147,7 +146,7 @@ def test_errors_when_sending_a_mutation_via_get(client):
""" """
) )
) )
assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED assert response.status_code == 405
assert response_json(response) == { assert response_json(response) == {
"errors": [ "errors": [
{"message": "Can only perform a mutation operation from a POST request."} {"message": "Can only perform a mutation operation from a POST request."}
@ -166,7 +165,7 @@ def test_errors_when_selecting_a_mutation_within_a_get(client):
) )
) )
assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED assert response.status_code == 405
assert response_json(response) == { assert response_json(response) == {
"errors": [ "errors": [
{"message": "Can only perform a mutation operation from a POST request."} {"message": "Can only perform a mutation operation from a POST request."}
@ -185,14 +184,14 @@ def test_allows_mutation_to_exist_within_a_get(client):
) )
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello World"}} assert response_json(response) == {"data": {"test": "Hello World"}}
def test_allows_post_with_json_encoding(client): def test_allows_post_with_json_encoding(client):
response = client.post(url_string(), j(query="{test}"), "application/json") response = client.post(url_string(), j(query="{test}"), "application/json")
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello World"}} assert response_json(response) == {"data": {"test": "Hello World"}}
@ -201,7 +200,7 @@ def test_batch_allows_post_with_json_encoding(client):
batch_url_string(), jl(id=1, query="{test}"), "application/json" batch_url_string(), jl(id=1, query="{test}"), "application/json"
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == [ assert response_json(response) == [
{"id": 1, "data": {"test": "Hello World"}, "status": 200} {"id": 1, "data": {"test": "Hello World"}, "status": 200}
] ]
@ -210,7 +209,7 @@ def test_batch_allows_post_with_json_encoding(client):
def test_batch_fails_if_is_empty(client): def test_batch_fails_if_is_empty(client):
response = client.post(batch_url_string(), "[]", "application/json") response = client.post(batch_url_string(), "[]", "application/json")
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [{"message": "Received an empty list in the batch request."}] "errors": [{"message": "Received an empty list in the batch request."}]
} }
@ -223,7 +222,7 @@ def test_allows_sending_a_mutation_via_post(client):
"application/json", "application/json",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"writeTest": {"test": "Hello World"}}} assert response_json(response) == {"data": {"writeTest": {"test": "Hello World"}}}
@ -234,7 +233,7 @@ def test_allows_post_with_url_encoding(client):
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello World"}} assert response_json(response) == {"data": {"test": "Hello World"}}
@ -248,7 +247,7 @@ def test_supports_post_json_query_with_string_variables(client):
"application/json", "application/json",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello Dolly"}} assert response_json(response) == {"data": {"test": "Hello Dolly"}}
@ -263,7 +262,7 @@ def test_batch_supports_post_json_query_with_string_variables(client):
"application/json", "application/json",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == [ assert response_json(response) == [
{"id": 1, "data": {"test": "Hello Dolly"}, "status": 200} {"id": 1, "data": {"test": "Hello Dolly"}, "status": 200}
] ]
@ -279,7 +278,7 @@ def test_supports_post_json_query_with_json_variables(client):
"application/json", "application/json",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello Dolly"}} assert response_json(response) == {"data": {"test": "Hello Dolly"}}
@ -294,7 +293,7 @@ def test_batch_supports_post_json_query_with_json_variables(client):
"application/json", "application/json",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == [ assert response_json(response) == [
{"id": 1, "data": {"test": "Hello Dolly"}, "status": 200} {"id": 1, "data": {"test": "Hello Dolly"}, "status": 200}
] ]
@ -312,7 +311,7 @@ def test_supports_post_url_encoded_query_with_string_variables(client):
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello Dolly"}} assert response_json(response) == {"data": {"test": "Hello Dolly"}}
@ -323,7 +322,7 @@ def test_supports_post_json_quey_with_get_variable_values(client):
"application/json", "application/json",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello Dolly"}} assert response_json(response) == {"data": {"test": "Hello Dolly"}}
@ -334,7 +333,7 @@ def test_post_url_encoded_query_with_get_variable_values(client):
"application/x-www-form-urlencoded", "application/x-www-form-urlencoded",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello Dolly"}} assert response_json(response) == {"data": {"test": "Hello Dolly"}}
@ -345,7 +344,7 @@ def test_supports_post_raw_text_query_with_get_variable_values(client):
"application/graphql", "application/graphql",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"test": "Hello Dolly"}} assert response_json(response) == {"data": {"test": "Hello Dolly"}}
@ -366,7 +365,7 @@ def test_allows_post_with_operation_name(client):
"application/json", "application/json",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == { assert response_json(response) == {
"data": {"test": "Hello World", "shared": "Hello Everyone"} "data": {"test": "Hello World", "shared": "Hello Everyone"}
} }
@ -390,7 +389,7 @@ def test_batch_allows_post_with_operation_name(client):
"application/json", "application/json",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == [ assert response_json(response) == [
{ {
"id": 1, "id": 1,
@ -414,7 +413,7 @@ def test_allows_post_with_get_operation_name(client):
"application/graphql", "application/graphql",
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == { assert response_json(response) == {
"data": {"test": "Hello World", "shared": "Hello Everyone"} "data": {"test": "Hello World", "shared": "Hello Everyone"}
} }
@ -431,7 +430,7 @@ def test_inherited_class_with_attributes_works(client):
# Check graphiql works # Check graphiql works
response = client.get(url_string(inherited_url), HTTP_ACCEPT="text/html") response = client.get(url_string(inherited_url), HTTP_ACCEPT="text/html")
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
@pytest.mark.urls("graphene_django.tests.urls_pretty") @pytest.mark.urls("graphene_django.tests.urls_pretty")
@ -453,7 +452,7 @@ def test_supports_pretty_printing_by_request(client):
def test_handles_field_errors_caught_by_graphql(client): def test_handles_field_errors_caught_by_graphql(client):
response = client.get(url_string(query="{thrower}")) response = client.get(url_string(query="{thrower}"))
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == { assert response_json(response) == {
"data": None, "data": None,
"errors": [ "errors": [
@ -468,7 +467,7 @@ def test_handles_field_errors_caught_by_graphql(client):
def test_handles_syntax_errors_caught_by_graphql(client): def test_handles_syntax_errors_caught_by_graphql(client):
response = client.get(url_string(query="syntaxerror")) response = client.get(url_string(query="syntaxerror"))
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [ "errors": [
{ {
@ -482,7 +481,7 @@ def test_handles_syntax_errors_caught_by_graphql(client):
def test_handles_errors_caused_by_a_lack_of_query(client): def test_handles_errors_caused_by_a_lack_of_query(client):
response = client.get(url_string()) response = client.get(url_string())
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [{"message": "Must provide query string."}] "errors": [{"message": "Must provide query string."}]
} }
@ -491,7 +490,7 @@ def test_handles_errors_caused_by_a_lack_of_query(client):
def test_handles_not_expected_json_bodies(client): def test_handles_not_expected_json_bodies(client):
response = client.post(url_string(), "[]", "application/json") response = client.post(url_string(), "[]", "application/json")
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [{"message": "The received data is not a valid JSON query."}] "errors": [{"message": "The received data is not a valid JSON query."}]
} }
@ -500,7 +499,7 @@ def test_handles_not_expected_json_bodies(client):
def test_handles_invalid_json_bodies(client): def test_handles_invalid_json_bodies(client):
response = client.post(url_string(), "[oh}", "application/json") response = client.post(url_string(), "[oh}", "application/json")
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [{"message": "POST body sent invalid JSON."}] "errors": [{"message": "POST body sent invalid JSON."}]
} }
@ -515,14 +514,14 @@ def test_handles_django_request_error(client, monkeypatch):
valid_json = json.dumps({"foo": "bar"}) valid_json = json.dumps({"foo": "bar"})
response = client.post(url_string(), valid_json, "application/json") response = client.post(url_string(), valid_json, "application/json")
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == {"errors": [{"message": "foo-bar"}]} assert response_json(response) == {"errors": [{"message": "foo-bar"}]}
def test_handles_incomplete_json_bodies(client): def test_handles_incomplete_json_bodies(client):
response = client.post(url_string(), '{"query":', "application/json") response = client.post(url_string(), '{"query":', "application/json")
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [{"message": "POST body sent invalid JSON."}] "errors": [{"message": "POST body sent invalid JSON."}]
} }
@ -534,7 +533,7 @@ def test_handles_plain_post_text(client):
"query helloWho($who: String){ test(who: $who) }", "query helloWho($who: String){ test(who: $who) }",
"text/plain", "text/plain",
) )
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [{"message": "Must provide query string."}] "errors": [{"message": "Must provide query string."}]
} }
@ -546,7 +545,7 @@ def test_handles_poorly_formed_variables(client):
query="query helloWho($who: String){ test(who: $who) }", variables="who:You" query="query helloWho($who: String){ test(who: $who) }", variables="who:You"
) )
) )
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
assert response_json(response) == { assert response_json(response) == {
"errors": [{"message": "Variables are invalid JSON."}] "errors": [{"message": "Variables are invalid JSON."}]
} }
@ -554,7 +553,7 @@ def test_handles_poorly_formed_variables(client):
def test_handles_unsupported_http_methods(client): def test_handles_unsupported_http_methods(client):
response = client.put(url_string(query="{test}")) response = client.put(url_string(query="{test}"))
assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED assert response.status_code == 405
assert response["Allow"] == "GET, POST" assert response["Allow"] == "GET, POST"
assert response_json(response) == { assert response_json(response) == {
"errors": [{"message": "GraphQL only supports GET and POST requests."}] "errors": [{"message": "GraphQL only supports GET and POST requests."}]
@ -564,7 +563,7 @@ def test_handles_unsupported_http_methods(client):
def test_passes_request_into_context_request(client): def test_passes_request_into_context_request(client):
response = client.get(url_string(query="{request}", q="testing")) response = client.get(url_string(query="{request}", q="testing"))
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == {"data": {"request": "testing"}} assert response_json(response) == {"data": {"request": "testing"}}
@ -858,7 +857,7 @@ def test_allow_introspection(client):
response = client.post( response = client.post(
url_string("/graphql/", query="{__schema {queryType {name}}}") url_string("/graphql/", query="{__schema {queryType {name}}}")
) )
assert response.status_code == HTTPStatus.OK assert response.status_code == 200
assert response_json(response) == { assert response_json(response) == {
"data": {"__schema": {"queryType": {"name": "QueryRoot"}}} "data": {"__schema": {"queryType": {"name": "QueryRoot"}}}
@ -870,7 +869,7 @@ def test_allow_introspection(client):
def test_validation_disallow_introspection(client, url): def test_validation_disallow_introspection(client, url):
response = client.post(url_string(url, query="{__schema {queryType {name}}}")) response = client.post(url_string(url, query="{__schema {queryType {name}}}"))
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
json_response = response_json(response) json_response = response_json(response)
assert "data" not in json_response assert "data" not in json_response
@ -889,7 +888,7 @@ def test_validation_disallow_introspection(client, url):
def test_within_max_validation_errors(client, url): def test_within_max_validation_errors(client, url):
response = client.post(url_string(url, query=QUERY_WITH_TWO_INTROSPECTIONS)) response = client.post(url_string(url, query=QUERY_WITH_TWO_INTROSPECTIONS))
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
json_response = response_json(response) json_response = response_json(response)
assert "data" not in json_response assert "data" not in json_response
@ -914,7 +913,7 @@ def test_within_max_validation_errors(client, url):
def test_exceeds_max_validation_errors(client, url): def test_exceeds_max_validation_errors(client, url):
response = client.post(url_string(url, query=QUERY_WITH_TWO_INTROSPECTIONS)) response = client.post(url_string(url, query=QUERY_WITH_TWO_INTROSPECTIONS))
assert response.status_code == HTTPStatus.BAD_REQUEST assert response.status_code == 400
json_response = response_json(response) json_response = response_json(response)
assert "data" not in json_response assert "data" not in json_response

View File

@ -111,7 +111,24 @@ def is_valid_django_model(model):
def import_single_dispatch(): def import_single_dispatch():
from functools import singledispatch try:
from functools import singledispatch
except ImportError:
singledispatch = None
if not singledispatch:
try:
from singledispatch import singledispatch
except ImportError:
pass
if not singledispatch:
raise Exception(
"It seems your python version does not include "
"functools.singledispatch. Please install the 'singledispatch' "
"package. More information here: "
"https://pypi.python.org/pypi/singledispatch"
)
return singledispatch return singledispatch

View File

@ -10,7 +10,3 @@ omit = */tests/*
[tool:pytest] [tool:pytest]
DJANGO_SETTINGS_MODULE = examples.django_test_settings DJANGO_SETTINGS_MODULE = examples.django_test_settings
addopts = --random-order addopts = --random-order
filterwarnings =
error
# we can't do anything about the DeprecationWarning about typing.ByteString in graphql
default:'typing\.ByteString' is deprecated:DeprecationWarning:graphql\.pyutils\.is_iterable

View File

@ -55,8 +55,6 @@ setup(
"Framework :: Django :: 3.2", "Framework :: Django :: 3.2",
"Framework :: Django :: 4.1", "Framework :: Django :: 4.1",
"Framework :: Django :: 4.2", "Framework :: Django :: 4.2",
"Framework :: Django :: 5.1",
"Framework :: Django :: 5.2",
], ],
keywords="api graphql protocol rest relay graphene", keywords="api graphql protocol rest relay graphene",
packages=find_packages(exclude=["tests", "examples", "examples.*"]), packages=find_packages(exclude=["tests", "examples", "examples.*"]),

View File

@ -2,7 +2,8 @@
envlist = envlist =
py{38,39,310}-django32 py{38,39,310}-django32
py{38,39}-django42 py{38,39}-django42
py{310,311,312}-django{42,50,51,main} py{310,311}-django{42,50,main}
py312-django{42,50,main}
pre-commit pre-commit
[gh-actions] [gh-actions]
@ -18,8 +19,6 @@ DJANGO =
3.2: django32 3.2: django32
4.2: django42 4.2: django42
5.0: django50 5.0: django50
5.1: django51
5.2: django52
main: djangomain main: djangomain
[testenv] [testenv]
@ -34,8 +33,6 @@ deps =
django32: Django>=3.2,<4.0 django32: Django>=3.2,<4.0
django42: Django>=4.2,<4.3 django42: Django>=4.2,<4.3
django50: Django>=5.0,<5.1 django50: Django>=5.0,<5.1
django51: Django>=5.1,<5.2
django52: Django>=5.2,<6.0
djangomain: https://github.com/django/django/archive/main.zip djangomain: https://github.com/django/django/archive/main.zip
commands = {posargs:pytest --cov=graphene_django graphene_django examples} commands = {posargs:pytest --cov=graphene_django graphene_django examples}