* Remove Python 2 support

* Upgrade Python & Django versions

* Remove unsupported Django versions
* Remove unsupported Python versions
* Add Python 3.8

* Drop support for django-filter < 2

* Update LoginRequiredMixin doc link

* Remove redundant import

* Resolve RemovedInDjango40Warning warnings

* gql/graphene-django/graphene_django/tests/test_converter.py:175:
RemovedInDjango40Warning: django.utils.translation.ugettext_lazy() is
deprecated in favor of django.utils.translation.gettext_lazy().

* graphene-django/graphene_django/utils/utils.py:28:
RemovedInDjango40Warning: force_text() is deprecated in favor of
force_str().

* No need to use unicode strings with Python3

* Remove singledispatch dependency

singledispatch is inluded with Python >= 3.4, no need for external
package.
This commit is contained in:
Ülgen Sarıkavak 2020-04-06 15:21:07 +03:00 committed by GitHub
parent b84f61afab
commit dd0d6ef28f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 53 additions and 145 deletions

View File

@ -24,24 +24,8 @@ jobs:
- env: DJANGO=master - env: DJANGO=master
include: include:
- python: 2.7
env: DJANGO=1.11
- python: 3.5
env: DJANGO=1.11
- python: 3.5
env: DJANGO=2.0
- python: 3.5
env: DJANGO=2.1
- python: 3.5
env: DJANGO=2.2
- python: 3.6 - python: 3.6
env: DJANGO=1.11 env: DJANGO=1.11
- python: 3.6
env: DJANGO=2.0
- python: 3.6
env: DJANGO=2.1
- python: 3.6 - python: 3.6
env: DJANGO=2.2 env: DJANGO=2.2
- python: 3.6 - python: 3.6
@ -51,10 +35,6 @@ jobs:
- python: 3.7 - python: 3.7
env: DJANGO=1.11 env: DJANGO=1.11
- python: 3.7
env: DJANGO=2.0
- python: 3.7
env: DJANGO=2.1
- python: 3.7 - python: 3.7
env: DJANGO=2.2 env: DJANGO=2.2
- python: 3.7 - python: 3.7
@ -62,12 +42,21 @@ jobs:
- python: 3.7 - python: 3.7
env: DJANGO=master env: DJANGO=master
- python: 3.7 - python: 3.8
env: DJANGO=1.11
- python: 3.8
env: DJANGO=2.2
- python: 3.8
env: DJANGO=3.0
- python: 3.8
env: DJANGO=master
- python: 3.8
env: TOXENV=black,flake8 env: TOXENV=black,flake8
- stage: deploy - stage: deploy
script: skip script: skip
python: 3.7 python: 3.8
after_success: true after_success: true
deploy: deploy:
provider: pypi provider: pypi

View File

@ -184,4 +184,4 @@ For Django 2.0 and above:
path('graphql', PrivateGraphQLView.as_view(graphiql=True, schema=schema)), path('graphql', PrivateGraphQLView.as_view(graphiql=True, schema=schema)),
] ]
.. _LoginRequiredMixin: https://docs.djangoproject.com/en/1.10/topics/auth/default/#the-loginrequired-mixin .. _LoginRequiredMixin: https://docs.djangoproject.com/en/dev/topics/auth/default/#the-loginrequired-mixin

View File

