pytest-dev / pytest-dev/pytest-django
Unable to fully override db setup fixture
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.5k
- Forks
- 367
- PR merge metrics
- No merged PRs in 30d
Description
My project uses django_audit_log. When pytest creates the test database it fails because whilst creating it and applying migrations it generates models. These trigger the auditlog, whose own latest migrations have not yet been applied, which attempts to insert an audit record. This fails due to a database schema mismatch with the audit log model.
To avoid the issue I would like to override the django_db_setup fixture, something akin to the following:
@pytest.fixture(scope="session")
def django_db_setup(django_db_setup):
from auditlog.context import disable_auditlog
with disable_auditlog():
# Invoke the db_setup logic here...
The issue of course is that by the time the overriding fixture is called, the overridden one has already been executed. The examples at https://docs.pytest.org/en/7.1.x/how-to/fixtures.html#overriding-fixtures-on-various-levels show various means of overriding fixtures but they are all fairly trivial, there are no examples where the fixture does something relatively heavyweight.
I don't want to copy/paste the logic of django_db_setup and amend as that will likely lead to issues if the code changes in future.
To ease this, would you consider splitting "complex" fixtures such as this into two parts? The fixture and an implementing function, the fixture simply delegating to the function? The fixture should also be documented as simply delegating to the defined function.
This would allow users to override just the fixture, but reuse the implementation.
The implementation itself can't be a fixture as the same issue arises. If it's passed as a dependency then it's already executed by the time the overriding fixture is invoked.
One alternative is to have a delegate fixture that returns a function that invokes the original fixture, wrapping with whatever logic is desired. The issue here is that pytest checks for direct calls and rejects them. This of course can be worked around by poking inside the wrapped object, but that too is a little 'ick'.
E.g.
@pytest.fixture(scope="session")
def django_db_setup(db_setup_delegate):
db_setup_delegate()
@pytest.fixture(scope="session")
def db_setup_delegate(request, django_test_environment, \
django_db_blocker, django_db_use_migrations, \
django_db_keepdb, django_db_createdb, django_db_modify_db_settings):
def do_setup():
from auditlog.context import disable_auditlog
with disable_auditlog():
from pytest_django.fixtures import django_db_setup as dbs
# This fails if called on dbs directly
next(dbs.__pytest_wrapped__.obj(
request,
django_test_environment,
django_db_blocker,
django_db_use_migrations,
django_db_keepdb,
django_db_createdb,
django_db_modify_db_settings)
)
return do_setup
Thoughts? Is there an easy solution I've missed?
Thanks
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the django_db_setup implementation in pytest_django.fixtures and the pytest fixture-overriding documentation linked in the issue. Examine how the current fixture dependencies and direct-call checks constrain reuse; done should include a maintainable override mechanism with coverage and documentation for the described setup case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- django, python
- Domain
- testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100