Metadata-Version: 2.4
Name: django-nplus1-hunter
Version: 0.2.1
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.

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

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