Skip to main content

django-query-guard 🛡️

Created by:

Atiqur Rahman
Software QA Engineer | SDET | Test Automation Architect | Microsoft Contributor | Open Source Contributor
📍 Dhaka, Bangladesh | ✉️ rahman.atiqur.pro@gmail.com | 🌐 LinkedIn | 🐙 GitHub Profile


PyPI version License: MIT Python Versions Django Versions Pytest Integration

Stop N+1 database queries before they hit production.
django-query-guard is an ultra-fast, zero-dependency Python & Pytest plugin that automatically detects N+1 queries and enforces strict query count limits in your Django test suites and backend code.


📦 Installation (2 Ways to Install)

You can install django-query-guard using either of the two official methods below:

1️⃣ Standard Installation via PyPI (Recommended)

Install the official stable package directly from the PyPI Repository:

pip install django-query-guard

For development and testing tools (Pytest & Pytest-Django):

pip install django-query-guard[dev]

2️⃣ Direct Installation via GitHub (Latest Bleeding-Edge Version)

Install the latest main branch version directly from the GitHub Source Repository:

pip install git+https://github.com/atiqur-rahman-pro/django-query-guard.git

💡 Why Django Developers Need This

In Django, the ORM makes database queries so easy that it’s terrifyingly easy to accidentally write N+1 queries:

# ❌ THE N+1 ACCIDENT
# Fetches 100 users, then executes 100 individual queries for each profile!
# Total: 101 Database Queries! 🐌
users = User.objects.all()
profiles = [user.profile.bio for user in users]

The Solution: django-query-guard

Instead of relying on manual code reviews or checking server logs, django-query-guard turns query limits and N+1 prevention into automated, enforceable CI/CD tests:

# ✅ THE SOLUTION
import pytest

@pytest.mark.django_db
@pytest.mark.query_guard(max_queries=2, detect_n_plus_one=True)
def test_user_profiles_api(client):
    response = client.get("/api/users/")
    assert response.status_code == 200

If your endpoint accidentally runs 101 queries instead of 2, Pytest fails instantly with an exact breakdown of which query repeated! 💥


🔥 Key Features

  • 🎯 Pytest Marker Integration: Simple @pytest.mark.query_guard(max_queries=N).
  • 🧠 Smart SQL Normalization: Normalizes SQL queries (e.g. WHERE id = 1 and WHERE id = 2 are recognized as the exact same query pattern).
  • 🛡️ Zero Heavy Dependencies: Built purely on Django's native database execution wrapper and standard library.
  • Ultra-Fast: Sub-millisecond execution overhead (< 1ms per test).
  • 🐍 Python 3.10+ & Django 4.0+ Compatible: Works out-of-the-box with all modern Django versions.

📖 A to Z Guide: How to Use

1. Using @pytest.mark.query_guard in Pytest

Simply decorate any test function that accesses the database:

import pytest

@pytest.mark.django_db
@pytest.mark.query_guard(max_queries=3)
def test_fetch_dashboard_data(client):
    response = client.get("/api/dashboard/")
    assert response.status_code == 200

Parameters for query_guard Marker:

Parameter Type Default Description
max_queries int None Maximum allowed total SQL queries. Raises QueryCountExceededError if exceeded.
detect_n_plus_one bool True Automatically detect N+1 query patterns.
n_plus_one_threshold int 2 Minimum repetitions of a normalized query required to trigger N+1 detection.

2. Strict N+1 Detection Mode

Even if your total query count is under max_queries, a loop executing duplicate queries will trigger an NPlusOneQueryError:

@pytest.mark.django_db
@pytest.mark.query_guard(detect_n_plus_one=True, n_plus_one_threshold=2)
def test_user_loop():
    # Executes SELECT * FROM auth_user WHERE id = ? twice
    for user_id in [10, 20]:
        User.objects.get(id=user_id)

3. Using as a Context Manager (with query_guard(...))

You can also use query_guard directly inside Django views, Celery tasks, management commands, or standard unit tests:

from django_query_guard import query_guard, NPlusOneQueryError

def process_latest_orders():
    with query_guard(max_queries=5, detect_n_plus_one=True):
        orders = Order.objects.filter(status="pending").select_related("user")
        for order in orders:
            print(order.user.email)

4. How to Fix Detected N+1 Queries in Django

When django-query-guard catches an N+1 query, fix it using Django's ORM optimization methods:

Fix 1: Use select_related for Foreign Keys (One-to-One / Many-to-One)

# ❌ Before (N+1 Queries)
books = Book.objects.all()
authors = [book.author.name for book in books]

# ✅ After (1 Query via JOIN)
books = Book.objects.select_related("author").all()
authors = [book.author.name for book in books]

Fix 2: Use prefetch_related for Reverse Foreign Keys / Many-to-Many

# ❌ Before (N+1 Queries)
authors = Author.objects.all()
books = [author.books.all() for author in authors]

# ✅ After (2 Queries Total)
authors = Author.objects.prefetch_related("books").all()
books = [author.books.all() for author in authors]

🛠️ Custom Exceptions

django-query-guard provides explicit exceptions to catch in your application or test suites:

  • QueryGuardError: Base class for all package exceptions.
  • QueryCountExceededError: Raised when query count exceeds max_queries.
  • NPlusOneQueryError: Raised when duplicate normalized SQL statements are executed.

📄 License

This project is licensed under the MIT License.


👤 Author & Maintainer

Atiqur Rahman
Software QA Engineer | SDET | Test Automation Architect | Microsoft Contributor | Open Source Contributor
📍 Dhaka, Bangladesh | ✉️ rahman.atiqur.pro@gmail.com | 🌐 LinkedIn | 🐙 GitHub Profile

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_query_guard-0.1.2.tar.gz (8.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_query_guard-0.1.2-py2.py3-none-any.whl (10.4 kB view details)

Uploaded Python 2Python 3

File details

Details for the file django_query_guard-0.1.2.tar.gz.

File metadata

  • Download URL: django_query_guard-0.1.2.tar.gz
  • Upload date:
  • Size: 8.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.3

File hashes

Hashes for django_query_guard-0.1.2.tar.gz
Algorithm Hash digest
SHA256 4fd9a478b784acfc3f08d2d8bd21c1f6912f01d42c9386cbf2146322068506b2
MD5 7dae3881c4876afa708812eed3796706
BLAKE2b-256 63855a49086e5980beb6ead7694e4de2f84f44cef2de1a2482b67300cc0d1757

See more details on using hashes here.

File details

Details for the file django_query_guard-0.1.2-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for django_query_guard-0.1.2-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 018f6200490c308454129eb8c0c0e85f171801d779af52e9b6d103016ef48605
MD5 11bafd1e394f876b78e5bafedd756a18
BLAKE2b-256 4b9f8f62f7751e77c942f9c6686f55dc8bb327d4da135aef37f0e1e526e16393

See more details on using hashes here.

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