diff options
| author | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2021-01-19 08:35:16 +0100 |
|---|---|---|
| committer | Carlton Gibson <carlton@noumenal.es> | 2021-02-10 10:20:54 +0100 |
| commit | ec0ff406311de88f4e2a135d784363424fe602aa (patch) | |
| tree | c1659b85ea145704a1b733d40a6a9a45e9332d0f /django | |
| parent | 9c6ba876928fd20194ac3238dc06aeae66d7bd50 (diff) | |
Fixed #32355 -- Dropped support for Python 3.6 and 3.7
Diffstat (limited to 'django')
| -rw-r--r-- | django/core/management/commands/compilemessages.py | 4 | ||||
| -rw-r--r-- | django/db/backends/postgresql/base.py | 7 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/base.py | 16 | ||||
| -rw-r--r-- | django/db/backends/sqlite3/client.py | 8 | ||||
| -rw-r--r-- | django/db/migrations/questioner.py | 1 | ||||
| -rw-r--r-- | django/http/cookie.py | 3 | ||||
| -rw-r--r-- | django/http/request.py | 9 | ||||
| -rw-r--r-- | django/test/runner.py | 22 | ||||
| -rw-r--r-- | django/utils/autoreload.py | 8 | ||||
| -rw-r--r-- | django/utils/http.py | 74 | ||||
| -rw-r--r-- | django/utils/module_loading.py | 5 | ||||
| -rw-r--r-- | django/utils/version.py | 2 |
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) |
