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.1.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.1-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: django_nplus1_hunter-0.2.1.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.1.tar.gz
Algorithm Hash digest
SHA256 ae63a55fb0822df3f76d0d030bca8d002932808d93b33898e288079da6e48b49
MD5 33ab3fbe1a67b227d613f80b796564e4
BLAKE2b-256 563d6e20469cf261ad8325cb09800a61f5d03c9273193ec6b2757a287d52df12

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for django_nplus1_hunter-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 55dd8054920b81ed517a562c645265b503e59e27b8191fdd34c6e423e17555b4
MD5 41640de9fd83cab761bf82bb0d84699b
BLAKE2b-256 0252aedda2defa3ef890328cfd67292921eba9e20b8089ca1711a5da15df425e

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_nplus1_hunter-0.2.1-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