Skip to content

Commit 90c8e04

Browse files
authored
Merge branch 'main' into patch-5
2 parents 6307f9a + a2a9495 commit 90c8e04

19 files changed

Lines changed: 282 additions & 41 deletions

.github/workflows/deploy.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ jobs:
1414
timeout-minutes: 10
1515

1616
steps:
17-
- uses: actions/checkout@v5
17+
- uses: actions/checkout@v6
1818
with:
1919
persist-credentials: false
2020

2121
- name: Build and Check Package
22-
uses: hynek/build-and-inspect-python-package@c52c3a4710070b50470d903818a7b25115dcd076 # v2.13.0
22+
uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0
2323

2424
deploy:
2525
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest-django'
@@ -34,7 +34,7 @@ jobs:
3434

3535
steps:
3636
- name: Download Package
37-
uses: actions/download-artifact@v6
37+
uses: actions/download-artifact@v7
3838
with:
3939
name: Packages
4040
path: dist

.github/workflows/main.yml

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
env:
3030
TOXENV: ${{ matrix.name }}
3131
steps:
32-
- uses: actions/checkout@v5
32+
- uses: actions/checkout@v6
3333
with:
3434
persist-credentials: false
3535

@@ -86,6 +86,18 @@ jobs:
8686
python: '3.13'
8787
allow_failure: false
8888

89+
- name: py314-djmain-postgres-xdist-coverage
90+
python: '3.14'
91+
allow_failure: true
92+
93+
- name: py314-dj60-postgres-xdist-coverage
94+
python: '3.14'
95+
allow_failure: false
96+
97+
- name: py314-dj52-postgres-xdist-coverage
98+
python: '3.14'
99+
allow_failure: false
100+
89101
- name: py313-dj52-postgres-xdist-coverage
90102
python: '3.13'
91103
allow_failure: false
@@ -94,12 +106,12 @@ jobs:
94106
python: '3.13'
95107
allow_failure: false
96108

97-
- name: py312-dj42-postgres-xdist-coverage
109+
- name: py312-dj60-postgres-xdist-coverage
98110
python: '3.12'
99111
allow_failure: false
100112

101-
- name: py311-dj50-postgres-xdist-coverage
102-
python: '3.11'
113+
- name: py312-dj42-postgres-xdist-coverage
114+
python: '3.12'
103115
allow_failure: false
104116

105117
- name: py311-dj42-postgres-xdist-coverage
@@ -126,17 +138,17 @@ jobs:
126138
python: '3.10'
127139
allow_failure: false
128140

129-
- name: py39-dj42-mysql-xdist-coverage
130-
python: '3.9'
131-
allow_failure: false
132-
133141
- name: py313-djmain-sqlite-coverage
134142
python: '3.13'
135143
allow_failure: true
136144

145+
- name: py313-dj60-sqlite-coverage
146+
python: '3.13'
147+
allow_failure: false
148+
137149
- name: py313-dj52-sqlite-coverage
138150
python: '3.13'
139-
allow_failure: true
151+
allow_failure: false
140152

141153
- name: py312-dj51-sqlite-xdist-coverage
142154
python: '3.12'
@@ -148,7 +160,7 @@ jobs:
148160

149161
# pypy3: not included with coverage reports (much slower then).
150162
- name: pypy3-dj42-postgres
151-
python: 'pypy3.9'
163+
python: 'pypy3.10'
152164
allow_failure: false
153165

154166
check: # This job does nothing and is only used for the branch protection

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ pytest-django allows you to test your Django project/applications with the
3232
<https://pytest-django.readthedocs.io/en/latest/contributing.html>`_
3333
* Version compatibility:
3434

