diff --git a/.travis.yml b/.travis.yml index a0908c7..331a6a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,9 @@ dist: xenial language: python python: - - "2.7" - "3.6" - "3.7" + - "3.8" sudo: false diff --git a/README.md b/README.md index d82e0aa..f9f0a43 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ API, we suggest you consider switching to HashIds instead. ## Requirements Tested against: -* Python (2.7, 3.6, 3.7) -* [Django](https://github.com/django/django) (1.11, 2.1, 2.2) -* [Django REST Framework](https://github.com/tomchristie/django-rest-framework) (3.7, 3.8, 3.9) +* Python (3.6, 3.7, 3.8) +* [Django](https://github.com/django/django) (2.1, 2.2, 3.0) +* [Django REST Framework](https://github.com/tomchristie/django-rest-framework) (3.8, 3.9, 3.10) * [HashIds](https://github.com/davidaurelio/hashids-python) (>1.0) diff --git a/requirements.txt b/requirements.txt index 86c84e4..1845e8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ # Minimum Django and REST framework version -Django>=1.11 -djangorestframework>=3.7.7 +Django>=2.0 +djangorestframework>=3.8.2 # Test requirements pytest-django==3.4.8 pytest==4.4.1 pytest-cov==2.6.1 flake8==3.7.7 -django-test-plus==1.1.1 +django-test-plus==1.4.0 mock==2.0.0 # App requirements diff --git a/rest_framework_serializer_extensions/fields.py b/rest_framework_serializer_extensions/fields.py index 9906144..9543e52 100644 --- a/rest_framework_serializer_extensions/fields.py +++ b/rest_framework_serializer_extensions/fields.py @@ -1,5 +1,4 @@ from django.core.exceptions import ObjectDoesNotExist -from django.utils import six from rest_framework.fields import Field from rest_framework.relations import ( HyperlinkedIdentityField, HyperlinkedRelatedField) @@ -37,7 +36,7 @@ def get_model(self): 'No "model" value passed to field "{0}"' .format(type(self).__name__) ) - elif isinstance(self.model, six.string_types): + elif isinstance(self.model, str): return utils.model_from_definition(self.model) else: return self.model diff --git a/rest_framework_serializer_extensions/serializers.py b/rest_framework_serializer_extensions/serializers.py index 97beebe..2ad74c6 100644 --- a/rest_framework_serializer_extensions/serializers.py +++ b/rest_framework_serializer_extensions/serializers.py @@ -1,4 +1,3 @@ -from __future__ import absolute_import from collections import OrderedDict from pprint import pformat @@ -6,7 +5,6 @@ from django.db.models import Prefetch from django.db.models.fields.related import ForeignKey from django.db.models.query import QuerySet -from django.utils import six from rest_framework import serializers from rest_framework.fields import empty @@ -336,7 +334,7 @@ def _construct_relations(self, matcher): """ expand_fields = self._get_expand_fields() - for field_name, field in six.iteritems(expand_fields): + for field_name, field in expand_fields.items(): if field_name.endswith('_id'): field_definition = self.expandable_fields[field_name[:-3]] else: @@ -416,7 +414,7 @@ def _get_related_name(self, field): def _standardise_expandable_definitions(self, expandable_fields): return { key: self._standardise_expandable_definition(definition) - for key, definition in six.iteritems(expandable_fields) + for key, definition in expandable_fields.items() } def _standardise_expandable_definition(self, definition): @@ -427,7 +425,7 @@ def _standardise_expandable_definition(self, definition): definition = dict(serializer=definition) # Resolve string references to serializers - if isinstance(definition['serializer'], six.string_types): + if isinstance(definition['serializer'], str): reference = definition['serializer'] serializer = utils.import_local(reference) assert issubclass(serializer, serializers.BaseSerializer), ( @@ -471,7 +469,7 @@ def _parse_root_instructions(self): def _validate_max_depth(self, root_instructions): max_depth = self.get_max_expand_depth() - for nested_field_names in six.itervalues(root_instructions): + for nested_field_names in root_instructions.values(): for nested_field_name in nested_field_names: depth = len(nested_field_name.split(EXPAND_DELIMITER)) @@ -500,7 +498,7 @@ def _expand_instructions(self, root_instructions): hierarchy = _get_serializer_hierarchy(self) instructions = {} - for method, root_nested_names in six.iteritems(root_instructions): + for method, root_nested_names in root_instructions.items(): instructions[method] = { n.split(EXPAND_DELIMITER)[0] for n in _get_nested_field_names(hierarchy, root_nested_names) @@ -592,7 +590,7 @@ def _validate_instructions(self, instructions, standard_fields): ): return - for method, field_names in six.iteritems(instructions): + for method, field_names in instructions.items(): valid_field_names = set(self.expandable_fields) # Allow unmatched full expand instructions provided that the @@ -625,7 +623,7 @@ def _get_expand_fields(self, standard_fields=None): if standard_fields: self._validate_instructions(instructions, standard_fields) - field_iterator = six.iteritems(self.expandable_fields) + field_iterator = self.expandable_fields.items() # As we add _id fields for foreign keys, take note of any # that require translation to model instances in the case of an update @@ -739,7 +737,7 @@ def get_fields(self): return OrderedDict( (name, field) - for name, field in six.iteritems(fields) + for name, field in fields.items() if name in only_names ) @@ -779,7 +777,7 @@ def get_fields(self): return OrderedDict( (name, field) - for name, field in six.iteritems(fields) + for name, field in fields.items() if name not in exclude_names ) diff --git a/rest_framework_serializer_extensions/utils.py b/rest_framework_serializer_extensions/utils.py index 9df1ce2..90a4bf1 100644 --- a/rest_framework_serializer_extensions/utils.py +++ b/rest_framework_serializer_extensions/utils.py @@ -3,7 +3,6 @@ from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db import models -from django.utils import six def import_local(path_to_object): @@ -93,7 +92,7 @@ def model_from_definition(model_definition): Returns: (django.db.models.Model) """ - if isinstance(model_definition, six.string_types): + if isinstance(model_definition, str): model = import_local(model_definition) else: model = model_definition diff --git a/runtests.py b/runtests.py index 9268a67..c05972f 100755 --- a/runtests.py +++ b/runtests.py @@ -1,5 +1,4 @@ #! /usr/bin/env python -from __future__ import print_function import pytest import sys diff --git a/setup.py b/setup.py index ed9154c..eeb837f 100644 --- a/setup.py +++ b/setup.py @@ -98,11 +98,10 @@ def get_long_description(): 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Natural Language :: English', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: WWW/HTTP', ] ) diff --git a/tests/base.py b/tests/base.py index 91fe8b3..e316a47 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.test import TestCase from hashids import Hashids diff --git a/tests/test_auto_optimise.py b/tests/test_auto_optimise.py index 318e300..def03b7 100644 --- a/tests/test_auto_optimise.py +++ b/tests/test_auto_optimise.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - import django from django.db import connection from django.http import QueryDict diff --git a/tests/test_fields.py b/tests/test_fields.py index 3d6214d..a11ebd5 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,13 +1,5 @@ -from __future__ import absolute_import - -# Django 1 vs. 2 -try: - from django.core.urlresolvers import reverse -except ImportError: - from django.urls import reverse - from django.test import override_settings, RequestFactory, TestCase -from django.utils import six +from django.urls import reverse from rest_framework.exceptions import ValidationError from rest_framework.serializers import Serializer, ModelSerializer @@ -78,7 +70,7 @@ class HashIdFieldTests(BaseFieldsTestCase): Unit tests for the HashIdField """ def test_representation_requires_model(self): - with six.assertRaisesRegex(self, AssertionError, 'No "model"'): + with self.assertRaisesRegex(AssertionError, 'No "model"'): fields.HashIdField().to_representation(self.car_model.pk) def test_representation_explicit_model_object(self): @@ -96,7 +88,7 @@ def test_representation_explicit_model_reference(self): self.assertEqual(self.external_id(self.car_model), representation) def test_representation_explicit_invalid_model_reference(self): - with six.assertRaisesRegex(self, AssertionError, 'not a Django model'): + with self.assertRaisesRegex(AssertionError, 'not a Django model'): ( fields.HashIdField(model='tests.base.TEST_HASH_IDS') .to_representation(self.car_model.pk) @@ -119,7 +111,7 @@ def test_representation_explicit_model_serializer_method(self): self.assertEqual(self.external_id(self.manufacturer), representation) def test_internal_value_requires_model(self): - with six.assertRaisesRegex(self, AssertionError, 'No "model"'): + with self.assertRaisesRegex(AssertionError, 'No "model"'): fields.HashIdField().to_internal_value( self.external_id(self.car_model) ) @@ -139,16 +131,14 @@ def test_internal_value_explicit_model_reference(self): self.assertEqual(self.car_model.pk, internal_value) def test_internal_value_explicit_invalid_model_reference(self): - with six.assertRaisesRegex(self, AssertionError, 'not a Django model'): + with self.assertRaisesRegex(AssertionError, 'not a Django model'): ( fields.HashIdField(model='tests.base.TEST_HASH_IDS') .to_internal_value(self.external_id(self.car_model)) ) def test_internal_value_hash_id_validation(self): - with six.assertRaisesRegex( - self, ValidationError, 'not a valid HashId' - ): + with self.assertRaisesRegex(ValidationError, 'not a valid HashId'): ( fields.HashIdField(model='tests.models.CarModel') .to_internal_value('abc123') diff --git a/tests/test_serializers__exclude_fields_mixin.py b/tests/test_serializers__exclude_fields_mixin.py index 252036b..be3b5ec 100644 --- a/tests/test_serializers__exclude_fields_mixin.py +++ b/tests/test_serializers__exclude_fields_mixin.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from rest_framework import serializers from rest_framework_serializer_extensions.serializers import ExcludeFieldsMixin diff --git a/tests/test_serializers__expandable_fields_mixin.py b/tests/test_serializers__expandable_fields_mixin.py index 1b3fec6..9614c20 100644 --- a/tests/test_serializers__expandable_fields_mixin.py +++ b/tests/test_serializers__expandable_fields_mixin.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.test import override_settings from rest_framework import serializers diff --git a/tests/test_serializers__helpers_mixin.py b/tests/test_serializers__helpers_mixin.py index 59afdd1..696523b 100644 --- a/tests/test_serializers__helpers_mixin.py +++ b/tests/test_serializers__helpers_mixin.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.test import TestCase from rest_framework import serializers diff --git a/tests/test_serializers__only_fields_mixin.py b/tests/test_serializers__only_fields_mixin.py index f1a0dba..18e8d9a 100644 --- a/tests/test_serializers__only_fields_mixin.py +++ b/tests/test_serializers__only_fields_mixin.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from rest_framework import serializers from rest_framework_serializer_extensions.serializers import OnlyFieldsMixin diff --git a/tests/test_utils.py b/tests/test_utils.py index 6cb6bd0..921ee93 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,8 +1,5 @@ -from __future__ import absolute_import - from django.contrib.contenttypes.models import ContentType from django.test import override_settings, TestCase -from django.utils import six from rest_framework_serializer_extensions import fields, utils from test_package.test_module.serializers import TestSerializer @@ -71,7 +68,7 @@ class GetHashIdsSourceTests(TestCase): Unit tests for the get_hash_ids_source() utility method. """ def test_unset_raises(self): - with six.assertRaisesRegex(self, AssertionError, 'No HASH_IDS_SOURCE'): + with self.assertRaisesRegex(AssertionError, 'No HASH_IDS_SOURCE'): utils.get_hash_ids_source() @override_settings( @@ -169,7 +166,7 @@ def test_pass_non_model_class_raises_assertion_error(self): """ An error should be raised when a non-Django model class is passed. """ - with six.assertRaisesRegex(self, AssertionError, 'not a Django model'): + with self.assertRaisesRegex(AssertionError, 'not a Django model'): utils.model_from_definition(dict) def test_pass_string_as_model_definition(self): diff --git a/tests/test_views.py b/tests/test_views.py index 14cc7d8..6ea55ee 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from django.http import QueryDict from django.test import override_settings, RequestFactory, TestCase from mock import patch diff --git a/tox.ini b/tox.ini index 5b735ec..e9de668 100644 --- a/tox.ini +++ b/tox.ini @@ -1,8 +1,8 @@ [tox] envlist = - py37-{flake8,codecov,docs}, - {py27,py36}-django111-drf{37,38,39}, - {py36,py37}-django{21,22}-drf{37,38,39} + py38-{flake8,codecov,docs}, + {py36,py37,py38}-django{21,22}-drf{38,39,310} + {py36,py37,py38}-django{30}-drf{310} [testenv] passenv = CI TRAVIS TRAVIS_* @@ -10,36 +10,36 @@ setenv = PYTHONDONTWRITEBYTECODE=1 commands = ./runtests.py --fast {posargs} --coverage deps = - django111: Django==1.11.8 - django21: Django==2.1.8 - django22: Django==2.2.0 - drf37: djangorestframework==3.7.7 + django21: Django==2.1.15 + django22: Django==2.2.8 + django30: Django==3.0.0 drf38: djangorestframework==3.8.2 - drf39: djangorestframework==3.9.2 - django-test-plus==1.1.1 + drf39: djangorestframework==3.9.4 + drf310: djangorestframework==3.10.3 + django-test-plus==1.4.0 pytest==4.4.1 pytest-django==3.4.8 pytest-cov==2.6.1 hashids==1.1.0 mock==2.0.0 -[testenv:py37-flake8] +[testenv:py38-flake8] commands = ./runtests.py --lintonly deps = pytest==3.0.5 flake8==3.7.7 -[testenv:py37-codecov] +[testenv:py38-codecov] commands = {[testenv]commands} # Run the tests and generate coverage report codecov -e TOX_ENV # Post coverage stats to codecov.io deps = {[testenv]deps} - Django==2.2.0 - djangorestframework==3.9.2 + Django==3.0.0 + djangorestframework==3.10.3 codecov==2.0.15 -[testenv:py37-docs] +[testenv:py38-docs] commands = mkdocs build deps = mkdocs>=0.16.1