mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #36005 -- Dropped support for Python 3.10 and 3.11.
This commit is contained in:
		
				
					committed by
					
						 Sarah Boyce
						Sarah Boyce
					
				
			
			
				
	
			
			
			
						parent
						
							61dae11df5
						
					
				
				
					commit
					f5772de696
				
			
							
								
								
									
										60
									
								
								.github/workflows/schedule_tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										60
									
								
								.github/workflows/schedule_tests.yml
									
									
									
									
										vendored
									
									
								
							| @@ -16,8 +16,6 @@ jobs: | ||||
|     strategy: | ||||
|       matrix: | ||||
|         python-version: | ||||
|           - '3.10' | ||||
|           - '3.11' | ||||
|           - '3.12' | ||||
|           - '3.13' | ||||
|           - '3.14-dev' | ||||
| @@ -64,64 +62,6 @@ jobs: | ||||
|       - name: Run tests | ||||
|         run: python -Wall tests/runtests.py --verbosity=2 | ||||
|  | ||||
|   pypy-sqlite: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: Ubuntu, SQLite, PyPy3.10 | ||||
|     continue-on-error: true | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: pypy-3.10-nightly | ||||
|           cache: 'pip' | ||||
|           cache-dependency-path: 'tests/requirements/py3.txt' | ||||
|       - name: Install libmemcached-dev for pylibmc | ||||
|         run: sudo apt-get install libmemcached-dev | ||||
|       - name: Install and upgrade packaging tools | ||||
|         run: python -m pip install --upgrade pip setuptools wheel | ||||
|       - run: python -m pip install -r tests/requirements/py3.txt -e . | ||||
|       - name: Run tests | ||||
|         run: python -Wall tests/runtests.py --verbosity=2 | ||||
|  | ||||
|   pypy-postgresql: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: Ubuntu, PostgreSQL, PyPy3.10 | ||||
|     continue-on-error: true | ||||
|     services: | ||||
|       postgres: | ||||
|         image: postgres:14-alpine | ||||
|         env: | ||||
|           POSTGRES_DB: django | ||||
|           POSTGRES_USER: user | ||||
|           POSTGRES_PASSWORD: postgres | ||||
|         ports: | ||||
|           - 5432:5432 | ||||
|         options: >- | ||||
|           --health-cmd pg_isready | ||||
|           --health-interval 10s | ||||
|           --health-timeout 5s | ||||
|           --health-retries 5 | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: pypy-3.10-nightly | ||||
|           cache: 'pip' | ||||
|           cache-dependency-path: 'tests/requirements/py3.txt' | ||||
|       - name: Install libmemcached-dev for pylibmc | ||||
|         run: sudo apt-get install libmemcached-dev | ||||
|       - name: Install and upgrade packaging tools | ||||
|         run: python -m pip install --upgrade pip setuptools wheel | ||||
|       - run: python -m pip install -r tests/requirements/py3.txt -r tests/requirements/postgres.txt -e . | ||||
|       - name: Create PostgreSQL settings file | ||||
|         run: mv ./.github/workflows/data/test_postgres.py.tpl ./tests/test_postgres.py | ||||
|       - name: Run tests | ||||
|         run: python -Wall tests/runtests.py --settings=test_postgres --verbosity=2 | ||||
|  | ||||
|   javascript-tests: | ||||
|     runs-on: ubuntu-latest | ||||
|     name: JavaScript tests | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/screenshots.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/screenshots.yml
									
									
									
									
										vendored
									
									
								
							| @@ -24,7 +24,7 @@ jobs: | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: '3.11' | ||||
