Detect N+1 query patterns in Django ORM code via test-suite runs (primary) or per-request profiling on a local development server (secondary).
This project is a hard fork of jmcarp/nplusone, which has been unmaintained since 2018. It is not a drop-in replacement for the upstream package and does not attempt to track upstream changes or preserve the upstream API surface outside of the Django code paths.
What this fork keeps from upstream:
- The ORM monkey-patch +
blinkersignal architecture that drives detection. - The
LazyListenerheuristic for spotting lazy loads on already-fetched rows. - The
EagerListenermachinery for detecting unused eager loads (kept but disabled in v1). - The Django middleware (
NPlusOneMiddleware) for per-request detection on a local development server.
What this fork explicitly drops:
- SQLAlchemy support (
nplusone.ext.sqlalchemy). - Flask-SQLAlchemy support (
nplusone.ext.flask_sqlalchemy). - Peewee support (
nplusone.ext.peewee). - Generic WSGI middleware (
nplusone.ext.wsgi). - The Python 2 /
sixcompatibility layer. - Support for Django < 4.2.
What this fork adds (planned):
- A custom Django
TEST_RUNNERthat scopes detection per test and emits a structuredFindingreport (JSON + Markdown) as a CI artifact. - A checked-in ignore list with reason-field auditing.
- A rewritten stack-frame walker that captures the full user frame with source snippet (replacing upstream's single-frame heuristic).
No upstream compatibility intent. This project will not be contributed
back to jmcarp/nplusone, will not match the upstream version-numbering
scheme, and may freely break upstream-compatible patterns when a Django-only
implementation is cleaner.
Bootstrap complete. The fork is renamed to nplusone_django, the package
builds with uv, lint and format are managed by ruff, the bundled test
suite uses Django's unittest-based runner, and the non-Django code paths are
removed. The structured test-suite detection (custom runner + reporter + ignore
list) is planned but not yet implemented.
Once a release is published this section will list the install commands. Pre-release, install from a git pin or path dependency.
- Python 3.10+
- Django 4.2 LTS, 5.0, 5.1
Per-request detection (dev only — never enable in production). Add the middleware to your Django settings:
INSTALLED_APPS = [
# ...
"nplusone_django",
]
MIDDLEWARE = [
# ...
"nplusone_django.NPlusOneMiddleware",
]
NPLUSONE_LOGGER = logging.getLogger("nplusone")
NPLUSONE_LOG_LEVEL = logging.WARNWhen a potential N+1 is detected during a request, it is logged via
NPLUSONE_LOGGER. Set NPLUSONE_RAISE = True to raise
NPlusOneError instead.
Test-suite detection (CI). Planned; not yet available.
Suppress specific patterns via NPLUSONE_WHITELIST in settings:
NPLUSONE_WHITELIST = [
{"model": "myapp.Carrier", "field": "user"},
{"model": "myapp.*"}, # fnmatch on app_label.ModelName
]MIT, preserved from upstream. See LICENSE. Upstream copyright is preserved
in AUTHORS.rst.
The detection heuristics, ORM monkey-patches, and signal architecture in this
fork are the work of Joshua Carp (@jmcarp) and the upstream contributors.
See AUTHORS.rst.