Modernize Python syntax, add Python 3.8 (#398)

* Modernize Python syntax, add Python 3.8

* Update Python version & dist in TravisCI

* Add postgresql as addon

* Switch to psycopg2-binary

* Drop django.utils.six
This commit is contained in:
Adam Dobrawy 2019-11-14 17:50:04 +01:00 committed by Asif Saif Uddin
parent aa94194dbc
commit ffa1a85dc7
32 changed files with 85 additions and 133 deletions

View file

@ -1,19 +1,23 @@
sudo: True
dist: xenial
version: ~> 1.0
dist: bionic
os: linux
language: python
cache: pip
python:
- 3.7
- 3.6
- 3.7
- 3.8
install: pip install tox-travis codecov
# positional args ({posargs}) to pass into tox.ini
script: tox -- --cov --cov-append
addons:
postgresql: '10'
services:
- postgresql
after_success: codecov
deploy:
provider: pypi
user: jazzband
username: jazzband
server: https://jazzband.co/projects/django-model-utils/upload
distributions: sdist bdist_wheel
password:

View file

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# django-model-utils documentation build configuration file, created by
# sphinx-quickstart on Wed Jul 31 22:27:07 2013.
@ -40,8 +39,8 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'django-model-utils'
copyright = u'2015, Carl Meyer'
project = 'django-model-utils'
copyright = '2015, Carl Meyer'
parent_dir = os.path.dirname(os.path.dirname(__file__))
@ -194,8 +193,8 @@ latex_elements = {
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'django-model-utils.tex', u'django-model-utils Documentation',
u'Carl Meyer', 'manual'),
('index', 'django-model-utils.tex', 'django-model-utils Documentation',
'Carl Meyer', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@ -224,8 +223,8 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'django-model-utils', u'django-model-utils Documentation',
[u'Carl Meyer'], 1)
('index', 'django-model-utils', 'django-model-utils Documentation',
['Carl Meyer'], 1)
]
# If true, show URL addresses after external links.
@ -238,8 +237,8 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'django-model-utils', u'django-model-utils Documentation',
u'Carl Meyer', 'django-model-utils', 'One line description of project.',
('index', 'django-model-utils', 'django-model-utils Documentation',
'Carl Meyer', 'django-model-utils', 'One line description of project.',
'Miscellaneous'),
]

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
import copy
@ -132,9 +130,9 @@ class Choices:
return False
def __repr__(self):
return '%s(%s)' % (
return '{}({})'.format(
self.__class__.__name__,
', '.join(("%s" % repr(i) for i in self._triples))
', '.join("%s" % repr(i) for i in self._triples)
)
def __contains__(self, item):

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
import django
import uuid
from django.db import models
@ -22,7 +20,7 @@ class AutoCreatedField(models.DateTimeField):
def __init__(self, *args, **kwargs):
kwargs.setdefault('editable', False)
kwargs.setdefault('default', now)
super(AutoCreatedField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
class AutoLastModifiedField(AutoCreatedField):
@ -73,7 +71,7 @@ class StatusField(models.CharField):
kwargs.setdefault('max_length', 100)
self.check_for_status = not kwargs.pop('no_check_for_status', False)
self.choices_name = kwargs.pop('choices_name', DEFAULT_CHOICES_NAME)
super(StatusField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def prepare_class(self, sender, **kwargs):
if not sender._meta.abstract and self.check_for_status:
@ -90,10 +88,10 @@ class StatusField(models.CharField):
# the STATUS class attr being available), but we need to set some dummy
# choices now so the super method will add the get_FOO_display method
self.choices = [(0, 'dummy')]
super(StatusField, self).contribute_to_class(cls, name)
super().contribute_to_class(cls, name)
def deconstruct(self):
name, path, args, kwargs = super(StatusField, self).deconstruct()
name, path, args, kwargs = super().deconstruct()
kwargs['no_check_for_status'] = True
return name, path, args, kwargs
@ -117,12 +115,12 @@ class MonitorField(models.DateTimeField):
if when is not None:
when = set(when)
self.when = when
super(MonitorField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
self.monitor_attname = '_monitor_%s' % name
models.signals.post_init.connect(self._save_initial, sender=cls)
super(MonitorField, self).contribute_to_class(cls, name)
super().contribute_to_class(cls, name)
def get_monitored_value(self, instance):
return getattr(instance, self.monitor)
@ -141,10 +139,10 @@ class MonitorField(models.DateTimeField):
if self.when is None or current in self.when:
setattr(model_instance, self.attname, value)
self._save_initial(model_instance.__class__, model_instance)
return super(MonitorField, self).pre_save(model_instance, add)
return super().pre_save(model_instance, add)
def deconstruct(self):
name, path, args, kwargs = super(MonitorField, self).deconstruct()
name, path, args, kwargs = super().deconstruct()
kwargs['monitor'] = self.monitor
if self.when is not None:
kwargs['when'] = self.when
@ -236,17 +234,17 @@ class SplitField(models.TextField):
# _excerpt field itself is frozen as well. See introspection
# rules below.
self.add_excerpt_field = not kwargs.pop('no_excerpt_field', False)
super(SplitField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def contribute_to_class(self, cls, name):
if self.add_excerpt_field and not cls._meta.abstract:
excerpt_field = models.TextField(editable=False)
cls.add_to_class(_excerpt_field_name(name), excerpt_field)
super(SplitField, self).contribute_to_class(cls, name)
super().contribute_to_class(cls, name)
setattr(cls, self.name, SplitDescriptor(self))
def pre_save(self, model_instance, add):
value = super(SplitField, self).pre_save(model_instance, add)
value = super().pre_save(model_instance, add)
excerpt = get_excerpt(value.content)
setattr(model_instance, _excerpt_field_name(self.attname), excerpt)
return value.content
@ -262,7 +260,7 @@ class SplitField(models.TextField):
return value
def deconstruct(self):
name, path, args, kwargs = super(SplitField, self).deconstruct()
name, path, args, kwargs = super().deconstruct()
kwargs['no_excerpt_field'] = True
return name, path, args, kwargs
@ -310,4 +308,4 @@ class UUIDField(models.UUIDField):
kwargs.setdefault('primary_key', primary_key)
kwargs.setdefault('editable', editable)
kwargs.setdefault('default', default)
super(UUIDField, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)

View file

@ -1,4 +1,3 @@
from __future__ import unicode_literals
import django
from django.db import models
from django.db.models.fields.related import OneToOneField, OneToOneRel
@ -7,7 +6,6 @@ from django.db.models.query import ModelIterable
from django.core.exceptions import ObjectDoesNotExist
from django.db.models.constants import LOOKUP_SEP
from django.utils.six import string_types
from django.db import connection
from django.db.models.sql.datastructures import Join
@ -40,13 +38,12 @@ class InheritanceIterable(ModelIterable):
yield sub_obj
else:
for obj in iter:
yield obj
yield from iter
class InheritanceQuerySetMixin:
def __init__(self, *args, **kwargs):
super(InheritanceQuerySetMixin, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self._iterable_class = InheritanceIterable
def select_subclasses(self, *subclasses):
@ -65,7 +62,7 @@ class InheritanceQuerySetMixin:
if subclass is self.model:
continue
if not isinstance(subclass, string_types):
if not isinstance(subclass, (str,)):
subclass = self._get_ancestors_path(
subclass, levels=levels)
@ -73,7 +70,7 @@ class InheritanceQuerySetMixin:
verified_subclasses.append(subclass)
else:
raise ValueError(
'%r is not in the discovered subclasses, tried: %s' % (
'{!r} is not in the discovered subclasses, tried: {}'.format(
subclass, ', '.join(calculated_subclasses))
)
subclasses = verified_subclasses
@ -93,11 +90,11 @@ class InheritanceQuerySetMixin:
if hasattr(self, name):
kwargs[name] = getattr(self, name)
return super(InheritanceQuerySetMixin, self)._chain(**kwargs)
return super()._chain(**kwargs)
def _clone(self, klass=None, setup=False, **kwargs):
if django.VERSION >= (2, 0):
qs = super(InheritanceQuerySetMixin, self)._clone()
qs = super()._clone()
for name in ['subclasses', '_annotated']:
if hasattr(self, name):
setattr(qs, name, getattr(self, name))
@ -107,10 +104,10 @@ class InheritanceQuerySetMixin:
if hasattr(self, name):
kwargs[name] = getattr(self, name)
return super(InheritanceQuerySetMixin, self)._clone(**kwargs)
return super()._clone(**kwargs)
def annotate(self, *args, **kwargs):
qset = super(InheritanceQuerySetMixin, self).annotate(*args, **kwargs)
qset = super().annotate(*args, **kwargs)
qset._annotated = [a.default_alias for a in args] + list(kwargs.keys())
return qset
@ -152,7 +149,7 @@ class InheritanceQuerySetMixin:
"""
if not issubclass(model, self.model):
raise ValueError(
"%r is not a subclass of %r" % (model, self.model))
"{!r} is not a subclass of {!r}".format(model, self.model))
ancestry = []
# should be a OneToOneField or None
@ -208,7 +205,7 @@ class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet):
where_queries = []
for model in models:
where_queries.append('(' + ' AND '.join([
'"%s"."%s" IS NOT NULL' % (
'"{}"."{}" IS NOT NULL'.format(
model._meta.db_table,
field.attname, # Should this be something else?
) for field in model._meta.parents.values()
@ -244,14 +241,14 @@ class QueryManagerMixin:
else:
self._q = models.Q(**kwargs)
self._order_by = None
super(QueryManagerMixin, self).__init__()
super().__init__()
def order_by(self, *args):
self._order_by = args
return self
def get_queryset(self):
qs = super(QueryManagerMixin, self).get_queryset().filter(self._q)
qs = super().get_queryset().filter(self._q)
if self._order_by is not None:
return qs.order_by(*self._order_by)
return qs

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.core.exceptions import ImproperlyConfigured
from django.db import models, transaction, router
from django.db.models.signals import post_save, pre_save
@ -136,7 +134,7 @@ class SoftDeletableModel(models.Model):
self.is_removed = True
self.save(using=using)
else:
return super(SoftDeletableModel, self).delete(using=using, *args, **kwargs)
return super().delete(using=using, *args, **kwargs)
class UUIDModel(models.Model):
@ -170,7 +168,7 @@ class SaveSignalHandlingModel(models.Model):
self.signals_to_disable = signals_to_disable or []
super(SaveSignalHandlingModel, self).save(*args, **kwargs)
super().save(*args, **kwargs)
def save_base(self, raw=False, force_insert=False,
force_update=False, using=None, update_fields=None):

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from copy import deepcopy
import django
@ -20,7 +18,7 @@ class DescriptorMixin:
if field_name in instance._deferred_fields:
instance._deferred_fields.remove(field_name)
was_deferred = True
value = super(DescriptorMixin, self).__get__(instance, owner)
value = super().__get__(instance, owner)
if was_deferred:
self.tracker_instance.saved_data[field_name] = deepcopy(value)
return value
@ -125,7 +123,7 @@ class FieldInstanceTracker:
else:
fields = self.fields
return dict((f, self.get_field_value(f)) for f in fields)
return {f: self.get_field_value(f) for f in fields}
def has_changed(self, field):
"""Returns ``True`` if field has changed from currently saved value"""
@ -159,11 +157,11 @@ class FieldInstanceTracker:
def changed(self):
"""Returns dict of fields that changed since save (with old values)"""
return dict(
(field, self.previous(field))
return {
field: self.previous(field)
for field in self.fields
if self.has_changed(field)
)
}
def init_deferred_fields(self):
self.instance._deferred_fields = set()
@ -199,10 +197,10 @@ class FieldTracker:
def get_field_map(self, cls):
"""Returns dict mapping fields names to model attribute names"""
field_map = dict((field, field) for field in self.fields)
all_fields = dict((f.name, f.attname) for f in cls._meta.fields)
field_map.update(**dict((k, v) for (k, v) in all_fields.items()
if k in field_map))
field_map = {field: field for field in self.fields}
all_fields = {f.name: f.attname for f in cls._meta.fields}
field_map.update(**{k: v for (k, v) in all_fields.items()
if k in field_map})
return field_map
def contribute_to_class(self, cls, name):
@ -280,11 +278,11 @@ class ModelInstanceTracker(FieldInstanceTracker):
return {}
saved = self.saved_data.items()
current = self.current()
return dict((k, v) for k, v in saved if v != current[k])
return {k: v for k, v in saved if v != current[k]}
class ModelTracker(FieldTracker):
tracker_class = ModelInstanceTracker
def get_field_map(self, cls):
return dict((field, field) for field in self.fields)
return {field: field for field in self.fields}

View file

@ -1,4 +1,4 @@
pytest==4.5.0
pytest-django==3.4.7
psycopg2==2.7.6.1
psycopg2-binary==2.8.4
pytest-cov==2.7.1

View file

@ -42,11 +42,12 @@ setup(
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Framework :: Django',
'Framework :: Django :: 2.1',
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.1',
'Framework :: Django :: 2.2',
],
zip_safe=False,

View file

@ -1,13 +1,12 @@
import django
from django.db import models
from django.utils.six import with_metaclass, string_types
def mutable_from_db(value):
if value == '':
return None
try:
if isinstance(value, string_types):
if isinstance(value, (str,)):
return [int(i) for i in value.split(',')]
except ValueError:
pass
@ -18,7 +17,7 @@ def mutable_to_db(value):
if value is None:
return ''
if isinstance(value, list):
value = ','.join((str(i) for i in value))
value = ','.join(str(i) for i in value)
return str(value)
@ -30,5 +29,5 @@ class MutableField(models.TextField):
return mutable_from_db(value)
def get_db_prep_save(self, value, connection):
value = super(MutableField, self).get_db_prep_save(value, connection)
value = super().get_db_prep_save(value, connection)
return mutable_to_db(value)

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals, absolute_import
from model_utils.managers import SoftDeletableQuerySet, SoftDeletableManager

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals, absolute_import
import django
from django.db import models
from django.db.models.query_utils import DeferredAttribute
@ -48,7 +46,7 @@ class InheritanceManagerTestParent(models.Model):
objects = InheritanceManager()
def __str__(self):
return "%s(%s)" % (
return "{}({})".format(
self.__class__.__name__[len('InheritanceManagerTest'):],
self.pk,
)
@ -246,7 +244,7 @@ class Tracked(models.Model):
def save(self, *args, **kwargs):
""" No-op save() to ensure that FieldTracker.patch_save() works. """
super(Tracked, self).save(*args, **kwargs)
super().save(*args, **kwargs)
class TrackedFK(models.Model):
@ -399,7 +397,7 @@ class StringyDescriptor:
class CustomDescriptorField(models.IntegerField):
def contribute_to_class(self, cls, name, **kwargs):
super(CustomDescriptorField, self).contribute_to_class(cls, name, **kwargs)
super().contribute_to_class(cls, name, **kwargs)
setattr(cls, name, StringyDescriptor(name))

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.test import TestCase
from model_utils import Choices

View file

@ -1,4 +1,3 @@
from __future__ import unicode_literals
from unittest import skip
import django
from django.core.exceptions import FieldError
@ -414,7 +413,7 @@ class FieldTrackedModelMultiTests(FieldTrackerTestCase,
def test_pre_save_previous(self):
for tracker in self.trackers:
self.tracker = tracker
super(FieldTrackedModelMultiTests, self).test_pre_save_previous()
super().test_pre_save_previous()
def test_post_save_has_changed(self):
self.update_instance(name='retro', number=4)

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from datetime import datetime
from freezegun import freeze_time

View file

@ -1,6 +1,3 @@
from __future__ import unicode_literals
from django.utils.six import text_type
from django.test import TestCase
from tests.models import Article, SplitFieldAbstractParent
@ -15,7 +12,7 @@ class SplitFieldTests(TestCase):
title='example post', body=self.full_text)
def test_unicode_content(self):
self.assertEqual(text_type(self.post.body), self.full_text)
self.assertEqual(str(self.post.body), self.full_text)
def test_excerpt(self):
self.assertEqual(self.post.body.excerpt, self.excerpt)
@ -40,13 +37,13 @@ class SplitFieldTests(TestCase):
new_text = 'different\n\n<!-- split -->\n\nother'
self.post.body = new_text
self.post.save()
self.assertEqual(text_type(self.post.body), new_text)
self.assertEqual(str(self.post.body), new_text)
def test_assign_to_content(self):
new_text = 'different\n\n<!-- split -->\n\nother'
self.post.body.content = new_text
self.post.save()
self.assertEqual(text_type(self.post.body), new_text)
self.assertEqual(str(self.post.body), new_text)
def test_assign_to_excerpt(self):
with self.assertRaises(AttributeError):

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.test import TestCase
from model_utils.fields import StatusField

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
import uuid
from django.core.exceptions import ValidationError

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from unittest import skipIf
import django

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from unittest import skipUnless
import django
@ -27,16 +25,16 @@ class InheritanceManagerTests(TestCase):
return InheritanceManagerTestParent.objects
def test_normal(self):
children = set([
children = {
InheritanceManagerTestParent(pk=self.child1.pk),
InheritanceManagerTestParent(pk=self.child2.pk),
InheritanceManagerTestParent(pk=self.grandchild1.pk),
InheritanceManagerTestParent(pk=self.grandchild1_2.pk),
])
}
self.assertEqual(set(self.get_manager().all()), children)
def test_select_all_subclasses(self):
children = set([self.child1, self.child2])
children = {self.child1, self.child2}
children.add(self.grandchild1)
children.add(self.grandchild1_2)
self.assertEqual(
@ -53,12 +51,12 @@ class InheritanceManagerTests(TestCase):
self.get_manager().select_subclasses('user')
def test_select_specific_subclasses(self):
children = set([
children = {
self.child1,
InheritanceManagerTestParent(pk=self.child2.pk),
InheritanceManagerTestChild1(pk=self.grandchild1.pk),
InheritanceManagerTestChild1(pk=self.grandchild1_2.pk),
])
}
self.assertEqual(
set(
self.get_manager().select_subclasses(
@ -68,12 +66,12 @@ class InheritanceManagerTests(TestCase):
)
def test_select_specific_grandchildren(self):
children = set([
children = {
InheritanceManagerTestParent(pk=self.child1.pk),
InheritanceManagerTestParent(pk=self.child2.pk),
self.grandchild1,
InheritanceManagerTestParent(pk=self.grandchild1_2.pk),
])
}
self.assertEqual(
set(
self.get_manager().select_subclasses(
@ -84,12 +82,12 @@ class InheritanceManagerTests(TestCase):
)
def test_children_and_grandchildren(self):
children = set([
children = {
self.child1,
InheritanceManagerTestParent(pk=self.child2.pk),
self.grandchild1,
InheritanceManagerTestChild1(pk=self.grandchild1_2.pk),
])
}
self.assertEqual(
set(
self.get_manager().select_subclasses(
@ -463,14 +461,14 @@ class InheritanceManagerUsingModelsTests(TestCase):
results = InheritanceManagerTestParent.objects.instance_of(InheritanceManagerTestChild3, InheritanceManagerTestGrandChild1).select_subclasses()
self.assertEqual(set([child3, grandchild1]), set(results))
self.assertEqual({child3, grandchild1}, set(results))
def test_select_subclasses_interaction_with_instance_of(self):
child3 = InheritanceManagerTestChild3.objects.create()
results = InheritanceManagerTestParent.objects.select_subclasses(InheritanceManagerTestChild1).instance_of(InheritanceManagerTestChild3)
self.assertEqual(set([child3]), set(results))
self.assertEqual({child3}, set(results))

View file

@ -1,4 +1,3 @@
from django.test import TestCase
from tests.models import JoinItemForeignKey, BoxJoinModel

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.test import TestCase
from tests.models import Post

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.test import TestCase
from tests.models import CustomSoftDelete

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.db import models
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.core.management import call_command
from django.test import TestCase

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
import django
from django.test import TestCase

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.test import TestCase
from tests.models import SaveSignalHandlingTestModel

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.db.utils import ConnectionDoesNotExist
from django.test import TestCase

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from datetime import datetime, timedelta
from django.db import models

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from datetime import datetime, timedelta
from freezegun import freeze_time

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.test import TestCase
from tests.models import CustomUUIDModel, CustomNotPrimaryUUIDModel

View file

@ -2,13 +2,14 @@
envlist =
py37-django{202,201}
py36-django{111,202,201,trunk}
py38-django{202,201,trunk}
flake8
[testenv]
deps =
django111: Django>=1.11,<1.12
django202: Django>=2.2,<3.0
django201: Django>=2.1,<2.2
django111: Django==1.11.*
django202: Django==2.2.*
django201: Django==2.1.*
djangotrunk: https://github.com/django/django/archive/master.tar.gz
freezegun == 0.3.8
-rrequirements-test.txt