Skip to main content

A shield transforms many scattered database query paths into two controlled paths

django-fetch-guard

Prevent Django N+1 queries and accidental lazy database fetches—with explicit contracts or automatic batching.

PyPI version Monthly downloads Listed on Django Packages Python 3.10+ Django 4.2–6.1 License MIT Status alpha

django-fetch-guard gives Django querysets an explicit fetch policy. Use peers to collapse common N+1 patterns into batched queries, or use strict to fail immediately when code touches a field or relation that was not loaded intentionally.

It uses Django 6.1's native fetch modes when available and provides a carefully scoped compatibility engine for Django 4.2 through 6.0.

Normal mode performs one plus N queries, peers performs two, and strict uses one explicit query or blocks the access

Why this exists

This innocent loop can perform 101 queries for 100 books:

books = Book.objects.all()  # 1 query

for book in books:
    print(book.author.name)  # N more queries

Fetch guard lets the queryset state its contract:

# Batch related authors: usually 2 queries in total.
books = Book.objects.fetch_peers()

# Or require an explicit plan: 1 joined query, no hidden fetching allowed.
books = Book.objects.select_related("author").strict()

That contract follows the queryset into serializers, views, templates, and service functions instead of relying on every caller to remember a query-count assertion.

Compatibility

Django version fetch_peers() strict()
6.1 Native on-demand FETCH_PEERS Native RAISE mode
4.2–6.0 Prefetch direct or named relations Compatibility model mixin

The public API is the same across supported versions. See the compatibility guide for exact differences and honest limitations.

Installation

Install the package from PyPI:

python -m pip install django-fetch-guard

For Django REST Framework integration:

python -m pip install "django-fetch-guard[drf]"

Install the current development version directly from GitHub:

python -m pip install "django-fetch-guard @ git+https://github.com/yassinbahri/django-fetch-guard.git"

Contributors should use the editable setup in CONTRIBUTING.md.

Five-minute setup

1. Add the mixin and manager

from django.db import models
from fetch_guard import FetchGuardModelMixin, GuardedManager


class Author(models.Model):
    name = models.CharField(max_length=100)


class Book(FetchGuardModelMixin, models.Model):
    title = models.CharField(max_length=150)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

    objects = GuardedManager(default_mode="raise")

The mixin is needed only by the Django 4.2–6.0 strict compatibility engine, but keeping it makes the model portable. It adds no database fields and requires no migration.

2. Choose a mode

Book.objects.strict()       # Block an unloaded field or relation.
Book.objects.fetch_peers()  # Batch missing direct relations.
Book.objects.normal()       # Restore traditional lazy loading.

Use explicit relations for consistent eager behavior on every version:

Book.objects.fetch_peers("author")

3. Make strict querysets complete

books = Book.objects.select_related("author").strict()

for book in books:
    print(book.author.name)  # Already loaded; no extra query.

Without select_related("author"), the relation access raises the portable exception:

from fetch_guard import FieldFetchBlocked

Import this package exception rather than Django's exception directly. It maps to Django's native class on 6.1 and the compatibility class on older versions.

Project-wide defaults and checks

Add the application when you want settings validation:

INSTALLED_APPS = [
    # ...
    "fetch_guard",
]

FETCH_GUARD = {
    "DEFAULT_MODE": "raise",
}

Then the manager can inherit the project setting:

objects = GuardedManager()

Validate it with:

python manage.py check --tag fetch_guard

Guard any queryset

An existing manager can remain unchanged:

from fetch_guard import guard_queryset

books = guard_queryset(Book.objects.filter(is_published=True), "raise")

For custom manager/queryset composition, see the getting-started guide.

Django REST Framework

from fetch_guard.drf import FetchGuardMixin
from rest_framework.viewsets import ModelViewSet


class BookViewSet(FetchGuardMixin, ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializer
    fetch_guard = {
        "list": "peers",
        "retrieve": "raise",
        "default": "raise",
    }

    def get_queryset(self):
        queryset = super().get_queryset()
        if self.action == "retrieve":
            queryset = queryset.select_related("author")
        return queryset

Read the DRF guide for resolution order, opt-outs, nested serializers, and queryset planning.

Pytest

The package registers a focused fixture:

import pytest
from fetch_guard import FieldFetchBlocked


@pytest.mark.django_db
def test_book_serializer(fetch_guard):
    book = fetch_guard.strict(Book.objects.all()).first()

    with pytest.raises(FieldFetchBlocked):
        serialize_book(book)

The fixture affects only the queryset passed to it; it does not monkey-patch Django globally. See the testing guide.

Verified integration behavior

The test suite includes a minimal Django application and real HTTP server coverage. It measures the same serialization path under every policy:

Policy Queries for two books Result
Normal 3 Demonstrates N+1
Peers 2 Authors loaded in one batch
Strict with select_related() 1 Explicit query plan succeeds
Strict without related loading Fetch is blocked

Run all unit and integration tests with:

python -m pip install -e ".[test]"
python -m pytest

Documentation

Current status

0.1.0 is the initial alpha release. The core API, legacy compatibility engine, DRF mixin, pytest fixture, system check, and cross-version CI matrix are implemented. The tests include Django's request client and a live HTTP server. Rich call-site diagnostics and production audit sampling are planned for later releases.

License

MIT

Release files for django-fetch-guard 0.1.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-fetch-guard 0.1.1
File Size Uploaded
django_fetch_guard-0.1.1.tar.gz 1.7 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-fetch-guard 0.1.1
File Interpreter ABI Platform
django_fetch_guard-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 1.7 MB

Release files / django_fetch_guard-0.1.1.tar.gz

Download URL django_fetch_guard-0.1.1.tar.gz
Size 1.7 MB
Tags Source
SHA-256 checksum
How to use checksums
e602883ce16f403f35d8a7741f86520b9de7bb188148e2b6508dfdb75356c942
BLAKE2b-256 checksum
How to use checksums
2f54ac3e69b3021aa22f76401b9f58a10c1f7e00200ca8b5298e20a363d4e53d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release files / django_fetch_guard-0.1.1-py3-none-any.whl

Download URL django_fetch_guard-0.1.1-py3-none-any.whl
Size 12.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fa478b92553aa0c3e52e24d8eed14e6255ea55b5126ceef919cdeec3ef27fd4f
BLAKE2b-256 checksum
How to use checksums
6142f057c8524d6acb4d203a4125e59ec13567b25df3abd9ecc43da799212988
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 26, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.2

2 release files

This release

0.1.1 This release

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