Skip to main content

django-memoized-prefetch

A Django package that provides efficient memoized prefetching for processing data in chunks, reducing database queries through intelligent caching. In some cases it can be useful even when not processing data in chunks, for example, when there are multiple foreign keys to the same table.

Overview

django-memoized-prefetch optimizes Django ORM queries when processing large datasets by:

  • Reusing previously fetched objects across chunks
  • Memoizing prefetched objects using LRU (Least Recently Used) cache
  • Supporting both foreign key and many-to-many relationships
  • Minimizing database queries across chunk processing operations

Installation

pip install django-memoized-prefetch

Requirements

  • Python 3.9+
  • Django 4.2+
  • lru-dict 1.3.0+

Usage Examples

Models used in examples, click to expand
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=255)
    email = models.EmailField()

class Publisher(models.Model):
    name = models.CharField(max_length=255)
    country = models.CharField(max_length=100)

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

class Book(models.Model):
    title = models.CharField(max_length=255)
    isbn = models.CharField(max_length=13)
    author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
    translator = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="translations", null=True)
    publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE, related_name="books")
    categories = models.ManyToManyField(Category, related_name="books")

class Review(models.Model):
    book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name="reviews")
    rating = models.IntegerField()
    comment = models.TextField()

Basic Usage

Imagine you want to process all books, but there are too many of them to load them all into memory at once. You therefore need to process them in chunks.

If you use just native django, it will look something like this:

from chunkator import chunkator_page

for chunk in chunkator_page(Book.objects.all().prefetch_related("author", "translator", "publisher"), 10_000):
    for book in chunk:
        print(book.author.name, book.translator.name if book.translator is not None else None)
        print(book.publisher.name)

This will work, with two caveats:

  1. On each chunk, Django will make separate queries to fetch the author and translator
  2. The author, translator and publisher objects will be fetched from the database for each chunk

This is the primary usecase for this package. When used like this:

from django_memoized_prefetch import MemoizedPrefetch, MemoizedPrefetchConfig
from chunkator import chunkator_page

memoized_prefetch = MemoizedPrefetch(
    MemoizedPrefetchConfig(Author, ["author", "translator"]),
    MemoizedPrefetchConfig(Publisher, ["publisher"], prefetch_all=True),
)

for chunk in chunkator_page(Book.objects.all(), 10_000):
    memoized_prefetch.process_chunk(chunk)
    
    for book in chunk:
        print(book.author.name, book.translator.name if book.translator is not None else None)
        print(book.publisher.name)

The processing will be more efficient, because:

  1. All publishers will get fetched before processing any chunks, and they will be reused across all chunks
  2. The author and translator objects will be fetched using one query
  3. Any authors and translators that appeared in previous chunks will not be fetched again

Nested attributes

You can also prefetch nested attributes using both dotted notation and undersore notation, for example, in this example both would work.

memoized_prefetch = MemoizedPrefetch(
    MemoizedPrefetchConfig(Publisher, ["book.publisher"]),
    MemoizedPrefetchConfig(Author, ["book__author"]),
)

for chunk in chunkator_page(Review.objects.all(), 10000):
    memoized_prefetch.process_chunk(chunk)
    ...

Many-to-Many Relationships

Many-to-many relationships are supported as well, caching the target model, while fetching the through model for each chunk.

from django_memoized_prefetch import MemoizedPrefetch, MemoizedPrefetchConfig
from chunkator import chunkator_page

# Configure for many-to-many relationships
memoized_prefetch = MemoizedPrefetch(
    MemoizedPrefetchConfig(
        model=Category,
        attributes=["categories"],
        is_many_to_many=True,
        through_model=Book.categories.through,
        source_field="book_id",
        target_field="category_id",
    )
)

# Process books with their categories
for chunk in chunkator_page(Book.objects.all(), 10000):
    memoized_prefetch.process_chunk(chunk)
    
    for book in chunk:
        # Categories are prefetched and available
        category_names = [cat.name for cat in book.categories.all()]
        print(f"Book: {book.title}, Categories: {', '.join(category_names)}")

Usage outside chunked processing

If you have multiple foreign keys to the same table, this package can be used to optimise the database queries even when not processing data in chunks.

Configuration Options

MemoizedPrefetchConfig Parameters

  • model (required): The Django model class to prefetch
  • attributes (required): List of attribute names to prefetch on your objects
  • queryset (optional): Custom queryset for the model (for additional select_related/prefetch_related)
  • prefetch_all (optional, default: False): Whether to prefetch all objects at initialisation
  • lru_cache_size (optional, default: 10,000): Maximum number of objects to keep in cache
  • is_many_to_many (optional, default: False): Set to True for many-to-many relationships
  • through_model (optional): Through model for many-to-many relationships
  • source_field (optional): Source field name in the through model
  • target_field (optional): Target field name in the through model

Advanced Configuration

from django.db import models

# Custom queryset with select_related
config = MemoizedPrefetchConfig(
    model=Author,
    attributes=["author"],
    queryset=Author.objects.select_related(...),
    lru_cache_size=5000,
)

# Prefetch all objects at startup (useful for small, frequently accessed tables)
config = MemoizedPrefetchConfig(
    model=Publisher,
    attributes=["publisher"],
    prefetch_all=True,
)

Integrations with other packages.

The package automatically supports django-seal when available, all querysets which are sealable will be automatically sealed.

This package works when using django-tenants.

Best Practices

  1. Use appropriate cache sizes: Set lru_cache_size based on your expected data volume and available memory
  2. Prefetch related objects: Use custom querysets with select_related or prefetch_related for nested relationships
  3. Consider prefetch_all: Use prefetch_all=True for small, frequently accessed reference tables
  4. Process in reasonable chunks: Balance memory usage with query efficiency when choosing chunk sizes
  5. Monitor cache hit rates: Ensure your cache size is appropriate for your data access patterns

Testing

Run the test suite:

uv run pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Authors

Metadata

Release files for django-memoized-prefetch 0.1.2

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-memoized-prefetch 0.1.2
File Size Uploaded Provenance
django_memoized_prefetch-0.1.2.tar.gz 17.3 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for django-memoized-prefetch 0.1.2
File Interpreter ABI Platform
django_memoized_prefetch-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 26.3 kB

Release files / django_memoized_prefetch-0.1.2.tar.gz

Download URL django_memoized_prefetch-0.1.2.tar.gz
Size 17.3 kB
Tags Source
SHA-256 checksum
How to use checksums
7cd051bd29500dd892794276fb017040d7e752ef0f436448a0d38318817b0ced
BLAKE2b-256 checksum
How to use checksums
7594181eb0c6fda8a5109220997a1b38c59cf541051ef097b957fcf93219734d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Nov 10, 2025.

Transparency log

Release files / django_memoized_prefetch-0.1.2-py3-none-any.whl

Download URL django_memoized_prefetch-0.1.2-py3-none-any.whl
Size 9.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a58e914b90d16921006a74eb435f04ebea9744030b0e613c1f58bf6d4c4ceac6
BLAKE2b-256 checksum
How to use checksums
5f410b517f6cc0106d34f8d2e28b5e9264438ca524d6612c71142c2d53ff4962
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Nov 10, 2025.

Transparency log

Security for django-memoized-prefetch 0.1.2

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_memoized_prefetch-0.1.2.tar.gz

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 Nov 10, 2025.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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