Coverage for src / lexigram / contracts / webhook / types.py: 0%
45 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-15 18:57 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-15 18:57 +0800
1"""Webhook domain types: subscriptions, events, and delivery attempts."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from datetime import UTC, datetime
7from enum import Enum
8from typing import Any
11class DeliveryStatus(str, Enum):
12 """Status of a webhook delivery attempt."""
14 PENDING = "pending"
15 DELIVERED = "delivered"
16 FAILED = "failed"
17 DEAD_LETTER = "dead_letter"
18 CANCELLED = "cancelled"
21@dataclass(frozen=True)
22class WebhookSubscription:
23 """A registered webhook endpoint subscription.
25 Attributes:
26 subscription_id: Unique identifier (UUID).
27 url: HTTP(S) endpoint URL to deliver events to.
28 secret: Shared secret for HMAC signature computation.
29 event_types: Set of event type names to deliver. None = all events.
30 active: Whether the subscription is currently active.
31 description: Human-readable label for the subscription.
32 tenant_id: Optional multi-tenant scoping.
33 created_at: When the subscription was created.
34 metadata: Arbitrary key-value context.
35 """
37 subscription_id: str
38 url: str
39 secret: str
40 event_types: frozenset[str] | None = None
41 active: bool = True
42 description: str = ""
43 tenant_id: str | None = None
44 created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
45 metadata: dict[str, Any] = field(default_factory=dict)
47 def __repr__(self) -> str:
48 return (
49 f"WebhookSubscription(subscription_id={self.subscription_id!r}, "
50 f"url={self.url!r}, secret='***', event_types={self.event_types!r}, "
51 f"active={self.active!r}, description={self.description!r}, "
52 f"tenant_id={self.tenant_id!r}, created_at={self.created_at!r}, "
53 f"metadata={self.metadata!r})"
54 )
57@dataclass(frozen=True)
58class WebhookEvent:
59 """An event prepared for webhook delivery.
61 Attributes:
62 event_id: Unique identifier for deduplication (UUID).
63 event_type: Dot-notation event type (e.g. "user.created").
64 payload: JSON-serializable event data.
65 occurred_at: When the original event occurred.
66 source: Originating service or module.
67 """
69 event_id: str
70 event_type: str
71 payload: dict[str, Any]
72 occurred_at: datetime = field(default_factory=lambda: datetime.now(UTC))
73 source: str = ""
76@dataclass(frozen=True)
77class DeliveryAttempt:
78 """Record of a single webhook delivery attempt.
80 Attributes:
81 attempt_id: Unique identifier for this attempt.
82 subscription_id: Target subscription.
83 event_id: The event being delivered.
84 event_type: Event type name.
85 status: Outcome of this attempt.
86 status_code: HTTP response status code (None if connection failed).
87 attempt_number: 1-based attempt counter.
88 attempted_at: When this attempt was made.
89 next_retry_at: Scheduled time for the next retry (None if terminal).
90 error_message: Error details on failure.
91 duration_ms: Round-trip time in milliseconds.
92 """
94 attempt_id: str
95 subscription_id: str
96 event_id: str
97 event_type: str
98 status: DeliveryStatus
99 status_code: int | None = None
100 attempt_number: int = 1
101 attempted_at: datetime = field(default_factory=lambda: datetime.now(UTC))
102 next_retry_at: datetime | None = None
103 error_message: str | None = None
104 duration_ms: float | None = None
107__all__ = [
108 "DeliveryAttempt",
109 "DeliveryStatus",
110 "WebhookEvent",
111 "WebhookSubscription",
112]