Skip to main content

Django extension providing async capabilities for database and other components

Project description

Django Async Backend

๐Ÿš€ Installation & Django Integration

1. Install the package

pip install django-async-backend

2. Django settings

In your settings.py, set the database engine to use the async backend

DATABASES = {
    "default": {
        "ENGINE": "django_async_backend.db.backends.postgresql",
        ...
    },
}

Make sure your Django app (and any other required apps) are listed in INSTALLED_APPS

INSTALLED_APPS = [
    ...
    "django_async_backend",
    ...
]

Connection Pooling

โš ๏ธ Connection pooling is not supported when running under a WSGI server (including the Django development server), because WSGI creates a new event loop for each request. This prevents reliable management of connection pool state. To disable the warning, set ASYNC_BACKEND_DISABLE_POOL_WARNING=True


Middleware

When running under ASGI, add close_async_connections to MIDDLEWARE so connections are returned to the pool at the end of each request. Django's request_finished signal only closes sync connections.

MIDDLEWARE = [
    "django_async_backend.middleware.close_async_connections",
    ...
]

Connection Handler

The connection handler manages database connections for your async backend.

from django_async_backend.db import async_connections

connection = async_connections['default']

async with await connection.cursor() as cursor:
    await cursor.execute("SELECT ...")
    rows = await cursor.fetchall()

await connection.close()
  • Connections are reused and managed automatically.
  • Use await connection.close() to manually close a connection if needed.

Cursor

Async cursors provide the following methods:

  • execute
  • executemany
  • fetchone
  • fetchmany
  • fetchall
async with await connection.cursor() as cursor:
    await cursor.execute("SELECT 1")
    row = await cursor.fetchone()

Async Transactions with async_atomic

Basic Usage

Use async_atomic to run async database operations atomically. All changes inside the block are committed together; if an error occurs, all changes are rolled back.

from django_async_backend.db.transaction import async_atomic

async with async_atomic():
    await create_instance(1)
    # If no error, changes are committed
    # If error, changes are rolled back

Rollback on Error

If an exception is raised inside the block, all changes are rolled back:

async with async_atomic():
    await create_instance(1)
    raise Exception("fail")  # Nothing is committed

Nested Transactions (Savepoints)

You can nest async_atomic blocks. Each inner block creates a savepoint. If an error occurs in the inner block, only its changes are rolled back; outer changes remain.

async with async_atomic():
    await create_instance(1)
    try:
        async with async_atomic():
            await create_instance(2)
            raise Exception("fail inner")  # Only instance 2 is rolled back
    except Exception:
        pass
# Only instance 1 is in the database

Using on_commit with async transactions

You can register a callback to run after a successful transaction commit using connection.on_commit.

connection = async_connections[DEFAULT_DB_ALIAS]

async with async_atomic():
    await connection.on_commit(callback)

Writing Async Tests

AsyncioTestCase

Use for async tests that do not require database transactions.

from django_async_backend.test import AsyncioTestCase


class MyAsyncTests(AsyncioTestCase):
    async def asyncSetUp(self):
        # Setup code

    async def asyncTearDown(self):
        # Cleanup code

    async def test_something(self):
        # Your async test logic
        await do_async_stuff()

AsyncioTransactionTestCase

Use for async tests that need database transaction support (rollbacks, atomic blocks).

from django_async_backend.test import AsyncioTransactionTestCase


class MyTransactionTests(AsyncioTransactionTestCase):
    async def asyncSetUp(self):
        # Setup database

    async def asyncTearDown(self):
        # Cleanup database

    async def test_something(self):
        async with async_atomic():
            # DB operations here
            await do_db_stuff()

ORM support:

Manager:

from django.db import models, DEFAULT_DB_ALIAS
from django_async_backend.db import async_connections
from django_async_backend.db.models.manager import AsyncManager

class Book(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=100)

    async_object = AsyncManager()

    class Meta:
        db_table = "books"


async def main():
    async for i in Book.async_object.all():
        print(i.id)

    await async_connections[DEFAULT_DB_ALIAS].close()
