Skip to main content

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, and ManyToManyField (including reverse relationships).
  • Generic Relations: GenericForeignKey and reverse GenericRelations.
  • 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

  1. Raw Database Connections: If you run raw queries using external drivers bypassing Django's ORM (e.g., using a raw psycopg2 or sqlite3 connection directly), Django's wrapper is bypassed and the hunter cannot capture it.
  2. Polymorphic Prefetches: prefetch_related on highly polymorphic relations (like a GenericForeignKey pointing to 3+ different model types) will execute 1 + C queries (where C is the number of content types). If the sum of these queries exceeds the NPLUS1_HUNTER_THRESHOLD (default 3), the prefetch line will trigger a false-positive warning.

Installation & Setup

  1. Install the package via pip:

    pip install django-nplus1-hunter
    
  2. 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',
        ]
    
  3. 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

django_nplus1_hunter-0.2.0.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_nplus1_hunter-0.2.0-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

File details

Details for the file django_nplus1_hunter-0.2.0.tar.gz.

File metadata

  • Download URL: django_nplus1_hunter-0.2.0.tar.gz
  • Upload date:
  • Size: 10.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_nplus1_hunter-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b69ce7b8b723cb1f82f65094594b946011aa9d684c22188daa53101fc8c4b21e
MD5 92bf476fcfb46a5525e2f575c02eada7
BLAKE2b-256 40d8ee1e572bd24f0e88d33459f65ade147560ee80dbe38f3af338fafa095855

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_nplus1_hunter-0.2.0.tar.gz:

Publisher: publish.yml on iamjalipo/django-nplus1-hunter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file django_nplus1_hunter-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_nplus1_hunter-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 562e4baca257a731d42b12175ab556c71a94a9448a47992226a3279200f60040
MD5 70d0cf9fee4daf3987f3a87f502bae47
BLAKE2b-256 912be39a773399f7f06aeb60349149f10ea5ad495fe98568d1c7c6bd740912b4

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_nplus1_hunter-0.2.0-py3-none-any.whl:

Publisher: publish.yml on iamjalipo/django-nplus1-hunter

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page