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

1"""SMS driver configuration.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from typing import Any 

7 

8from lexigram.domain import DomainModel 

9from lexigram.notification.constants import DEFAULT_TWILIO_TIMEOUT 

10from lexigram.validation import Field, SecretStr, field_validator 

11 

12 

13@dataclass(init=False) 

14class TwilioDriverConfig(DomainModel): 

15 """Twilio SMS delivery configuration.""" 

16 

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 ) 

34 

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)) 

41 

42 

43@dataclass(init=False) 

44class NamedSMSConfig(DomainModel): 

45 """Configuration for a single named SMS backend. 

46 

47 Used in NotificationConfig.sms_backends to declare multiple SMS backends 

48 that the framework registers as named DI bindings. 

49 

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 

59 

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 """ 

67 

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 ) 

78 

79 

80__all__ = [ 

81 "NamedSMSConfig", 

82 "TwilioDriverConfig", 

83]