Coverage for src / lexigram / ui / htmx / action_response.py: 100%
30 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1"""Typed builder for HTMX action responses.
3Consolidates the HX-Trigger header construction that Piccolina's action
4bridge (and other handlers) build by hand.
5"""
7from __future__ import annotations
9from dataclasses import dataclass, field
10from enum import Enum
11from typing import Any
13from lexigram.serialization import dumps_str
14from lexigram.ui.layouts.server_toasts import ToastData
17class ToastType(str, Enum):
18 """Severity levels for the ``show-toast`` HTMX event payload."""
20 SUCCESS = "success"
21 ERROR = "error"
22 WARNING = "warning"
23 INFO = "info"
26@dataclass
27class HtmxActionResponse:
28 """Builder for HTMX action responses with merged HX-Trigger headers.
30 Args:
31 toast: Optional toast notification to include in the trigger payload.
32 trigger: Additional HX-Trigger events to merge alongside the toast.
33 status_code: HTTP status code for the response (default 200).
35 Usage::
37 return HtmxActionResponse(
38 toast=ToastData(message="User deleted", type=ToastType.SUCCESS),
39 trigger={"refresh-list": True},
40 status_code=200,
41 ).to_response()
42 """
44 toast: ToastData | None = None
45 trigger: dict[str, Any] = field(default_factory=dict)
46 status_code: int = 200
48 def _build_trigger(self) -> dict[str, Any]:
49 payload: dict[str, Any] = {}
50 if self.toast is not None:
51 payload["show-toast"] = {
52 "message": self.toast.message,
53 "type": str(self.toast.type),
54 }
55 payload.update(self.trigger)
56 return payload
58 def to_response(self) -> Any:
59 """Return a Starlette ``HTMLResponse`` with the merged HX-Trigger header.
61 Returns:
62 ``starlette.responses.HTMLResponse`` with empty body and
63 ``HX-Trigger`` header set.
64 """
65 from starlette.responses import HTMLResponse
67 trigger_payload = self._build_trigger()
68 headers: dict[str, str] = {}
69 if trigger_payload:
70 headers["HX-Trigger"] = dumps_str(trigger_payload)
71 return HTMLResponse("", status_code=self.status_code, headers=headers)
74__all__ = ["HtmxActionResponse", "ToastData", "ToastType"]