Skip to content

Exceptions

HassetteForgottenAwaitWarning

Bases: RuntimeWarning

Warning emitted when a protected registration/scheduling method is called without await.

Fired from RegistrationHandle.__del__ when the handle is garbage-collected without ever being awaited, sent to, thrown into, or closed. The message names the owning app and the source location of the forgotten call site.

Integrates with -W error/pytest.warns/filterwarnings like any RuntimeWarning.

Source code in src/hassette/exceptions.py
11
12
13
14
15
16
17
18
19
class HassetteForgottenAwaitWarning(RuntimeWarning):
    """Warning emitted when a protected registration/scheduling method is called without ``await``.

    Fired from ``RegistrationHandle.__del__`` when the handle is garbage-collected without
    ever being awaited, sent to, thrown into, or closed. The message names the owning app
    and the source location of the forgotten call site.

    Integrates with ``-W error``/``pytest.warns``/``filterwarnings`` like any ``RuntimeWarning``.
    """

HassetteBlockingIOWarning

Bases: RuntimeWarning

Warning emitted when blocking I/O is detected on the shared event loop.

Fired by Tier 1 (responsiveness watchdog) when loop lag exceeds the configured threshold, and by Tier 2 (call-site interception) when a blocking primitive is called on the loop thread. The message names the offending app, handler/job, and (where available) the source line.

Integrates with -W error/pytest.warns/filterwarnings like any RuntimeWarning.

Source code in src/hassette/exceptions.py
22
23
24
25
26
27
28
29
30
class HassetteBlockingIOWarning(RuntimeWarning):
    """Warning emitted when blocking I/O is detected on the shared event loop.

    Fired by Tier 1 (responsiveness watchdog) when loop lag exceeds the configured threshold,
    and by Tier 2 (call-site interception) when a blocking primitive is called on the loop thread.
    The message names the offending app, handler/job, and (where available) the source line.

    Integrates with ``-W error``/``pytest.warns``/``filterwarnings`` like any ``RuntimeWarning``.
    """

HassetteError

Bases: Exception

Base exception for all Hassette errors.

Source code in src/hassette/exceptions.py
33
34
class HassetteError(Exception):
    """Base exception for all Hassette errors."""

FatalError

Bases: HassetteError

Custom exception to indicate a fatal error in the application.

Exceptions that indicate that the service should not be restarted should inherit from this class.

Source code in src/hassette/exceptions.py
37
38
39
40
41
class FatalError(HassetteError):
    """Custom exception to indicate a fatal error in the application.

    Exceptions that indicate that the service should not be restarted should inherit from this class.
    """

BaseUrlRequiredError

Bases: FatalError

Custom exception to indicate that the base_url configuration is required.

Source code in src/hassette/exceptions.py
44
45
class BaseUrlRequiredError(FatalError):
    """Custom exception to indicate that the base_url configuration is required."""

IPV6NotSupportedError

Bases: FatalError

Custom exception to indicate that IPv6 addresses are not supported in base_url.

Source code in src/hassette/exceptions.py
48
49
class IPV6NotSupportedError(FatalError):
    """Custom exception to indicate that IPv6 addresses are not supported in base_url."""

SchemeRequiredInBaseUrlError

Bases: FatalError