@ -60,18 +60,18 @@ source_suffix = ".rst"
master_doc = "index" master_doc = "index"
# General information about the project. # General information about the project.
project = u"Graphene Django" project = "Graphene Django"
copyright = u"Graphene 2017" copyright = "Graphene 2017"
author = u"Syrus Akbary" author = "Syrus Akbary"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = u"1.0" version = "1.0"
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = u"1.0.dev" release = "1.0.dev"
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@ -276,7 +276,7 @@ latex_elements = {
# (source start file, target name, title, # (source start file, target name, title,
# author, documentclass [howto, manual, or own class]). # author, documentclass [howto, manual, or own class]).
latex_documents = [ latex_documents = [
(master_doc, "Graphene.tex", u"Graphene Documentation", u"Syrus Akbary", "manual") (master_doc, "Graphene.tex", "Graphene Documentation", "Syrus Akbary", "manual")
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
@ -317,7 +317,7 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
(master_doc, "graphene_django", u"Graphene Django Documentation", [author], 1) (master_doc, "graphene_django", "Graphene Django Documentation", [author], 1)
] ]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
@ -334,7 +334,7 @@ texinfo_documents = [
( (
master_doc, master_doc,
"Graphene-Django", "Graphene-Django",
u"Graphene Django Documentation", "Graphene Django Documentation",
author, author,
"Graphene Django", "Graphene Django",
"One line description of project.", "One line description of project.",

View File

@ -2,8 +2,7 @@ Filtering
========= =========
Graphene integrates with Graphene integrates with
`django-filter <https://django-filter.readthedocs.io/en/master/>`__ (2.x for `django-filter <https://django-filter.readthedocs.io/en/master/>`__ to provide filtering of results. See the `usage
Python 3 or 1.x for Python 2) to provide filtering of results. See the `usage
documentation <https://django-filter.readthedocs.io/en/master/guide/usage.html#the-filter>`__ documentation <https://django-filter.readthedocs.io/en/master/guide/usage.html#the-filter>`__
for details on the format for ``filter_fields``. for details on the format for ``filter_fields``.

View File

@ -1,4 +1,6 @@
from collections import OrderedDict from collections import OrderedDict
from functools import singledispatch
from django.db import models from django.db import models
from django.utils.encoding import force_str from django.utils.encoding import force_str
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
@ -26,9 +28,6 @@ from graphql import assert_valid_name
from .settings import graphene_settings from .settings import graphene_settings
from .compat import ArrayField, HStoreField, JSONField, RangeField from .compat import ArrayField, HStoreField, JSONField, RangeField
from .fields import DjangoListField, DjangoConnectionField from .fields import DjangoListField, DjangoConnectionField
from .utils import import_single_dispatch
singledispatch = import_single_dispatch()
def convert_choice_name(name): def convert_choice_name(name):

View File

@ -5,7 +5,6 @@ import json
from threading import local from threading import local
from time import time from time import time
import six
from django.utils.encoding import force_str from django.utils.encoding import force_str
from .types import DjangoDebugSQL from .types import DjangoDebugSQL
@ -77,7 +76,7 @@ class NormalCursorWrapper(object):
self.logger = logger self.logger = logger
def _quote_expr(self, element): def _quote_expr(self, element):
if isinstance(element, six.string_types): if isinstance(element, str):
return "'%s'" % force_str(element).replace("'", "''") return "'%s'" % force_str(element).replace("'", "''")
else: else:
return repr(element) return repr(element)

View File

@ -1,6 +1,5 @@
from functools import partial from functools import partial
import six
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from graphql_relay.connection.arrayconnection import connection_from_list_slice from graphql_relay.connection.arrayconnection import connection_from_list_slice
from promise import Promise from promise import Promise

View File

@ -1,7 +1,7 @@
import itertools import itertools
from django.db import models from django.db import models
from django_filters import Filter, MultipleChoiceFilter, VERSION from django_filters import Filter, MultipleChoiceFilter
from django_filters.filterset import BaseFilterSet, FilterSet from django_filters.filterset import BaseFilterSet, FilterSet
from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS from django_filters.filterset import FILTER_FOR_DBFIELD_DEFAULTS
@ -50,36 +50,6 @@ class GrapheneFilterSetMixin(BaseFilterSet):
) )
# To support a Django 1.11 + Python 2.7 combination django-filter must be
# < 2.x.x. To support the earlier version of django-filter, the
# filter_for_reverse_field method must be present on GrapheneFilterSetMixin and
# must not be present for later versions of django-filter.
if VERSION[0] < 2:
from django.utils.text import capfirst
class GrapheneFilterSetMixinPython2(GrapheneFilterSetMixin):
@classmethod
def filter_for_reverse_field(cls, f, name):
"""Handles retrieving filters for reverse relationships
We override the default implementation so that we can handle
Global IDs (the default implementation expects database
primary keys)
"""
try:
rel = f.field.remote_field
except AttributeError:
rel = f.field.rel
default = {"name": name, "label": capfirst(rel.related_name)}
if rel.multiple:
# For to-many relationships
return GlobalIDMultipleChoiceFilter(**default)
else:
# For to-one relationships
return GlobalIDFilter(**default)
GrapheneFilterSetMixin = GrapheneFilterSetMixinPython2
def setup_filterset(filterset_class): def setup_filterset(filterset_class):
""" Wrap a provided filterset in Graphene-specific functionality """ Wrap a provided filterset in Graphene-specific functionality
""" """

View File

@ -1,5 +1,3 @@
import six
from django_filters.utils import get_model_field from django_filters.utils import get_model_field
from .filterset import custom_filterset_factory, setup_filterset from .filterset import custom_filterset_factory, setup_filterset
@ -13,7 +11,7 @@ def get_filtering_args_from_filterset(filterset_class, type):
args = {} args = {}
model = filterset_class._meta.model model = filterset_class._meta.model
for name, filter_field in six.iteritems(filterset_class.base_filters): for name, filter_field in filterset_class.base_filters.items():
form_field = None form_field = None
if name in filterset_class.declared_filters: if name in filterset_class.declared_filters:

View File

@ -1,13 +1,11 @@
from functools import singledispatch
from django import forms from django import forms
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from graphene import ID, Boolean, Float, Int, List, String, UUID, Date, DateTime, Time from graphene import ID, Boolean, Float, Int, List, String, UUID, Date, DateTime, Time
from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField from .forms import GlobalIDFormField, GlobalIDMultipleChoiceField
from ..utils import import_single_dispatch
singledispatch = import_single_dispatch()
@singledispatch @singledispatch

View File

@ -1,3 +1,5 @@
from functools import singledispatch
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from rest_framework import serializers from rest_framework import serializers
@ -5,11 +7,8 @@ import graphene
from ..registry import get_global_registry from ..registry import get_global_registry
from ..converter import convert_choices_to_named_enum_with_descriptions from ..converter import convert_choices_to_named_enum_with_descriptions
from ..utils import import_single_dispatch
from .types import DictType from .types import DictType
singledispatch = import_single_dispatch()
@singledispatch @singledispatch
def get_graphene_type_from_serializer_field(field): def get_graphene_type_from_serializer_field(field):

View File

@ -13,7 +13,6 @@ back to the defaults.
""" """
from __future__ import unicode_literals from __future__ import unicode_literals
import six
from django.conf import settings from django.conf import settings
from django.test.signals import setting_changed from django.test.signals import setting_changed
@ -55,7 +54,7 @@ def perform_import(val, setting_name):
""" """
if val is None: if val is None:
return None return None
elif isinstance(val, six.string_types): elif isinstance(val, str):
return import_from_string(val, setting_name) return import_from_string(val, setting_name)
elif isinstance(val, (list, tuple)): elif isinstance(val, (list, tuple)):
return [import_from_string(item, setting_name) for item in val] return [import_from_string(item, setting_name) for item in val]

View File

@ -1,7 +1,7 @@
from __future__ import absolute_import from __future__ import absolute_import
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
CHOICES = ((1, "this"), (2, _("that"))) CHOICES = ((1, "this"), (2, _("that")))
@ -46,7 +46,7 @@ class Reporter(models.Model):
"Reporter Type", "Reporter Type",
null=True, null=True,
blank=True, blank=True,
choices=[(1, u"Regular"), (2, u"CNN Reporter")], choices=[(1, "Regular"), (2, "CNN Reporter")],
) )
def __str__(self): # __unicode__ on Python 2 def __str__(self): # __unicode__ on Python 2
@ -105,7 +105,7 @@ class Article(models.Model):
"Importance", "Importance",
null=True, null=True,
blank=True, blank=True,
choices=[(1, u"Very important"), (2, u"Not as important")], choices=[(1, "Very important"), (2, "Not as important")],
) )
def __str__(self): # __unicode__ on Python 2 def __str__(self): # __unicode__ on Python 2

