Coverage for custom_components/supernotify/notify.py: 97%

39 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-09-25 21:14 +0000

1from __future__ import annotations 

2 

3import logging 

4from typing import TYPE_CHECKING, Any, cast 

5 

6from homeassistant.components.notify.legacy import BaseNotificationService 

7from homeassistant.const import ( 

8 CONF_TARGET, 

9) 

10from homeassistant.core import ( 

11 HomeAssistant, 

12 ServiceCall, 

13) 

14 

15from .engine import SupernotifyEngine 

16from .model import NotifyEntityPlatform 

17 

18if TYPE_CHECKING: 

19 from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback 

20 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType 

21 

22 from . import SupernotifyConfigEntry 

23from .const import ( 

24 ATTR_DATA, 

25 CONF_MESSAGE, 

26 CONF_TITLE, 

27) 

28 

29_LOGGER = logging.getLogger(__name__) 

30 

31PARALLEL_UPDATES = 0 

32 

33 

34class SuperNotificationService(BaseNotificationService): 

35 """Legacy notify-platform compatibility shim. 

36 

37 The only reason this exists is to satisfy HA core's notify/legacy.py 

38 BaseNotificationService contract, so notify.supernotify and notify.<target> keep working. 

39 It adds nothing but that glue on top of SupernotifyEngine - nothing else in this 

40 integration (SupernotifyEntity, RecipientNotifyEntity, the supplemental actions, tests 

41 targeting the engine) references this class or BaseNotificationService. If/when HA core 

42 drops BaseNotificationService, delete this class and the matching async_setup/ 

43 async_register_services/async_unregister_services calls in __init__.py; everything else 

44 keeps working unchanged. 

45 """ 

46 

47 def __init__(self, engine: SupernotifyEngine, *args: Any, **kwargs: Any) -> None: 

48 super().__init__() 

49 self.engine = engine 

50 

51 async def async_unregister_services(self) -> None: 

52 _LOGGER.info("SUPERNOTIFY Unregistering notify service") 

53 return await super().async_unregister_services() 

54 

55 async def _async_notify_message_service(self, service: ServiceCall) -> None: 

56 """Override of BaseNotificationService._async_notify_message_service (notify/legacy.py) 

57 to forward the calling action's Context through to async_send_message. HA core's 

58 implementation builds its own kwargs from service.data and drops service.context 

59 entirely, which is why every notify.supernotify/notify.<target> call otherwise loses 

60 its link back to the triggering automation, showing up downstream (e.g. in the mobile 

61 app's notification history) as having no recorded cause. 

62 """ 

63 kwargs: dict[str, Any] = {} 

64 message: str = service.data[CONF_MESSAGE] 

65 if title := service.data.get(CONF_TITLE): 

66 kwargs[CONF_TITLE] = title 

67 if self.registered_targets.get(service.service) is not None: 

68 kwargs[CONF_TARGET] = [self.registered_targets[service.service]] 

69 elif service.data.get(CONF_TARGET) is not None: 

70 kwargs[CONF_TARGET] = service.data.get(CONF_TARGET) 

71 kwargs[CONF_MESSAGE] = message 

72 kwargs[ATTR_DATA] = service.data.get(ATTR_DATA) 

73 kwargs["context"] = service.context 

74 

75 await self.engine.async_send_message(**kwargs) 

76 

77 

78async def async_get_service( 

79 hass: HomeAssistant, 

80 config: ConfigType, 

81 discovery_info: DiscoveryInfoType | None = None, 

82) -> None: 

83 """Legacy `notify: - platform: supernotify` entrypoint - see async_setup_legacy in legacy 

84 BaseNotificationService. 

85 

86 The config entry is now the sole, unconditional owner of notify.supernotify (see 

87 async_setup_entry in __init__.py), so this leftover legacy YAML block never builds or 

88 registers a service any more - it only raises a fixable repair pointing at the migration 

89 (see repairs.py) and declines to set up (returning None is HA's supported "decline" path for 

90 a legacy notify platform - a clean one-line log, no exception). 

91 

92 A `name:` in this leftover block still gets synced onto the owning entry every load though 

93 (not gated behind that repair), and likewise for its template_path/media_path/etc and 

94 archive/dupe_check/housekeeping settings - otherwise an entry auto-bootstrapped blank by 

95 async_setup (see __init__.py), which happens before anyone gets around to opening and 

96 confirming the migration repair, would keep running on defaults with nothing configured, 

97 silently breaking automations, template/media paths and archiving on every restart until the 

98 repair is manually confirmed. That repair is only ever needed for delivery/transports/ 

99 scenarios/etc - a "simple" install with none of that has no reason to see it at all, so this 

100 core migration must not depend on it. 

101 """ 

102 _ = discovery_info 

103 

104 from .repairs import async_create_legacy_yaml_issue, async_sync_entry_from_legacy_config 

105 

106 legacy_config = dict(config) 

107 async_sync_entry_from_legacy_config(hass, legacy_config) 

108 async_create_legacy_yaml_issue(hass, legacy_config) 

109 

110 

111async def async_setup_entry( 

112 hass: HomeAssistant, 

113 entry: SupernotifyConfigEntry, 

114 async_add_entities: AddConfigEntryEntitiesCallback, 

115) -> None: 

116 """Expose each configured recipient as its own notify entity. 

117 

118 Forwarded to from async_setup_entry in __init__.py once the SupernotifyEngine (entry. 

119 runtime_data) is fully initialized, so people_registry is already populated. 

120 """ 

121 # _ = hass 

122 entry.runtime_data.context.people_registry.expose_notify_entities( 

123 entry.entry_id, async_add_entities, cast(NotifyEntityPlatform, entry.runtime_data) 

124 )