django-nplus1
N+1 query detection for Django. The API is in beta and can still change before 1.0.
Quick Start
pip install django-nplus1
# settings.py
INSTALLED_APPS = [..., "django_nplus1"]
# settings/testing.py
MIDDLEWARE = [..., "django_nplus1.NPlus1Middleware"]
NPLUS1_RAISE = True
Adding the middleware to your test settings means every view test that goes through the Django test client will fail on N+1 queries. This catches real problems in actual request paths without false positives from helper functions or scripts that intentionally defer prefetching.
For existing projects, introducing django-nplus1 will likely surface many N+1 queries at once. Whitelist the known issues and fix them over time:
# settings/testing.py
NPLUS1_WHITELIST = [
{"model": "myapp.Author", "field": "books"},
{"model": "myapp.Book", "field": "publisher"},
]
The middleware can also run in development or production settings to log warnings instead of raising. See the docs for all options, including the pytest plugin and the Profiler context manager.
See examples/ for a working project.
Corpus Mode
Per-request unused_eager_load detection can produce false positives on shared prefetch patterns. Corpus mode collects eager loads across the full pytest session and only reports the ones no test read:
uv run pytest --nplus1-eager-corpus
It also reports concrete fields that were loaded but never read across the suite, as unused_field_load. Suppress noisy models with NPLUS1_FIELD_EXCLUDE = ["auth.User", "contenttypes.*"].
See docs for suppression markers and pytest-xdist support.
Celery Integration
The equivalent of the middleware for Celery tasks. Each task execution gets its own detection scope.
pip install "django-nplus1[celery]"
# settings.py (or settings/testing.py)
NPLUS1_CELERY = True
Lazy loads, .get()-in-a-loop and unused eager loads are detected per task, just as they are per request, and so are duplicate queries when NPLUS1_DETECT_DUPLICATE_QUERIES is on. nplus1_allow() works inside tasks the same way it does in views.
Limitations:
nplus1_allow()context does not propagate across task boundaries. If a view callstask.delay()inside annplus1_allow()block, the allow rules do not carry into the worker (ContextVars don't survive serialization).- A detection made when a task ends, such as an unused eager load, or one that the task catches, can't fail the task, because Celery has already recorded its result. It is logged at ERROR level on the
django_nplus1logger instead. For a task run with.apply()inside another scope, such as a request or thenplus1test fixture, that scope reports the detection when it ends. - With
NPLUS1_RAISE, a detection fails the task, and a task withautoretry_for=(Exception,)is retried for it.
Credits
This project builds on the work of:
- nplusone by Joshua Carp, the original automatic N+1 detection library for Python ORMs. django-nplus1 started as a Django-specific fork of nplusone's architecture.
- django-zeal by Tao Bojlen, which inspired several features: deferred field detection,
.get()-in-a-loop detection,ContextVar-based async safety, call-site tracking, and configurable thresholds.
License
MIT
Metadata
Release files for django-nplus1 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_nplus1-0.6.0.tar.gz | 28.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_nplus1-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 63.4 kB
Release files / django_nplus1-0.6.0.tar.gz
| Download URL | django_nplus1-0.6.0.tar.gz |
|---|---|
| Size | 28.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f526ddc3ec59cc489752fc1a0b859dc33bb3a1cd31dafba310fb0b254586580
|
|
BLAKE2b-256 checksum How to use checksums |
ca9c3ec0a8c70ab830d7ff2258c41c62df2b067a90b13fc930b605d7f2e70f56
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / django_nplus1-0.6.0-py3-none-any.whl
| Download URL | django_nplus1-0.6.0-py3-none-any.whl |
|---|---|
| Size | 34.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cd9ad7115dff7268986574954860d6523c41ca874bd7a243504963f957b8493a
|
|
BLAKE2b-256 checksum How to use checksums |
8912d305350300d072e22b5e865ba14a46b6522ef47baf03fe3f6c965b8a172d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency log