Coverage for src/lexigram/notification/config/push.py: 90%
51 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 02:32 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-26 02:32 +0800
1"""Push notification driver configuration."""
3from __future__ import annotations
5from dataclasses import dataclass
6from typing import Any
8from lexigram.domain import DomainModel
9from lexigram.notification.constants import (
10 DEFAULT_APNS_TIMEOUT,
11 DEFAULT_FCM_TIMEOUT,
12)
13from lexigram.validation import Field, SecretStr, field_validator
16@dataclass(init=False)
17class FCMDriverConfig(DomainModel):
18 """Firebase Cloud Messaging (FCM) push notification configuration."""
20 server_key: SecretStr | None = Field(
21 default=None,
22 description="FCM Server API Key",
23 )
25 @field_validator("server_key")
26 @classmethod
27 def _coerce_server_key(cls, value: Any) -> Any:
28 if value is None or isinstance(value, SecretStr):
29 return value
30 return SecretStr(str(value))
32 timeout: int = Field(
33 default=DEFAULT_FCM_TIMEOUT,
34 ge=1,
35 description="HTTP timeout (s)",
36 )
39@dataclass(init=False)
40class APNsDriverConfig(DomainModel):
41 """Apple Push Notification service (APNs) configuration."""
43 team_id: str | None = Field(
44 default=None,
45 description="Apple Developer Team ID (10-character string)",
46 )
47 key_id: str | None = Field(
48 default=None,
49 description="APNs Auth Key ID (10-character string)",
50 )
51 apns_auth_key: SecretStr | None = Field(
52 default=None,
53 description=(
54 "ECDSA private key — PEM string starting with "
55 "'-----BEGIN PRIVATE KEY-----' or path to a .p8 key file"
56 ),
57 )
58 bundle_id: str | None = Field(
59 default=None,
60 description="App bundle identifier (e.g. com.example.MyApp)",
61 )
62 sandbox: bool = Field(
63 default=False,
64 description="Use the APNs sandbox endpoint instead of production",
65 )
66 timeout: int = Field(
67 default=DEFAULT_APNS_TIMEOUT,
68 ge=1,
69 description="HTTP timeout (s)",
70 )
72 @field_validator("apns_auth_key")
73 @classmethod
74 def _coerce_apns_auth_key(cls, value: Any) -> Any:
75 if value is None or isinstance(value, SecretStr):
76 return value
77 return SecretStr(str(value))
80@dataclass(init=False)
81class WebPushDriverConfig(DomainModel):
82 """Web Push (RFC 8030) driver configuration."""
84 vapid_private_key: SecretStr | None = Field(
85 default=None,
86 description="VAPID private key (PEM-encoded EC prime256v1)",
87 )
88 vapid_public_key: str | None = Field(
89 default=None,
90 description="VAPID public key (base64url-encoded)",
91 )
92 vapid_claims_subject: str | None = Field(
93 default=None,
94 description="VAPID claims subject URI (e.g. mailto:ops@example.com)",
95 )
96 timeout: int = Field(
97 default=30,
98 ge=1,
99 description="HTTP timeout (s)",
100 )
102 @field_validator("vapid_private_key")
103 @classmethod
104 def _coerce_vapid_private_key(cls, value: Any) -> Any:
105 if value is None or isinstance(value, SecretStr):
106 return value
107 return SecretStr(str(value))
110@dataclass(init=False)
111class NamedPushConfig(DomainModel):
112 """Configuration for a single named push notification backend.
114 Used in NotificationConfig.push_backends to declare multiple push backends
115 that the framework registers as named DI bindings.
117 Example:
118 push_backends:
119 - name: mobile
120 driver: fcm
121 primary: true
122 fcm:
123 server_key: AAAA...
125 Args:
126 name: Unique backend identifier. Used as the Named() DI key.
127 primary: Whether this is the primary backend. Primary backends
128 also receive the unnamed PushChannelProtocol binding.
129 driver: Push driver. One of 'fcm' or other supported drivers.
130 fcm: FCM-specific config.
131 """
133 name: str = Field(..., description="Unique backend name used as the Named() DI key")
134 primary: bool = Field(
135 default=False,
136 description="Also register under unnamed PushChannelProtocol binding",
137 )
138 driver: str = Field(default="fcm", description="Push driver name")
139 fcm: FCMDriverConfig | None = Field(
140 default=None,
141 description="FCM driver config",
142 )
143 apns: APNsDriverConfig | None = Field(
144 default=None,
145 description="APNs driver config",
146 )
147 web_push: WebPushDriverConfig | None = Field(
148 default=None,
149 description="Web Push driver config",
150 )
153__all__ = [
154 "APNsDriverConfig",
155 "FCMDriverConfig",
156 "NamedPushConfig",
157 "WebPushDriverConfig",
158]