Coverage for custom_components/supernotify/envelope.py: 93%
70 statements
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-28 14:21 +0000
« prev ^ index » next coverage.py v7.6.8, created at 2024-12-28 14:21 +0000
1# mypy: disable-error-code="name-defined"
3import copy
4import logging
5import time
6import typing
7from pathlib import Path
8from typing import Any
10from . import ATTR_TIMESTAMP, CONF_MESSAGE, CONF_TITLE, PRIORITY_MEDIUM
12if typing.TYPE_CHECKING:
13 from custom_components.supernotify.common import CallRecord
15_LOGGER = logging.getLogger(__name__)
18class Envelope:
19 """Wrap a notification with a specific set of targets and service data possibly customized for those targets"""
21 def __init__(
22 self,
23 delivery_name: str,
24 notification: "Notification | None" = None, # noqa: F821 # type: ignore
25 targets: list | None = None,
26 data: dict | None = None,
27 ) -> None:
28 self.targets: list = targets or []
29 self.delivery_name: str = delivery_name
30 self._notification = notification
31 self.notification_id = None
32 self.media = None
33 self.action_groups = None
34 self.priority = PRIORITY_MEDIUM
35 self.message: str | None = None
36 self.title: str | None = None
37 self.message_html: str | None = None
38 self.data: dict = {}
39 self.actions: list[dict] = []
40 delivery_config_data: dict = {}
41 if notification:
42 self.notification_id = notification.id
43 self.media = notification.media
44 self.action_groups = notification.action_groups
45 self.actions = notification.actions
46 self.priority = notification.priority
47 self.message = notification.message(delivery_name)
48 self.message_html = notification.message_html
49 self.title = notification.title(delivery_name)
50 delivery_config_data = notification.delivery_data(delivery_name)
52 if data:
53 self.data = copy.deepcopy(delivery_config_data) if delivery_config_data else {}
54 self.data |= data
55 else:
56 self.data = delivery_config_data if delivery_config_data else {}
58 self.delivered: int = 0
59 self.errored: int = 0
60 self.skipped: int = 0
61 self.calls: list[CallRecord] = []
62 self.failed_calls: list[CallRecord] = []
63 self.delivery_error: list[str] | None = None
65 async def grab_image(self) -> Path | None:
66 """Grab an image from a camera, snapshot URL, MQTT Image etc"""
67 if self._notification:
68 return await self._notification.grab_image(self.delivery_name)
69 return None
71 def core_action_data(self) -> dict:
72 """Build the core set of `service_data` dict to pass to underlying notify service"""
73 data: dict = {}
74 # message is mandatory for notify platform
75 data[CONF_MESSAGE] = self.message or ""
76 timestamp = self.data.get(ATTR_TIMESTAMP)
77 if timestamp:
78 data[CONF_MESSAGE] = f"{data[CONF_MESSAGE]} [{time.strftime(timestamp, time.localtime())}]"
79 if self.title:
80 data[CONF_TITLE] = self.title
81 return data
83 def contents(self, minimal: bool = True) -> dict[str, typing.Any]:
84 exclude_attrs = ["_notification"]
85 if minimal:
86 exclude_attrs.extend("resolved")
87 json_ready = {k: v for k, v in self.__dict__.items() if k not in exclude_attrs}
88 json_ready["calls"] = [call.contents() for call in self.calls]
89 json_ready["failedcalls"] = [call.contents() for call in self.failed_calls]
90 return json_ready
92 def __eq__(self, other: Any | None) -> bool:
93 """Specialized equality check for subset of attributesfl"""
94 if other is None or not isinstance(other, Envelope):
95 return False
96 return (
97 self.targets == other.targets
98 and self.delivery_name == other.delivery_name
99 and self.data == other.data
100 and self.notification_id == other.notification_id
101 )