Skip to content

Commit

Permalink
Fixed #30116 -- Dropped support for Python 3.5.
Browse files Browse the repository at this point in the history
  • Loading branch information
timgraham committed Jan 30, 2019
1 parent 5a5c77d commit 7e6b214
Show file tree
Hide file tree
Showing 23 changed files with 25 additions and 141 deletions.
2 changes: 1 addition & 1 deletion INSTALL
@@ -1,6 +1,6 @@
Thanks for downloading Django.

To install it, make sure you have Python 3.5 or greater installed. Then run
To install it, make sure you have Python 3.6 or greater installed. Then run
this command from the command prompt:

python setup.py install
Expand Down
6 changes: 2 additions & 4 deletions django/core/checks/model_checks.py
Expand Up @@ -77,10 +77,8 @@ def extract_operation(obj):
"""
operation, args, keywords = obj, [], {}
while hasattr(operation, 'func'):
# The or clauses are redundant but work around a bug (#25945) in
# functools.partial in Python <= 3.5.1.
args.extend(getattr(operation, 'args', []) or [])
keywords.update(getattr(operation, 'keywords', {}) or {})
args.extend(getattr(operation, 'args', []))
keywords.update(getattr(operation, 'keywords', {}))
operation = operation.func
return operation, args, keywords

Expand Down
4 changes: 0 additions & 4 deletions django/db/backends/base/features.py
Expand Up @@ -160,10 +160,6 @@ class BaseDatabaseFeatures:
# Support for the DISTINCT ON clause
can_distinct_on_fields = False

# Does the backend decide to commit before SAVEPOINT statements
# when autocommit is disabled? https://bugs.python.org/issue8145#msg109965
autocommits_when_autocommit_is_off = False

# Does the backend prevent running SQL queries in broken transactions?
atomic_transactions = True

Expand Down
3 changes: 0 additions & 3 deletions django/db/backends/sqlite3/features.py
@@ -1,5 +1,3 @@
import sys

from django.db.backends.base.features import BaseDatabaseFeatures

from .base import Database
Expand All @@ -15,7 +13,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_timezones = False
max_query_params = 999
supports_mixed_date_datetime_comparisons = False
autocommits_when_autocommit_is_off = sys.version_info < (3, 6)
can_introspect_autofield = True
can_introspect_decimal_field = False
can_introspect_duration_field = False
Expand Down
8 changes: 0 additions & 8 deletions django/db/transaction.py
Expand Up @@ -173,14 +173,6 @@ def __enter__(self):
connection.commit_on_exit = True
connection.needs_rollback = False
if not connection.get_autocommit():
# sqlite3 in Python < 3.6 doesn't handle transactions and
# savepoints properly when autocommit is off.
# Turning autocommit back on isn't an option; it would trigger
# a premature commit. Give up if that happens.
if connection.features.autocommits_when_autocommit_is_off:
raise TransactionManagementError(
"Your database backend doesn't behave properly when "
"autocommit is off. Turn it on before using 'atomic'.")
# Pretend we're already in an atomic block to bypass the code
# that disables autocommit to enter a transaction, and make a
# note to deal with this case in __exit__.
Expand Down
29 changes: 3 additions & 26 deletions django/utils/functional.py
Expand Up @@ -3,8 +3,6 @@
import operator
from functools import total_ordering, wraps

from django.utils.version import PY36, get_docs_version


# You can't trivially replace this with `functools.partial` because this binds
# to classes and returns bound instances, whereas functools.partial (on
Expand All @@ -22,8 +20,8 @@ class cached_property:
A cached property can be made out of an existing method:
(e.g. ``url = cached_property(get_absolute_url)``).
On Python < 3.6, the optional ``name`` argument must be provided, e.g.
``url = cached_property(get_absolute_url, name='url')``.
The optional ``name`` argument is obsolete as of Python 3.6 and will be
deprecated in Django 4.0 (#30127).
"""
name = None

Expand All @@ -34,29 +32,8 @@ def func(instance):
'__set_name__() on it.'
)

