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.
  • 📊 HTML Report Generation: Beautiful dark-themed HTML reports with summary cards, test results table, and SQL query details.
  • 🔔 CI/CD GitHub Actions Ready: Ready-made workflow template for automatic N+1 detection on every Pull Request.
  • 📈 Query Count Trend Tracking: JSON-based run history with regression/improvement detection across Pytest runs.

📖 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]

📊 HTML Report Generation (New in v0.2.0)

Generate a premium dark-themed HTML report after every Pytest run:

pytest --query-guard-report=report.html

The report includes:

  • Summary Cards: Total tests, passed/failed counts, total queries, and N+1 detections at a glance.
  • Test Results Table: Per-test query count, max allowed, N+1 status, and duration.
  • SQL Query Details: Full SQL breakdown for failed tests with exact query text and execution time.

📈 Query Count Trend Tracking (New in v0.2.0)

Track query performance across multiple Pytest runs with automatic regression detection:

pytest --query-guard-trend=.query_guard_history.json

Terminal output after each run:

========================================================================
===================== QUERY GUARD TREND COMPARISON =====================
========================================================================
  Current Run:  3 queries across 5 tests
  Previous Run: 30 queries across 5 tests

  [IMPROVEMENT] Query Delta: -27 queries
========================================================================

Combine both flags for full reporting:

pytest --query-guard-report=report.html --query-guard-trend=.query_guard_history.json

🔔 CI/CD GitHub Actions Integration (New in v0.2.0)

Copy the ready-made workflow file to your project:

mkdir -p .github/workflows
cp query_guard_ci.yml .github/workflows/

Or add to your existing workflow:

- name: Run Query Guard Tests
  run: |
    pip install django-query-guard
    pytest --query-guard-report=report.html --query-guard-trend=.query_guard_history.json -v

- name: Upload Query Guard Report
  if: always()
  uses: actions/upload-artifact@v4
  with:
    name: query-guard-report
    path: report.html

This ensures every Pull Request is automatically checked for N+1 query regressions before merging!


🛠️ 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

Release files for django-query-guard 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-query-guard 0.2.1
File Size Uploaded
django_query_guard-0.2.1.tar.gz 20.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-query-guard 0.2.1
File Interpreter ABI Platform
django_query_guard-0.2.1-py2.py3-none-any.whl Python 2, Python 3 none any Details

Total release size: 39.8 kB

Release files / django_query_guard-0.2.1.tar.gz

Download URL django_query_guard-0.2.1.tar.gz
Size 20.9 kB
Tags Source
SHA-256 checksum
How to use checksums
158868fc83d640dab8e52a6c85c9c581eb3489f9e5ca9061cef9b71aff1ac68d
BLAKE2b-256 checksum
How to use checksums
fedc06b5d6778259e2c6252e8dd85d7ef2cc8d9e7c147a026930f42003e349c7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release files / django_query_guard-0.2.1-py2.py3-none-any.whl

Download URL django_query_guard-0.2.1-py2.py3-none-any.whl
Size 18.8 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
cd29d46556762d4e77f4ff82a632d88cc2f6a32f9a166c931447d787b4c96e6e
BLAKE2b-256 checksum
How to use checksums
81d31bb04ae3e05fe6d7ae9c200768ef68e479759201d16f913ebe7e034b3b22
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.3

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page