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 = """ """ @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).