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:
.. code-block:: python
from djxi import DXEndpointBattery, dx_get, dx_post
class AsyncItemBattery(DXEndpointBattery):
inline_template = """
{% for item in items %}- {{ item.title }}
{% endfor %}
"""
@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:
.. code-block:: python
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.
.. code-block:: python
response = await self.arender_section(request, section_name, context)
It accepts the same arguments as ``render_section()``:
.. py:method:: DXEndpointBattery.arender_section(self, request, section_name, context=None)
:param request: The incoming ``HttpRequest``.
:param section_name: Name of the ```` to render.
:param 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:
.. code-block:: python
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).