Skip to content

Commit

Permalink
Merge branch 'main' into issue-1147
Browse files Browse the repository at this point in the history
  • Loading branch information
boxed authored Sep 28, 2024
2 parents 062ab4e + 23adcbe commit 3904a67
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 71 deletions.
112 changes: 51 additions & 61 deletions pytest_django/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ def _django_db_helper(
django_db_setup: None,
django_db_blocker: DjangoDbBlocker,
) -> Generator[None, None, None]:
from django import VERSION

if is_django_unittest(request):
yield
return
Expand Down Expand Up @@ -199,72 +197,64 @@ def _django_db_helper(
"django_db_serialized_rollback" in request.fixturenames
)

django_db_blocker.unblock()

import django.db
import django.test
with django_db_blocker.unblock():
import django.db
import django.test

if transactional:
test_case_class = django.test.TransactionTestCase
else:
test_case_class = django.test.TestCase

_reset_sequences = reset_sequences
_serialized_rollback = serialized_rollback
_databases = databases
_available_apps = available_apps

class PytestDjangoTestCase(test_case_class): # type: ignore[misc,valid-type]
reset_sequences = _reset_sequences
serialized_rollback = _serialized_rollback
if _databases is not None:
databases = _databases
if _available_apps is not None:
available_apps = _available_apps

# For non-transactional tests, skip executing `django.test.TestCase`'s
# `setUpClass`/`tearDownClass`, only execute the super class ones.
#
# `TestCase`'s class setup manages the `setUpTestData`/class-level
# transaction functionality. We don't use it; instead we (will) offer
# our own alternatives. So it only adds overhead, and does some things
# which conflict with our (planned) functionality, particularly, it
# closes all database connections in `tearDownClass` which inhibits
# wrapping tests in higher-scoped transactions.
#
# It's possible a new version of Django will add some unrelated
# functionality to these methods, in which case skipping them completely
# would not be desirable. Let's cross that bridge when we get there...
if not transactional:

@classmethod
def setUpClass(cls) -> None:
super(django.test.TestCase, cls).setUpClass()
if VERSION < (4, 1):
django.db.transaction.Atomic._ensure_durability = False

@classmethod
def tearDownClass(cls) -> None:
if VERSION < (4, 1):
django.db.transaction.Atomic._ensure_durability = True
super(django.test.TestCase, cls).tearDownClass()

PytestDjangoTestCase.setUpClass()

test_case = PytestDjangoTestCase(methodName="__init__")
test_case._pre_setup()
if transactional:
test_case_class = django.test.TransactionTestCase
else:
test_case_class = django.test.TestCase

_reset_sequences = reset_sequences
_serialized_rollback = serialized_rollback
_databases = databases
_available_apps = available_apps

class PytestDjangoTestCase(test_case_class): # type: ignore[misc,valid-type]
reset_sequences = _reset_sequences
serialized_rollback = _serialized_rollback
if _databases is not None:
databases = _databases
if _available_apps is not None:
available_apps = _available_apps

# For non-transactional tests, skip executing `django.test.TestCase`'s
# `setUpClass`/`tearDownClass`, only execute the super class ones.
#
# `TestCase`'s class setup manages the `setUpTestData`/class-level
# transaction functionality. We don't use it; instead we (will) offer
# our own alternatives. So it only adds overhead, and does some things
# which conflict with our (planned) functionality, particularly, it
# closes all database connections in `tearDownClass` which inhibits
# wrapping tests in higher-scoped transactions.
#
# It's possible a new version of Django will add some unrelated
# functionality to these methods, in which case skipping them completely
# would not be desirable. Let's cross that bridge when we get there...
if not transactional:

@classmethod
def setUpClass(cls) -> None:
super(django.test.TestCase, cls).setUpClass()

@classmethod
def tearDownClass(cls) -> None:
super(django.test.TestCase, cls).tearDownClass()

PytestDjangoTestCase.setUpClass()

test_case = PytestDjangoTestCase(methodName="__init__")
test_case._pre_setup()

yield
yield

test_case._post_teardown()
test_case._post_teardown()

PytestDjangoTestCase.tearDownClass()
PytestDjangoTestCase.tearDownClass()

if VERSION >= (4, 0):
PytestDjangoTestCase.doClassCleanups()

django_db_blocker.restore()


def validate_django_db(marker: pytest.Mark) -> _DjangoDb:
"""Validate the django_db marker.
Expand Down
29 changes: 19 additions & 10 deletions pytest_django/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,17 +513,26 @@ def django_test_environment(request: pytest.FixtureRequest) -> Generator[None, N

@pytest.fixture(scope="session")
def django_db_blocker(request: pytest.FixtureRequest) -> DjangoDbBlocker | None:
"""Wrapper around Django's database access.
"""Block or unblock database access.
This object can be used to re-enable database access. This fixture is used
internally in pytest-django to build the other fixtures and can be used for
special database handling.
This is an advanced feature for implementing database fixtures.
The object is a context manager and provides the methods
.unblock()/.block() and .restore() to temporarily enable database access.
By default, pytest-django blocks access the the database. In tests which
request access to the database, the access is automatically unblocked.
This is an advanced feature that is meant to be used to implement database
fixtures.
In a test or fixture context where database access is blocked, you can
temporarily unblock access as follows::
with django_db_blocker.unblock():
...
In a test or fixture context where database access is not blocked, you can
temporarily block access as follows::
with django_db_blocker.block():
...
This fixture is also used internally by pytest-django.
"""
if not django_settings_is_configured():
return None
Expand Down Expand Up @@ -805,8 +814,8 @@ def __init__(self, *, _ispytest: bool = False) -> None:
def _dj_db_wrapper(self) -> django.db.backends.base.base.BaseDatabaseWrapper:
from django.db.backends.base.base import BaseDatabaseWrapper

# The first time the _dj_db_wrapper is accessed, we will save a
# reference to the real implementation.
# The first time the _dj_db_wrapper is accessed, save a reference to the
# real implementation.
if self._real_ensure_connection is None:
self._real_ensure_connection = BaseDatabaseWrapper.ensure_connection

Expand Down

0 comments on commit 3904a67

Please sign in to comment.