|           python-version: '3.13' | ||||
|           cache: 'pip' | ||||
|           cache-dependency-path: 'tests/requirements/py3.txt' | ||||
|       - name: Install and upgrade packaging tools | ||||
|   | ||||
							
								
								
									
										2
									
								
								INSTALL
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								INSTALL
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| Thanks for downloading Django. | ||||
|  | ||||
| To install it, make sure you have Python 3.10 or greater installed. Then run | ||||
| To install it, make sure you have Python 3.12 or greater installed. Then run | ||||
| this command from the command prompt: | ||||
|  | ||||
|     python -m pip install . | ||||
|   | ||||
| @@ -16,7 +16,7 @@ from django.db import models | ||||
| from django.db.migrations.operations.base import Operation | ||||
| from django.db.migrations.utils import COMPILED_REGEX_TYPE, RegexObject | ||||
| from django.utils.functional import LazyObject, Promise | ||||
| from django.utils.version import PY311, get_docs_version | ||||
| from django.utils.version import get_docs_version | ||||
|  | ||||
| FUNCTION_TYPES = (types.FunctionType, types.BuiltinFunctionType, types.MethodType) | ||||
|  | ||||
| @@ -140,11 +140,7 @@ class EnumSerializer(BaseSerializer): | ||||
|         enum_class = self.value.__class__ | ||||
|         module = enum_class.__module__ | ||||
|         if issubclass(enum_class, enum.Flag): | ||||
|             if PY311: | ||||
|                 members = list(self.value) | ||||
|             else: | ||||
|                 members, _ = enum._decompose(enum_class, self.value) | ||||
|                 members = reversed(members) | ||||
|             members = list(self.value) | ||||
|         else: | ||||
|             members = (self.value,) | ||||
|         return ( | ||||
|   | ||||
| @@ -1,25 +1,8 @@ | ||||
| import enum | ||||
| from enum import EnumType, IntEnum, StrEnum | ||||
| from enum import property as enum_property | ||||
|  | ||||
| from django.utils.functional import Promise | ||||
| from django.utils.version import PY311, PY312 | ||||
|  | ||||
| if PY311: | ||||
|     from enum import EnumType, IntEnum, StrEnum | ||||
|     from enum import property as enum_property | ||||
| else: | ||||
|     from enum import EnumMeta as EnumType | ||||
|     from types import DynamicClassAttribute as enum_property | ||||
|  | ||||
|     class ReprEnum(enum.Enum): | ||||
|         def __str__(self): | ||||
|             return str(self.value) | ||||
|  | ||||
|     class IntEnum(int, ReprEnum): | ||||
|         pass | ||||
|  | ||||
|     class StrEnum(str, ReprEnum): | ||||
|         pass | ||||
|  | ||||
|  | ||||
| __all__ = ["Choices", "IntegerChoices", "TextChoices"] | ||||
|  | ||||
| @@ -49,14 +32,6 @@ class ChoicesType(EnumType): | ||||
|             member._label_ = label | ||||
|         return enum.unique(cls) | ||||
|  | ||||
|     if not PY312: | ||||
|  | ||||
|         def __contains__(cls, member): | ||||
|             if not isinstance(member, enum.Enum): | ||||
|                 # Allow non-enums to match against member values. | ||||
|                 return any(x.value == member for x in cls) | ||||
|             return super().__contains__(member) | ||||
|  | ||||
|     @property | ||||
|     def names(cls): | ||||
|         empty = ["__empty__"] if hasattr(cls, "__empty__") else [] | ||||
| @@ -79,13 +54,7 @@ class ChoicesType(EnumType): | ||||
| class Choices(enum.Enum, metaclass=ChoicesType): | ||||
|     """Class for creating enumerated choices.""" | ||||
|  | ||||
|     if PY311: | ||||
|         do_not_call_in_templates = enum.nonmember(True) | ||||
|     else: | ||||
|  | ||||
|         @property | ||||
|         def do_not_call_in_templates(self): | ||||
|             return True | ||||
|     do_not_call_in_templates = enum.nonmember(True) | ||||
|  | ||||
|     @enum_property | ||||
|     def label(self): | ||||
|   | ||||
| @@ -14,7 +14,6 @@ from django.db.models.fields import Field | ||||
| from django.db.models.query_utils import DeferredAttribute | ||||
| from django.db.models.utils import AltersData | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.utils.version import PY311 | ||||
|  | ||||
|  | ||||
| class FieldFile(File, AltersData): | ||||
| @@ -329,7 +328,7 @@ class FileField(Field): | ||||
|                 f"File for {self.name} must have " | ||||
|                 "the name attribute specified to be saved." | ||||
|             ) | ||||
|             if PY311 and isinstance(file._file, ContentFile): | ||||
|             if isinstance(file._file, ContentFile): | ||||
|                 exc.add_note("Pass a 'name' argument to ContentFile.") | ||||
|             raise exc | ||||
|  | ||||
|   | ||||
| @@ -28,7 +28,7 @@ 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.datastructures import OrderedSet | ||||
| from django.utils.version import PY312, PY313 | ||||
| from django.utils.version import PY313 | ||||
|  | ||||
| try: | ||||
|     import ipdb as pdb | ||||
| @@ -829,15 +829,14 @@ class DiscoverRunner: | ||||
|                 "unittest -k option." | ||||
|             ), | ||||
|         ) | ||||
|         if PY312: | ||||
|             parser.add_argument( | ||||
|                 "--durations", | ||||
|                 dest="durations", | ||||
|                 type=int, | ||||
|                 default=None, | ||||
|                 metavar="N", | ||||
|                 help="Show the N slowest test cases (N=0 for all).", | ||||
|             ) | ||||
|         parser.add_argument( | ||||
|             "--durations", | ||||
|             dest="durations", | ||||
|             type=int, | ||||
|             default=None, | ||||
|             metavar="N", | ||||
|             help="Show the N slowest test cases (N=0 for all).", | ||||
|         ) | ||||
|  | ||||
|     @property | ||||
|     def shuffle_seed(self): | ||||
| @@ -1005,9 +1004,8 @@ class DiscoverRunner: | ||||
|             "resultclass": self.get_resultclass(), | ||||
|             "verbosity": self.verbosity, | ||||
|             "buffer": self.buffer, | ||||
|             "durations": self.durations, | ||||
|         } | ||||
|         if PY312: | ||||
|             kwargs["durations"] = self.durations | ||||
|         return kwargs | ||||
|  | ||||
|     def run_checks(self, databases): | ||||
|   | ||||
| @@ -54,7 +54,6 @@ from django.test.utils import ( | ||||
|     override_settings, | ||||
| ) | ||||
| from django.utils.functional import classproperty | ||||
| from django.utils.version import PY311 | ||||
| from django.views.static import serve | ||||
|  | ||||
| logger = logging.getLogger("django.test") | ||||
| @@ -71,24 +70,6 @@ __all__ = ( | ||||
| __unittest = True | ||||
|  | ||||
|  | ||||
| if not PY311: | ||||
|     # Backport of unittest.case._enter_context() from Python 3.11. | ||||
|     def _enter_context(cm, addcleanup): | ||||
|         # Look up the special methods on the type to match the with statement. | ||||
|         cls = type(cm) | ||||
|         try: | ||||
|             enter = cls.__enter__ | ||||
|             exit = cls.__exit__ | ||||
|         except AttributeError: | ||||
|             raise TypeError( | ||||
|                 f"'{cls.__module__}.{cls.__qualname__}' object does not support the " | ||||
|                 f"context manager protocol" | ||||
|             ) from None | ||||
|         result = enter(cm) | ||||
|         addcleanup(exit, cm, None, None, None) | ||||
|         return result | ||||
|  | ||||
|  | ||||
| def to_list(value): | ||||
|     """Put value into a list if it's not already one.""" | ||||
|     if not isinstance(value, list): | ||||
| @@ -398,12 +379,6 @@ class SimpleTestCase(unittest.TestCase): | ||||
|         """Perform post-test things.""" | ||||
|         pass | ||||
|  | ||||
|     if not PY311: | ||||
|         # Backport of unittest.TestCase.enterClassContext() from Python 3.11. | ||||
|         @classmethod | ||||
|         def enterClassContext(cls, cm): | ||||
|             return _enter_context(cm, cls.addClassCleanup) | ||||
|  | ||||
|     def settings(self, **kwargs): | ||||
|         """ | ||||
|         A context manager that temporarily sets a setting and reverts to the | ||||
|   | ||||
| @@ -17,7 +17,7 @@ from django.utils.datastructures import MultiValueDict | ||||
| from django.utils.encoding import force_str | ||||
| from django.utils.module_loading import import_string | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from django.utils.version import PY311, get_docs_version | ||||
| from django.utils.version import get_docs_version | ||||
| from django.views.decorators.debug import coroutine_functions_to_sensitive_variables | ||||
|  | ||||
| # Minimal Django templates engine to render the error templates | ||||
| @@ -567,22 +567,19 @@ class ExceptionReporter: | ||||
|                 post_context = [] | ||||
|  | ||||
|             colno = tb_area_colno = "" | ||||
|             if PY311: | ||||
|                 _, _, start_column, end_column = next( | ||||
|                     itertools.islice( | ||||
|                         tb.tb_frame.f_code.co_positions(), tb.tb_lasti // 2, None | ||||
|                     ) | ||||
|             _, _, start_column, end_column = next( | ||||
|                 itertools.islice( | ||||
|                     tb.tb_frame.f_code.co_positions(), tb.tb_lasti // 2, None | ||||
|                 ) | ||||
|                 if start_column and end_column: | ||||
|                     underline = "^" * (end_column - start_column) | ||||
|                     spaces = " " * (start_column + len(str(lineno + 1)) + 2) | ||||
|                     colno = f"\n{spaces}{underline}" | ||||
|                     tb_area_spaces = " " * ( | ||||
|                         4 | ||||
|                         + start_column | ||||
|                         - (len(context_line) - len(context_line.lstrip())) | ||||
|                     ) | ||||
|                     tb_area_colno = f"\n{tb_area_spaces}{underline}" | ||||
|             ) | ||||
|             if start_column and end_column: | ||||
|                 underline = "^" * (end_column - start_column) | ||||
|                 spaces = " " * (start_column + len(str(lineno + 1)) + 2) | ||||
|                 colno = f"\n{spaces}{underline}" | ||||
|                 tb_area_spaces = " " * ( | ||||
|                     4 + start_column - (len(context_line) - len(context_line.lstrip())) | ||||
|                 ) | ||||
|                 tb_area_colno = f"\n{tb_area_spaces}{underline}" | ||||
|             yield { | ||||
|                 "exc_cause": exc_cause, | ||||
|                 "exc_cause_explicit": exc_cause_explicit, | ||||
|   | ||||
| @@ -92,14 +92,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.10 | ||||
| <running-unit-tests-settings>`. For example, to run the tests on Python 3.12 | ||||
| using PostgreSQL: | ||||
|  | ||||
| .. console:: | ||||
|  | ||||
|     $ tox -e py310-postgres -- --settings=my_postgres_settings | ||||
|     $ tox -e py312-postgres -- --settings=my_postgres_settings | ||||
|  | ||||
| This command sets up a Python 3.10 virtual environment, installs Django's | ||||
| This command sets up a Python 3.12 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``). | ||||
| @@ -114,14 +114,14 @@ above: | ||||
|  | ||||
| .. code-block:: console | ||||
|  | ||||
|     $ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py310-postgres | ||||
|     $ DJANGO_SETTINGS_MODULE=my_postgres_settings tox -e py312-postgres | ||||
|  | ||||
| Windows users should use: | ||||
|  | ||||
| .. code-block:: doscon | ||||
|  | ||||
|     ...\> set DJANGO_SETTINGS_MODULE=my_postgres_settings | ||||
|     ...\> tox -e py310-postgres | ||||
|     ...\> tox -e py312-postgres | ||||
|  | ||||
| Running the JavaScript tests | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|   | ||||
| @@ -220,7 +220,7 @@ this. For a small app like polls, this process isn't too difficult. | ||||
|        ] | ||||
|        description = "A Django app to conduct web-based polls." | ||||
|        readme = "README.rst" | ||||
|        requires-python = ">= 3.10" | ||||
|        requires-python = ">= 3.12" | ||||
|        authors = [ | ||||
|            {name = "Your Name", email = "yourname@example.com"}, | ||||
|        ] | ||||
| @@ -234,8 +234,6 @@ 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.10", | ||||
|            "Programming Language :: Python :: 3.11", | ||||
|            "Programming Language :: Python :: 3.12", | ||||
|            "Programming Language :: Python :: 3.13", | ||||
|            "Topic :: Internet :: WWW/HTTP", | ||||
|   | ||||
| @@ -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.10 and | ||||
| This tutorial is written for Django |version|, which supports Python 3.12 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 | ||||
|   | ||||
| @@ -1583,10 +1583,6 @@ Outputs timings, including database setup and total run time. | ||||
|  | ||||
| Shows the N slowest test cases (N=0 for all). | ||||
|  | ||||
| .. admonition:: Python 3.12 and later | ||||
|  | ||||
|     This feature is only available for Python 3.12 and later. | ||||
|  | ||||
| ``testserver`` | ||||
| -------------- | ||||
|  | ||||
|   | ||||
| @@ -21,6 +21,8 @@ Python compatibility | ||||
| Django 6.0 supports Python 3.12 and 3.13. We **highly recommend** and only | ||||
| officially support the latest release of each series. | ||||
|  | ||||
| The Django 5.2.x series is the last to support Python 3.10 and 3.11. | ||||
|  | ||||
| Third-party library support for older version of Django | ||||
| ======================================================= | ||||
|  | ||||
|   | ||||
| @@ -419,7 +419,8 @@ performance gains, typically for heavyweight applications. | ||||
|  | ||||
| A key aim of the PyPy project is `compatibility | ||||
| <https://www.pypy.org/compat.html>`_ with existing Python APIs and libraries. | ||||
| Django is compatible, but you will need to check the compatibility of other | ||||
| Django is compatible with versions of PyPy corresponding to the supported | ||||
| Python versions, but you will need to check the compatibility of other | ||||
| libraries you rely on. | ||||
|  | ||||
| C implementations of Python libraries | ||||
|   | ||||
| @@ -608,7 +608,6 @@ and tear down the test suite. | ||||
|  | ||||
|     ``durations`` will show a list of the N slowest test cases. Setting this | ||||
|     option to ``0`` will result in the duration for all tests being shown. | ||||
|     Requires Python 3.12+. | ||||
|  | ||||
|     Django may, from time to time, extend the capabilities of the test runner | ||||
|     by adding new arguments. The ``**kwargs`` declaration allows for this | ||||
|   | ||||
| @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" | ||||
| [project] | ||||
| name = "Django" | ||||
| dynamic = ["version"] | ||||
| requires-python = ">= 3.10" | ||||
| requires-python = ">= 3.12" | ||||
| dependencies = [ | ||||
|     "asgiref>=3.8.1", | ||||
|     "sqlparse>=0.3.1", | ||||
| @@ -27,8 +27,6 @@ classifiers = [ | ||||
|     "Programming Language :: Python", | ||||
|     "Programming Language :: Python :: 3", | ||||
|     "Programming Language :: Python :: 3 :: Only", | ||||
|     "Programming Language :: Python :: 3.10", | ||||
|     "Programming Language :: Python :: 3.11", | ||||
|     "Programming Language :: Python :: 3.12", | ||||
|     "Programming Language :: Python :: 3.13", | ||||
|     "Topic :: Internet :: WWW/HTTP", | ||||
| @@ -54,7 +52,7 @@ Source = "https://github.com/django/django" | ||||
| Tracker = "https://code.djangoproject.com/" | ||||
|  | ||||
| [tool.black] | ||||
| target-version = ["py310"] | ||||
| target-version = ["py312"] | ||||
| force-exclude = "tests/test_runner_apps/tagged/tests_syntax_error.py" | ||||
|  | ||||
| [tool.isort] | ||||
|   | ||||
| @@ -184,7 +184,7 @@ class MailTests(MailTestsMixin, SimpleTestCase): | ||||
|         """Line length check should encode the payload supporting `surrogateescape`. | ||||
|  | ||||
|         Following https://github.com/python/cpython/issues/76511, newer | ||||
|         versions of Python (3.11.9, 3.12.3 and 3.13) ensure that a message's | ||||
|         versions of Python (3.12.3 and 3.13) ensure that a message's | ||||
|         payload is encoded with the provided charset and `surrogateescape` is | ||||
|         used as the error handling strategy. | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,6 @@ from django.template import Context, Template | ||||
| from django.test import SimpleTestCase | ||||
| from django.utils.functional import Promise | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.utils.version import PY311 | ||||
|  | ||||
|  | ||||
| class Suit(models.IntegerChoices): | ||||
| @@ -200,13 +199,7 @@ class ChoicesTests(SimpleTestCase): | ||||
|  | ||||
|     def test_do_not_call_in_templates_nonmember(self): | ||||
|         self.assertNotIn("do_not_call_in_templates", Suit.__members__) | ||||
|         if PY311: | ||||
|             self.assertIs(Suit.do_not_call_in_templates, True) | ||||
|         else: | ||||
|             # Using @property on an enum does not behave as expected. | ||||
|             self.assertTrue(Suit.do_not_call_in_templates) | ||||
|             self.assertIsNot(Suit.do_not_call_in_templates, True) | ||||
|             self.assertIsInstance(Suit.do_not_call_in_templates, property) | ||||
|         self.assertIs(Suit.do_not_call_in_templates, True) | ||||
|  | ||||
|  | ||||
| class Separator(bytes, models.Choices): | ||||
|   | ||||
| @@ -12,7 +12,6 @@ from django.core.files.uploadedfile import TemporaryUploadedFile | ||||
| from django.db import IntegrityError, models | ||||
| from django.test import TestCase, override_settings | ||||
| from django.test.utils import isolate_apps | ||||
| from django.utils.version import PY311 | ||||
|  | ||||
| from .models import Document | ||||
|  | ||||
| @@ -80,10 +79,9 @@ class FileFieldTests(TestCase): | ||||
|         with self.assertRaisesMessage(FieldError, msg) as cm: | ||||
|             d.save() | ||||
|  | ||||
|         if PY311: | ||||
|             self.assertEqual( | ||||
|                 cm.exception.__notes__, ["Pass a 'name' argument to ContentFile."] | ||||
|             ) | ||||
|         self.assertEqual( | ||||
|             cm.exception.__notes__, ["Pass a 'name' argument to ContentFile."] | ||||
|         ) | ||||
|  | ||||
|     def test_delete_content_file(self): | ||||
|         file = ContentFile(b"", name="foo") | ||||
|   | ||||
| @@ -34,7 +34,7 @@ else: | ||||
|     ) | ||||
|     from django.utils.functional import classproperty | ||||
|     from django.utils.log import DEFAULT_LOGGING | ||||
|     from django.utils.version import PY312, PYPY | ||||
|     from django.utils.version import PYPY | ||||
|  | ||||
|  | ||||
| try: | ||||
| @@ -691,15 +691,14 @@ if __name__ == "__main__": | ||||
|             "Same as unittest -k option. Can be used multiple times." | ||||
|         ), | ||||
|     ) | ||||
|     if PY312: | ||||
|         parser.add_argument( | ||||
|             "--durations", | ||||
|             dest="durations", | ||||
|             type=int, | ||||
|             default=None, | ||||
|             metavar="N", | ||||
|             help="Show the N slowest test cases (N=0 for all).", | ||||
|         ) | ||||
|     parser.add_argument( | ||||
|         "--durations", | ||||
|         dest="durations", | ||||
|         type=int, | ||||
|         default=None, | ||||
|         metavar="N", | ||||
|         help="Show the N slowest test cases (N=0 for all).", | ||||
|     ) | ||||
|  | ||||
|     options = parser.parse_args() | ||||
|  | ||||
|   | ||||
| @@ -4,7 +4,6 @@ from io import StringIO | ||||
| from django.db import connection | ||||
| from django.test import TestCase | ||||
| from django.test.runner import DiscoverRunner | ||||
| from django.utils.version import PY311 | ||||
|  | ||||
| from .models import Person | ||||
|  | ||||
| @@ -114,17 +113,15 @@ class TestDebugSQL(unittest.TestCase): | ||||
|         ), | ||||
|     ] | ||||
|  | ||||
|     # Python 3.11 uses fully qualified test name in the output. | ||||
|     method_name = ".runTest" if PY311 else "" | ||||
|     test_class_path = "test_runner.test_debug_sql.TestDebugSQL" | ||||
|     verbose_expected_outputs = [ | ||||
|         f"runTest ({test_class_path}.FailingTest{method_name}) ... FAIL", | ||||
|         f"runTest ({test_class_path}.ErrorTest{method_name}) ... ERROR", | ||||
|         f"runTest ({test_class_path}.PassingTest{method_name}) ... ok", | ||||
|         f"runTest ({test_class_path}.FailingTest.runTest) ... FAIL", | ||||
|         f"runTest ({test_class_path}.ErrorTest.runTest) ... ERROR", | ||||
|         f"runTest ({test_class_path}.PassingTest.runTest) ... ok", | ||||
|         # If there are errors/failures in subtests but not in test itself, | ||||
|         # the status is not written. That behavior comes from Python. | ||||
|         f"runTest ({test_class_path}.FailingSubTest{method_name}) ...", | ||||
|         f"runTest ({test_class_path}.ErrorSubTest{method_name}) ...", | ||||
|         f"runTest ({test_class_path}.FailingSubTest.runTest) ...", | ||||
|         f"runTest ({test_class_path}.ErrorSubTest.runTest) ...", | ||||
|         ( | ||||
|             """SELECT COUNT(*) AS "__count"\n""" | ||||
|             """FROM "test_runner_person"\nWHERE """ | ||||
|   | ||||
| @@ -16,7 +16,6 @@ from django.test.utils import ( | ||||
|     captured_stderr, | ||||
|     captured_stdout, | ||||
| ) | ||||
| from django.utils.version import PY312 | ||||
|  | ||||
|  | ||||
| @contextmanager | ||||
| @@ -768,7 +767,6 @@ class DiscoverRunnerTests(SimpleTestCase): | ||||
|                 failures = runner.suite_result(suite, result) | ||||
|                 self.assertEqual(failures, expected_failures) | ||||
|  | ||||
|     @unittest.skipUnless(PY312, "unittest --durations option requires Python 3.12") | ||||
|     def test_durations(self): | ||||
|         with captured_stderr() as stderr, captured_stdout(): | ||||
|             runner = DiscoverRunner(durations=10) | ||||
| @@ -776,7 +774,6 @@ class DiscoverRunnerTests(SimpleTestCase): | ||||
|             runner.run_suite(suite) | ||||
|         self.assertIn("Slowest test durations", stderr.getvalue()) | ||||
|  | ||||
|     @unittest.skipUnless(PY312, "unittest --durations option requires Python 3.12") | ||||
|     def test_durations_debug_sql(self): | ||||
|         with captured_stderr() as stderr, captured_stdout(): | ||||
|             runner = DiscoverRunner(durations=10, debug_sql=True) | ||||
|   | ||||
| @@ -7,7 +7,6 @@ from unittest.suite import TestSuite, _ErrorHolder | ||||
|  | ||||
| from django.test import SimpleTestCase | ||||
| from django.test.runner import ParallelTestSuite, RemoteTestResult | ||||
| from django.utils.version import PY311, PY312 | ||||
|  | ||||
| try: | ||||
|     import tblib.pickling_support | ||||
| @@ -193,27 +192,21 @@ class RemoteTestResultTest(SimpleTestCase): | ||||
|         subtest_test.run(result=result) | ||||
|  | ||||
|         events = result.events | ||||
|         # addDurations added in Python 3.12. | ||||
|         if PY312: | ||||
|             self.assertEqual(len(events), 5) | ||||
|         else: | ||||
|             self.assertEqual(len(events), 4) | ||||
|         self.assertEqual(len(events), 5) | ||||
|         self.assertIs(result.wasSuccessful(), False) | ||||
|  | ||||
|         event = events[1] | ||||
|         self.assertEqual(event[0], "addSubTest") | ||||
|         self.assertEqual( | ||||
|             str(event[2]), | ||||
|             "dummy_test (test_runner.test_parallel.SampleFailingSubtest%s) (index=0)" | ||||
|             # Python 3.11 uses fully qualified test name in the output. | ||||
|             % (".dummy_test" if PY311 else ""), | ||||
|             "dummy_test (test_runner.test_parallel.SampleFailingSubtest.dummy_test) " | ||||
|             "(index=0)", | ||||
|         ) | ||||
|         self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1')") | ||||
|  | ||||
|         event = events[2] | ||||
|         self.assertEqual(repr(event[3][1]), "AssertionError('2 != 1')") | ||||
|  | ||||
|     @unittest.skipUnless(PY312, "unittest --durations option requires Python 3.12") | ||||
|     def test_add_duration(self): | ||||
|         result = RemoteTestResult() | ||||
|         result.addDuration(None, 2.3) | ||||
|   | ||||
| @@ -15,7 +15,7 @@ from django import db | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.core.management import call_command | ||||
| from django.core.management.base import CommandError, SystemCheckError | ||||
| from django.core.management.base import SystemCheckError | ||||
| from django.test import SimpleTestCase, TransactionTestCase, skipUnlessDBFeature | ||||
| from django.test.runner import ( | ||||
|     DiscoverRunner, | ||||
| @@ -32,7 +32,6 @@ from django.test.utils import ( | ||||
|     get_unique_databases_and_mirrors, | ||||
|     iter_test_cases, | ||||
| ) | ||||
| from django.utils.version import PY312 | ||||
|  | ||||
| from .models import B, Person, Through | ||||
|  | ||||
| @@ -479,7 +478,6 @@ class ManageCommandTests(unittest.TestCase): | ||||
|             ) | ||||
|         self.assertIn("Total run took", stderr.getvalue()) | ||||
|  | ||||
|     @unittest.skipUnless(PY312, "unittest --durations option requires Python 3.12") | ||||
|     def test_durations(self): | ||||
|         with captured_stderr() as stderr: | ||||
|             call_command( | ||||
| @@ -490,17 +488,6 @@ class ManageCommandTests(unittest.TestCase): | ||||
|             ) | ||||
|         self.assertIn("durations=10", stderr.getvalue()) | ||||
|  | ||||
|     @unittest.skipIf(PY312, "unittest --durations option requires Python 3.12") | ||||
|     def test_durations_lt_py312(self): | ||||
|         msg = "Error: unrecognized arguments: --durations=10" | ||||
|         with self.assertRaises(CommandError, msg=msg): | ||||
|             call_command( | ||||
|                 "test", | ||||
|                 "--durations=10", | ||||
|                 "sites", | ||||
|                 testrunner="test_runner.tests.MockTestRunner", | ||||
|             ) | ||||
|  | ||||
|  | ||||
| # Isolate from the real environment. | ||||
| @mock.patch.dict(os.environ, {}, clear=True) | ||||
|   | ||||
| @@ -49,7 +49,6 @@ from django.test.utils import ( | ||||
| ) | ||||
| from django.urls import NoReverseMatch, path, reverse, reverse_lazy | ||||
| from django.utils.html import VOID_ELEMENTS | ||||
| from django.utils.version import PY311 | ||||
|  | ||||
| from .models import Car, Person, PossessedCar | ||||
| from .views import empty_response | ||||
| @@ -103,11 +102,9 @@ class SkippingTestCase(SimpleTestCase): | ||||
|             SkipTestCase("test_foo").test_foo, | ||||
|             ValueError, | ||||
|             "skipUnlessDBFeature cannot be used on test_foo (test_utils.tests." | ||||
|             "SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase%s) " | ||||
|             "as SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase " | ||||
|             "doesn't allow queries against the 'default' database." | ||||
|             # Python 3.11 uses fully qualified test name in the output. | ||||
|             % (".test_foo" if PY311 else ""), | ||||
|             "SkippingTestCase.test_skip_unless_db_feature.<locals>.SkipTestCase." | ||||
|             "test_foo) as SkippingTestCase.test_skip_unless_db_feature.<locals>." | ||||
|             "SkipTestCase doesn't allow queries against the 'default' database.", | ||||
|         ) | ||||
|  | ||||
|     def test_skip_if_db_feature(self): | ||||
| @@ -150,11 +147,9 @@ class SkippingTestCase(SimpleTestCase): | ||||
|             SkipTestCase("test_foo").test_foo, | ||||
|             ValueError, | ||||
|             "skipIfDBFeature cannot be used on test_foo (test_utils.tests." | ||||
|             "SkippingTestCase.test_skip_if_db_feature.<locals>.SkipTestCase%s) " | ||||
|             "SkippingTestCase.test_skip_if_db_feature.<locals>.SkipTestCase.test_foo) " | ||||
|             "as SkippingTestCase.test_skip_if_db_feature.<locals>.SkipTestCase " | ||||
|             "doesn't allow queries against the 'default' database." | ||||
|             # Python 3.11 uses fully qualified test name in the output. | ||||
|             % (".test_foo" if PY311 else ""), | ||||
|             "doesn't allow queries against the 'default' database.", | ||||
|         ) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,6 @@ from django.utils.dateparse import ( | ||||
|     parse_time, | ||||
| ) | ||||
| from django.utils.timezone import get_fixed_timezone | ||||
| from django.utils.version import PY311 | ||||
|  | ||||
|  | ||||
| class DateParseTests(unittest.TestCase): | ||||
| @@ -16,8 +15,7 @@ class DateParseTests(unittest.TestCase): | ||||
|         # Valid inputs | ||||
|         self.assertEqual(parse_date("2012-04-23"), date(2012, 4, 23)) | ||||
|         self.assertEqual(parse_date("2012-4-9"), date(2012, 4, 9)) | ||||
|         if PY311: | ||||
|             self.assertEqual(parse_date("20120423"), date(2012, 4, 23)) | ||||
|         self.assertEqual(parse_date("20120423"), date(2012, 4, 23)) | ||||
|         # Invalid inputs | ||||
|         self.assertIsNone(parse_date("2012423")) | ||||
|         with self.assertRaises(ValueError): | ||||
| @@ -26,8 +24,7 @@ class DateParseTests(unittest.TestCase): | ||||
|     def test_parse_time(self): | ||||
|         # Valid inputs | ||||
|         self.assertEqual(parse_time("09:15:00"), time(9, 15)) | ||||
|         if PY311: | ||||
|             self.assertEqual(parse_time("091500"), time(9, 15)) | ||||
|         self.assertEqual(parse_time("091500"), time(9, 15)) | ||||
|         self.assertEqual(parse_time("10:10"), time(10, 10)) | ||||
|         self.assertEqual(parse_time("10:20:30.400"), time(10, 20, 30, 400000)) | ||||
|         self.assertEqual(parse_time("10:20:30,400"), time(10, 20, 30, 400000)) | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| from django.test import SimpleTestCase | ||||
| from django.utils.functional import cached_property, classproperty, lazy | ||||
| from django.utils.version import PY312 | ||||
|  | ||||
|  | ||||
| class FunctionalTests(SimpleTestCase): | ||||
| @@ -133,14 +132,10 @@ class FunctionalTests(SimpleTestCase): | ||||
|             "Cannot assign the same cached_property to two different names ('a' and " | ||||
|             "'b')." | ||||
|         ) | ||||
|         if PY312: | ||||
|             error_type = TypeError | ||||
|             msg = type_msg | ||||
|         else: | ||||
|             error_type = RuntimeError | ||||
|             msg = "Error calling __set_name__" | ||||
|         error_type = TypeError | ||||
|         msg = type_msg | ||||
|  | ||||
|         with self.assertRaisesMessage(error_type, msg) as ctx: | ||||
|         with self.assertRaisesMessage(error_type, msg): | ||||
|  | ||||
|             class ReusedCachedProperty: | ||||
|                 @cached_property | ||||
| @@ -149,9 +144,6 @@ class FunctionalTests(SimpleTestCase): | ||||
|  | ||||
|                 b = a | ||||
|  | ||||
|         if not PY312: | ||||
|             self.assertEqual(str(ctx.exception.__context__), str(TypeError(type_msg))) | ||||
|  | ||||
|     def test_cached_property_reuse_same_name(self): | ||||
|         """ | ||||
|         Reusing a cached_property on different classes under the same name is | ||||
|   | ||||
| @@ -7,7 +7,7 @@ import tempfile | ||||
| import threading | ||||
| from io import StringIO | ||||
| from pathlib import Path | ||||
| from unittest import mock, skipIf, skipUnless | ||||
| from unittest import mock, skipIf | ||||
|  | ||||
| from asgiref.sync import async_to_sync, iscoroutinefunction | ||||
|  | ||||
| @@ -24,7 +24,6 @@ from django.urls.converters import IntConverter | ||||
| from django.utils.functional import SimpleLazyObject | ||||
| from django.utils.regex_helper import _lazy_re_compile | ||||
| from django.utils.safestring import mark_safe | ||||
| from django.utils.version import PY311 | ||||
| from django.views.debug import ( | ||||
|     CallableSettingWrapper, | ||||
|     ExceptionCycleWarning, | ||||
| @@ -695,7 +694,6 @@ class ExceptionReporterTests(SimpleTestCase): | ||||
|             text, | ||||
|         ) | ||||
|  | ||||
|     @skipUnless(PY311, "Exception notes were added in Python 3.11.") | ||||
|     def test_exception_with_notes(self): | ||||
|         request = self.rf.get("/test_view/") | ||||
|         try: | ||||
| @@ -806,7 +804,6 @@ class ExceptionReporterTests(SimpleTestCase): | ||||
|         or os.environ.get("PYTHONNODEBUGRANGES", False), | ||||
|         "Fine-grained error locations are disabled.", | ||||
|     ) | ||||
|     @skipUnless(PY311, "Fine-grained error locations were added in Python 3.11.") | ||||
|     def test_highlight_error_position(self): | ||||
|         request = self.rf.get("/test_view/") | ||||
|         try: | ||||
|   | ||||
							
								
								
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -26,7 +26,7 @@ setenv = | ||||
|     PYTHONDONTWRITEBYTECODE=1 | ||||
| deps = | ||||
|     -e . | ||||
|     py{3,310,311,312,313,py3}: -rtests/requirements/py3.txt | ||||
|     py{3,312,313}: -rtests/requirements/py3.txt | ||||
|     postgres: -rtests/requirements/postgres.txt | ||||
|     mysql: -rtests/requirements/mysql.txt | ||||
|     oracle: -rtests/requirements/oracle.txt | ||||
|   | ||||
		Reference in New Issue
	
	Block a user