From 7129b12ac47825591183aaf2797e97cc843d866b Mon Sep 17 00:00:00 2001 From: cherith <315844584+Cherith1222@users.noreply.github.com> Date: Tue, 6 Oct 2026 21:20:39 +0800 Subject: [PATCH] Restore settings active at settings fixture setup on teardown When an override_settings() was entered after the settings fixture was set up but exited before the fixture's teardown, finalizing the fixture restored that override's settings, leaking them into later tests and, after enough repetitions, causing a RecursionError (#809). The settings fixture now enables an empty override_settings() when it is created and disables it last, so teardown always returns to the settings that were active when the fixture was set up. AI-assisted (Cursor). Not yet reviewed by a human. --- docs/changelog.rst | 9 +++++++++ pytest_django/fixtures.py | 9 ++++++++- tests/test_fixtures.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e1c8a982..2474fb30 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,15 @@ Compatibility * Official Django 6.1 support. +Bugfixes +^^^^^^^^ + +* Fixed the :fixture:`settings` fixture restoring the settings of an + ``override_settings`` that was entered after the fixture was set up but + exited before it was torn down, which leaked those settings into later + tests and could eventually cause a ``RecursionError`` + (`#809 `__). + v4.14.0 (2026-08-10) -------------------- diff --git a/pytest_django/fixtures.py b/pytest_django/fixtures.py index 2acf0e2f..09f16dae 100644 --- a/pytest_django/fixtures.py +++ b/pytest_django/fixtures.py @@ -562,8 +562,15 @@ def __init__( assert _is_pytest_django, ( "Settings should only be instantiated from the `settings` fixture" ) + from django.test import override_settings + + # Disabled last, so the settings active when the fixture was set up + # are restored even if an override_settings entered later exits + # before the fixture is finalized. + base = override_settings() + base.enable() self._to_restore: list[django.test.override_settings] - object.__setattr__(self, "_to_restore", []) + object.__setattr__(self, "_to_restore", [base]) def __delattr__(self, attr: str) -> None: from django.test import override_settings diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index 5626de01..01803cb4 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -467,6 +467,38 @@ def test_set_non_existent(settings): ] ) + def test_override_settings_exited_before_teardown( + self, django_pytester: DjangoPytester + ) -> None: + django_pytester.create_test_module( + """ + import pytest + + from django.conf import settings as django_settings + from django.test import override_settings + + + @pytest.fixture + def overrides(): + with override_settings(OVERRIDDEN=1): + yield + + + def test_override_inside_settings_fixture(settings, overrides): + settings.ADDED = 1 + assert django_settings.OVERRIDDEN == 1 + assert django_settings.ADDED == 1 + + + def test_settings_restored(): + assert not hasattr(django_settings, "OVERRIDDEN") + assert not hasattr(django_settings, "ADDED") + """ + ) + + result = django_pytester.runpytest_subprocess("-v") + result.assert_outcomes(passed=2) + class TestLiveServer: @pytest.mark.skipif("PYTEST_XDIST_WORKER" in os.environ, reason="xdist in use")