@staticmethod
def _is_mangled(name):
return name.startswith('__') and not name.endswith('__')

def __init__(self, func, name=None):
if PY36:
self.real_func = func
else:
func_name = func.__name__
name = name or func_name
if not (isinstance(name, str) and name.isidentifier()):
raise ValueError(
"%r can't be used as the name of a cached_property." % name,
)
if self._is_mangled(name):
raise ValueError(
'cached_property does not work with mangled methods on '
'Python < 3.6 without the appropriate `name` argument. See '
'https://docs.djangoproject.com/en/%s/ref/utils/'
'#cached-property-mangled-name' % get_docs_version(),
)
self.name = name
self.func = func
self.real_func = func
self.__doc__ = getattr(func, '__doc__')

def __set_name__(self, owner, name):
Expand Down
9 changes: 4 additions & 5 deletions django/utils/module_loading.py
Expand Up @@ -72,11 +72,10 @@ def module_has_submodule(package, module_name):
full_module_name = package_name + '.' + module_name
try:
return importlib_find(full_module_name, package_path) is not None
except (ImportError, AttributeError):
# When module_name is an invalid dotted path, Python raises ImportError
# (or ModuleNotFoundError in Python 3.6+). AttributeError may be raised
except (ModuleNotFoundError, AttributeError):
# When module_name is an invalid dotted path, Python raises
# ModuleNotFoundError. AttributeError is raised on PY36 (fixed in PY37)
# if the penultimate part of the path is not a package.
# (https://bugs.python.org/issue30436)
return False


