|
2 | 2 |
|
3 | 3 | from __future__ import with_statement
|
4 | 4 |
|
| 5 | +from contextlib import contextmanager |
5 | 6 | import os
|
| 7 | +import sys |
6 | 8 | import warnings
|
7 | 9 |
|
8 | 10 | import pytest
|
|
13 | 15 | from .django_compat import is_django_unittest
|
14 | 16 | from .lazy_django import get_django_version, skip_if_no_django
|
15 | 17 |
|
16 |
| -__all__ = ['_django_db_setup', 'db', 'transactional_db', 'admin_user', |
| 18 | +__all__ = ['_django_db_setup', 'db', 'transactional_db', 'shared_db_wrapper', |
| 19 | + 'admin_user', |
17 | 20 | 'django_user_model', 'django_username_field',
|
18 | 21 | 'client', 'admin_client', 'rf', 'settings', 'live_server',
|
19 | 22 | '_live_server_helper']
|
@@ -195,6 +198,62 @@ def transactional_db(request, _django_db_setup, _django_cursor_wrapper):
|
195 | 198 | return _django_db_fixture_helper(True, request, _django_cursor_wrapper)
|
196 | 199 |
|
197 | 200 |
|
| 201 | +@pytest.fixture(scope='session') |
| 202 | +def shared_db_wrapper(_django_db_setup, _django_cursor_wrapper): |
| 203 | + """Wrapper for common database initialization code. |
| 204 | +
|
| 205 | + This fixture provides a context manager that let's you access the database |
| 206 | + from a transaction spanning multiple tests. |
| 207 | + """ |
| 208 | + from django.db import connection, transaction |
| 209 | + |
| 210 | + if get_django_version() < (1, 6): |
| 211 | + raise Exception('shared_db_wrapper is only supported on Django >= 1.6.') |
| 212 | + |
| 213 | + class DummyException(Exception): |
| 214 | + """Dummy for use with Atomic.__exit__.""" |
| 215 | + |
| 216 | + @contextmanager |
| 217 | + def wrapper(request): |
| 218 | + # We need to take the request |
| 219 | + # to bind finalization to the place where this is used |
| 220 | + if 'transactional_db' in request.funcargnames: |
| 221 | + raise Exception( |
| 222 | + 'shared_db_wrapper cannot be used with `transactional_db`.') |
| 223 | + |
| 224 | + with _django_cursor_wrapper: |
| 225 | + if not connection.features.supports_transactions: |
| 226 | + raise Exception( |
| 227 | + "shared_db_wrapper cannot be used when " |
| 228 | + "the database doesn't support transactions.") |
| 229 | + |
| 230 | + exc_type, exc_value, traceback = DummyException, DummyException(), None |
| 231 | + # Use atomic instead of calling .savepoint* directly. |
| 232 | + # This way works for both top-level transactions and "subtransactions". |
| 233 | + atomic = transaction.atomic() |
| 234 | + |
| 235 | + def finalize(): |
| 236 | + # Only run __exit__ if there was no error running the wrapped function. |
| 237 | + # Otherwise we've run it already. |
| 238 | + if exc_type == DummyException: |
| 239 | + # dummy exception makes `atomic` rollback the savepoint |
| 240 | + atomic.__exit__(exc_type, exc_value, traceback) |
| 241 | + |
| 242 | + try: |
| 243 | + _django_cursor_wrapper.enable() |
| 244 | + atomic.__enter__() |
| 245 | + yield |
| 246 | + except: |
| 247 | + exc_type, exc_value, traceback = sys.exc_info() |
| 248 | + atomic.__exit__(exc_type, exc_value, traceback) |
| 249 | + raise |
| 250 | + finally: |
| 251 | + request.addfinalizer(finalize) |
| 252 | + _django_cursor_wrapper.restore() |
| 253 | + |
| 254 | + return wrapper |
| 255 | + |
| 256 | + |
198 | 257 | @pytest.fixture()
|
199 | 258 | def client():
|
200 | 259 | """A Django test client instance."""
|
|
0 commit comments