Fixed #34233 -- Dropped support for Python 3.8 and 3.9.

This commit is contained in:
Mariusz Felisiak 2023-01-18 09:46:01 +01:00 committed by GitHub
parent d547171183
commit 3bbe22dafc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 51 additions and 327 deletions

View File

@ -16,8 +16,6 @@ jobs:
strategy:
matrix:
python-version:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12-dev'

View File

@ -1,6 +1,6 @@
Thanks for downloading Django.
To install it, make sure you have Python 3.8 or greater installed. Then run
To install it, make sure you have Python 3.10 or greater installed. Then run
this command from the command prompt:
python -m pip install .

View File

@ -14,7 +14,6 @@ from django.utils.crypto import (
RANDOM_STRING_CHARS,
constant_time_compare,
get_random_string,
md5,
pbkdf2,
)
from django.utils.deprecation import RemovedInDjango51Warning
@ -684,7 +683,7 @@ class MD5PasswordHasher(BasePasswordHasher):
def encode(self, password, salt):
self._check_encode_args(password, salt)
hash = md5((salt + password).encode()).hexdigest()
hash = hashlib.md5((salt + password).encode()).hexdigest()
return "%s$%s$%s" % (self.algorithm, salt, hash)
def decode(self, encoded):
@ -799,7 +798,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
def encode(self, password, salt):
if salt != "":
raise ValueError("salt must be empty.")
return md5(password.encode()).hexdigest()
return hashlib.md5(password.encode()).hexdigest()
def decode(self, encoded):
return {

View File

@ -2,6 +2,7 @@ import json
import os
import posixpath
import re
from hashlib import md5
from urllib.parse import unquote, urldefrag, urlsplit, urlunsplit
from django.conf import STATICFILES_STORAGE_ALIAS, settings
@ -9,7 +10,6 @@ from django.contrib.staticfiles.utils import check_settings, matches_patterns
from django.core.exceptions import ImproperlyConfigured
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage, storages
from django.utils.crypto import md5
from django.utils.functional import LazyObject

View File

@ -6,11 +6,11 @@ import random
import tempfile
import time
import zlib
from hashlib import md5
from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
from django.core.files import locks
from django.core.files.move import file_move_safe
from django.utils.crypto import md5
class FileBasedCache(BaseCache):

View File

@ -1,4 +1,4 @@
from django.utils.crypto import md5
from hashlib import md5
TEMPLATE_FRAGMENT_KEY_TEMPLATE = "template.cache.%s.%s"

View File

@ -2,6 +2,7 @@ import logging
import sys
import tempfile
import traceback
from contextlib import aclosing
from asgiref.sync import ThreadSensitiveContext, sync_to_async
@ -19,7 +20,6 @@ from django.http import (
parse_cookie,
)
from django.urls import set_script_prefix
from django.utils.asyncio import aclosing
from django.utils.functional import cached_property
logger = logging.getLogger("django.request")

View File

@ -275,16 +275,6 @@ def validate_ipv4_address(value):
raise ValidationError(
_("Enter a valid IPv4 address."), code="invalid", params={"value": value}
)
else:
# Leading zeros are forbidden to avoid ambiguity with the octal
# notation. This restriction is included in Python 3.9.5+.
# TODO: Remove when dropping support for PY39.
if any(octet != "0" and octet[0] == "0" for octet in value.split(".")):
raise ValidationError(
_("Enter a valid IPv4 address."),
code="invalid",
params={"value": value},
)
def validate_ipv6_address(value):

View File

@ -5,22 +5,17 @@ import logging
import threading
import time
import warnings
import zoneinfo
from collections import deque
from contextlib import contextmanager
from django.db.backends.utils import debug_transaction
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import DEFAULT_DB_ALIAS, DatabaseError, NotSupportedError
from django.db.backends import utils
from django.db.backends.base.validation import BaseDatabaseValidation
from django.db.backends.signals import connection_created
from django.db.backends.utils import debug_transaction
from django.db.transaction import TransactionManagementError
from django.db.utils import DatabaseErrorWrapper
from django.utils.asyncio import async_unsafe

View File

@ -4,8 +4,9 @@ Implementations of SQL functions for SQLite.
import functools
import random
import statistics
import zoneinfo
from datetime import timedelta
from hashlib import sha1, sha224, sha256, sha384, sha512
from hashlib import md5, sha1, sha224, sha256, sha384, sha512
from math import (
acos,
asin,
@ -32,14 +33,8 @@ from django.db.backends.utils import (
typecast_timestamp,
)
from django.utils import timezone
from django.utils.crypto import md5
from django.utils.duration import duration_microseconds
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
def register(connection):
create_deterministic_function = functools.partial(

View File

@ -4,9 +4,9 @@ import functools
import logging
import time
from contextlib import contextmanager
from hashlib import md5
from django.db import NotSupportedError
from django.utils.crypto import md5
from django.utils.dateparse import parse_time
logger = logging.getLogger("django.db.backends")

View File

@ -1,12 +1,8 @@
import zoneinfo
from datetime import datetime
from datetime import timezone as datetime_timezone
from datetime import tzinfo
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.template import Library, Node, TemplateSyntaxError
from django.utils import timezone

View File

@ -1,6 +1,7 @@
import argparse
import ctypes
import faulthandler
import hashlib
import io
import itertools
import logging
@ -27,7 +28,6 @@ from django.test.utils import setup_databases as _setup_databases
from django.test.utils import setup_test_environment
from django.test.utils import teardown_databases as _teardown_databases
from django.test.utils import teardown_test_environment
from django.utils.crypto import new_hash
from django.utils.datastructures import OrderedSet
try:
@ -580,7 +580,7 @@ class Shuffler:
@classmethod
def _hash_text(cls, text):
h = new_hash(cls.hash_algorithm, usedforsecurity=False)
h = hashlib.new(cls.hash_algorithm, usedforsecurity=False)
h.update(text.encode("utf-8"))
return h.hexdigest()

View File

@ -53,7 +53,6 @@ from django.test.utils import (
)
from django.utils.deprecation import RemovedInDjango51Warning
from django.utils.functional import classproperty
from django.utils.version import PY310
from django.views.static import serve
logger = logging.getLogger("django.test")
@ -795,32 +794,6 @@ class SimpleTestCase(unittest.TestCase):
**kwargs,
)
# A similar method is available in Python 3.10+.
if not PY310:
@contextmanager
def assertNoLogs(self, logger, level=None):
"""
Assert no messages are logged on the logger, with at least the
given level.
"""
if isinstance(level, int):
level = logging.getLevelName(level)
elif level is None:
level = "INFO"
try:
with self.assertLogs(logger, level) as cm:
yield
except AssertionError as e:
msg = e.args[0]
expected_msg = (
f"no logs of level {level} or higher triggered on {logger}"
)
if msg != expected_msg:
raise e
else:
self.fail(f"Unexpected logs found: {cm.output!r}")
def assertFieldOutput(
self,
fieldclass,

View File

@ -37,28 +37,3 @@ def async_unsafe(message):
return decorator(func)
else:
return decorator
try:
from contextlib import aclosing
except ImportError:
# TODO: Remove when dropping support for PY39.
from contextlib import AbstractAsyncContextManager
# Backport of contextlib.aclosing() from Python 3.10. Copyright (C) Python
# Software Foundation (see LICENSE.python).
class aclosing(AbstractAsyncContextManager):
"""
Async context manager for safely finalizing an asynchronously
cleaned-up resource such as an async generator, calling its
``aclose()`` method.
"""
def __init__(self, thing):
self.thing = thing
async def __aenter__(self):
return self.thing
async def __aexit__(self, *exc_info):
await self.thing.aclose()

View File

@ -16,11 +16,11 @@ An example: i18n middleware would need to distinguish caches by the
"""
import time
from collections import defaultdict
from hashlib import md5
from django.conf import settings
from django.core.cache import caches
from django.http import HttpResponse, HttpResponseNotModified
from django.utils.crypto import md5
from django.utils.http import http_date, parse_etags, parse_http_date_safe, quote_etag
from django.utils.log import log_response
from django.utils.regex_helper import _lazy_re_compile

View File

@ -7,7 +7,6 @@ import secrets
from django.conf import settings
from django.utils.encoding import force_bytes
from django.utils.inspect import func_supports_parameter
class InvalidAlgorithm(ValueError):
@ -75,19 +74,3 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
password = force_bytes(password)
salt = force_bytes(salt)
return hashlib.pbkdf2_hmac(digest().name, password, salt, iterations, dklen)
# TODO: Remove when dropping support for PY38. inspect.signature() is used to
# detect whether the usedforsecurity argument is available as this fix may also
# have been applied by downstream package maintainers to other versions in
# their repositories.
if func_supports_parameter(hashlib.md5, "usedforsecurity"):
md5 = hashlib.md5
new_hash = hashlib.new
else:
def md5(data=b"", *, usedforsecurity=True):
return hashlib.md5(data)
def new_hash(hash_algorithm, *, usedforsecurity=True):
return hashlib.new(hash_algorithm)

View File

@ -4,18 +4,9 @@ import re
import unicodedata
from binascii import Error as BinasciiError
from email.utils import formatdate
from urllib.parse import (
ParseResult,
SplitResult,
_coerce_args,
_splitnetloc,
_splitparams,
quote,
scheme_chars,
unquote,
)
from urllib.parse import quote, unquote
from urllib.parse import urlencode as original_urlencode
from urllib.parse import uses_params
from urllib.parse import urlparse
from django.utils.datastructures import MultiValueDict
from django.utils.regex_helper import _lazy_re_compile
@ -47,10 +38,6 @@ ASCTIME_DATE = _lazy_re_compile(r"^\w{3} %s %s %s %s$" % (__M, __D2, __T, __Y))
RFC3986_GENDELIMS = ":/?#[]@"
RFC3986_SUBDELIMS = "!$&'()*+,;="
# TODO: Remove when dropping support for PY38.
# Unsafe bytes to be removed per WHATWG spec.
_UNSAFE_URL_BYTES_TO_REMOVE = ["\t", "\r", "\n"]
def urlencode(query, doseq=False):
"""
@ -283,74 +270,13 @@ def url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
)
# TODO: Remove when dropping support for PY38.
# Copied from urllib.parse.urlparse() but uses fixed urlsplit() function.
def _urlparse(url, scheme="", allow_fragments=True):
"""Parse a URL into 6 components:
<scheme>://<netloc>/<path>;<params>?<query>#<fragment>
Return a 6-tuple: (scheme, netloc, path, params, query, fragment).
Note that we don't break the components up in smaller bits
(e.g. netloc is a single string) and we don't expand % escapes."""
url, scheme, _coerce_result = _coerce_args(url, scheme)
splitresult = _urlsplit(url, scheme, allow_fragments)
scheme, netloc, url, query, fragment = splitresult
if scheme in uses_params and ";" in url:
url, params = _splitparams(url)
else:
params = ""
result = ParseResult(scheme, netloc, url, params, query, fragment)
return _coerce_result(result)
# TODO: Remove when dropping support for PY38.
def _remove_unsafe_bytes_from_url(url):
for b in _UNSAFE_URL_BYTES_TO_REMOVE:
url = url.replace(b, "")
return url
# TODO: Remove when dropping support for PY38.
# Backport of urllib.parse.urlsplit() from Python 3.9.
def _urlsplit(url, scheme="", allow_fragments=True):
"""Parse a URL into 5 components:
<scheme>://<netloc>/<path>?<query>#<fragment>
Return a 5-tuple: (scheme, netloc, path, query, fragment).
Note that we don't break the components up in smaller bits
(e.g. netloc is a single string) and we don't expand % escapes."""
url, scheme, _coerce_result = _coerce_args(url, scheme)
url = _remove_unsafe_bytes_from_url(url)
scheme = _remove_unsafe_bytes_from_url(scheme)
netloc = query = fragment = ""
i = url.find(":")
if i > 0:
for c in url[:i]:
if c not in scheme_chars:
break
else:
scheme, url = url[:i].lower(), url[i + 1 :]
if url[:2] == "//":
netloc, url = _splitnetloc(url, 2)
if ("[" in netloc and "]" not in netloc) or (
"]" in netloc and "[" not in netloc
):
raise ValueError("Invalid IPv6 URL")
if allow_fragments and "#" in url:
url, fragment = url.split("#", 1)
if "?" in url:
url, query = url.split("?", 1)
v = SplitResult(scheme, netloc, url, query, fragment)
return _coerce_result(v)
def _url_has_allowed_host_and_scheme(url, allowed_hosts, require_https=False):
# Chrome considers any URL with more than two slashes to be absolute, but
# urlparse is not so flexible. Treat any url with three slashes as unsafe.
if url.startswith("///"):
return False
try:
url_info = _urlparse(url)
url_info = urlparse(url)
except ValueError: # e.g. invalid IPv6 addresses
return False
# Forbid URLs like http:///example.com - with a scheme, but without a hostname.

View File

@ -3,12 +3,7 @@ Timezone-related classes and functions.
"""
import functools
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
import zoneinfo
from contextlib import ContextDecorator
from datetime import datetime, timedelta, timezone, tzinfo

View File

@ -4,7 +4,7 @@ How to install Django on Windows
.. highlight:: doscon
This document will guide you through installing Python 3.8 and Django on
This document will guide you through installing Python 3.11 and Django on
Windows. It also provides instructions for setting up a virtual environment,
which makes it easier to work on Python projects. This is meant as a beginner's
guide for users working on Django projects and does not reflect how Django
@ -20,7 +20,7 @@ Install Python
==============
Django is a Python web framework, thus requiring Python to be installed on your
machine. At the time of writing, Python 3.8 is the latest version.
machine. At the time of writing, Python 3.11 is the latest version.
To install Python on your machine go to https://www.python.org/downloads/. The
website should offer you a download button for the latest Python version.

View File

@ -91,14 +91,14 @@ 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.9
<running-unit-tests-settings>`. For example, to run the tests on Python 3.10
using PostgreSQL:
.. console::
$ tox -e py39-postgres -- --settings=my_postgres_settings
$ tox -e py310-postgres -- --settings=my_postgres_settings
This command sets up a Python 3.9 virtual environment, installs Django's
This command sets up a Python 3.10 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``).
@ -113,14 +113,14 @@ above:
.. code-block:: console
$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py39-postgres
$ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py310-postgres
Windows users should use:
.. code-block:: doscon
...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings
...\> tox -e py39-postgres
...\> tox -e py310-postgres
Running the JavaScript tests
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -220,15 +220,15 @@ this. For a small app like polls, this process isn't too difficult.
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Topic :: Internet :: WWW/HTTP
Topic :: Internet :: WWW/HTTP :: Dynamic Content
[options]
include_package_data = true
packages = find:
python_requires = >=3.8
python_requires = >=3.10
install_requires =
Django >= X.Y # Replace "X.Y" as appropriate

View File

@ -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.8 and
This tutorial is written for Django |version|, which supports Python 3.10 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

View File

@ -21,6 +21,8 @@ Python compatibility
Django 5.0 supports Python 3.10, 3.11, and 3.12. We **highly recommend** and
only officially support the latest release of each series.
The Django 4.2.x series is the last to support Python 3.8 and 3.9.
Third-party library support for older version of Django
=======================================================

View File

@ -32,8 +32,7 @@ False <USE_TZ>` in your settings file.
In older version, time zone support was disabled by default.
Time zone support uses :mod:`zoneinfo`, which is part of the Python standard
library from Python 3.9. The ``backports.zoneinfo`` package is automatically
installed alongside Django if you are using Python 3.8.
library from Python 3.9.
If you're wrestling with a particular problem, start with the :ref:`time zone
FAQ <time-zones-faq>`.

View File

@ -17,8 +17,6 @@ classifiers =
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Topic :: Internet :: WWW/HTTP
@ -34,13 +32,12 @@ project_urls =
Tracker = https://code.djangoproject.com/
[options]
python_requires = >=3.8
python_requires = >=3.10
packages = find:
include_package_data = true
zip_safe = false
install_requires =
asgiref >= 3.6.0
backports.zoneinfo; python_version<"3.9"
sqlparse >= 0.2.2
tzdata; sys_platform == 'win32'

View File

@ -32,7 +32,6 @@ from django.db.migrations.recorder import MigrationRecorder
from django.test import LiveServerTestCase, SimpleTestCase, TestCase, override_settings
from django.test.utils import captured_stderr, captured_stdout
from django.urls import path
from django.utils.version import PY39
from django.views.static import serve
from . import urls
@ -107,7 +106,7 @@ class AdminScriptTestCase(SimpleTestCase):
paths.append(os.path.dirname(backend_dir))
return paths
def run_test(self, args, settings_file=None, apps=None, umask=None):
def run_test(self, args, settings_file=None, apps=None, umask=-1):
base_dir = os.path.dirname(self.test_dir)
# The base dir for Django's tests is one level up.
tests_dir = os.path.dirname(os.path.dirname(__file__))
@ -136,12 +135,11 @@ class AdminScriptTestCase(SimpleTestCase):
cwd=self.test_dir,
env=test_environ,
text=True,
# subprocess.run()'s umask was added in Python 3.9.
**({"umask": umask} if umask and PY39 else {}),
umask=umask,
)
return p.stdout, p.stderr
def run_django_admin(self, args, settings_file=None, umask=None):
def run_django_admin(self, args, settings_file=None, umask=-1):
return self.run_test(["-m", "django", *args], settings_file, umask=umask)
def run_manage(self, args, settings_file=None, manage_py=None):
@ -2812,7 +2810,6 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
sys.platform == "win32",
"Windows only partially supports umasks and chmod.",
)
@unittest.skipUnless(PY39, "subprocess.run()'s umask was added in Python 3.9.")
def test_honor_umask(self):
_, err = self.run_django_admin(["startproject", "testproject"], umask=0o077)
self.assertNoOutput(err)

View File

@ -2,14 +2,10 @@ import datetime
import os
import re
import unittest
import zoneinfo
from unittest import mock
from urllib.parse import parse_qsl, urljoin, urlparse
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.contrib import admin
from django.contrib.admin import AdminSite, ModelAdmin
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME

View File

@ -1,15 +1,11 @@
import gettext
import os
import re
import zoneinfo
from datetime import datetime, timedelta
from importlib import import_module
from unittest import skipUnless
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django import forms
from django.conf import settings
from django.contrib import admin

View File

@ -1,11 +1,7 @@
import zoneinfo
from datetime import datetime, timedelta
from datetime import timezone as datetime_timezone
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.conf import settings
from django.db import DataError, OperationalError
from django.db.models import (

View File

@ -8,13 +8,9 @@ import pathlib
import re
import sys
import uuid
import zoneinfo
from unittest import mock
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
import custom_migration_operations.more_operations
import custom_migration_operations.operations

View File

@ -1,7 +1,6 @@
aiosmtpd
asgiref >= 3.6.0
argon2-cffi >= 19.2.0
backports.zoneinfo; python_version < '3.9'
bcrypt
black
docutils

View File

@ -1,4 +1,3 @@
import logging
import os
import unittest
import warnings
@ -47,7 +46,6 @@ from django.test.utils import (
)
from django.urls import NoReverseMatch, path, reverse, reverse_lazy
from django.utils.deprecation import RemovedInDjango51Warning
from django.utils.log import DEFAULT_LOGGING
from django.utils.version import PY311
from .models import Car, Person, PossessedCar
@ -1198,47 +1196,6 @@ class AssertWarnsMessageTests(SimpleTestCase):
func1()
# TODO: Remove when dropping support for PY39.
class AssertNoLogsTest(SimpleTestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
logging.config.dictConfig(DEFAULT_LOGGING)
cls.addClassCleanup(logging.config.dictConfig, settings.LOGGING)
def setUp(self):
self.logger = logging.getLogger("django")
@override_settings(DEBUG=True)
def test_fails_when_log_emitted(self):
msg = "Unexpected logs found: ['INFO:django:FAIL!']"
with self.assertRaisesMessage(AssertionError, msg):
with self.assertNoLogs("django", "INFO"):
self.logger.info("FAIL!")
@override_settings(DEBUG=True)
def test_text_level(self):
with self.assertNoLogs("django", "INFO"):
self.logger.debug("DEBUG logs are ignored.")
@override_settings(DEBUG=True)
def test_int_level(self):
with self.assertNoLogs("django", logging.INFO):
self.logger.debug("DEBUG logs are ignored.")
@override_settings(DEBUG=True)
def test_default_level(self):
with self.assertNoLogs("django"):
self.logger.debug("DEBUG logs are ignored.")
@override_settings(DEBUG=True)
def test_does_not_hide_other_failures(self):
msg = "1 != 2"
with self.assertRaisesMessage(AssertionError, msg):
with self.assertNoLogs("django"):
self.assertEqual(1, 2)
class AssertFieldOutputTests(SimpleTestCase):
def test_assert_field_output(self):
error_invalid = ["Enter a valid email address."]

View File

@ -1,15 +1,11 @@
import datetime
import re
import sys
import zoneinfo
from contextlib import contextmanager
from unittest import SkipTest, skipIf
from xml.dom.minidom import parseString
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.contrib.auth.models import User
from django.core import serializers
from django.db import connection

View File

@ -9,16 +9,12 @@ import time
import types
import weakref
import zipfile
import zoneinfo
from importlib import import_module
from pathlib import Path
from subprocess import CompletedProcess
from unittest import mock, skip, skipIf
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
import django.__main__
from django.apps.registry import Apps
from django.test import SimpleTestCase

View File

@ -11,7 +11,6 @@ from django.utils.module_loading import (
import_string,
module_has_submodule,
)
from django.utils.version import PY310
class DefaultLoader(unittest.TestCase):
@ -205,35 +204,12 @@ class AutodiscoverModulesTestCase(SimpleTestCase):
self.assertEqual(site._registry, {"lorem": "ipsum"})
if PY310:
class TestFinder:
def __init__(self, *args, **kwargs):
self.importer = zipimporter(*args, **kwargs)
class TestFinder:
def __init__(self, *args, **kwargs):
self.importer = zipimporter(*args, **kwargs)
def find_spec(self, path, target=None):
return self.importer.find_spec(path, target)
else:
class TestFinder:
def __init__(self, *args, **kwargs):
self.importer = zipimporter(*args, **kwargs)
def find_module(self, path):
importer = self.importer.find_module(path)
if importer is None:
return
return TestLoader(importer)
class TestLoader:
def __init__(self, importer):
self.importer = importer
def load_module(self, name):
mod = self.importer.load_module(name)
mod.__loader__ = self
return mod
def find_spec(self, path, target=None):
return self.importer.find_spec(path, target)
class CustomLoader(EggLoader):

View File

@ -1,11 +1,7 @@
import datetime
import zoneinfo
from unittest import mock
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from django.test import SimpleTestCase, override_settings
from django.utils import timezone

View File

@ -24,7 +24,7 @@ passenv = DJANGO_SETTINGS_MODULE PYTHONPATH HOME DISPLAY OBJC_DISABLE_INITIALIZE
setenv =
PYTHONDONTWRITEBYTECODE=1
deps =
py{3,38,39,310,311}: -rtests/requirements/py3.txt
py{3,310,311}: -rtests/requirements/py3.txt
postgres: -rtests/requirements/postgres.txt
mysql: -rtests/requirements/mysql.txt
oracle: -rtests/requirements/oracle.txt