App Lifecycle
Every app goes through startup and shutdown phases. You don't need to manage resources yourself — Hassette handles that, so you can focus on your automation logic.
Initialization
During startup, Hassette transitions the app through STARTING → RUNNING.
All core services (API, Bus, Scheduler) are fully ready before your initialization hooks run.
The initialization hooks are called in this order:
before_initializeon_initializeafter_initialize
Use these to register event handlers, schedule jobs, or perform any startup logic.
from hassette import App
class MyApp(App):
async def on_initialize(self):
self.logger.info("App starting up!")
await self.bus.on_state_change("sensor.power", handler=self.on_power, name="power_sensor")
async def on_power(self, event):
pass
Note
You do not need to call super() in these hooks as the base implementations are empty.
Shutdown
When shutting down or reloading, Hassette transitions the app through STOPPING → STOPPED.
The shutdown hooks are called in this order:
before_shutdownon_shutdownafter_shutdown
Automatic Cleanup
After the shutdown hooks run, Hassette automatically performs cleanup:
- Cancels all active subscriptions created by
self.bus. - Cancels all scheduled jobs created by
self.scheduler. - Cancels any background tasks tracked by the app.
This means you do not need to manually unsubscribe or cancel jobs in on_shutdown. Only implement shutdown logic if you have allocated external resources (like opening a file or a raw socket).
Warning
Do not override initialize, shutdown, or cleanup methods directly. These are internal methods that manage resource setup, lifecycle ordering, and teardown — they are marked final to prevent accidental overrides that could break the resource contract.
Use the on_initialize and on_shutdown hooks instead — they are called at the correct point within these methods. Attempting to override a final method will raise a CannotOverrideFinalError when your app class is loaded.
AppSync Lifecycle Hooks
If you use AppSync instead of App, use the _sync variants of each lifecycle hook:
App (async) |
AppSync (sync) |
|---|---|
on_initialize |
on_initialize_sync |
on_shutdown |
on_shutdown_sync |
before_initialize |
before_initialize_sync |
before_shutdown |
before_shutdown_sync |
after_initialize |
after_initialize_sync |
after_shutdown |
after_shutdown_sync |
AppSync runs each lifecycle hook in a thread pool via run_in_thread, so the hooks must be synchronous — they cannot use await. The async hooks (on_initialize, on_shutdown, etc.) are marked @final on AppSync and will raise NotImplementedError if you try to override them directly.
Use on_initialize_sync, not on_initialize, in AppSync
In AppSync, overriding on_initialize will raise NotImplementedError at startup. Override on_initialize_sync instead. The bus, scheduler, and API are async — reach them through their .sync facades:
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, 60, name="cleanup")
def on_light_change(self):
pass
def cleanup(self):
pass