summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
authorMariusz Felisiak <felisiak.mariusz@gmail.com>2021-01-19 08:35:16 +0100
committerCarlton Gibson <carlton@noumenal.es>2021-02-10 10:20:54 +0100
commitec0ff406311de88f4e2a135d784363424fe602aa (patch)
treec1659b85ea145704a1b733d40a6a9a45e9332d0f /django
parent9c6ba876928fd20194ac3238dc06aeae66d7bd50 (diff)
Fixed #32355 -- Dropped support for Python 3.6 and 3.7
Diffstat (limited to 'django')
-rw-r--r--django/core/management/commands/compilemessages.py4
-rw-r--r--django/db/backends/postgresql/base.py7
-rw-r--r--django/db/backends/sqlite3/base.py16
-rw-r--r--django/db/backends/sqlite3/client.py8
-rw-r--r--django/db/migrations/questioner.py1
-rw-r--r--django/http/cookie.py3
-rw-r--r--django/http/request.py9
-rw-r--r--django/test/runner.py22
-rw-r--r--django/utils/autoreload.py8
-rw-r--r--django/utils/http.py74
-rw-r--r--django/utils/module_loading.py5
-rw-r--r--django/utils/version.py2
12 files changed, 23 insertions, 136 deletions
diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py
index cad24f8140..308fa8831b 100644
--- a/django/core/management/commands/compilemessages.py
+++ b/django/core/management/commands/compilemessages.py
@@ -154,9 +154,7 @@ class Command(BaseCommand):
self.has_errors = True
return
- # PY37: Remove str() when dropping support for PY37.
- # https://bugs.python.org/issue31961
- args = [self.program, *self.program_options, '-o', str(mo_path), str(po_path)]
+ args = [self.program, *self.program_options, '-o', mo_path, po_path]
futures.append(executor.submit(popen_wrapper, args))
for future in concurrent.futures.as_completed(futures):
diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
index 126f30b462..ad8d85da29 100644
--- a/django/db/backends/postgresql/base.py
+++ b/django/db/backends/postgresql/base.py
@@ -261,12 +261,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
# For now, it's here so that every use of "threading" is
# also async-compatible.
try:
- if hasattr(asyncio, 'current_task'):
- # Python 3.7 and up
- current_task = asyncio.current_task()
- else:
- # Python 3.6
- current_task = asyncio.Task.current_task()
+ current_task = asyncio.current_task()
except RuntimeError:
current_task = None
# Current task can be none even if the current_task call didn't error
diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
index f8e1def982..32d767878e 100644
--- a/django/db/backends/sqlite3/base.py
+++ b/django/db/backends/sqlite3/base.py
@@ -25,7 +25,6 @@ from django.utils.asyncio import async_unsafe
from django.utils.dateparse import parse_datetime, parse_time
from django.utils.duration import duration_microseconds
from django.utils.regex_helper import _lazy_re_compile
-from django.utils.version import PY38
from .client import DatabaseClient
from .creation import DatabaseCreation
@@ -180,9 +179,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
"settings.DATABASES is improperly configured. "
"Please supply the NAME value.")
kwargs = {
- # TODO: Remove str() when dropping support for PY36.
- # https://bugs.python.org/issue33496
- 'database': str(settings_dict['NAME']),
+ 'database': settings_dict['NAME'],
'detect_types': Database.PARSE_DECLTYPES | Database.PARSE_COLNAMES,
**settings_dict['OPTIONS'],
}
@@ -206,13 +203,10 @@ class DatabaseWrapper(BaseDatabaseWrapper):
@async_unsafe
def get_new_connection(self, conn_params):
conn = Database.connect(**conn_params)
- if PY38:
- create_deterministic_function = functools.partial(
- conn.create_function,
- deterministic=True,
- )
- else:
- create_deterministic_function = conn.create_function
+ create_deterministic_function = functools.partial(
+ conn.create_function,
+ deterministic=True,
+ )
create_deterministic_function('django_date_extract', 2, _sqlite_datetime_extract)
create_deterministic_function('django_date_trunc', 4, _sqlite_date_trunc)
create_deterministic_function('django_datetime_cast_date', 3, _sqlite_datetime_cast_date)
diff --git a/django/db/backends/sqlite3/client.py b/django/db/backends/sqlite3/client.py
index 59a2fe7f50..69b9568db3 100644
--- a/django/db/backends/sqlite3/client.py
+++ b/django/db/backends/sqlite3/client.py
@@ -6,11 +6,5 @@ class DatabaseClient(BaseDatabaseClient):
@classmethod
def settings_to_cmd_args_env(cls, settings_dict, parameters):
- args = [
- cls.executable_name,
- # TODO: Remove str() when dropping support for PY37. args
- # parameter accepts path-like objects on Windows since Python 3.8.
- str(settings_dict['NAME']),
- *parameters,
- ]
+ args = [cls.executable_name, settings_dict['NAME'], *parameters]
return args, None
diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py
index 9edb8180ef..216f2af6a1 100644
--- a/django/db/migrations/questioner.py
+++ b/django/db/migrations/questioner.py
@@ -44,7 +44,6 @@ class MigrationQuestioner:
except ImportError:
return self.defaults.get("ask_initial", False)
else:
- # getattr() needed on PY36 and older (replace with attribute access).
if getattr(migrations_module, "__file__", None):
filenames = os.listdir(os.path.dirname(migrations_module.__file__))
elif hasattr(migrations_module, "__path__"):
diff --git a/django/http/cookie.py b/django/http/cookie.py
index 5c418d7e35..b94d2b0386 100644
--- a/django/http/cookie.py
+++ b/django/http/cookie.py
@@ -3,9 +3,6 @@ from http import cookies
# For backwards compatibility in Django 2.1.
SimpleCookie = cookies.SimpleCookie
-# Add support for the SameSite attribute (obsolete when PY37 is unsupported).
-cookies.Morsel._reserved.setdefault('samesite', 'SameSite')
-
def parse_cookie(cookie):
"""
diff --git a/django/http/request.py b/django/http/request.py
index bdf9b508b1..79fc8350fd 100644
--- a/django/http/request.py
+++ b/django/http/request.py
@@ -18,19 +18,10 @@ from django.utils.datastructures import (
from django.utils.encoding import escape_uri_path, iri_to_uri
from django.utils.functional import cached_property
from django.utils.http import is_same_domain
-from django.utils.inspect import func_supports_parameter
from django.utils.regex_helper import _lazy_re_compile
from .multipartparser import parse_header
-# TODO: Remove when dropping support for PY37. inspect.signature() is used to
-# detect whether the max_num_fields argument is available as this security fix
-# was backported to Python 3.6.8 and 3.7.2, and may also have been applied by
-# downstream package maintainers to other versions in their repositories.
-if not func_supports_parameter(parse_qsl, 'max_num_fields'):
- from django.utils.http import parse_qsl
-
-
RAISE_ERROR = object()
host_validation_re = _lazy_re_compile(r"^([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9\.:]+\])(:\d+)?$")
diff --git a/django/test/runner.py b/django/test/runner.py
index 8e6c29b0b4..e14c6381eb 100644
--- a/django/test/runner.py
+++ b/django/test/runner.py
@@ -21,7 +21,6 @@ from django.test.utils import (
teardown_test_environment,
)
from django.utils.datastructures import OrderedSet
-from django.utils.version import PY37
try:
import ipdb as pdb
@@ -240,8 +239,8 @@ failure and get a correct traceback.
self.stop_if_failfast()
def addSubTest(self, test, subtest, err):
- # Follow Python 3.5's implementation of unittest.TestResult.addSubTest()
- # by not doing anything when a subtest is successful.
+ # Follow Python's implementation of unittest.TestResult.addSubTest() by
+ # not doing anything when a subtest is successful.
if err is not None:
# Call check_picklable() before check_subtest_picklable() since
# check_picklable() performs the tblib check.
@@ -540,15 +539,14 @@ class DiscoverRunner:
'Output timings, including database set up and total run time.'
),
)
- if PY37:
- parser.add_argument(
- '-k', action='append', dest='test_name_patterns',
- help=(
- 'Only run test methods and classes that match the pattern '
- 'or substring. Can be used multiple times. Same as '
- 'unittest -k option.'
- ),
- )
+ parser.add_argument(
+ '-k', action='append', dest='test_name_patterns',
+ help=(
+ 'Only run test methods and classes that match the pattern '
+ 'or substring. Can be used multiple times. Same as '
+ 'unittest -k option.'
+ ),
+ )
def setup_test_environment(self, **kwargs):
setup_test_environment(debug=self.debug_mode)
diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py
index faa3252c71..3847252632 100644
--- a/django/utils/autoreload.py
+++ b/django/utils/autoreload.py
@@ -231,15 +231,11 @@ def get_child_arguments():
exe_entrypoint = py_script.with_suffix('.exe')
if exe_entrypoint.exists():
# Should be executed directly, ignoring sys.executable.
- # TODO: Remove str() when dropping support for PY37.
- # args parameter accepts path-like on Windows from Python 3.8.
- return [str(exe_entrypoint), *sys.argv[1:]]
+ return [exe_entrypoint, *sys.argv[1:]]
script_entrypoint = py_script.with_name('%s-script.py' % py_script.name)
if script_entrypoint.exists():
# Should be executed as usual.
- # TODO: Remove str() when dropping support for PY37.
- # args parameter accepts path-like on Windows from Python 3.8.
- return [*args, str(script_entrypoint), *sys.argv[1:]]
+ return [*args, script_entrypoint, *sys.argv[1:]]
raise RuntimeError('Script %s does not exist.' % py_script)
else:
args += sys.argv
diff --git a/django/utils/http.py b/django/utils/http.py
index 962716eb00..5397bb8190 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -7,7 +7,7 @@ from binascii import Error as BinasciiError
from email.utils import formatdate
from urllib.parse import (
ParseResult, SplitResult, _coerce_args, _splitnetloc, _splitparams,
- scheme_chars, unquote, urlencode as original_urlencode, uses_params,
+ scheme_chars, urlencode as original_urlencode, uses_params,
)
from django.utils.datastructures import MultiValueDict
@@ -343,78 +343,6 @@ def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
(not scheme or scheme in valid_schemes))
-# TODO: Remove when dropping support for PY37.
-def parse_qsl(
- qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8',
- errors='replace', max_num_fields=None,
-):
- """
- Return a list of key/value tuples parsed from query string.
-
- Backport of urllib.parse.parse_qsl() from Python 3.8.
- Copyright (C) 2020 Python Software Foundation (see LICENSE.python).
-
- ----
-
- Parse a query given as a string argument.
-
- Arguments:
-
- qs: percent-encoded query string to be parsed
-
- keep_blank_values: flag indicating whether blank values in
- percent-encoded queries should be treated as blank strings. A
- true value indicates that blanks should be retained as blank
- strings. The default false value indicates that blank values
- are to be ignored and treated as if they were not included.
-
- strict_parsing: flag indicating what to do with parsing errors. If false
- (the default), errors are silently ignored. If true, errors raise a
- ValueError exception.
-
- encoding and errors: specify how to decode percent-encoded sequences
- into Unicode characters, as accepted by the bytes.decode() method.
-
- max_num_fields: int. If set, then throws a ValueError if there are more
- than n fields read by parse_qsl().
-
- Returns a list, as G-d intended.
- """
- qs, _coerce_result = _coerce_args(qs)
-
- # If max_num_fields is defined then check that the number of fields is less
- # than max_num_fields. This prevents a memory exhaustion DOS attack via
- # post bodies with many fields.
- if max_num_fields is not None:
- num_fields = 1 + qs.count('&') + qs.count(';')
- if max_num_fields < num_fields:
- raise ValueError('Max number of fields exceeded')
-
- pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
- r = []
- for name_value in pairs:
- if not name_value and not strict_parsing:
- continue
- nv = name_value.split('=', 1)
- if len(nv) != 2:
- if strict_parsing:
- raise ValueError("bad query field: %r" % (name_value,))
- # Handle case of a control-name with no equal sign.
- if keep_blank_values:
- nv.append('')
- else:
- continue
- if len(nv[1]) or keep_blank_values:
- name = nv[0].replace('+', ' ')
- name = unquote(name, encoding=encoding, errors=errors)
- name = _coerce_result(name)
- value = nv[1].replace('+', ' ')
- value = unquote(value, encoding=encoding, errors=errors)
- value = _coerce_result(value)
- r.append((name, value))
- return r
-
-
def escape_leading_slashes(url):
"""
If redirecting to an absolute path (two leading slashes), a slash must be
diff --git a/django/utils/module_loading.py b/django/utils/module_loading.py
index df8e65098d..9f58c06856 100644
--- a/django/utils/module_loading.py
+++ b/django/utils/module_loading.py
@@ -72,10 +72,9 @@ 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 (ModuleNotFoundError, AttributeError):
+ except ModuleNotFoundError:
# 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.
+ # ModuleNotFoundError.
return False
diff --git a/django/utils/version.py b/django/utils/version.py
index 4b26586b36..68708df7aa 100644
--- a/django/utils/version.py
+++ b/django/utils/version.py
@@ -9,8 +9,6 @@ from distutils.version import LooseVersion
# or later". So that third-party apps can use these values, each constant
# should remain as long as the oldest supported Django version supports that
# Python version.
-PY36 = sys.version_info >= (3, 6)
-PY37 = sys.version_info >= (3, 7)
PY38 = sys.version_info >= (3, 8)
PY39 = sys.version_info >= (3, 9)