methods supported comments
Model.objects.aget โœ…
Model.objects.acreate โŒ
Model.objects.acount โœ…
Model.objects.none โœ…
Model.objects.abulk_create โŒ
Model.objects.abulk_update โŒ
Model.objects.aget_or_create โŒ
Model.objects.aupdate_or_create โŒ
Model.objects.aearliest โœ…
Model.objects.alatest โœ…
Model.objects.afirst โœ…
Model.objects.alast โœ…
Model.objects.ain_bulk โœ…
Model.objects.adelete โŒ
Model.objects.aupdate โŒ
Model.objects.aexists โœ…
Model.objects.acontains โŒ
Model.objects.aexplain โœ…
Model.objects.araw โŒ
Model.objects.all โœ…
Model.objects.filter โœ…
Model.objects.exclude โœ…
Model.objects.complex_filter โœ…
Model.objects.union โœ…
Model.objects.intersection โœ…
Model.objects.difference โœ…
Model.objects.select_related โŒ
Model.objects.select_for_update โŒ
Model.objects.prefetch_related โŒ
Model.objects.annotate โœ…
Model.objects.order_by โœ…
Model.objects.distinct โœ…
Model.objects.extra โœ…
Model.objects.reverse โœ…
Model.objects.defer โŒ
Model.objects.only โŒ
Model.objects.using โœ…
Model.objects.resolve_expression โŒ
Model.objects.ordered โŒ
Model.objects.values โœ…
Model.objects.values_list โœ…
Model.objects.dates โŒ
Model.objects.datetimes โŒ
Model.objects.alias โŒ
__aiter__ โœ…
__repr__ โŒ
__len__ โŒ
__and__ โŒ
__or__ โŒ
__xor__ โŒ
__getitem__ โœ…
Model.objects.aiterator โŒ

RawQuerySet

Not supported โŒ

Model:

methods supported comments
Model.asave โŒ
Model.aupdate โŒ
Model.adelete โŒ
... โŒ

User Model / Manager

methods supported comments
User.is_authenticated โŒ
User.is_super_user โŒ
User.objects.acreate_user โŒ
... โŒ

โš™๏ธ Development Setup

Install pre-commit hooks:

pip install pre-commit
pre-commit install

Install dependencies:

poetry install --with dev

๐Ÿงช Running Tests

This project uses a comprehensive test suite powered by unittest.

To run tests:

docker-compose up postgres -d
DJANGO_SETTINGS_MODULE=settings poetry run python -m unittest discover -s tests

Integration tests run locally.

The django_async_backend.db.backends.postgresql backend is fully compatible with Django's default django.db.backends.postgresql backend, as it leverages the default implementation under the hood. To confirm this compatibility, run Django's test suite using the custom backend.

DATABASES = {
    "default": {
        "ENGINE": "django_async_backend.db.backends.postgresql",
        ...
    },
    "other": {
        "ENGINE": "django_async_backend.db.backends.postgresql",
        ...
    },
}

To execute them:

cd tests_django
docker-compose run --build --rm test_django_integration

DEP 0009

https://github.com/django/deps/blob/main/accepted/0009-async.rst

Whenever a new_connections() block is entered, Django sets a new context with new database connections.

To show show an example how it might looks like with a current implementation we have the independent_connection context manager.

import asyncio
from django_async_backend.db import async_connections

async def run_query():
    async with async_connections._independent_connection():
        conn = await async_connections['default']
        async with conn.cursor() as cursor:
            await cursor.execute("SELECT ...")
            return await cursor.fetchall()

results = await asyncio.gather(run_query(), run_query(), run_query())

It's just a concept that is not ready for production usage.

Project details


Download files

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

Source Distribution

django_async_backend-0.0.4.tar.gz (97.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_async_backend-0.0.4-py3-none-any.whl (104.1 kB view details)

Uploaded Python 3

File details

Details for the file django_async_backend-0.0.4.tar.gz.

File metadata

  • Download URL: django_async_backend-0.0.4.tar.gz
  • Upload date:
  • Size: 97.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.1 CPython/3.12.13 Linux/6.17.0-1010-azure

File hashes

Hashes for django_async_backend-0.0.4.tar.gz
Algorithm Hash digest
SHA256 af0146b02564557c47bc933d8d43255bef00f4fe8db3f9fb155a929728c3d601
MD5 cba848ffaf95e01215ad6346af0d32e0
BLAKE2b-256 9a6521322a80995d104f4f65afaf87bf0b9660e8d32a1d129040402be2368479

See more details on using hashes here.

File details

Details for the file django_async_backend-0.0.4-py3-none-any.whl.

File metadata

  • Download URL: django_async_backend-0.0.4-py3-none-any.whl
  • Upload date:
  • Size: 104.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.1 CPython/3.12.13 Linux/6.17.0-1010-azure

File hashes

Hashes for django_async_backend-0.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 c832b0024c2761c5291eaf1af467e77c88085341b40512fb0812b1a69a0f1f5a
MD5 2882322fbb383ffc88fdc00abe6b4ec2
BLAKE2b-256 54d363bf9cbb69eaec73d8fe6ef90d5a3d6c3fd1defdef6473251f9391e0e269

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