A development tool to detect N+1 queries in Django.
Project description
Django N+1 Hunter
A powerful, zero-configuration middleware for detecting N+1 queries in your Django applications during development.
N+1 queries are the silent performance killers of Django apps. This tool automatically monitors your SQL executions and points you exactly to the line of code in your view or model that caused the problem.
Supports: Django 5.2+ and Python 3.10+ Databases: Works with PostgreSQL, MySQL, SQLite, and any other Django-supported backend.
Features
- Automatic Interception: Hooks into Django's query execution to monitor SQL statements seamlessly across all configured databases.
- Traceback Filtering: Analyzes the call stack to point you exactly to the line of user code that triggered the loop.
- Async & ASGI Ready: Built using Python's
contextvars, guaranteeing absolute thread safety and async isolation without data bleeding between concurrent requests. - Test-Suite Integration: Capable of raising exceptions rather than just logging warnings, enabling you to actively fail CI/CD pipelines when N+1 queries are introduced.
- Production Safe: Automatically disables itself if
DEBUG = False. It also raises a startup warning if accidentally deployed to production. - Zero Dependencies: Relies entirely on built-in Python and Django features.
Supported Database Structures
Because the hunter hooks into the database driver executions at the connection layer using Django's connection.execute_wrapper(), it is highly robust and database-structure-agnostic:
- Standard Relations:
ForeignKey,OneToOneField, andManyToManyField(including reverse relationships). - Generic Relations:
GenericForeignKeyand reverseGenericRelations. - Model Inheritance: Queries generated by accessing fields on a parent table in multi-table inheritance.
- Deferred Fields: When using
.defer()or.only(), accessing a deferred field later in a loop triggers a query per object, which is successfully intercepted and flagged as an N+1. - Multi-Database Setups: Wraps
connections.all(), so queries to replica databases or secondary write-routers are fully captured.
Limitations & Caveats
- Raw Database Connections: If you run raw queries using external drivers bypassing Django's ORM (e.g., using a raw
psycopg2orsqlite3connection directly), Django's wrapper is bypassed and the hunter cannot capture it. - Polymorphic Prefetches:
prefetch_relatedon highly polymorphic relations (like aGenericForeignKeypointing to 3+ different model types) will execute1 + Cqueries (whereCis the number of content types). If the sum of these queries exceeds theNPLUS1_HUNTER_THRESHOLD(default 3), the prefetch line will trigger a false-positive warning.
Installation & Setup
-
Install the package via pip:
pip install django-nplus1-hunter
-
Add it to your
INSTALLED_APPS. For safety, it is highly recommended to only add it in your local/development settings:# settings.py if DEBUG: INSTALLED_APPS += [ 'django_nplus1_hunter', ]
-
Add the middleware to
MIDDLEWARE. Place it near the top of the list so it can track queries generated by other middlewares (like SessionMiddleware or AuthenticationMiddleware):# settings.py if DEBUG: MIDDLEWARE.insert(0, 'django_nplus1_hunter.middleware.NPlus1HunterMiddleware')
That's it! Just browse your app locally. If a view triggers an N+1 query, your runserver console will light up with a warning showing the exact file and line number.
Configuration Options
You can customize the detection sensitivity and behavior by adding these variables to your settings.py:
# Number of queries generated from the exact same line of code before it is flagged as an N+1.
# Default: 3
NPLUS1_HUNTER_THRESHOLD = 3
# The maximum number of total queries allowed in a single request before a warning is thrown.
# Default: 50
NPLUS1_HUNTER_TOTAL_THRESHOLD = 50
# A list of URL path prefixes to completely ignore. Useful for admin panels or debug toolbars.
# Default: []
NPLUS1_HUNTER_IGNORE_PATHS = [
'/admin/',
'/__debug__/',
]
# If True, the middleware will raise an exception (NPlus1QueryDetectedError or HighQueryCountDetectedError)
# instead of just logging a warning. Highly recommended for running during automated testing (pytest/CI)
# to fail the build if an N+1 is introduced.
# Default: False
NPLUS1_HUNTER_RAISE_EXCEPTION = False
How it works under the hood
The middleware utilizes contextlib.ExitStack and Django's built-in connections.all()[...].execute_wrapper to wrap every database connection during the request/response lifecycle. When a query executes, the wrapper captures the raw SQL, execution time, and a full Python stack trace.
It filters out internal Django frames (django/db/models/* and django/template/*) to find the first frame belonging to your codebase. If that specific line of user code executes more times than NPLUS1_HUNTER_THRESHOLD, a warning is logged or an exception is raised.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_nplus1_hunter-1.0.0.tar.gz.
File metadata
- Download URL: django_nplus1_hunter-1.0.0.tar.gz
- Upload date:
- Size: 10.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ce702549517b70fde839f6fd672ea4bd675e1b036c1a892a924aca076c3429f
|
|
| MD5 |
8a5eeeca67dba379f210eeaf84694ea3
|
|
| BLAKE2b-256 |
d71d788fbf168342dc4b4a4f074c0cc46d4e615ad63641807a57cf7f54fe66cb
|
Provenance
The following attestation bundles were made for django_nplus1_hunter-1.0.0.tar.gz:
Publisher:
publish.yml on iamjalipo/django-nplus1-hunter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_nplus1_hunter-1.0.0.tar.gz -
Subject digest:
8ce702549517b70fde839f6fd672ea4bd675e1b036c1a892a924aca076c3429f - Sigstore transparency entry: 1732667870
- Sigstore integration time:
-
Permalink:
iamjalipo/django-nplus1-hunter@1b7a893e16b4f0b991c6e34effa594e23fe7c579 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/iamjalipo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1b7a893e16b4f0b991c6e34effa594e23fe7c579 -
Trigger Event:
push
-
Statement type:
File details
Details for the file django_nplus1_hunter-1.0.0-py3-none-any.whl.
File metadata
- Download URL: django_nplus1_hunter-1.0.0-py3-none-any.whl
- Upload date:
- Size: 8.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e240199781347de42023cdea735755fcbe4c04954b57feb26ad51d28906e52d
|
|
| MD5 |
5a9f3984693068375c8c12b8a9c47f0f
|
|
| BLAKE2b-256 |
f7ef06f0a81634eb263f4c42131433d2e0fc16a7925246a19fac3258d6e8c2bd
|
Provenance
The following attestation bundles were made for django_nplus1_hunter-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on iamjalipo/django-nplus1-hunter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_nplus1_hunter-1.0.0-py3-none-any.whl -
Subject digest:
1e240199781347de42023cdea735755fcbe4c04954b57feb26ad51d28906e52d - Sigstore transparency entry: 1732667912
- Sigstore integration time:
-
Permalink:
iamjalipo/django-nplus1-hunter@1b7a893e16b4f0b991c6e34effa594e23fe7c579 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/iamjalipo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1b7a893e16b4f0b991c6e34effa594e23fe7c579 -
Trigger Event:
push
-
Statement type: