Async Battery Support

Battery handler methods can be defined with async def. djxi detects this at class-definition time and wraps them in proper ASGI-compatible view functions so Django can serve them without blocking the event loop.

Writing an async handler

Use async def for the handler method and await self.arender_section() for the rendering step:

from djxi import DXEndpointBattery, dx_get, dx_post

class AsyncItemBattery(DXEndpointBattery):
    inline_template = """
    <dx-section name="item-list">
      <ul>{% for item in items %}<li>{{ item.title }}</li>{% endfor %}</ul>
    </dx-section>
    """

    @dx_get("items/", name="list")
    async def list(self, request):
        # Django ORM async API
        items = [item async for item in Item.objects.order_by("-pk")]
        return await self.arender_section(request, "item-list", {"items": items})

    @dx_post("items/create", name="create")
    async def create(self, request):
        title = request.POST.get("title", "").strip()
        if title:
            await Item.objects.acreate(title=title)
        items = [item async for item in Item.objects.order_by("-pk")]
        return await self.arender_section(request, "item-list", {"items": items})

Mixing sync and async handlers

A single battery can contain both sync and async methods. djxi generates the correct (sync or async) view wrapper for each decorated method independently:

class MixedBattery(DXEndpointBattery):
    inline_template = "..."

    @dx_get("light/", name="light")
    def light(self, request):
        # No async I/O needed; sync is fine
        return self.render_section(request, "light")

    @dx_get("heavy/", name="heavy")
    async def heavy(self, request):
        # Needs async database or HTTP calls
        result = await some_async_api_call()
        return await self.arender_section(request, "heavy", {"result": result})

arender_section()

DXEndpointBattery.arender_section() is an awaitable counterpart to render_section(). Django’s template rendering is synchronous; this method wraps it with asgiref.sync.sync_to_async so the event loop is not blocked.

response = await self.arender_section(request, section_name, context)

It accepts the same arguments as render_section():

DXEndpointBattery.arender_section(self, request, section_name, context=None)
Parameters:
  • request – The incoming HttpRequest.

  • section_name – Name of the <dx-section> to render.

  • context – Optional dict passed to the template.

Returns:

Awaitable that resolves to an HttpResponse.

Permission checks with async handlers

check_permissions() is always synchronous. The async view wrapper calls it before await-ing the handler, so it works the same way for both sync and async methods:

class ProtectedAsyncBattery(DXEndpointBattery):
    requires_auth = True  # works with async handlers too

    @dx_get("secure/")
    async def secure(self, request):
        return await self.arender_section(request, "secure")

Requirements

  • Django 3.1+ (ASGI support)

  • asgiref (bundled with Django 3.1+)

  • An ASGI server (e.g. uvicorn, daphne) for async views to actually run asynchronously in production. Under the WSGI server the view is served synchronously via asgiref’s thread-pool adapter.

Notes

  • Template rendering (django.template.Template.render) is always synchronous. arender_section() runs it in a thread executor via sync_to_async so it does not block the event loop.

  • The section cache is class-level and thread-safe (built once, read-only thereafter).