Custom exception to indicate that the base_url must include a scheme (http:// or https://).

Source code in src/hassette/exceptions.py
52
53
class SchemeRequiredInBaseUrlError(FatalError):
    """Custom exception to indicate that the base_url must include a scheme (http:// or https://)."""

ConnectionClosedError

Bases: HassetteError

Custom exception to indicate that the WebSocket connection was closed unexpectedly.

Source code in src/hassette/exceptions.py
56
57
class ConnectionClosedError(HassetteError):
    """Custom exception to indicate that the WebSocket connection was closed unexpectedly."""

TelemetryUnavailableError

Bases: HassetteError

The telemetry store could not satisfy a read (down, slow, or closed).

Source code in src/hassette/exceptions.py
60
61
class TelemetryUnavailableError(HassetteError):
    """The telemetry store could not satisfy a read (down, slow, or closed)."""

SchemaVersionError

Bases: HassetteError

Raised when the on-disk database schema version is ahead of the code's expected head.

This indicates the database was created by a newer binary. The service should not auto-delete the database in this case; manual intervention is required.

Listed in DatabaseService.restart_spec.fatal_error_names so the ServiceWatcher triggers immediate shutdown (FAILED path) rather than retrying.

Source code in src/hassette/exceptions.py
64
65
66
67
68
69
70
71
72
class SchemaVersionError(HassetteError):
    """Raised when the on-disk database schema version is ahead of the code's expected head.

    This indicates the database was created by a newer binary. The service should not
    auto-delete the database in this case; manual intervention is required.

    Listed in ``DatabaseService.restart_spec.fatal_error_names`` so the ServiceWatcher
    triggers immediate shutdown (FAILED path) rather than retrying.
    """

CouldNotFindHomeAssistantError

Bases: FatalError

Custom exception to indicate that the Home Assistant instance could not be found.

Source code in src/hassette/exceptions.py
75
76
77
78
79
80
81
82
83
class CouldNotFindHomeAssistantError(FatalError):
    """Custom exception to indicate that the Home Assistant instance could not be found."""

    def __init__(self, url: str):
        yurl = URL(url)
        msg = f"Could not find Home Assistant instance at {url}, ensure it is running and accessible"
        if not yurl.explicit_port:
            msg += " and that the port is specified if necessary"
        super().__init__(msg)

RetryableConnectionClosedError

Bases: ConnectionClosedError

Custom exception to indicate that the WebSocket connection was closed but can be retried.

Source code in src/hassette/exceptions.py
86
87
88
89
90
91
class RetryableConnectionClosedError(ConnectionClosedError):
    """Custom exception to indicate that the WebSocket connection was closed but can be retried."""

    def __init__(self, msg: str, *, close_code: int | None = None) -> None:
        super().__init__(msg)
        self.close_code = close_code

FailedMessageError

Bases: HassetteError

Custom exception to indicate that a message sent to the WebSocket failed.

Exposes HA's structured error surface as instance attributes so callers can react programmatically::

try:
    await api.helpers.update(
        "vacation_mode",
        UpdateInputBooleanParams(initial=False),
    )
except FailedMessageError as exc:
    if exc.code == "not_found":
        # Helper was deleted between list and update — recreate it
        ...

code is populated when the error originates from an HA error envelope (see FailedMessageError.from_error_response). It is None for locally-synthesized failures such as transport timeouts.

Source code in src/hassette/exceptions.py
 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
class FailedMessageError(HassetteError):
    """Custom exception to indicate that a message sent to the WebSocket failed.

    Exposes HA's structured error surface as instance attributes so callers can
    react programmatically::

        try:
            await api.helpers.update(
                "vacation_mode",
                UpdateInputBooleanParams(initial=False),
            )
        except FailedMessageError as exc:
            if exc.code == "not_found":
                # Helper was deleted between list and update — recreate it
                ...

    ``code`` is populated when the error originates from an HA error envelope
    (see ``FailedMessageError.from_error_response``). It is ``None`` for
    locally-synthesized failures such as transport timeouts.
    """

    def __init__(
        self,
        msg: str,
        *,
        code: str | None = None,
        original_data: dict | None = None,
    ) -> None:
        super().__init__(msg)
        self.code = code
        self.original_data = original_data

    @classmethod
    def from_error_response(
        cls,
        error: str | None = None,
        code: str | None = None,
        original_data: dict | None = None,
    ) -> "FailedMessageError":
        msg = f"WebSocket message failed with response '{error}' (data={original_data})"
        return cls(msg, code=code, original_data=original_data)

InvalidAuthError

Bases: FatalError

Custom exception to indicate that the authentication token is invalid.

Source code in src/hassette/exceptions.py
137
138
class InvalidAuthError(FatalError):
    """Custom exception to indicate that the authentication token is invalid."""

InvalidInheritanceError

Bases: TypeError, HassetteError

Raised when a class inherits from App incorrectly.

Source code in src/hassette/exceptions.py
141
142
class InvalidInheritanceError(TypeError, HassetteError):
    """Raised when a class inherits from App incorrectly."""

UndefinedUserConfigError

Bases: TypeError, HassetteError

Raised when a class does not define a user_config_class.

Source code in src/hassette/exceptions.py
145
146
class UndefinedUserConfigError(TypeError, HassetteError):
    """Raised when a class does not define a user_config_class."""

EntityNotFoundError

Bases: ValueError, HassetteError

Custom error for handling 404 in the Api.

Source code in src/hassette/exceptions.py
149
150
class EntityNotFoundError(ValueError, HassetteError):
    """Custom error for handling 404 in the Api."""

ResourceNotReadyError

Bases: HassetteError

Custom exception to indicate that a resource is not ready for use.

Source code in src/hassette/exceptions.py
153
154
class ResourceNotReadyError(HassetteError):
    """Custom exception to indicate that a resource is not ready for use."""

AppPrecheckFailedError

Bases: HassetteError

Custom exception to indicate that one or more prechecks for an app failed.

Source code in src/hassette/exceptions.py
157
158
class AppPrecheckFailedError(HassetteError):
    """Custom exception to indicate that one or more prechecks for an app failed."""

CannotOverrideFinalError

Bases: TypeError, HassetteError

Custom exception to indicate that a final method or class cannot be overridden.

Source code in src/hassette/exceptions.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
class CannotOverrideFinalError(TypeError, HassetteError):
    """Custom exception to indicate that a final method or class cannot be overridden."""

    def __init__(
        self,
        method_name: str,
        origin_name: str,
        subclass_name: str,
        suggested_alt: str | None = None,
        location: str | None = None,
    ):
        msg = (
            f"App '{subclass_name}' attempted to override the final lifecycle method "
            f"'{method_name}' defined in {origin_name!r}. "
        )
        if suggested_alt:
            msg += f"Use '{suggested_alt}' instead."
        if location:
            msg += f" (at {location})"
        super().__init__(msg)

DependencyError

Bases: HassetteError

Base class for dependency-related errors.

Source code in src/hassette/exceptions.py
183
184
class DependencyError(HassetteError):
    """Base class for dependency-related errors."""

DependencyInjectionError

Bases: DependencyError

Raised when dependency injection fails due to invalid handler signature or annotations.

This exception indicates a user error in handler definition, such as: - Using invalid parameter types (*args, positional-only) - Missing required type annotations - Incompatible annotation types

These errors should be fixed by updating the handler signature.

Source code in src/hassette/exceptions.py
187
188
189
190
191
192
193
194
195
196
class DependencyInjectionError(DependencyError):
    """Raised when dependency injection fails due to invalid handler signature or annotations.

    This exception indicates a user error in handler definition, such as:
    - Using invalid parameter types (*args, positional-only)
    - Missing required type annotations
    - Incompatible annotation types

    These errors should be fixed by updating the handler signature.
    """

DependencyResolutionError

Bases: DependencyError

Raised when dependency injection fails during runtime extraction or conversion.

This exception indicates a runtime issue with: - Extracting parameter values from events - Converting values to expected types - Type mismatches between extracted values and annotations

These errors may indicate issues with event data, converter logic, or type registry.

Source code in src/hassette/exceptions.py
199
200
201
202
203
204
205
206
207
208
class DependencyResolutionError(DependencyError):
    """Raised when dependency injection fails during runtime extraction or conversion.

    This exception indicates a runtime issue with:
    - Extracting parameter values from events
    - Converting values to expected types
    - Type mismatches between extracted values and annotations

    These errors may indicate issues with event data, converter logic, or type registry.
    """

StateRegistryError

Bases: HassetteError

Base exception for state registry errors.

Source code in src/hassette/exceptions.py
211
212
class StateRegistryError(HassetteError):
    """Base exception for state registry errors."""

RegistryNotReadyError

Bases: StateRegistryError

Raised when attempting to use the registry before any classes are registered.

Source code in src/hassette/exceptions.py
215
216
217
218
219
220
221
222
223
class RegistryNotReadyError(StateRegistryError):
    """Raised when attempting to use the registry before any classes are registered."""

    def __init__(self) -> None:
        super().__init__(
            "State registry has not been initialized. "
            "No state classes have been registered yet. "
            "Ensure state modules are imported before attempting state conversion."
        )

NoDomainAnnotationError

Bases: StateRegistryError

Raised when a state class does not define a domain annotation or the annotation is empty.

Generally ignored, this indicates that the class is a base class and not intended to be registered.

Source code in src/hassette/exceptions.py
226
227
228
229
230
231
232
233
234
235
236
237
class NoDomainAnnotationError(StateRegistryError):
    """Raised when a state class does not define a domain annotation or the annotation is empty.

    Generally ignored, this indicates that the class is a base class and not intended to be registered.

    """

    def __init__(self, state_class: type["BaseState[Any]"]) -> None:
        super().__init__(
            f"State class {state_class.__name__} does not define a domain annotation or the annotation is empty."
        )
        self.state_class = state_class

DomainNotFoundError

Bases: StateRegistryError

Raised when no state class is found for a given domain.

Source code in src/hassette/exceptions.py
240
241
242
243
244
245
class DomainNotFoundError(StateRegistryError):
    """Raised when no state class is found for a given domain."""

    def __init__(self, domain: str):
        super().__init__(f"No state class found for domain '{domain}'.")
        self.domain = domain

HassetteNotInitializedError

Bases: RuntimeError

Exception raised when Hassette is not initialized in the current context.

Source code in src/hassette/exceptions.py
248
249
class HassetteNotInitializedError(RuntimeError):
    """Exception raised when Hassette is not initialized in the current context."""

InvalidDataForStateConversionError

Bases: StateRegistryError

Raised when the data provided for state conversion is invalid or malformed.

Source code in src/hassette/exceptions.py
252
253
254
255
256
257
class InvalidDataForStateConversionError(StateRegistryError):
    """Raised when the data provided for state conversion is invalid or malformed."""

    def __init__(self, data: Any):
        super().__init__(f"Invalid or malformed data provided for state conversion: {data!r}")
        self.data = data

UnableToConvertStateError

Bases: StateRegistryError

Raised when a state dictionary cannot be converted to a specific state class.

Source code in src/hassette/exceptions.py
260
261
262
263
264
265
266
class UnableToConvertStateError(StateRegistryError):
    """Raised when a state dictionary cannot be converted to a specific state class."""

    def __init__(self, entity_id: str, state_class: type["BaseState"]) -> None:
        super().__init__(f"Unable to convert state for entity_id '{entity_id}' to class {state_class.__name__}.")
        self.entity_id = entity_id
        self.state_class = state_class

ConvertedTypeDoesNotMatchError

Bases: StateRegistryError

Raised when a converted state does not match the expected type.

Source code in src/hassette/exceptions.py
269
270
271
272
273
274
275
276
277
278
279
class ConvertedTypeDoesNotMatchError(StateRegistryError):
    """Raised when a converted state does not match the expected type."""

    def __init__(self, entity_id: str, expected_class: type["BaseState"], actual_class: type["BaseState"]) -> None:
        super().__init__(
            f"Converted state for entity_id '{entity_id}' is of type {actual_class.__name__}, "
            f"expected {expected_class.__name__}."
        )
        self.entity_id = entity_id
        self.expected_class = expected_class
        self.actual_class = actual_class

InvalidEntityIdError

Bases: StateRegistryError

Raised when an entity ID is invalid or malformed.

Source code in src/hassette/exceptions.py
282
283
284
285
286
287
class InvalidEntityIdError(StateRegistryError):
    """Raised when an entity ID is invalid or malformed."""

    def __init__(self, entity_id: Any):
        super().__init__(f"Invalid or malformed entity ID: {entity_id!r}")
        self.entity_id = entity_id

UnableToConvertValueError

Bases: HassetteError

Raised when a raw value cannot be converted from one type to another via the TypeRegistry.

Source code in src/hassette/exceptions.py
290
291
class UnableToConvertValueError(HassetteError):
    """Raised when a raw value cannot be converted from one type to another via the TypeRegistry."""

InvalidLifecycleTransitionError

Bases: HassetteError

Raised when a ResourceStatus transition is invalid in strict lifecycle mode.

Only raised when HassetteConfig.strict_lifecycle is True. In non-strict mode the same condition logs a WARNING instead.

Attributes:

Name Type Description
from_status

The status the resource was in before the attempted transition.

to_status

The status the resource was attempting to transition to.

resource_name

The unique_name of the resource that made the invalid transition.

Source code in src/hassette/exceptions.py
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
class InvalidLifecycleTransitionError(HassetteError):
    """Raised when a ResourceStatus transition is invalid in strict lifecycle mode.

    Only raised when ``HassetteConfig.strict_lifecycle`` is True. In non-strict
    mode the same condition logs a WARNING instead.

    Attributes:
        from_status: The status the resource was in before the attempted transition.
        to_status: The status the resource was attempting to transition to.
        resource_name: The unique_name of the resource that made the invalid transition.
    """

    def __init__(self, from_status: Any, to_status: Any, resource_name: str) -> None:
        self.from_status = from_status
        self.to_status = to_status
        self.resource_name = resource_name
        super().__init__(f"Invalid lifecycle transition for '{resource_name}': {from_status!r}{to_status!r}")

ListenerNameRequiredError

Bases: HassetteError

Raised at call time when name= is omitted on a DB-registered listener.

Attributes:

Name Type Description
handler_method

Fully-qualified name of the handler function.

topic

The event topic the listener was being registered for.

Source code in src/hassette/exceptions.py
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
class ListenerNameRequiredError(HassetteError):
    """Raised at call time when ``name=`` is omitted on a DB-registered listener.

    Attributes:
        handler_method: Fully-qualified name of the handler function.
        topic: The event topic the listener was being registered for.
    """

    def __init__(self, handler_method: str, topic: str) -> None:
        self.handler_method = handler_method
        self.topic = topic
        super().__init__(
            f"Listener registration requires a name.\n\n"
            f"  handler: {handler_method}\n"
            f"  topic:   {topic}\n\n"
            f"Provide a stable name via the `name=` parameter:\n\n"
            f'  await self.bus.on_state_change({topic!r}, handler=self.handler, name="my_listener")'
        )

SchedulerNameRequiredError

Bases: HassetteError

Raised at call time when name= is omitted on a scheduled job.

Attributes:

Name Type Description
handler_method

Fully-qualified name of the handler function.

trigger_description

Human-readable description of the trigger the job was being scheduled with.

Source code in src/hassette/exceptions.py
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
class SchedulerNameRequiredError(HassetteError):
    """Raised at call time when ``name=`` is omitted on a scheduled job.

    Attributes:
        handler_method: Fully-qualified name of the handler function.
        trigger_description: Human-readable description of the trigger the job was being scheduled with.
    """

    def __init__(self, handler_method: str, trigger_description: str) -> None:
        self.handler_method = handler_method
        self.trigger_description = trigger_description
        super().__init__(
            f"Scheduled job registration requires a name.\n\n"
            f"  handler: {handler_method}\n"
            f"  trigger: {trigger_description}\n\n"
            f"Provide a stable name via the `name=` parameter:\n\n"
            f'  await self.scheduler.run_in(self.handler, 5, name="my_job")'
        )

DuplicateListenerError

Bases: HassetteError

Raised at call time when a second listener with the same (name, topic) is registered within the same app instance in the same session.

Detected in-memory by the Bus before any database write. Cross-session duplicates are handled by upsert and are not an error.

Attributes:

Name Type Description
name

The stable name that collided.

topic

The event topic both listeners were registered for.

existing_handler

Fully-qualified name of the already-registered handler.

duplicate_handler

Fully-qualified name of the handler that triggered the error.

Source code in src/hassette/exceptions.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
class DuplicateListenerError(HassetteError):
    """Raised at call time when a second listener with the same ``(name, topic)`` is
    registered within the same app instance in the same session.

    Detected in-memory by the Bus before any database write. Cross-session duplicates
    are handled by upsert and are not an error.

    Attributes:
        name: The stable name that collided.
        topic: The event topic both listeners were registered for.
        existing_handler: Fully-qualified name of the already-registered handler.
        duplicate_handler: Fully-qualified name of the handler that triggered the error.
    """

    def __init__(self, name: str, topic: str, existing_handler: str, duplicate_handler: str) -> None:
        self.name = name
        self.topic = topic
        self.existing_handler = existing_handler
        self.duplicate_handler = duplicate_handler
        super().__init__(
            f"A listener named {name!r} is already registered for topic {topic!r}.\n\n"
            f"  existing handler: {existing_handler}\n"
            f"  duplicate handler: {duplicate_handler}\n\n"
            f"Use a different name for the second listener, or remove the first registration before re-registering."
        )

RegistryValidationError

Bases: HassetteError

Raised when startup registry validation finds error-level issues.

Raised by validate_registries(strict=True) after collecting all issues. Hassette.wire_services() passes strict=config.strict_lifecycle, so in production this only fires when the user explicitly enables strict mode.

Attributes:

Name Type Description
issues

The full list of validation issues found. Always contains at least one error-severity issue when this exception is raised.

Source code in src/hassette/exceptions.py
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
class RegistryValidationError(HassetteError):
    """Raised when startup registry validation finds error-level issues.

    Raised by ``validate_registries(strict=True)`` after collecting all issues.
    ``Hassette.wire_services()`` passes ``strict=config.strict_lifecycle``, so in
    production this only fires when the user explicitly enables strict mode.

    Attributes:
        issues: The full list of validation issues found. Always contains at least
            one error-severity issue when this exception is raised.
    """

    def __init__(self, issues: list[Any]) -> None:
        self.issues = issues
        error_count = sum(1 for i in issues if i.severity == "error")
        warning_count = sum(1 for i in issues if i.severity == "warning")
        total = len(issues)
        summary_lines = [f"Registry validation failed: {error_count} error(s), {warning_count} warning(s)"]
        summary_lines.extend(
            f"  [{i.severity.upper()}] {i.registry}: {i.message}" for i in issues[:MAX_ISSUES_IN_SUMMARY]
        )
        if total > MAX_ISSUES_IN_SUMMARY:
            summary_lines.append(f"  ... and {total - MAX_ISSUES_IN_SUMMARY} more issue(s)")
        super().__init__("\n".join(summary_lines))