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