Expand All @@ -87,7 +86,7 @@ def module_dir(module):
Raise ValueError otherwise, e.g. for namespace packages that are split
over several directories.
"""
# Convert to list because _NamespacePath does not support indexing on 3.3.
# Convert to list because _NamespacePath does not support indexing.
paths = list(getattr(module, '__path__', []))
if len(paths) == 1:
return paths[0]
Expand Down
6 changes: 3 additions & 3 deletions docs/howto/windows.txt
Expand Up @@ -2,7 +2,7 @@
How to install Django on Windows
================================

This document will guide you through installing Python 3.5 and Django on
This document will guide you through installing Python 3.7 and Django on
Windows. It also provides instructions for installing `virtualenv`_ and
`virtualenvwrapper`_, which make it easier to work on Python projects. This is
meant as a beginner's guide for users working on Django projects and does not
Expand All @@ -17,12 +17,12 @@ Install Python
==============

Django is a Python web framework, thus requiring Python to be installed on your
machine. At the time of writing, Python 3.5 is the latest version.
machine. At the time of writing, Python 3.7 is the latest version.

To install Python on your machine go to https://python.org/downloads/. The
website should offer you a download button for the latest Python version.
Download the executable installer and run it. Check the box next to ``Add
Python 3.5 to PATH`` and then click ``Install Now``.
Python 3.7 to PATH`` and then click ``Install Now``.

After installation, open the command prompt and check that the Python version
matches the version you installed by executing::
Expand Down
6 changes: 3 additions & 3 deletions docs/internals/contributing/writing-code/unit-tests.txt
Expand Up @@ -90,12 +90,12 @@ In addition to the default environments, ``tox`` supports running unit tests
for other versions of Python and other database backends. Since Django's test
suite doesn't bundle a settings file for database backends other than SQLite,
however, you must :ref:`create and provide your own test settings
<running-unit-tests-settings>`. For example, to run the tests on Python 3.5
<running-unit-tests-settings>`. For example, to run the tests on Python 3.7
using PostgreSQL::

$ tox -e py35-postgres -- --settings=my_postgres_settings
$ tox -e py37-postgres -- --settings=my_postgres_settings

This command sets up a Python 3.5 virtual environment, installs Django's
This command sets up a Python 3.7 virtual environment, installs Django's
test suite dependencies (including those for PostgreSQL), and calls
``runtests.py`` with the supplied arguments (in this case,
``--settings=my_postgres_settings``).
Expand Down
2 changes: 1 addition & 1 deletion docs/intro/reusable-apps.txt
Expand Up @@ -219,8 +219,8 @@ this. For a small app like polls, this process isn't too difficult.
'License :: OSI Approved :: BSD License', # example license
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
Expand Down
2 changes: 1 addition & 1 deletion docs/intro/tutorial01.txt
Expand Up @@ -23,7 +23,7 @@ in a shell prompt (indicated by the $ prefix):
If Django is installed, you should see the version of your installation. If it
isn't, you'll get an error telling "No module named django".

This tutorial is written for Django |version|, which supports Python 3.5 and
This tutorial is written for Django |version|, which supports Python 3.6 and
later. If the Django version doesn't match, you can refer to the tutorial for
your version of Django by using the version switcher at the bottom right corner
of this page, or update Django to the newest version. If you're using an older
Expand Down
12 changes: 0 additions & 12 deletions docs/ref/utils.txt
Expand Up @@ -514,18 +514,6 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004
z = person.friends # does not call
x is z # is True

.. warning::

.. _cached-property-mangled-name:

On Python < 3.6, ``cached_property`` doesn't work properly with a
mangled__ name unless it's passed a ``name`` of the form
``_Class__attribute``::

__friends = cached_property(get_friends, name='_Person__friends')

__ https://docs.python.org/faq/programming.html#i-try-to-use-spam-and-i-get-an-error-about-someclassname-spam

.. function:: keep_lazy(func, *resultclasses)

Django offers many utility functions (particularly in ``django.utils``)
Expand Down
17 changes: 0 additions & 17 deletions docs/topics/testing/tools.txt
Expand Up @@ -1329,23 +1329,6 @@ The decorator can also be applied to test case classes::
decorator. For a given class, :func:`~django.test.modify_settings` is
always applied after :func:`~django.test.override_settings`.

.. admonition:: Considerations with Python 3.5

If using Python 3.5 (or older, if using an older version of Django), avoid
mixing ``remove`` with ``append`` and ``prepend`` in
:func:`~django.test.modify_settings`. In some cases it matters whether a
value is first added and then removed or vice versa, and dictionary key
order isn't preserved until Python 3.6. Instead, apply the decorator twice
to guarantee the order of operations. For example, to ensure that
``SessionMiddleware`` appears first in ``MIDDLEWARE``::

@modify_settings(MIDDLEWARE={
'remove': ['django.contrib.sessions.middleware.SessionMiddleware'],
)
@modify_settings(MIDDLEWARE={
'prepend': ['django.contrib.sessions.middleware.SessionMiddleware'],
})

.. warning::

The settings file contains some settings that are only consulted during
Expand Down
3 changes: 1 addition & 2 deletions setup.py
Expand Up @@ -5,7 +5,7 @@
from setuptools import find_packages, setup

CURRENT_PYTHON = sys.version_info[:2]
REQUIRED_PYTHON = (3, 5)
REQUIRED_PYTHON = (3, 6)

# This check and everything above must remain compatible with Python 2.7.
if CURRENT_PYTHON < REQUIRED_PYTHON:
Expand Down Expand Up @@ -98,7 +98,6 @@ def read(fname):
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3 :: Only',
Expand Down
3 changes: 1 addition & 2 deletions tests/admin_scripts/tests.py
Expand Up @@ -32,7 +32,6 @@
from django.test import (
LiveServerTestCase, SimpleTestCase, TestCase, override_settings,
)
from django.utils.version import PY36

custom_templates_dir = os.path.join(os.path.dirname(__file__), 'custom_templates')

Expand Down Expand Up @@ -1145,7 +1144,7 @@ def test_nonexistent_app(self):
args = ['check']
out, err = self.run_manage(args)
self.assertNoOutput(out)
self.assertOutput(err, 'ModuleNotFoundError' if PY36 else 'ImportError')
self.assertOutput(err, 'ModuleNotFoundError')
self.assertOutput(err, 'No module named')
self.assertOutput(err, 'admin_scriptz')

Expand Down
5 changes: 1 addition & 4 deletions tests/mail/tests.py
Expand Up @@ -1211,10 +1211,7 @@ class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):

def __init__(self, *args, **kwargs):
threading.Thread.__init__(self)
# New kwarg added in Python 3.5; default switching to False in 3.6.
# Setting a value only silences a deprecation warning in Python 3.5.
kwargs['decode_data'] = True
smtpd.SMTPServer.__init__(self, *args, **kwargs)
smtpd.SMTPServer.__init__(self, *args, decode_data=True, **kwargs)
self._sink = []
self.active = False
self.active_lock = threading.Lock()
Expand Down
6 changes: 1 addition & 5 deletions tests/migrations/test_writer.py
Expand Up @@ -22,7 +22,6 @@
from django.utils.functional import SimpleLazyObject
from django.utils.timezone import get_default_timezone, get_fixed_timezone, utc
from django.utils.translation import gettext_lazy as _
from django.utils.version import PY36

from .models import FoodManager, FoodQuerySet

Expand Down Expand Up @@ -413,10 +412,7 @@ def test_serialize_class_based_validators(self):
# Test a string regex with flag
validator = RegexValidator(r'^[0-9]+$', flags=re.S)
string = MigrationWriter.serialize(validator)[0]
if PY36:
self.assertEqual(string, "django.core.validators.RegexValidator('^[0-9]+$', flags=re.RegexFlag(16))")
else:
self.assertEqual(string, "django.core.validators.RegexValidator('^[0-9]+$', flags=16)")
self.assertEqual(string, "django.core.validators.RegexValidator('^[0-9]+$', flags=re.RegexFlag(16))")
self.serialize_round_trip(validator)

# Test message and code
Expand Down
4 changes: 0 additions & 4 deletions tests/model_inheritance/tests.py
@@ -1,11 +1,9 @@
import unittest
from operator import attrgetter

from django.core.exceptions import FieldError, ValidationError
from django.db import connection, models
from django.test import SimpleTestCase, TestCase
from django.test.utils import CaptureQueriesContext, isolate_apps
from django.utils.version import PY36

from .models import (
Base, Chef, CommonInfo, GrandChild, GrandParent, ItalianRestaurant,
Expand Down Expand Up @@ -176,7 +174,6 @@ class C(B):

self.assertIs(C._meta.parents[A], C._meta.get_field('a'))

@unittest.skipUnless(PY36, 'init_subclass is new in Python 3.6')
@isolate_apps('model_inheritance')
def test_init_subclass(self):
saved_kwargs = {}
Expand All @@ -193,7 +190,6 @@ class B(A, **kwargs):

self.assertEqual(saved_kwargs, kwargs)

@unittest.skipUnless(PY36, '__set_name__ is new in Python 3.6')
@isolate_apps('model_inheritance')
def test_set_name(self):
class ClassAttr:
Expand Down
2 changes: 1 addition & 1 deletion tests/runtests.py
Expand Up @@ -234,7 +234,7 @@ def teardown(state):
# Discard the multiprocessing.util finalizer that tries to remove a
# temporary directory that's already removed by this script's
# atexit.register(shutil.rmtree, TMPDIR) handler. Prevents
# FileNotFoundError at the end of a test run on Python 3.6+ (#27890).
# FileNotFoundError at the end of a test run (#27890).
from multiprocessing.util import _finalizer_registry
_finalizer_registry.pop((-100, 0), None)

Expand Down
2 changes: 0 additions & 2 deletions tests/transactions/tests.py
Expand Up @@ -228,7 +228,6 @@ def tearDown(self):
self.atomic.__exit__(*sys.exc_info())


@skipIfDBFeature('autocommits_when_autocommit_is_off')
class AtomicWithoutAutocommitTests(AtomicTests):
"""All basic tests for atomic should also pass when autocommit is turned off."""

Expand Down Expand Up @@ -480,7 +479,6 @@ def test_mark_for_rollback_on_error_in_autocommit(self):
Reporter.objects.create()


@skipIfDBFeature('autocommits_when_autocommit_is_off')
class NonAutocommitTests(TransactionTestCase):

available_apps = []
Expand Down

0 comments on commit 7e6b214

Please sign in to comment.