View File

@ -1,8 +1,8 @@
from textwrap import dedent from textwrap import dedent
from django.core import management from django.core import management
from io import StringIO
from mock import mock_open, patch from mock import mock_open, patch
from six import StringIO
from graphene import ObjectType, Schema, String from graphene import ObjectType, Schema, String

View File

@ -1,7 +1,7 @@
import pytest import pytest
from collections import namedtuple from collections import namedtuple
from django.db import models from django.db import models
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
from graphene import NonNull from graphene import NonNull
from py.test import raises from py.test import raises

View File

@ -1,7 +1,7 @@
import warnings import warnings
from collections import OrderedDict from collections import OrderedDict
from typing import Type
import six
from django.db.models import Model from django.db.models import Model
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
@ -21,9 +21,6 @@ from .utils import (
is_valid_django_model, is_valid_django_model,
) )
if six.PY3:
from typing import Type
ALL_FIELDS = "__all__" ALL_FIELDS = "__all__"

View File

@ -4,7 +4,6 @@ from .utils import (
camelize, camelize,
get_model_fields, get_model_fields,
get_reverse_fields, get_reverse_fields,
import_single_dispatch,
is_valid_django_model, is_valid_django_model,
maybe_queryset, maybe_queryset,
) )
@ -16,6 +15,5 @@ __all__ = [
"get_model_fields", "get_model_fields",
"camelize", "camelize",
"is_valid_django_model", "is_valid_django_model",
"import_single_dispatch",
"GraphQLTestCase", "GraphQLTestCase",
] ]

