Skip to content

Commit e9abc79

Browse files
authored
Improve django_db_blocker (#380)
* Move BaseDatabaseWrapper import check to compat * Use block()/unblock() as method names in django_db_blocker. * Make both block() and unblock() be context managers and remove the implicit context manager of django_db_blocker itself. Fixes #372.
1 parent 0951c3b commit e9abc79

File tree

5 files changed

+69
-33
lines changed

5 files changed

+69
-33
lines changed

docs/database.rst

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,22 +252,22 @@ access for the specified block::
252252

253253
@pytest.fixture
254254
def myfixture(django_db_blocker):
255-
with django_db_blocker:
255+
with django_db_blocker.unblock():
256256
... # modify something in the database
257257

258258
You can also manage the access manually via these methods:
259259

260-
.. py:method:: django_db_blocker.enable_database_access()
260+
.. py:method:: django_db_blocker.unblock()
261261
262262
Enable database access. Should be followed by a call to
263-
:func:`~django_db_blocker.restore_previous_access`.
263+
:func:`~django_db_blocker.restore`.
264264

265-
.. py:method:: django_db_blocker.disable_database_access()
265+
.. py:method:: django_db_blocker.block()
266266
267267
Disable database access. Should be followed by a call to
268-
:func:`~django_db_blocker.restore_previous_access`.
268+
:func:`~django_db_blocker.restore`.
269269

270-
.. py:function:: django_db_blocker.restore_previous_access()
270+
.. py:function:: django_db_blocker.restore()
271271
272272
Restore the previous state of the database blocking.
273273

@@ -359,7 +359,7 @@ Put this in ``conftest.py``::
359359

360360
@pytest.fixture(scope='session')
361361
def django_db_setup(django_db_setup, django_db_blocker):
362-
with django_db_blocker:
362+
with django_db_blocker.unblock():
363363
call_command('loaddata', 'your_data_fixture.json')
364364

365365
Use the same database for all xdist processes
@@ -396,7 +396,7 @@ Put this in ``conftest.py``::
396396

397397
@pytest.fixture(scope='session')
398398
def django_db_setup(django_db_setup, django_db_blocker):
399-
with django_db_blocker:
399+
with django_db_blocker.unblock():
400400
cur = connection.cursor()
401401
cur.execute('ALTER SEQUENCE app_model_id_seq RESTART WITH %s;',
402402
[random.randint(10000, 20000)])
@@ -418,7 +418,7 @@ Put this in ``conftest.py``::
418418

419419
@pytest.fixture(scope='session')
420420
def django_db_setup(django_db_blocker):
421-
with django_db_blocker:
421+
with django_db_blocker.unblock():
422422
with connection.cursor() as c:
423423
c.executescript('''
424424
DROP TABLE IF EXISTS theapp_item;

pytest_django/compat.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,9 @@ def teardown_databases(db_cfg, verbosity):
99
(_DiscoverRunner(verbosity=verbosity,
1010
interactive=False)
1111
.teardown_databases(db_cfg))
12+
13+
try:
14+
from django.db.backends.base.base import BaseDatabaseWrapper # noqa
15+
except ImportError:
16+
# Django 1.7.
17+
from django.db.backends import BaseDatabaseWrapper # noqa

pytest_django/fixtures.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,18 @@ def django_db_setup(
8686
# Django 1.7 compatibility
8787
from .db_reuse import monkey_patch_creation_for_db_reuse
8888

89-
with django_db_blocker:
89+
with django_db_blocker.unblock():
9090
monkey_patch_creation_for_db_reuse()
9191

92-
with django_db_blocker:
92+
with django_db_blocker.unblock():
9393
db_cfg = setup_databases(
9494
verbosity=pytest.config.option.verbose,
9595
interactive=False,
9696
**setup_databases_args
9797
)
9898

9999
def teardown_database():
100-
with django_db_blocker:
100+
with django_db_blocker.unblock():
101101
teardown_databases(
102102
db_cfg,
103103
verbosity=pytest.config.option.verbose,
@@ -115,8 +115,8 @@ def _django_db_fixture_helper(transactional, request, django_db_blocker):
115115
# Do nothing, we get called with transactional=True, too.
116116
return
117117

118-
django_db_blocker.enable_database_access()
119-
request.addfinalizer(django_db_blocker.restore_previous_access)
118+
django_db_blocker.unblock()
119+
request.addfinalizer(django_db_blocker.restore)
120120

121121
if transactional:
122122
from django.test import TransactionTestCase as django_case

pytest_django/plugin.py

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def _setup_django():
146146
return
147147

148148
django.setup()
149-
_blocking_manager.disable_database_access()
149+
_blocking_manager.block()
150150

151151

152152
def _get_boolean_value(x, name, default=None):
@@ -348,8 +348,7 @@ def django_db_blocker():
348348
special database handling.
349349
350350
The object is a context manager and provides the methods
351-
.enable_database_access()/.disable_database_access() and
352-
.restore_database_access() to temporarily enable database access.
351+
.unblock()/.block() and .restore() to temporarily enable database access.
353352
354353
This is an advanced feature that is meant to be used to implement database
355354
fixtures.
@@ -383,7 +382,7 @@ def _django_setup_unittest(request, django_db_blocker):
383382
request.getfuncargvalue('django_test_environment')
384383
request.getfuncargvalue('django_db_setup')
385384

386-
django_db_blocker.enable_database_access()
385+
django_db_blocker.unblock()
387386

388387
cls = request.node.cls
389388

@@ -394,7 +393,7 @@ def _django_setup_unittest(request, django_db_blocker):
394393
def teardown():
395394
_restore_class_methods(cls)
396395
cls.tearDownClass()
397-
django_db_blocker.restore_previous_access()
396+
django_db_blocker.restore()
398397

399398
request.addfinalizer(teardown)
400399

@@ -518,6 +517,17 @@ def _template_string_if_invalid_marker(request):
518517
# ############### Helper Functions ################
519518

520519

520+
class _DatabaseBlockerContextManager(object):
521+
def __init__(self, db_blocker):
522+
self._db_blocker = db_blocker
523+
524+
def __enter__(self):
525+
pass
526+
527+
def __exit__(self, exc_type, exc_value, traceback):
528+
self._db_blocker.restore()
529+
530+
521531
class _DatabaseBlocker(object):
522532
"""Manager for django.db.backends.base.base.BaseDatabaseWrapper.
523533
@@ -530,11 +540,7 @@ def __init__(self):
530540

531541
@property
532542
def _dj_db_wrapper(self):
533-
try:
534-
from django.db.backends.base.base import BaseDatabaseWrapper
535-
except ImportError:
536-
# Django 1.7.
537-
from django.db.backends import BaseDatabaseWrapper
543+
from .compat import BaseDatabaseWrapper
538544

539545
# The first time the _dj_db_wrapper is accessed, we will save a
540546
# reference to the real implementation.
@@ -552,25 +558,21 @@ def _blocking_wrapper(*args, **kwargs):
552558
pytest.fail('Database access not allowed, '
553559
'use the "django_db" mark to enable it.')
554560

555-
def enable_database_access(self):
561+
def unblock(self):
556562
"""Enable access to the Django database."""
557563
self._save_active_wrapper()
558564
self._dj_db_wrapper.ensure_connection = self._real_ensure_connection
565+
return _DatabaseBlockerContextManager(self)
559566

560-
def disable_database_access(self):
567+
def block(self):
561568
"""Disable access to the Django database."""
562569
self._save_active_wrapper()
563570
self._dj_db_wrapper.ensure_connection = self._blocking_wrapper
571+
return _DatabaseBlockerContextManager(self)
564572

565-
def restore_previous_access(self):
573+
def restore(self):
566574
self._dj_db_wrapper.ensure_connection = self._history.pop()
567575

568-
def __enter__(self):
569-
self.enable_database_access()
570-
571-
def __exit__(self, exc_type, exc_value, traceback):
572-
self.restore_previous_access()
573-
574576

575577
_blocking_manager = _DatabaseBlocker()
576578

tests/test_fixtures.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,3 +304,31 @@ class Migration(migrations.Migration):
304304
result = django_testdir.runpytest_subprocess('-s')
305305
result.stdout.fnmatch_lines(['*1 passed*'])
306306
assert result.ret == 0
307+
308+
309+
class Test_django_db_blocker:
310+
@pytest.mark.django_db
311+
def test_block_manually(self, django_db_blocker):
312+
try:
313+
django_db_blocker.block()
314+
with pytest.raises(pytest.fail.Exception):
315+
Item.objects.exists()
316+
finally:
317+
django_db_blocker.restore()
318+
319+
@pytest.mark.django_db
320+
def test_block_with_block(self, django_db_blocker):
321+
with django_db_blocker.block():
322+
with pytest.raises(pytest.fail.Exception):
323+
Item.objects.exists()
324+
325+
def test_unblock_manually(self, django_db_blocker):
326+
try:
327+
django_db_blocker.unblock()
328+
Item.objects.exists()
329+
finally:
330+
django_db_blocker.restore()
331+
332+
def test_unblock_with_block(self, django_db_blocker):
333+
with django_db_blocker.unblock():
334+
Item.objects.exists()

0 commit comments

Comments
 (0)