Home Assistant Unofficial Reference 2024.12.1
exceptions.py
Go to the documentation of this file.
1 """The exceptions used by Home Assistant."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Generator, Sequence
6 from dataclasses import dataclass
7 from typing import TYPE_CHECKING, Any
8 
9 from .util.event_type import EventType
10 
11 if TYPE_CHECKING:
12  from .core import Context
13 
14 
15 _function_cache: dict[str, Callable[[str, str, dict[str, str] | None], str]] = {}
16 
17 
19  Callable[[str, str, dict[str, str] | None], str]
20 ):
21  """Return a method that can fetch a translated exception message.
22 
23  Defaults to English, requires translations to already be cached.
24  """
25 
26  # pylint: disable-next=import-outside-toplevel
27  from .helpers.translation import (
28  async_get_exception_message as async_get_exception_message_import,
29  )
30 
31  return async_get_exception_message_import
32 
33 
34 class HomeAssistantError(Exception):
35  """General Home Assistant exception occurred."""
36 
37  _message: str | None = None
38  generate_message: bool = False
39 
40  def __init__(
41  self,
42  *args: object,
43  translation_domain: str | None = None,
44  translation_key: str | None = None,
45  translation_placeholders: dict[str, str] | None = None,
46  ) -> None:
47  """Initialize exception."""
48  if not args and translation_key and translation_domain:
49  self.generate_messagegenerate_message = True
50  args = (translation_key,)
51 
52  super().__init__(*args)
53  self.translation_domaintranslation_domain = translation_domain
54  self.translation_keytranslation_key = translation_key
55  self.translation_placeholderstranslation_placeholders = translation_placeholders
56 
57  def __str__(self) -> str:
58  """Return exception message.
59 
60  If no message was passed to `__init__`, the exception message is generated from
61  the translation_key. The message will be in English, regardless of the configured
62  language.
63  """
64 
65  if self._message_message:
66  return self._message_message
67 
68  if not self.generate_messagegenerate_message:
69  self._message_message = super().__str__()
70  return self._message_message
71 
72  if TYPE_CHECKING:
73  assert self.translation_keytranslation_key is not None
74  assert self.translation_domaintranslation_domain is not None
75 
76  if "async_get_exception_message" not in _function_cache:
77  _function_cache["async_get_exception_message"] = (
79  )
80 
81  self._message_message = _function_cache["async_get_exception_message"](
82  self.translation_domaintranslation_domain, self.translation_keytranslation_key, self.translation_placeholderstranslation_placeholders
83  )
84  return self._message_message
85 
86 
87 class ConfigValidationError(HomeAssistantError, ExceptionGroup[Exception]):
88  """A validation exception occurred when validating the configuration."""
89 
90  def __init__(
91  self,
92  message_translation_key: str,
93  exceptions: list[Exception],
94  translation_domain: str | None = None,
95  translation_placeholders: dict[str, str] | None = None,
96  ) -> None:
97  """Initialize exception."""
98  super().__init__(
99  *(message_translation_key, exceptions),
100  translation_domain=translation_domain,
101  translation_key=message_translation_key,
102  translation_placeholders=translation_placeholders,
103  )
104  self.generate_messagegenerate_messagegenerate_message = True
105 
106 
108  """A validation exception occurred when calling a service."""
109 
110 
111 class InvalidEntityFormatError(HomeAssistantError):
112  """When an invalid formatted entity is encountered."""
113 
114 
116  """When no entity is specified."""
117 
118 
120  """Error during template rendering."""
121 
122  def __init__(self, exception: Exception | str) -> None:
123  """Init the error."""
124  if isinstance(exception, str):
125  super().__init__(exception)
126  else:
127  super().__init__(f"{exception.__class__.__name__}: {exception}")
128 
129 
130 @dataclass(slots=True)
132  """Error during condition evaluation."""
133 
134  type: str
135 
136  @staticmethod
137  def _indent(indent: int, message: str) -> str:
138  """Return indentation."""
139  return " " * indent + message
140 
141  def output(self, indent: int) -> Generator[str]:
142  """Yield an indented representation."""
143  raise NotImplementedError
144 
145  def __str__(self) -> str:
146  """Return string representation."""
147  return "\n".join(list(self.outputoutput(indent=0)))
148 
149 
150 @dataclass(slots=True)
152  """Condition error message."""
153 
154  # A message describing this error
155  message: str
156 
157  def output(self, indent: int) -> Generator[str]:
158  """Yield an indented representation."""
159  yield self._indent_indent(indent, f"In '{self.type}' condition: {self.message}")
160 
161 
162 @dataclass(slots=True)
164  """Condition error with index."""
165 
166  # The zero-based index of the failed condition, for conditions with multiple parts
167  index: int
168  # The total number of parts in this condition, including non-failed parts
169  total: int
170  # The error that this error wraps
171  error: ConditionError
172 
173  def output(self, indent: int) -> Generator[str]:
174  """Yield an indented representation."""
175  if self.total > 1:
176  yield self._indent_indent(
177  indent, f"In '{self.type}' (item {self.index+1} of {self.total}):"
178  )
179  else:
180  yield self._indent_indent(indent, f"In '{self.type}':")
181 
182  yield from self.error.output(indent + 1)
183 
184 
185 @dataclass(slots=True)
187  """Condition error with subconditions."""
188 
189  # List of ConditionErrors that this error wraps
190  errors: Sequence[ConditionError]
191 
192  def output(self, indent: int) -> Generator[str]:
193  """Yield an indented representation."""
194  for item in self.errors:
195  yield from item.output(indent)
196 
197 
199  """Base class for platform and config entry exceptions."""
200 
201  def __str__(self) -> str:
202  """Return a human readable error."""
203  return super().__str__() or str(self.__cause__)
204 
205 
207  """Error to indicate that platform is not ready."""
208 
209 
210 class ConfigEntryError(IntegrationError):
211  """Error to indicate that config entry setup has failed."""
212 
213 
215  """Error to indicate that config entry is not ready."""
216 
217 
219  """Error to indicate that config entry could not authenticate."""
220 
221 
223  """When an invalid state is encountered."""
224 
225 
227  """When an action is unauthorized."""
228 
229  def __init__(
230  self,
231  context: Context | None = None,
232  user_id: str | None = None,
233  entity_id: str | None = None,
234  config_entry_id: str | None = None,
235  perm_category: str | None = None,
236  permission: str | None = None,
237  ) -> None:
238  """Unauthorized error."""
239  super().__init__(self.__class__.__name__)
240  self.contextcontext = context
241 
242  if user_id is None and context is not None:
243  user_id = context.user_id
244 
245  self.user_iduser_id = user_id
246  self.entity_identity_id = entity_id
247  self.config_entry_idconfig_entry_id = config_entry_id
248  # Not all actions have an ID (like adding config entry)
249  # We then use this fallback to know what category was unauth
250  self.perm_categoryperm_category = perm_category
251  self.permissionpermission = permission
252 
253 
255  """When call is made with user ID that doesn't exist."""
256 
257 
258 class ServiceNotFound(ServiceValidationError):
259  """Raised when a service is not found."""
260 
261  def __init__(self, domain: str, service: str) -> None:
262  """Initialize error."""
263  super().__init__(
264  translation_domain="homeassistant",
265  translation_key="service_not_found",
266  translation_placeholders={"domain": domain, "service": service},
267  )
268  self.domaindomain = domain
269  self.serviceservice = service
270  self.generate_messagegenerate_messagegenerate_message = True
271 
272 
274  """Raised when a property value has exceeded the max character length."""
275 
276  def __init__(
277  self, value: EventType[Any] | str, property_name: str, max_length: int
278  ) -> None:
279  """Initialize error."""
280  if TYPE_CHECKING:
281  value = str(value)
282  super().__init__(
283  translation_domain="homeassistant",
284  translation_key="max_length_exceeded",
285  translation_placeholders={
286  "value": value,
287  "property_name": property_name,
288  "max_length": str(max_length),
289  },
290  )
291  self.valuevalue = value
292  self.property_nameproperty_name = property_name
293  self.max_lengthmax_length = max_length
294  self.generate_messagegenerate_messagegenerate_message = True
295 
296 
298  """Raised when dependencies cannot be setup."""
299 
300  def __init__(self, failed_dependencies: list[str]) -> None:
301  """Initialize error."""
302  super().__init__(
303  f"Could not setup dependencies: {', '.join(failed_dependencies)}",
304  )
305  self.failed_dependenciesfailed_dependencies = failed_dependencies
Generator[str] output(self, int indent)
Definition: exceptions.py:192
Generator[str] output(self, int indent)
Definition: exceptions.py:173
Generator[str] output(self, int indent)
Definition: exceptions.py:157
str _indent(int indent, str message)
Definition: exceptions.py:137
Generator[str] output(self, int indent)
Definition: exceptions.py:141
None __init__(self, str message_translation_key, list[Exception] exceptions, str|None translation_domain=None, dict[str, str]|None translation_placeholders=None)
Definition: exceptions.py:96
None __init__(self, list[str] failed_dependencies)
Definition: exceptions.py:300
None __init__(self, *object args, str|None translation_domain=None, str|None translation_key=None, dict[str, str]|None translation_placeholders=None)
Definition: exceptions.py:46
None __init__(self, EventType[Any]|str value, str property_name, int max_length)
Definition: exceptions.py:278
None __init__(self, str domain, str service)
Definition: exceptions.py:261
None __init__(self, Exception|str exception)
Definition: exceptions.py:122
None __init__(self, Context|None context=None, str|None user_id=None, str|None entity_id=None, str|None config_entry_id=None, str|None perm_category=None, str|None permission=None)
Definition: exceptions.py:237
( Callable[[str, str, dict[str, str]|None], str]) import_async_get_exception_message()
Definition: exceptions.py:20