Coverage for src/lexigram/notification/config/sms.py: 88%
25 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 07:17 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 07:17 +0800
1"""SMS driver configuration."""
3from __future__ import annotations
5from dataclasses import dataclass
6from typing import Any
8from lexigram.domain import DomainModel
9from lexigram.notification.constants import DEFAULT_TWILIO_TIMEOUT
10from lexigram.validation import Field, SecretStr, field_validator
13@dataclass(init=False)
14class TwilioDriverConfig(DomainModel):
15 """Twilio SMS delivery configuration."""
17 account_sid: str | None = Field(
18 default=None,
19 description="Twilio Account SID",
20 )
21 auth_token: SecretStr | None = Field(
22 default=None,
23 description="Twilio Auth Token",
24 )
25 from_number: str | None = Field(
26 default=None,
27 description="Twilio phone number (E.164 format)",
28 )
29 timeout: int = Field(
30 default=DEFAULT_TWILIO_TIMEOUT,
31 ge=1,
32 description="HTTP timeout (s)",
33 )
35 @field_validator("auth_token")
36 @classmethod
37 def _coerce_auth_token(cls, value: Any) -> Any:
38 if value is None or isinstance(value, SecretStr):
39 return value
40 return SecretStr(str(value))
43@dataclass(init=False)
44class NamedSMSConfig(DomainModel):
45 """Configuration for a single named SMS backend.
47 Used in NotificationConfig.sms_backends to declare multiple SMS backends
48 that the framework registers as named DI bindings.
50 Example:
51 sms_backends:
52 - name: alerts
53 driver: twilio
54 primary: true
55 twilio:
56 account_sid: AC...
57 auth_token: ...
58 from_number: +1234567890
60 Args:
61 name: Unique backend identifier. Used as the Named() DI key.
62 primary: Whether this is the primary backend. Primary backends
63 also receive the unnamed SMSChannelProtocol binding.
64 driver: SMS driver. One of 'twilio' or other supported drivers.
65 twilio: Twilio-specific config.
66 """
68 name: str = Field(..., description="Unique backend name used as the Named() DI key")
69 primary: bool = Field(
70 default=False,
71 description="Also register under unnamed SMSChannelProtocol binding",
72 )
73 driver: str = Field(default="twilio", description="SMS driver name")
74 twilio: TwilioDriverConfig | None = Field(
75 default=None,
76 description="Twilio driver config",
77 )
80__all__ = [
81 "NamedSMSConfig",
82 "TwilioDriverConfig",
83]