Apps — Lifecycle
Hassette manages app initialization and shutdown. The app declares what to do at each stage through lifecycle hooks.
Initialization
Hassette transitions the app through STARTING to RUNNING at startup. All core services (API, Bus, Scheduler, and the internal SQLite telemetry database) are ready before any hook runs.
Three hooks fire in order:
before_initializeon_initialize, the primary hook where the app subscribes to HA events and schedules recurring tasksafter_initialize
from hassette import App, D
from hassette.models import states
class MyApp(App):
async def on_initialize(self) -> None:
await self.bus.on_state_change("sensor.power", handler=self.on_power, name="power_sensor")
await self.scheduler.run_in(self.check_status, 30, name="startup_check")
async def on_power(self, new_state: D.StateNew[states.SensorState]) -> None:
pass
async def check_status(self) -> None:
pass
on_initialize is where most apps do their setup. self.bus.on_state_change registers a handler that fires on entity state changes. self.scheduler.run_in schedules a one-shot job after a fixed delay. Both calls are async and must be awaited. By the time on_initialize runs, the bus, scheduler, API, and database are all ready.
before_initialize and after_initialize exist for setup that must happen strictly before or after the main registration. Most apps only need on_initialize.
Note
The base implementations of these hooks are empty. No super() call is necessary.
Shutdown
Hassette transitions the app through STOPPING to STOPPED during shutdown or reload.
Three hooks fire in order:
before_shutdownon_shutdownafter_shutdown
on_shutdown is for releasing external resources the app allocated directly: open files, raw sockets, or third-party connections. Bus subscriptions, scheduled jobs, and task_bucket tasks are cleaned up automatically.
Automatic Cleanup
After the shutdown hooks complete, Hassette cancels all bus subscriptions created via self.bus, all scheduled jobs created via self.scheduler, and all background tasks tracked by self.task_bucket. Manual unsubscription or job cancellation in on_shutdown is unnecessary.
Warning
initialize, shutdown, and cleanup are marked @final — attempting to override any of them raises CannotOverrideFinalError at class load time. The before_*, on_*, and after_* hooks are the extension points.
Synchronous Lifecycle
AppSync lifecycle hooks
AppSync is for apps that wrap blocking (non-async) third-party libraries. It provides _sync-suffixed variants of each hook. Hassette runs each variant in a thread pool via task_bucket.run_in_thread, so blocking calls do not stall the event loop. The _sync hooks are synchronous and cannot use await.
App (async) |
AppSync (sync) |
|---|---|
before_initialize |
before_initialize_sync |
on_initialize |
on_initialize_sync |
after_initialize |
after_initialize_sync |
before_shutdown |
before_shutdown_sync |
on_shutdown |
on_shutdown_sync |
after_shutdown |
after_shutdown_sync |
The async hooks (on_initialize, on_shutdown, etc.) are marked @final on AppSync and delegate to the _sync variants via the thread pool. Overriding them raises CannotOverrideFinalError at class load time.
The bus, scheduler, and API are async. The .sync facades provide synchronous access from _sync hooks: self.bus.sync, self.scheduler.sync, and self.api.sync.
from hassette import AppSync
class MyApp(AppSync):
def on_initialize_sync(self) -> None:
self.bus.sync.on_state_change("light.kitchen", handler=self.on_light_change, name="kitchen")
self.scheduler.sync.run_in(self.cleanup_task, 60, name="cleanup")
def on_light_change(self) -> None:
pass
def cleanup_task(self) -> None:
pass
See Also
- Apps overview: app structure and configuration
- Task Bucket: background task lifecycle and shutdown behavior
Bus: handler registration inon_initialize