Dependency Injection
Hassette's dependency injection system extracts typed data from events and passes it to handler parameters. Like FastAPI's Depends(), Hassette resolves handler parameters at call time — but instead of a dependency function, type annotations from the D module declare what to extract. All annotations live in hassette.event_handling.dependencies, imported as D: from hassette import D.
from hassette import App, D, states
class LightMonitor(App):
async def on_initialize(self):
await self.bus.on_state_change(
"light.bedroom",
handler=self.on_light_change,
name="bedroom_light",
)
async def on_light_change(
self,
new_state: D.StateNew[states.LightState],
entity_id: D.EntityId,
):
brightness = new_state.attributes.brightness
self.logger.info("%s brightness: %s", entity_id, brightness)
D.StateNew[states.LightState] extracts the new state and converts it to a typed LightState. D.EntityId extracts the entity ID as a string. The handler receives clean data with no event parsing.
Annotation Reference
State Extractors
State extractors resolve typed state objects from state change events. T is any state class from hassette.models.states — a full list is at State Conversion.
| Annotation | Returns | If missing |
|---|---|---|
D.StateNew[T] |
T |
Handler skipped |
D.StateOld[T] |
T |
Handler skipped |
D.MaybeStateNew[T] |
T \| None |
None |
D.MaybeStateOld[T] |
T \| None |
None |
from hassette import App, D, states
class TempApp(App):
async def on_temp_change(
self,
new: D.StateNew[states.SensorState],
old: D.MaybeStateOld[states.SensorState],
):
if old and old.value and new.value:
delta = float(new.value) - float(old.value)
self.logger.info(
"Temperature moved %.1f°F", delta
)
When a required extractor finds no value, Hassette skips the handler invocation and logs the failure at ERROR level — no exception propagates to your app. MaybeStateOld returns None on the first event for a new entity with no previous state (typically on startup or when an entity first appears).
Identity Extractors
Identity extractors resolve entity IDs and domains from events.
| Annotation | Returns | If missing |
|---|---|---|
D.EntityId |
str |
Handler skipped |
D.MaybeEntityId |
str \| MISSING_VALUE |
Falsy sentinel |
D.Domain |
str |
Handler skipped |
D.MaybeDomain |
str \| MISSING_VALUE |
Falsy sentinel |
from hassette import App, D
class LightApp(App):
async def on_any_light(self, entity_id: D.EntityId, domain: D.Domain):
self.logger.info("Light entity %s in domain %s changed", entity_id, domain)
MISSING_VALUE is a falsy sentinel from hassette.const indicating a field does not exist on the event. It is not None — None means the field exists with a null value. Testing with if entity_id: covers both the present and absent cases. D.MaybeEntityId is useful in generic handlers registered via on() where the event may not have an entity_id field.
Other Extractors
| Annotation | Returns | If missing | Use case |
|---|---|---|---|
D.EventData[T] |
T |
Handler skipped | Typed payload from Bus.emit broadcast events |
D.EventContext |
HassContext |
Handler skipped | Home Assistant event context (user ID, parent/origin IDs) |
D.TypedStateChangeEvent[T] |
TypedStateChangeEvent[T] |
Always present | Full event with both old and new states typed |
D.EventData[T] pairs with Bus.emit for cross-app communication — one app sends a typed payload, and other apps subscribe to receive it. The emitting app sends a dataclass; the receiving handler annotates its parameter with the same type:
from dataclasses import dataclass
from hassette import App, AppConfig, D
@dataclass(frozen=True, slots=True)
class SensorAlert:
sensor_id: str
reading: float
class AlertApp(App[AppConfig]):
async def on_initialize(self) -> None:
await self.bus.on(topic="sensor.alert", handler=self.on_alert, name="alert_handler")
async def on_alert(self, alert: D.EventData[SensorAlert]) -> None:
self.logger.warning("Sensor %s reading: %.1f", alert.sensor_id, alert.reading)
Combining Annotations
Handlers accept multiple DI parameters. Hassette resolves each independently from the same event.
from hassette import App, D, states
class ClimateApp(App):
async def on_climate_change(
self,
new: D.StateNew[states.ClimateState],
entity_id: D.EntityId,
context: D.EventContext,
):
temp = new.attributes.current_temperature
self.logger.info(
"%s temperature: %s (user: %s)",
entity_id,
temp,
context.user_id or "system",
)
Union Types
State extractors accept union types for handlers that cover multiple entity domains.
from hassette import App, D, states
class SensorApp(App):
async def on_sensor_change(
self,
new: D.StateNew[
states.SensorState | states.BinarySensorState
],
entity_id: D.EntityId,
):
if isinstance(new, states.SensorState):
self.logger.info("Sensor %s: %s", entity_id, new.value)
else:
self.logger.info("Binary %s: %s", entity_id, new.value)
Hassette determines the concrete state class from the entity's domain at dispatch time — see State Conversion for details.
Custom Keyword Arguments
DI composes with kwargs= passed at registration. DI-annotated parameters resolve from the event; remaining keyword arguments pass through unchanged from the registration call.
from hassette import App, D, states
class TempApp(App):
async def on_initialize(self):
await self.bus.on_state_change(
"sensor.temperature",
handler=self.on_temp_change,
kwargs={"threshold": 75.0},
name="temp_threshold",
)
async def on_temp_change(
self,
new: D.StateNew[states.SensorState],
entity_id: D.EntityId,
threshold: float,
):
temp = float(new.value) if new.value else 0.0
if temp > threshold:
self.logger.warning(
"%s is %.1f°F (threshold: %.1f)",
entity_id,
temp,
threshold,
)
Handler Signature Restrictions
Any handler with at least one D.* annotation is a DI handler. DI handlers do not support positional-only parameters (those before /) or *args. Regular parameters and **kwargs are both valid. Every DI parameter requires a type annotation. Hassette uses the annotation to determine what to extract.
Not all D.* annotations work with every subscription method. Subscription Methods lists the compatible annotations for each method.
See Also
- Custom Extractors. Writing extractors, accessors,
AnnotationDetails, and automatic type conversion. - Writing Handlers. Handler signature patterns.
- Subscription Methods. Which
D.*annotations each method supports. - State Conversion. Domain-to-model mapping and automatic type conversion.