import json from enum import StrEnum from urllib.request import Request, urlopen from pydantic import AnyHttpUrl, BaseModel, Field from core.base import TypedTransport from reporting.models import RenderedReport class WebhookMethod(StrEnum): POST = "POST" PUT = "PUT" PATCH = "PATCH" class WebhookPayloadMode(StrEnum): RENDERED = "rendered" ENVELOPE = "envelope" class WebhookTransportConfig(BaseModel): url: AnyHttpUrl = Field() method: WebhookMethod = Field(WebhookMethod.POST) headers: dict[str, str] = Field(default_factory=dict) timeout_seconds: float = Field(10.0, gt=0) payload_mode: WebhookPayloadMode = Field(WebhookPayloadMode.RENDERED) class WebhookTransport(TypedTransport[WebhookTransportConfig]): transport_type = "webhook" config_model = WebhookTransportConfig def publish_typed( self, rendered_report: RenderedReport, transport: WebhookTransportConfig ) -> str: body, headers = self._build_request(rendered_report, transport) request = Request( url=str(transport.url), data=body, headers=headers, method=transport.method.value, ) with urlopen(request, timeout=transport.timeout_seconds) as response: return f"{response.status} {transport.url}" def _build_request( self, rendered_report: RenderedReport, transport: WebhookTransportConfig, ) -> tuple[bytes, dict[str, str]]: headers = dict(transport.headers) if transport.payload_mode is WebhookPayloadMode.ENVELOPE: payload = json.dumps( { "format": rendered_report.format_name, "content_type": rendered_report.media_type, "file_extension": rendered_report.file_extension, "content": rendered_report.content, }, ensure_ascii=True, ).encode("utf-8") headers.setdefault("Content-Type", "application/json") return payload, headers headers.setdefault("Content-Type", rendered_report.media_type) return rendered_report.content.encode("utf-8"), headers