ha-core/homeassistant/exceptions.py

253 lines
7.5 KiB
Python

"""The exceptions used by Home Assistant."""
from __future__ import annotations
from collections.abc import Generator, Sequence
from dataclasses import dataclass
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .core import Context
class HomeAssistantError(Exception):
"""General Home Assistant exception occurred."""
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."""
super().__init__(*args)
self.translation_domain = translation_domain
self.translation_key = translation_key
self.translation_placeholders = translation_placeholders
class ConfigValidationError(HomeAssistantError, ExceptionGroup[Exception]):
"""A validation exception occurred when validating the configuration."""
def __init__(
self,
message: str,
exceptions: list[Exception],
translation_domain: str | None = None,
translation_key: str | None = None,
translation_placeholders: dict[str, str] | None = None,
) -> None:
"""Initialize exception."""
super().__init__(
*(message, exceptions),
translation_domain=translation_domain,
translation_key=translation_key,
translation_placeholders=translation_placeholders,
)
self._message = message
def __str__(self) -> str:
"""Return exception message string."""
return self._message
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, None, None]:
"""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, None, None]:
"""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, None, None]:
"""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, None, None]:
"""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(HomeAssistantError):
"""Raised when a service is not found."""
def __init__(self, domain: str, service: str) -> None:
"""Initialize error."""
super().__init__(
self,
f"Service {domain}.{service} not found.",
translation_domain="homeassistant",
translation_key="service_not_found",
translation_placeholders={"domain": domain, "service": service},
)
self.domain = domain
self.service = service
def __str__(self) -> str:
"""Return string representation."""
return f"Service {self.domain}.{self.service} not found."
class MaxLengthExceeded(HomeAssistantError):
"""Raised when a property value has exceeded the max character length."""
def __init__(self, value: str, property_name: str, max_length: int) -> None:
"""Initialize error."""
super().__init__(
self,
(
f"Value {value} for property {property_name} has a max length of "
f"{max_length} characters"
),
)
self.value = value
self.property_name = property_name
self.max_length = max_length
class DependencyError(HomeAssistantError):
"""Raised when dependencies cannot be setup."""
def __init__(self, failed_dependencies: list[str]) -> None:
"""Initialize error."""
super().__init__(
self,
f"Could not setup dependencies: {', '.join(failed_dependencies)}",
)
self.failed_dependencies = failed_dependencies