View File

@ -1,9 +1,8 @@
import inspect import inspect
import six
from django.db import models from django.db import models
from django.db.models.manager import Manager from django.db.models.manager import Manager
from django.utils.encoding import force_text from django.utils.encoding import force_str
from django.utils.functional import Promise from django.utils.functional import Promise
from graphene.utils.str_converters import to_camel_case from graphene.utils.str_converters import to_camel_case
@ -26,14 +25,14 @@ def isiterable(value):
def _camelize_django_str(s): def _camelize_django_str(s):
if isinstance(s, Promise): if isinstance(s, Promise):
s = force_text(s) s = force_str(s)
return to_camel_case(s) if isinstance(s, six.string_types) else s return to_camel_case(s) if isinstance(s, str) else s
def camelize(data): def camelize(data):
if isinstance(data, dict): if isinstance(data, dict):
return {_camelize_django_str(k): camelize(v) for k, v in data.items()} return {_camelize_django_str(k): camelize(v) for k, v in data.items()}
if isiterable(data) and not isinstance(data, (six.string_types, Promise)): if isiterable(data) and not isinstance(data, (str, Promise)):
return [camelize(d) for d in data] return [camelize(d) for d in data]
return data return data
@ -77,26 +76,3 @@ def get_model_fields(model):
def is_valid_django_model(model): def is_valid_django_model(model):
return inspect.isclass(model) and issubclass(model, models.Model) return inspect.isclass(model) and issubclass(model, models.Model)
def import_single_dispatch():
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

View File

