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-1.0.0.tar.gz (10.7 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-1.0.0-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

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

Hashes for django_nplus1_hunter-1.0.0.tar.gz
Algorithm Hash digest
SHA256 8ce702549517b70fde839f6fd672ea4bd675e1b036c1a892a924aca076c3429f
MD5 8a5eeeca67dba379f210eeaf84694ea3
BLAKE2b-256 d71d788fbf168342dc4b4a4f074c0cc46d4e615ad63641807a57cf7f54fe66cb

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_nplus1_hunter-1.0.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-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_nplus1_hunter-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1e240199781347de42023cdea735755fcbe4c04954b57feb26ad51d28906e52d
MD5 5a9f3984693068375c8c12b8a9c47f0f
BLAKE2b-256 f7ef06f0a81634eb263f4c42131433d2e0fc16a7925246a19fac3258d6e8c2bd

See more details on using hashes here.

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

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