Coverage for src / lexigram / admin / services / notifications / models.py: 96%
55 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-13 22:14 +0800
1"""Notification data models and types for admin notifications."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from datetime import UTC, datetime
7from enum import StrEnum
8from typing import Any
11class NotificationType(StrEnum):
12 """Types of admin notifications."""
14 # User events
15 USER_CREATED = "user_created"
16 USER_INVITED = "user_invited"
17 USER_ACTIVATED = "user_activated"
18 USER_DEACTIVATED = "user_deactivated"
19 PASSWORD_RESET = "password_reset"
20 PASSWORD_CHANGED = "password_changed"
22 # Bulk operations
23 BULK_STARTED = "bulk_started"
24 BULK_PROGRESS = "bulk_progress"
25 BULK_COMPLETED = "bulk_completed"
26 BULK_FAILED = "bulk_failed"
28 # System events
29 SYSTEM_ALERT = "system_alert"
30 SYSTEM_WARNING = "system_warning"
31 SYSTEM_ERROR = "system_error"
33 # Export/Import
34 EXPORT_READY = "export_ready"
35 IMPORT_COMPLETED = "import_completed"
36 IMPORT_FAILED = "import_failed"
39class NotificationChannel(StrEnum):
40 """Notification delivery channels."""
42 EMAIL = "email"
43 IN_APP = "in_app"
44 PUSH = "push"
47@dataclass
48class NotificationRecipient:
49 """Notification recipient."""
51 email: str
52 name: str | None = None
53 user_id: Any = None
54 preferences: dict[str, bool] = field(default_factory=dict)
56 def can_receive(self, notification_type: NotificationType) -> bool:
57 """Check if recipient can receive notification type."""
58 key = f"notify_{notification_type.value}"
59 return self.preferences.get(key, True)
62@dataclass
63class Notification:
64 """Admin notification."""
66 type: NotificationType
67 subject: str
68 body: str
69 recipients: list[NotificationRecipient]
70 channels: list[NotificationChannel] = field(
71 default_factory=lambda: [NotificationChannel.EMAIL],
72 )
74 # Optional fields
75 data: dict[str, Any] = field(default_factory=dict)
76 html_body: str | None = None
77 priority: str = "normal"
78 scheduled_at: datetime | None = None
80 # Tracking
81 id: str | None = None
82 created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
85@dataclass
86class NotificationResult:
87 """Payload of a completed notification delivery.
89 Always carried inside ``Ok[NotificationResult, NotificationError]``.
90 Per-recipient failures are tracked in ``errors`` and ``recipients_failed``;
91 a non-zero ``recipients_failed`` does NOT mean the overall operation failed —
92 only an ``Err`` return (when every recipient failed) does.
93 """
95 notification_id: str | None = None
96 recipients_sent: int = 0
97 recipients_failed: int = 0
98 errors: list[str] = field(default_factory=list)
101__all__ = [
102 "Notification",
103 "NotificationChannel",
104 "NotificationRecipient",
105 "NotificationResult",
106 "NotificationType",
107]