Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Base class for Ring entity."""
2 
3 from collections.abc import Awaitable, Callable, Coroutine
4 from dataclasses import dataclass
5 from typing import Any, Concatenate, Generic, cast
6 
7 from ring_doorbell import (
8  AuthenticationError,
9  RingDevices,
10  RingError,
11  RingGeneric,
12  RingTimeout,
13 )
14 from typing_extensions import TypeVar
15 
16 from homeassistant.components.automation import automations_with_entity
17 from homeassistant.components.script import scripts_with_entity
18 from homeassistant.const import Platform
19 from homeassistant.core import HomeAssistant, callback
20 from homeassistant.exceptions import HomeAssistantError
21 from homeassistant.helpers import entity_registry as er
22 from homeassistant.helpers.device_registry import DeviceInfo
23 from homeassistant.helpers.entity import EntityDescription
24 from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
26  BaseCoordinatorEntity,
27  CoordinatorEntity,
28 )
29 
30 from .const import ATTRIBUTION, DOMAIN
31 from .coordinator import RingDataCoordinator, RingListenCoordinator
32 
33 RingDeviceT = TypeVar("RingDeviceT", bound=RingGeneric, default=RingGeneric)
34 
35 _RingCoordinatorT = TypeVar(
36  "_RingCoordinatorT",
37  bound=(RingDataCoordinator | RingListenCoordinator),
38 )
39 
40 
41 @dataclass(slots=True)
43  """Class to define deprecation info for deprecated entities."""
44 
45  new_platform: Platform
46  breaks_in_ha_version: str
47 
48 
49 @dataclass(frozen=True, kw_only=True)
51  """Base class for a ring entity description."""
52 
53  deprecated_info: DeprecatedInfo | None = None
54 
55 
56 def exception_wrap[_RingBaseEntityT: RingBaseEntity[Any, Any], **_P, _R](
57  async_func: Callable[Concatenate[_RingBaseEntityT, _P], Coroutine[Any, Any, _R]],
58 ) -> Callable[Concatenate[_RingBaseEntityT, _P], Coroutine[Any, Any, _R]]:
59  """Define a wrapper to catch exceptions and raise HomeAssistant errors."""
60 
61  async def _wrap(self: _RingBaseEntityT, *args: _P.args, **kwargs: _P.kwargs) -> _R:
62  try:
63  return await async_func(self, *args, **kwargs)
64  except AuthenticationError as err:
65  self.coordinator.config_entry.async_start_reauth(self.hass)
66  raise HomeAssistantError(err) from err
67  except RingTimeout as err:
68  raise HomeAssistantError(
69  f"Timeout communicating with API {async_func}: {err}"
70  ) from err
71  except RingError as err:
72  raise HomeAssistantError(
73  f"Error communicating with API{async_func}: {err}"
74  ) from err
75 
76  return _wrap
77 
78 
79 def refresh_after[_RingEntityT: RingEntity[Any], **_P](
80  func: Callable[Concatenate[_RingEntityT, _P], Awaitable[None]],
81 ) -> Callable[Concatenate[_RingEntityT, _P], Coroutine[Any, Any, None]]:
82  """Define a wrapper to handle api call errors or refresh after success."""
83 
84  @exception_wrap
85  async def _wrap(self: _RingEntityT, *args: _P.args, **kwargs: _P.kwargs) -> None:
86  await func(self, *args, **kwargs)
87  await self.coordinator.async_request_refresh()
88 
89  return _wrap
90 
91 
93  hass: HomeAssistant,
94  platform: Platform,
95  unique_id: str,
96  entity_description: RingEntityDescription,
97 ) -> bool:
98  """Return true if the entitty should be created based on the deprecated_info.
99 
100  If deprecated_info is not defined will return true.
101  If entity not yet created will return false.
102  If entity disabled will delete it and return false.
103  Otherwise will return true and create issues for scripts or automations.
104  """
105  if not entity_description.deprecated_info:
106  return True
107 
108  ent_reg = er.async_get(hass)
109  entity_id = ent_reg.async_get_entity_id(
110  platform,
111  DOMAIN,
112  unique_id,
113  )
114  if not entity_id:
115  return False
116 
117  entity_entry = ent_reg.async_get(entity_id)
118  assert entity_entry
119  if entity_entry.disabled:
120  # If the entity exists and is disabled then we want to remove
121  # the entity so that the user is just using the new entity.
122  ent_reg.async_remove(entity_id)
123  return False
124 
125  # Check for issues that need to be created
126  entity_automations = automations_with_entity(hass, entity_id)
127  entity_scripts = scripts_with_entity(hass, entity_id)
128  if entity_automations or entity_scripts:
129  deprecated_info = entity_description.deprecated_info
130  for item in entity_automations + entity_scripts:
132  hass,
133  DOMAIN,
134  f"deprecated_entity_{entity_id}_{item}",
135  breaks_in_ha_version=deprecated_info.breaks_in_ha_version,
136  is_fixable=False,
137  is_persistent=False,
138  severity=IssueSeverity.WARNING,
139  translation_key="deprecated_entity",
140  translation_placeholders={
141  "entity": entity_id,
142  "info": item,
143  "platform": platform,
144  "new_platform": deprecated_info.new_platform,
145  },
146  )
147  return True
148 
149 
151  BaseCoordinatorEntity[_RingCoordinatorT], Generic[_RingCoordinatorT, RingDeviceT]
152 ):
153  """Base implementation for Ring device."""
154 
155  _attr_attribution = ATTRIBUTION
156  _attr_should_poll = False
157  _attr_has_entity_name = True
158 
159  def __init__(
160  self,
161  device: RingDeviceT,
162  coordinator: _RingCoordinatorT,
163  ) -> None:
164  """Initialize a sensor for Ring device."""
165  super().__init__(coordinator, context=device.id)
166  self._device_device = device
167  self._attr_extra_state_attributes_attr_extra_state_attributes = {}
168  self._attr_device_info_attr_device_info = DeviceInfo(
169  identifiers={(DOMAIN, device.device_id)}, # device_id is the mac
170  manufacturer="Ring",
171  model=device.model,
172  name=device.name,
173  )
174 
175 
176 class RingEntity(RingBaseEntity[RingDataCoordinator, RingDeviceT], CoordinatorEntity):
177  """Implementation for Ring devices."""
178 
179  def _get_coordinator_data(self) -> RingDevices:
180  return self.coordinator.data
181 
182  @callback
183  def _handle_coordinator_update(self) -> None:
184  self._device_device_device = cast(
185  RingDeviceT,
186  self._get_coordinator_data_get_coordinator_data().get_device(self._device_device_device.device_api_id),
187  )
None __init__(self, RingDeviceT device, _RingCoordinatorT coordinator)
Definition: entity.py:163
list[str] automations_with_entity(HomeAssistant hass, str entity_id)
Definition: __init__.py:191
DeviceEntry get_device(HomeAssistant hass, str unique_id)
Definition: util.py:12
None async_create_issue(HomeAssistant hass, str entry_id)
Definition: repairs.py:69
bool async_check_create_deprecated(HomeAssistant hass, Platform platform, str unique_id, RingEntityDescription entity_description)
Definition: entity.py:97
list[str] scripts_with_entity(HomeAssistant hass, str entity_id)
Definition: __init__.py:126