35-
* Django: 4.2, 5.1, 5.2 and latest main branch (compatible at the time
35+
* Django: 4.2, 5.1, 5.2, 6.0 and latest main branch (compatible at the time
3636
of each release)
37-
* Python: CPython>=3.9 or PyPy 3
37+
* Python: CPython>=3.10 or PyPy 3
3838
* pytest: >=7.0
3939

4040
For compatibility with older versions, use previous pytest-django releases.

docs/changelog.rst

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
Changelog
22
=========
33

4-
v4.12.0 (Not released yet)
5-
--------------------------
4+
v4.12.0 (2026-02-14)
5+
--------------------
6+
7+
Compatibility
8+
^^^^^^^^^^^^^
9+
10+
* Official Python 3.14 support.
11+
* Dropped support for Python 3.9, minimum version is now Python 3.10.
12+
* Official Django 6.0 support.
613

714
Improvements
815
^^^^^^^^^^^^
916

1017
* The :ref:`multiple databases <multi-db>` support added in v4.3.0 is no longer considered experimental.
18+
* Added :func:`@pytest.mark.django_isolate_apps <pytest.mark.django_isolate_apps>`
19+
for isolating Django's app registry in pytest tests, and a
20+
:fixture:`django_isolated_apps` fixture to access the isolated Apps registry instance if needed.
1121

1222
v4.11.1 (2025-04-03)
1323
--------------------

docs/helpers.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,29 @@ dynamically in a hook or fixture.
128128
assert b'Success!' in client.get('/some_url_defined_in_test_urls/').content
129129

130130

131+
``pytest.mark.django_isolate_apps`` - isolate the app registry
132+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
133+
134+
.. decorator:: pytest.mark.django_isolate_apps(*app_labels)
135+
136+
Isolate models defined within the marked tests into their own isolated apps registry.
137+
See :func:`Isolating apps <django.test.utils.isolate_apps>` for when this might be useful.
138+
139+
:type app_labels: str
140+
:param app_labels:
141+
One or more application labels to include in the isolated registry.
142+
143+
The :fixture:`django_isolated_apps` fixture provides access to the isolated
144+
apps registry instance, if needed.
145+
146+
Example usage::
147+
148+
@pytest.mark.django_isolate_apps("myapp")
149+
def test_something(django_isolated_apps):
150+
assert django_isolated_apps.is_installed("myapp")
151+
assert not django_isolated_apps.is_installed("otherapp")
152+
153+
131154
``pytest.mark.ignore_template_errors`` - ignore invalid template variables
132155
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
133156

@@ -334,6 +357,14 @@ resolves to the user model's :attr:`~django.contrib.auth.models.CustomUser.USERN
334357
Use this fixture to make pluggable apps testable regardless what the username field
335358
is configured to be in the containing Django project.
336359

360+
.. fixture:: django_isolated_apps
361+
362+
``django_isolated_apps`` - isolated app registry
363+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
364+
365+
Access the isolated app registry created by
366+
:func:`@pytest.mark.django_isolate_apps([...]) <pytest.mark.django_isolate_apps>`.
367+
337368
.. fixture:: db
338369

339370
``db``

docs/index.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ Example using pytest.ini or tox.ini
3333
Example using pyproject.toml
3434
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3535

36+
For pytest 9.0 and later, use the native TOML format:
37+
38+
.. code-block:: toml
39+
40+
# -- Example FILE: pyproject.toml
41+
[tool.pytest]
42+
DJANGO_SETTINGS_MODULE = "test.settings"
43+
# -- recommended but optional:
44+
python_files = ["test_*.py", "*_test.py", "testing/python/*.py"]
45+
46+
For pytest 7.x and 8.x, use the INI-compatible format:
47+
3648
.. code-block:: toml
3749
3850
# -- Example FILE: pyproject.toml

pyproject.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
99
name = "pytest-django"
1010
description = "A Django plugin for pytest."
1111
readme = "README.rst"
12-
requires-python = ">=3.9"
12+
requires-python = ">=3.10"
1313
dynamic = ["version"]
1414
authors = [
1515
{ name = "Andreas Pelme", email = "andreas@pelme.se" },
@@ -24,15 +24,16 @@ classifiers = [
2424
"Framework :: Django :: 4.2",
2525
"Framework :: Django :: 5.1",
2626
"Framework :: Django :: 5.2",
27+
"Framework :: Django :: 6.0",
2728
"Intended Audience :: Developers",
2829
"License :: OSI Approved :: BSD License",
2930
"Operating System :: OS Independent",
3031
"Programming Language :: Python",
31-
"Programming Language :: Python :: 3.9",
3232
"Programming Language :: Python :: 3.10",
3333
"Programming Language :: Python :: 3.11",
3434
"Programming Language :: Python :: 3.12",
3535
"Programming Language :: Python :: 3.13",
36+
"Programming Language :: Python :: 3.14",
3637
"Programming Language :: Python :: Implementation :: CPython",
3738
"Programming Language :: Python :: Implementation :: PyPy",
3839
"Topic :: Software Development :: Testing",
@@ -48,6 +49,7 @@ docs = [
4849
testing = [
4950
"Django",
5051
"django-configurations>=2.0",
52+
"pytest>=9",
5153
]
5254
coverage = [
5355
"coverage[toml]",
@@ -83,13 +85,12 @@ pytest_django = ["py.typed"]
8385
[tool.setuptools_scm]
8486
write_to = "pytest_django/_version.py"
8587

86-
[tool.pytest.ini_options]
88+
[tool.pytest]
8789
addopts = [
88-
# Error on using unregistered marker.
89-
"--strict-markers",
9090
# Show extra test summary info for everything.
9191
"-ra",
9292
]
93+
strict = true
9394
pythonpath = ["."]
9495
DJANGO_SETTINGS_MODULE = "pytest_django_test.settings_sqlite_file"
9596
testpaths = ["tests"]

pytest_django/asserts.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import warnings
88
from functools import wraps
9-
from typing import TYPE_CHECKING, Any, Callable
9+
from typing import TYPE_CHECKING
1010

1111
from django import VERSION
1212
from django.test import LiveServerTestCase, SimpleTestCase, TestCase, TransactionTestCase
@@ -25,6 +25,15 @@ class MessagesTestCase(MessagesTestMixin, TestCase):
2525
else:
2626
test_case = TestCase("run")
2727

28+
if TYPE_CHECKING:
29+
from collections.abc import Callable, Collection, Iterator, Sequence
30+
from contextlib import AbstractContextManager
31+
from typing import Any, overload
32+
33+
from django import forms
34+
from django.db.models import Model, QuerySet, RawQuerySet
35+
from django.http.response import HttpResponseBase
36+
2837

2938
def _wrapper(name: str) -> Callable[..., Any]:
3039
func = getattr(test_case, name)
@@ -65,13 +74,6 @@ def assertion_func(*args: Any, **kwargs: Any) -> Any:
6574

6675

6776
if TYPE_CHECKING:
68-
from collections.abc import Collection, Iterator, Sequence
69-
from contextlib import AbstractContextManager
70-
from typing import overload
71-
72-
from django import forms
73-
from django.db.models import Model, QuerySet, RawQuerySet
74-
from django.http.response import HttpResponseBase
7577

7678
def assertRedirects(
7779
response: HttpResponseBase,

pytest_django/fixtures.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
import os
6-
from collections.abc import Generator, Iterable, Sequence
6+
from collections.abc import Callable, Generator, Iterable, Sequence
77
from contextlib import AbstractContextManager, contextmanager
88
from functools import partial
99
from typing import TYPE_CHECKING, Protocol
@@ -16,16 +16,16 @@
1616

1717

1818
if TYPE_CHECKING:
19-
from typing import Any, Callable, Literal, Optional, Union
19+
from typing import Any, Literal
2020

2121
import django
2222
import django.test
2323

2424
from . import DjangoDbBlocker
2525
from .django_compat import _User, _UserModel
2626

27-
_DjangoDbDatabases = Optional[Union[Literal["__all__"], Iterable[str]]]
28-
_DjangoDbAvailableApps = Optional[list[str]]
27+
_DjangoDbDatabases = Literal["__all__"] | Iterable[str] | None
28+
_DjangoDbAvailableApps = list[str] | None
2929
# transaction, reset_sequences, databases, serialized_rollback, available_apps
3030
_DjangoDb = tuple[bool, bool, _DjangoDbDatabases, bool, _DjangoDbAvailableApps]
3131

0 commit comments

Comments
 (0)