Skip to main content

Core Utilities and Services

This project provides a suite of core utilities and services that handle common web development tasks such as caching, data pagination, email delivery, object serialization, and decoupled event notification via signals. These utilities are designed to be backend-agnostic and support both synchronous and asynchronous execution patterns.

Caching

The caching framework provides a unified API for interacting with various cache backends (e.g., Memcached, Redis, or local memory).

The Cache Proxy

The primary entry point for caching is the cache variable defined in django/core/cache/__init__.py. This is a ConnectionProxy that points to the default cache alias defined in the CACHES setting.

from django.core.cache import cache

# Basic CRUD operations
cache.set("my_key", "my_value", timeout=300)
value = cache.get("my_key")
cache.delete("my_key")

Cache Handler and Backends

The CacheHandler class (a subclass of BaseConnectionHandler) manages the lifecycle of cache connections. It uses the CACHES configuration to instantiate the appropriate backend class via import_string.

For scenarios requiring multiple cache locations, you can use the caches dict-like object:

from django.core.cache import caches

# Access a specific cache backend defined in settings.CACHES
redis_cache = caches["redis"]
redis_cache.set("key", "value")

The framework also ensures that caches are cleaned up at the end of a request cycle by connecting the close_caches function to the request_finished signal.

Pagination

Pagination is handled by the Paginator and AsyncPaginator classes in django/core/paginator.py. These classes split a list of objects into manageable Page objects.

Synchronous Pagination

The standard Paginator is used for in-memory lists or standard Django QuerySets.

from django.core.paginator import Paginator

objects = ["a", "b", "c", "d"]
paginator = Paginator(objects, 2) # 2 items per page

page1 = paginator.page(1)
# page1.object_list -> ["a", "b"]

Asynchronous Pagination

For asynchronous environments, AsyncPaginator provides coroutine-based methods to avoid blocking the event loop during count or slicing operations.

from django.core.paginator import AsyncPaginator

async def get_async_page(request):
queryset = MyModel.objects.all()
paginator = AsyncPaginator(queryset, 10)

# Methods must be awaited
count = await paginator.acount()
page = await paginator.aget_page(request.GET.get("page"))

# AsyncPage must have its object list awaited before indexing
object_list = await page.aget_object_list()
return object_list

Warning: The Paginator will issue an UnorderedObjectListWarning if the provided object_list is a QuerySet without an explicit ordering, as this can lead to inconsistent results across pages.

Email Services

The email utility provides high-level functions for sending messages and low-level classes for constructing complex emails.

Sending Mail

The send_mail function in django/core/mail/__init__.py is the most common way to send email. It wraps the EmailMultiAlternatives class to support both plain text and HTML content.

from django.core.mail import send_mail

send_mail(
subject="Notification",
message="Plain text message",
from_email="admin@example.com",
recipient_list=["user@example.com"],
html_message="<p>HTML message</p>"
)

Mailers and Backends

This codebase is transitioning from the EMAIL_BACKEND setting to a more flexible MAILERS configuration. The MailersHandler manages these connections. While get_connection() is still available for backward compatibility, it is deprecated in favor of using the mailers handler or the using parameter in send_mail.

Complex Messages

For attachments or custom headers, use the EmailMessage class directly from django/core/mail/message.py.

Serialization

The serialization framework converts Django models into formats like JSON, XML, or YAML.

Serializing Data

The serialize function in django/core/serializers/__init__.py takes a format and a queryset (or any iterator of model instances).

from django.core import serializers
from myapp.models import MyModel

data = serializers.serialize("json", MyModel.objects.all())

Deserializing Data

The deserialize function returns an iterator of DeserializedObject instances. Note that these instances are not automatically saved to the database.

for obj in serializers.deserialize("json", data):
# obj.object is the actual model instance
obj.object.save()

Built-in formats include xml, python, json, yaml, and jsonl. Custom serializers can be registered via the SERIALIZATION_MODULES setting.

Signals

Signals allow decoupled applications to get notified when certain actions occur elsewhere in the framework.

Defining and Connecting Signals

Signals are instances of django.dispatch.Signal. You can connect "receiver" functions to these signals using the connect method or the @receiver decorator.

from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")

Sending Signals

Signals can be sent synchronously using send() or asynchronously using asend().

from django.dispatch import Signal

my_signal = Signal()

# Synchronous send
my_signal.send(sender=None, custom_arg="value")

# Asynchronous send (must be awaited)
await my_signal.asend(sender=None, custom_arg="value")

The Signal class in django/dispatch/dispatcher.py handles the execution of both sync and async receivers. When send() is called, any asynchronous receivers are executed concurrently using asyncio.TaskGroup via a call to async_to_sync. Conversely, asend() wraps synchronous receivers in sync_to_async.