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.
  • VS Code Extension: Integrates directly with your editor to show N+1 warnings as squiggly lines exactly where they occur in your code.
  • 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.

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

# Enable writing JSON lines to a log file for IDE integration (like VS Code).
# Default: True
NPLUS1_HUNTER_VSCODE_INTEGRATION = True

# The file path where N+1 events are logged for the IDE extension to read.
# Default: '.nplus1-hunter.jsonl' in your project BASE_DIR
NPLUS1_HUNTER_LOG_FILE = '.nplus1-hunter.jsonl'

VS Code Extension

django-nplus1-hunter comes with a companion VS Code extension! When an N+1 query is detected, the extension will automatically highlight the exact line of code in your editor with a yellow warning (squiggly line). Hovering over it will display the total query count, execution time, sample SQL, and an optimization tip.

Setup:

  1. The middleware is already configured to output IDE logs by default (via NPLUS1_HUNTER_VSCODE_INTEGRATION = True).
  2. Install the Extension:
    • Download the latest django-nplus1-hunter-x.x.x.vsix file from the GitHub Releases page.
    • Open VS Code, go to the Extensions view (Ctrl+Shift+X), click the ... menu at the top right, and select "Install from VSIX...". Choose the downloaded file.
  3. The extension will automatically activate in your workspace and watch for N+1 warnings.

Integrity & Security

This package was designed with strict security and memory integrity constraints:

  • No Data Leakage: While it intercepts both the raw SQL and its parameters, it deliberately only logs the SQL string and ignores the parameter arguments. This guarantees that sensitive user inputs (like passwords or PII) are never printed to your local terminal.
  • Thread & Async Safety: It utilizes Python's modern contextvars.ContextVar instead of threading.local(). This guarantees absolute thread-safety and prevents query data from bleeding between concurrent requests, which is crucial for ASGI and async Django deployments.
  • Memory Safe: The query tracking context is wrapped in a strict try...finally block that reliably purges memory after every request, preventing memory leaks even if views raise unexpected exceptions.
  • Precise Traceback Filtering: The package intelligently filters out Django core internals without accidentally filtering out important 3rd-party packages (like django-rest-framework), ensuring the stack trace correctly points to your view or serializer.

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.2.1.tar.gz (592.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-1.2.1-py3-none-any.whl (9.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: django_nplus1_hunter-1.2.1.tar.gz
  • Upload date:
  • Size: 592.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-1.2.1.tar.gz
Algorithm Hash digest
SHA256 7542b7c0d1425581956ec27ee93162daf70c4696ee893b7ef6555fcebeddf0c5
MD5 8ac4a93537cbb6ebad138450da9e5754
BLAKE2b-256 71d256deb5064e9ce9a7aa5d02ed6013cc56d35085a69f5529a7005ae2f065c4

See more details on using hashes here.

Provenance

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

File metadata

File hashes

Hashes for django_nplus1_hunter-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b9f93aaa5d02e7147477234a2b07a75fe490ba157e388284b30eb803cf815ba3
MD5 8dc5998ae7a678bb9e4b638ff479b2b9
BLAKE2b-256 249d7abad436afd5515cf385599c0d0062ca321e4f2711602ad0894361898d4b

See more details on using hashes here.

Provenance

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