Metadata-Version: 2.4
Name: django-nplus1-hunter
Version: 1.2.0
Summary: A development tool to detect N+1 queries in Django.
Project-URL: Documentation, https://github.com/iamjalipo/django-nplus1-hunter#readme
Project-URL: Issues, https://github.com/iamjalipo/django-nplus1-hunter/issues
Project-URL: Source, https://github.com/iamjalipo/django-nplus1-hunter
Author-email: Developer <developer@example.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Framework :: Django :: 5.2
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: django>=5.2
Description-Content-Type: text/markdown

# 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 `GenericRelation`s.
* **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:
   ```bash
   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:
   ```python
   # 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):
   ```python
   # 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`:

```python
# 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
```

## 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.