@ -2,7 +2,6 @@ import inspect
import json import json
import re import re
import six
from django.http import HttpResponse, HttpResponseNotAllowed from django.http import HttpResponse, HttpResponseNotAllowed
from django.http.response import HttpResponseBadRequest from django.http.response import HttpResponseBadRequest
from django.shortcuts import render from django.shortcuts import render
@ -314,7 +313,7 @@ class GraphQLView(View):
variables = request.GET.get("variables") or data.get("variables") variables = request.GET.get("variables") or data.get("variables")
id = request.GET.get("id") or data.get("id") id = request.GET.get("id") or data.get("id")
if variables and isinstance(variables, six.text_type): if variables and isinstance(variables, str):
try: try:
variables = json.loads(variables) variables = json.loads(variables)
except Exception: except Exception:
@ -331,7 +330,7 @@ class GraphQLView(View):
if isinstance(error, GraphQLError): if isinstance(error, GraphQLError):
return format_graphql_error(error) return format_graphql_error(error)
return {"message": six.text_type(error)} return {"message": str(error)}
@staticmethod @staticmethod
def get_content_type(request): def get_content_type(request):

View File

@ -1,5 +1,4 @@
from setuptools import find_packages, setup from setuptools import find_packages, setup
import sys
import ast import ast
import re import re
@ -19,8 +18,7 @@ tests_require = [
"coveralls", "coveralls",
"mock", "mock",
"pytz", "pytz",
"django-filter<2;python_version<'3'", "django-filter>=2",
"django-filter>=2;python_version>='3'",
"pytest-django>=3.3.2", "pytest-django>=3.3.2",
] + rest_framework_require ] + rest_framework_require
@ -45,22 +43,18 @@ setup(
"Development Status :: 3 - Alpha", "Development Status :: 3 - Alpha",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: PyPy",
], ],
keywords="api graphql protocol rest relay graphene", keywords="api graphql protocol rest relay graphene",
packages=find_packages(exclude=["tests"]), packages=find_packages(exclude=["tests"]),
install_requires=[ install_requires=[
"six>=1.10.0",
"graphene>=2.1.7,<3", "graphene>=2.1.7,<3",
"graphql-core>=2.1.0,<3", "graphql-core>=2.1.0,<3",
"Django>=1.11", "Django>=1.11,!=2.0.*,!=2.1.*",
"singledispatch>=3.4.0.3",
"promise>=2.1", "promise>=2.1",
], ],
setup_requires=["pytest-runner"], setup_requires=["pytest-runner"],

13
tox.ini
View File

@ -1,14 +1,11 @@
[tox] [tox]
envlist = envlist =
py{27,35,36,37}-django{111,20,21,22,master}, py{36,37,38}-django{111,django22,django30,master},
py{36,37}-django30,
black,flake8 black,flake8
[travis:env] [travis:env]
DJANGO = DJANGO =
1.11: django111 1.11: django111
2.0: django20
2.1: django21
2.2: django22 2.2: django22
3.0: django30 3.0: django30
master: djangomaster master: djangomaster
@ -20,23 +17,21 @@ setenv =
DJANGO_SETTINGS_MODULE=django_test_settings DJANGO_SETTINGS_MODULE=django_test_settings
deps = deps =
-e.[test] -e.[test]
psycopg2 psycopg2-binary
django111: Django>=1.11,<2.0 django111: Django>=1.11,<2.0
django20: Django>=2.0,<2.1
django21: Django>=2.1,<2.2
django22: Django>=2.2,<3.0 django22: Django>=2.2,<3.0
django30: Django>=3.0a1,<3.1 django30: Django>=3.0a1,<3.1
djangomaster: https://github.com/django/django/archive/master.zip djangomaster: https://github.com/django/django/archive/master.zip
commands = {posargs:py.test --cov=graphene_django graphene_django examples} commands = {posargs:py.test --cov=graphene_django graphene_django examples}
[testenv:black] [testenv:black]
basepython = python3.7 basepython = python3.8
deps = -e.[dev] deps = -e.[dev]
commands = commands =
black --exclude "/migrations/" graphene_django examples setup.py --check black --exclude "/migrations/" graphene_django examples setup.py --check
[testenv:flake8] [testenv:flake8]
basepython = python3.7 basepython = python3.8
deps = -e.[dev] deps = -e.[dev]
commands = commands =
flake8 graphene_django examples flake8 graphene_django examples