Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.42.0 (2026-06-11)
Features
Bug Fixes
- coerce numeric strings in Comparison condition (#1003) (ae751a6)
- Docker semver images not published since v0.39.0 (#999) (ee1d804)
Documentation
0.41.0 (2026-06-08)
Features
Bug Fixes
- local log timestamps, WS retry, and state proxy self-healing (#990) (f5378fa)
- re-establish state subscription on reconnect and add concurrency guard (#995) (c020ea6)
0.40.0 (2026-06-06)
⚠ BREAKING CHANGES
GET /api/healthnow returns HTTP 200 (not 503) while the process is serving — forstartinganddegradedas well asok. The handler never returns 503. Any healthcheck or restart automation pointed at/api/healthshould move to/api/health/live(liveness, HA-independent) for restart decisions, or/api/health/ready(200 only when fully connected) for traffic routing. Separately, a fatal, unrecoverable shutdown now exits with a non-zero status code instead of 0, so supervisors usingRestart=on-failurewill restart after a fatal crash; a clean operator shutdown (SIGTERM /docker stop) still exits 0.
Features
0.39.1 (2026-06-05)
Bug Fixes
0.39.0 (2026-06-01)
⚠ BREAKING CHANGES
App.send_event(topic, data)andAppSync.send_event_sync(topic, data)are removed. Useself.bus.emit(topic, data)instead — same arguments, same behavior, now on the bus where subscriptions live.HassettePayloadno longer accepts anevent_typeparameter. Removeevent_type=from anyHassettePayload(...)orHassetteServiceEvent(...)construction.EventPayloadbase class also no longer hasevent_type— useHassPayload.event_typefor HA events, orevent.topicfor routing.
Features
- broadcast custom events between apps with
Bus.emit()and receive typed payloads viaD.EventData[T](#952) (082a59e)
Bug Fixes
Refactoring
- remove event_type from EventPayload base and HassettePayload (#947) (8037297)
- remove redundant event_name parameter from send_event (#946) (e282f81), closes #943
0.38.0 (2026-05-31)
Features
- add Python 3.14 support (#939) (86049f2)
- add missing synchronous Bus and Scheduler methods for AppSync (#931) (962631f)
0.37.0 (2026-05-30)
⚠ BREAKING CHANGES
This release redesigns registration and telemetry. Most existing apps need code changes.
Registration is now async
Bus and scheduler registration methods are now async and must be awaited.
name= is now required on every bus registration (it was optional). Omitting it raises ListenerNameRequiredError.
# before
self.bus.on_state_change("light.kitchen", handler=self.on_change)
self.scheduler.run_in(self.task, 5)
# after
await self.bus.on_state_change("light.kitchen", handler=self.on_change, name="kitchen_light")
await self.scheduler.run_in(self.task, 5)
Affected bus methods: on_state_change, on_attribute_change, on_call_service, on_component_loaded, on.
Affected scheduler methods: schedule, run_in, run_once, run_every, run_minutely, run_hourly, run_daily, run_cron.
Subscription.registration_task is removed. Registration completes inline now, so sub.listener.db_id is a valid integer as soon as the awaited call returns.
Unified executions model
HandlerInvocation and JobExecution are replaced by a single Execution model with a kind discriminator ("listener" or "job").
The REST endpoint /api/telemetry/handler/{id}/invocations is now /api/telemetry/listener/{id}/executions. The WebSocket signals are unified under the same executions shape.
Migration runner replaces Alembic
The Alembic migration stack is replaced by a PRAGMA user_version runner with plain SQL files. Existing databases are migrated automatically on first startup. No manual migration steps required, but any tooling that reads alembic_version will need updating.
Removed: dropped_no_session
The dropped_no_session counter is removed from the API response, the dashboard status badge, and the sessions table.
Features
Bug Fixes
Internal
- decompose bus_service.py into focused modules (#919)
- extract Service and RestartSpec from resources/base.py (#921)
- docs voice guide, quality rules, and audit (#910, #917, #920)
0.36.0 (2026-05-28)
Features
0.35.0 (2026-05-27)
⚠ BREAKING CHANGES
hassettewith no arguments no longer starts the server. Usehassette runinstead. Thehassette servicecommand andGET /api/servicesendpoint have been removed.
Features
0.34.0 (2026-05-27)
Breaking Changes
state._has_feature(SomeFeature.FLAG)renamed tostate.has_feature()— remove the leading underscore. Thesupports_*convenience properties (e.g.,state.supports_brightness) are unchanged. (#891)Api.get_states_iterator()andApiSyncFacade.get_states_iterator()removed — the generator-based iterator does not work correctly in sync code. Useget_states()instead. (#891)
Logging
- Logging pipeline managed as a
LoggingServiceResource with lifecycle ordering — the async pipeline starts during service initialization and shuts down before the database, ensuring pending log records flush cleanly. (#887) - Timer-based flush bounds how long log records sit in the write queue under low load (default 5s), instead of waiting for the next batch to fill. (#890)
- Failed write batches retry with linear backoff (1s, 2s, 3s) instead of immediately, giving the database time to recover. (#890)
CLI
- Improved
hassette status/hassette queryoutput formatting — nested sub-models render as labeled sections instead of raw JSON,Noneshows as—, booleans show lowercase, and panel titles are humanized. (#880) - Job and listener tables show App column, actual schedule text, method-only handler names, and entity targets. (#880)
fmt_relative_timeshowsin 3hfor future timestamps instead ofsoon. (#880)
Bug Fixes
- Fix 3 bugs in example apps where
BinarySensorState.valueandLightState.value(bothbool) were compared against string"on"/"off". (#872)
0.33.0 (2026-05-26)
Breaking Changes
GET /api/logs/by-execution/{execution_id}has been removed. UseGET /api/executions/{execution_id}instead. The response shape (LogsByExecutionResponse) is unchanged. (#865)
CLI
hassette querysubcommand for querying all read-only API endpoints from the command line — apps, handlers, jobs, invocations, executions, logs, config, and sessions. (#838)--generate-completionreplaces--install-completion— outputs the shell completion script to stdout instead of modifying shell config files. Pipe to a file in your$fpathto install. (#870)- Fix zsh completion function name with cyclopts 4.16+ —
hassette --generate-completion zshnow produces the correct_hassettefunction name instead of_cyclopts_hassette. (#871)
Web UI
- Redesigned accent color system and improved visual depth with systematized design tokens. (#842)
- Mobile navigation integrated into the status bar with a hamburger menu. (#857)
- Unified table sort headers across all pages and fixed mobile table column rendering. (#853)
- Aria-live announcements for WebSocket connection status changes. (#827)
Configuration
- TOML
[hassette]section now deep-merges, preserving top-level app definitions alongside nested sections. (#826) - Auto-migrate legacy
HASSETTE__APP_DIRenvironment variable toHASSETTE__APPS__DIRECTORY. (#831) - Environment variables correctly override config file values for legacy flat config keys. (#836)
Performance
- Telemetry database queries now have a configurable read timeout, preventing indefinite hangs on slow or locked databases. (#860)
Documentation
- Rewrite web UI documentation with full page coverage. (#806)
- Update configuration references to post-v0.32.0 nested key format. (#818)
- Clean up docs site navigation — remove redundant tabs, fix mid-viewport layout. (#858)
0.32.0 (2026-05-20)
Breaking Changes
- TOML app configuration path changed from
[hassette.app.apps.<name>]to[hassette.apps.<name>]. The app settings section changed from[hassette.app]to[hassette.apps]. Environment variables changed fromHASSETTE__APP__*toHASSETTE__APPS__*. Python access changed fromconfig.app.*toconfig.apps.*. The API response field changed fromapptoapps. Update your hassette.toml, environment variables, and any code that accesses the config object.
Refactoring
0.31.0 (2026-05-20)
Breaking Changes
HassetteConfigrestructured into nested groups — all configuration fields moved under group prefixes. Environment variables change fromHASSETTE__LOG_LEVELtoHASSETTE__LOGGING__LOG_LEVEL,HASSETTE__DB_PATHtoHASSETTE__DATABASE__PATH, etc. TOML/YAML config files and programmaticconfig.*access follow the same pattern (config.log_level→config.logging.log_level). Old flat keys are detected at startup with a deprecation warning but will not take effect — update your env vars, config files, and any directHassetteConfigfield access to the nested format. Seesrc/hassette/config/legacy.pyfor the full old→new mapping. (#789)
Logging
- Replace
coloredlogswith a structlog-based logging pipeline — structured, context-rich log records with console and JSON formatters. All log I/O routed through an async queue soemit()never blocks the event loop. (#744) - Log records persisted to the telemetry database with configurable retention (
log_retention_days) and persistence level (log_persistence_level). (#744) - New log viewer page in the web UI with server-side level filtering, search, column picker, and expand-to-detail. (#744)
Scheduler
if_exists="replace"option on all scheduler registration methods — when a job with the same name already exists,"replace"cancels the old job and registers the new one in its place. Useful when job configuration changes between app reloads. (#780)
Web UI
- Complete UI redesign with apps-focused layout — sidebar navigation with live status dots, collapsible status groups, multi-instance app expansion, and command palette (Ctrl+K / Cmd+K). New app detail tabs: overview, handlers, code, config, and per-app logs. Global pages: cross-app handler/job table, diagnostics, and session history. (#710)
- Real-time updates for handler/job invocation counts, health metrics, last-fired timestamps, and dashboard stats via WebSocket — no page refresh needed. Relative timestamps ("5m ago") tick forward on a 30-second interval. (#735)
- Handler health table replaced with responsive card grid — each card shows status, handler name, kind, run stats (count, avg duration, error rate, last active), and error details. Keyboard accessible, responsive columns, scroll after 3 rows. (#761)
- Consistent table pattern across apps, handlers, and logs pages with shared sort headers, inline column filter popovers, and unified table shell. (#767)
- Multi-instance app parent page now shows shared tabs (overview, code, logs, config) with an instance column in the logs tab, instead of only an instance card grid. (#753)
- API errors surfaced via toast notifications (sonner) instead of silently swallowed — users now see a notification when data fails to load. (#751)
- Real-time handler updates on overview tab with failing-row background tint and blended hover state. (#748)
Configuration
HassetteConfigorganized into 8 nested groups:DatabaseConfig,WebSocketConfig,LoggingConfig,LifecycleConfig,WebApiConfig,AppConfig,SchedulerConfig,FileWatcherConfig. The/api/configREST endpoint and frontend config page reflect the nested structure. (#789)
Bug Fixes
- Handler cancel-then-resubscribe race and replacement ordering bugs fixed — Bus routing operations now execute synchronously in deterministic order, eliminating task interleaving between cancel and re-register. (#785, #658, #781)
- Log table REST API level filter fixed — was sending exact-match filter to the server instead of fetching all levels; column picker checkbox alignment, filter panel overflow, and mobile "reset filters" button also fixed. (#755)
- PyPI package now includes SPA frontend assets —
pip install hassettewas missing the web UI since v0.30.0. Also backported to v0.30.1. (#790, #788)
0.30.0 (2026-05-10)
Breaking Changes
- Field type narrowing (StrEnum) — Fields previously typed as
strare now typed with their domain StrEnum. StrEnums accept string construction sostate.attributes.hvac_mode == "heat"still works, but type checkers will flag comparisons against raw strings. Affected fields:ClimateAttributes.hvac_action,hvac_mode,hvac_modes;LightAttributes.color_mode,supported_color_modes. (#716) - Field type changes (runtime) —
MediaPlayerAttributes.media_position_updated_at:str | None→ZonedDateTime | None(validator now parses the ISO string).ClimateAttributes.current_temperature:int | float | None→float | None. (#716) - Removed deprecated fields — Fields typed as
Nonein HA source (deprecated) are no longer declared on the model. Values still land inmodel_extra(accessible via.extra("field_name")):LightAttributes.color_temp,max_mireds,min_mireds,xy_color;MediaPlayerAttributes.entity_picture_local. (#716) - Removed constraints —
LightAttributes.brightnessno longer hasgt=-1, lt=256field validators. (#716) hassette.models.states.featuresdeleted — IntFlag enums moved to per-domain files. All enums are still re-exported fromhassette.models.statessofrom hassette.models.states import LightEntityFeaturecontinues to work. (#716)
State Models
- State model attributes are now code-generated from Home Assistant core source via AST analysis, replacing the previous hand-maintained models. 34 entity domains have generated state models with proper
@field_validatorfor ZonedDateTime wire conversion, StrEnum attribute types, andsupports_*boolean properties. (#716) - The codegen pipeline includes a CI freshness check — if the pinned HA version falls behind, CI warns so models stay aligned with upstream. (#716)
0.29.0 (2026-05-09)
Breaking Changes
state.valuetype change for toggle entities —state.valuefor light, switch, fan, automation, humidifier, remote, script, siren, and update entities now returnsboolinstead ofstr. Code usingstate.value == "on"must change tostate.value is True. (#711)
State Models
- Audited all 50
BaseStatesubclasses against HA core source — corrected value types (9 toggle entities →BoolBaseState, 5 timestamp entities →DateTimeBaseState, air_quality →NumericBaseState), added missing attributes (person location fields, timer transitions, valve position, script/cover/device_tracker fields), and removed stale attributes not present in HA core. (#711)
Web UI
- Handler and condition summaries in the dashboard now display human-readable text (e.g.,
entity light.kitchen and state → on) instead of Python repr output. (#700) - Service readiness (not just status) now visible in the frontend monitoring dashboard — services show whether they are actually ready to serve, not just that they started. (#688)
Bug Fixes
- WebSocket
CancelledErrorno longer swallowed during task cancellation (#682) - REST API returns 404 (not 500) for non-existent
app_keyon start/stop/reload endpoints (#603, #680) - Immediate-fire synthetic events now display "---" instead of a misleading UUID in telemetry (#640, #680)
0.28.0 (2026-05-02)
Breaking Changes
HassettePayload.event_idchanged frominttostr(UUID4) — any user code comparingevent.payload.event_idagainst integers will silently fail. Update comparisons to use string UUIDs. (#641)
Telemetry
- Every handler invocation and job execution now receives a globally unique
execution_id(UUID4) for end-to-end tracing, with the triggering event'scontext_idandorigincaptured alongside it. (#641) CURRENT_EXECUTION_IDContextVar is set for the duration of each execution, enabling future causal chain tracking. (#641)- Frontend shows "Trace ID", "Trigger", and "Origin" columns in handler invocation and job execution tables — truncated monospace UUIDs with full value on hover. (#641)
0.27.0 (2026-04-29)
Service Supervision
- Per-service
RestartSpecreplaces the global one-size-fits-all restart policy — each service declares its own restart type (PERMANENT,TRANSIENT, orTEMPORARY), sliding-window budget, backoff parameters, and error classification. The 5 globalservice_restart_*config fields are removed. (#638) EXHAUSTED_DEADandEXHAUSTED_COOLINGservice statuses with frontend rendering including a countdown timer for cooling services. (#638)
WebSocket Resilience
- Early-drop retry loop detects post-ready connection drops within a configurable stable window (default 30s) and retries transparently — Home Assistant restarts no longer burn the ServiceWatcher restart budget or crash hassette after 5 cycles. (#631)
- Total recovery timeout cap (default 5 minutes) prevents multiplicative worst-case retry windows. 8 new config fields externalize all retry parameters. (#631)
Bug Fixes
- App bootstrap now completes before the app is marked ready, preventing premature requests against partially-initialized apps (#635)
0.26.0 (2026-04-28)
Bug Fixes
- Graceful shutdown no longer crashes on
ClosedResourceError(#627) ZonedDateTimevalues in REST API responses no longer include IANA timezone suffix ([America/Chicago]), fixing URL interpolation inget_historyandget_logbook(#619, #626)TaskBucketcrash logs now show meaningful task names (e.g.,AppLifecycleService.bootstrap_apps) instead ofTask-42(#623, #626)wait_forutility supports async predicates, eliminating hand-rolled async polling loops (#622, #626)AppTestHarnessper-class lock narrowed for concurrent same-class harnesses — multiple tests using the same app class can now run in parallel (#614)
0.25.0 (2026-04-26)
Breaking Changes
- Scheduler API redesigned around trigger objects —
run_cron("0 7 * * 1-5"),run_daily(at="07:00")replace the old keyword-per-field API. Custom triggers must implementTriggerProtocol. See the updated scheduler docs. (#517) RecordingApi.get_entityrequires explicit model argument — the previousBaseState-sentinel default silently aliased toget_state()and hid real bugs. Callers that want registry-converted state without a specific entity model should callget_state(entity_id)instead. (#525)- Docker startup uses constraints-based dependency protection — runtime
uv syncreplaced with a constraints file generated at build time. User dependency installs that conflict with hassette's pinned version now error instead of silently downgrading the framework. (#480)
Test Utilities
AppTestHarness— async context manager that wires a user'sAppclass into testBus,Scheduler,StateManager, andRecordingApiwith zero boilerplate:async with AppTestHarness(MyApp, config={...}) as harness:(#492)RecordingApirecords write calls for assertions and delegates reads toStateProxy, withApiProtocolconformance checking at import time (#492)- Time control for scheduler tests:
freeze_time(),advance_time(),trigger_due_jobs()via custom_TestClock(#492) - Event simulation:
simulate_state_change,simulate_attribute_change,simulate_call_servicewith reliable task-bucket drain (#492, #525) - State seeding:
set_state,set_statesviaStateProxy._test_seed_state()(#492)
Scheduler
- Built-in trigger types:
After,Once,Every,Daily,Cron— all importable fromhassette.scheduler.triggers(#517) - Job groups (
group=,cancel_group(),list_jobs(group=)) and jitter support (jitter=) (#517) - Frontend shows structured trigger labels, group filter chips (URL-persisted), jitter tags, cancelled badges, and expandable job rows with "Next: fires in..." countdown (#517)
Bus
immediate=Truefires handlers at registration time when the target entity already matches predicates. Synthetic event hasold_state=None. Composes withonce,debounce,throttle. (#570)duration=Nholds require the entity to remain in the matching state for N continuous seconds before the handler fires. Consultslast_changedon restart for resilience. (#570)Bus.on_error(handler)andScheduler.on_error(handler)register app-level error handlers; per-registrationon_error=parameter takes priority. Both sync and async handlers supported. (#575)
Error Handling
- Execution timeout enforcement (600s default) for all scheduled jobs and event handlers via
asyncio.timeout()—timed_outshown as a distinct status in telemetry with amber warning badges (#552) timeoutandtimeout_disabledparameters on allSchedulerandBusregistration methods (#552)- Error tracebacks visible in the dashboard error feed with expand/collapse toggle; framework-tier errors separated from app-tier errors (#537)
API
- Typed Home Assistant helper CRUD methods for
input_boolean,input_number,input_select,input_text,input_datetime,counter, andtimerentities (#506)
Web UI
- Dashboard hierarchy redesign with app detail polish, quiet-canvas status indicators, and sorted grids (#485, #523)
- Listener and job registrations now persist across session restarts — telemetry no longer loses data on reconnect (#466, #487)
Bug Fixes
- Subscription race between
cancel()and in-flight dispatch fixed; cancel semantics prevent double-fire (#451, #518, #520) - Dashboard no longer shows zero handlers/jobs after page refresh (#578)
- Docker uv cache pruned on startup to prevent unbounded volume growth (#542)
- SIGTERM handled for graceful Docker shutdown (#479)
Documentation
- Comprehensive docs rewrite with scheduler parameter tables, troubleshooting index, security admonition for unauthenticated web API, and simplified getting-started first app (#507)
- System internals page with architecture deep-dive diagrams (#605)
0.24.0 - 2026-04-03
Breaking Changes
TriggerProtocolsplit — custom triggers must now implement bothfirst_run_time(current_time)andnext_run_time(previous_run, current_time)instead of a single method. Triggers are now stateless. (#452)/api/healthzremoved — update Docker Compose health checks from/api/healthzto/api/health, which returns structured JSON and 503 for non-ok status. (#448)- Scheduler enforces job name uniqueness — duplicate job names per instance raise
ValueError. Passif_exists="skip"for idempotent registration. (#297) once=True+ rate limiting raisesValueError— combiningonce=Truewithdebounceorthrottleis no longer silently accepted. Remove the rate-limiting parameter from affected listeners. (#430)
Web UI
- Complete rebuild as a Preact SPA with 5 pages: Dashboard, Apps, App Detail, Sessions, Logs (#343)
- Graphite + Emerald design token system with light/dark mode toggle and
[data-theme]persistence (#343, #442) - Session list page with status badges, "This Session" / "All Time" scope toggle, and localStorage persistence (#464)
- Source location display in handler/job detail panels and log table (#464, #410)
- Handler/job drill-down with invocation history, plain-language handler summaries, and error feed with exception type names (#343, #411)
- Dashboard app cards show invocation/execution counts, error rate percentage, instance count badge, and multi-instance telemetry aggregation (#392, #410, #411, #448)
- Multi-column sort on log table with live-streaming auto-pause and click-to-expand messages (#411, #381)
- Keyboard accessibility: focus-visible indicators, skip-nav link, ARIA roles, keyboard-navigable log table (#442, #390)
- Self-hosted fonts (DM Sans, JetBrains Mono, Space Grotesk) — no external CDN requests (#442)
- Real-time log streaming via WebSocket with server-side level filtering and deduplication (#409)
- WebSocket reconnection automatically refreshes all REST-fetched data without page reload (#379)
- Status bar "DB degraded" indicator with exponential backoff polling (#448)
Database & Telemetry
- Persistent SQLite telemetry storage for sessions, handler invocations, and job executions with automatic schema migrations (#305, #329)
- Configurable retention (
db_retention_days) and size limit (db_max_size_mb, default 500 MB) with automatic oldest-record deletion (#464) - Telemetry status endpoint (
/api/telemetry/status) returns 503 when degraded, usable by Docker HEALTHCHECK (#448) /api/healthreturns 503 for non-ok system status while preserving structured JSON (#448)
Bus & Scheduler
- Rate limiter redesign: throttle no longer blocks concurrent dispatch, debounced handlers produce accurate telemetry,
once=Truelisteners cannot double-fire under rapid events (#430) - Zero or negative
debounceandthrottlevalues rejected withValueErrorat registration (#430) if_existsparameter on allScheduler.run_*methods —"error"(default) or"skip"for idempotent registration (#297)IntervalTriggerrejects zero/negative intervals withValueErrorat construction (#452)
Logging
- Per-service log level tuning via 13 dedicated
*_log_levelconfig fields (e.g.,api_log_level,bus_service_log_level) — no service falls through to global default (#463)
Configuration
total_shutdown_timeout_seconds— caps total shutdown wall-clock time (default: 30s) (#453)db_path,db_retention_days,db_max_size_mb— telemetry database location, retention, and size limit (#305, #464)
Bug Fixes
- Startup race condition: phased startup ensures session exists before handlers fire, eliminating "Dropping N handler invocation record(s)" warnings (#343)
CommandExecutorstartup crash resolved — registration methods wait forDatabaseServicereadiness before DB access (#330)- SQLite write serialization eliminates
OperationalError: cannot commit transactionraces at startup (#333) - Stale listener/job registrations cleaned up on app restart; telemetry no longer shows removed handlers (#390)
- Connection status bar no longer flashes "Disconnected" on page refresh (#390)
- WebSocket hook no longer causes infinite reconnect loop (#379)
- App start/stop/reload endpoints work in production mode without
dev_modeflag (#390) - Strip literal quote characters from
base_url, fixing Docker Compose connection failures (#298) - ServiceWatcher triggers shutdown on max restart failures instead of silently giving up (#301)
- Scheduler auto-generated job names include trigger info, preventing name collisions (#446)
- Scheduler job and listener filters return correct results after
owner_id/app_keymismatch fix (#335, #336)
Documentation
- Fixed broken code examples:
run_hourly(minute=15)TypeError, invalidapi_portfield, wronginstance_namekey in config snippet (#469, #472) - Added parameter tables to all scheduler methods with types, defaults, and cross-references (#469, #472)
- Corrected
data_dirdefault from~/.hassetteto actual platform-dependent path (#469, #472) - Added troubleshooting index page with symptom-based navigation (#469, #472)
- Added security admonition for unauthenticated web API (#469, #472)
- Extracted duplicated config file-discovery table into a shared snippet (#469, #472)
- Simplified getting-started first app to use raw events; added typed-handler forward reference (#469, #472)
- Removed ghost
getting-started/configuration.mdpage (#469, #472)
[0.23.0] - 2026-02-19
Changed
- Replaced Bulma CSS framework with a custom
ht-prefixed design system featuring cool slate surfaces, warm amber accent, and Space Grotesk + JetBrains Mono typography (#262) - Extracted all design tokens into
tokens.csswith[data-theme]selector support for future theming (#262) - Redesigned dashboard with app status chip grid, activity timeline, and streamlined layout (#262)
- App detail pages now use a flat single-page layout with collapsible metadata, inline tracebacks, and instance switcher dropdown (#262)
- Bus listener and scheduler job tables show expanded detail rows with predicate, rate-limiting, and trigger information (#262)
- Replaced hardcoded CSS fallback colors in alerts and detail panels with proper design tokens (
--ht-surface-inset,--ht-surface-code,--ht-warning-*,--ht-danger-*) - Toggle buttons now show fallback text before Alpine.js initializes and expose
aria-expandedfor accessibility (#262) - E2E tests now run by default with
uv run pytestinstead of requiring-m e2e; addednox -s e2esession for CI HassetteHarnessnow uses a fluent builder API (with_bus(),with_state_proxy(), etc.) with automatic dependency resolution instead of boolean flags (#253)- Consolidated duplicate mock Hassette, DataSyncService, and web test helper fixtures into shared factories in
test_utils/(#253) - All test helper functions now exported from
hassette.test_utilspublic API; tests import from the package instead of submodules (#253) - Replaced 28
asyncio.sleep()synchronization calls across 8 integration test files withwait_forpolling helper for deterministic, faster tests (#253) - Renamed
create_mock_hassette()tocreate_hassette_stub()andmock_hassette.pytoweb_mocks.pyto clarify web/API stub vs harness distinction (#259) - Added autouse cleanup fixtures for bus, scheduler, and mock API to prevent test pollution in module-scoped fixtures (#256)
Fixed
- WebSocket service now fires disconnect event and marks not-ready immediately on unexpected connection loss, preventing stale state in StateProxy (#270)
- App detail page now uses the actual instance index instead of hardcoded 0, fixing data/URL desync for non-zero instances (#262)
- Detail panel labels now have proper text contrast on dark
--ht-surface-codebackground (#262) - Collapsible panels and tracebacks no longer flash visible before Alpine.js initializes (#262)
- Entity browser "Load more" button now appends rows instead of replacing existing ones on domain-filtered views (#247)
model_dump()andmodel_dump_json()onAppManifestandHassetteConfigno longer leak extra fields (e.g. tokens from environment variables)
Added
- Aligned all state model attributes with Home Assistant core — added missing fields to sensor, humidifier, light, climate, weather, fan, camera, and media_player; created dedicated
LockStatemodule withLockAttributesandLockEntityFeature(#294) supports_*boolean properties on light, climate, cover, fan, media_player, and vacuum attribute classes for checking entity capabilities without manual bitmask operations (#272)IntFlagenums (LightEntityFeature,ClimateEntityFeature, etc.) matching Home Assistant core feature flags (#272)- Global alert banner showing HA disconnect warnings and failed app errors with expandable tracebacks (#262)
ht-btn--ghostandht-btn--xsbutton modifier classes (#262)extrasproperty andextra()helper onBaseStateandAttributesBasefor safe access to integration-specific attributes (#271)- JSDoc comments across all web UI JavaScript files (#251)
- ESLint linting, TypeScript type-checking, and
mise run lint:js/mise run typecheck:jstasks (#251)
Removed
- Bulma CSS CDN dependency (#262)
- Entity Browser page and related partials (#262)
[0.22.1] - 2026-02-15
Added
- Issue template migration from Markdown to YAML form templates (bug report, feature request, task, documentation)
/triage-issuesClaude command for auditing and cleaning up GitHub issues against project conventions- Updated CLAUDE.md with GitHub Issues conventions (title, labels, milestones, body sections)
web_ui_hot_reloadconfig option — watches web UI static files and templates for changes, pushing live reloads to the browser via WebSocket. CSS changes are hot-swapped without a page reload; template and JS changes trigger a full reload.- Collapsible sidebar with persistent icon rail on desktop and mobile — click the toggle or press Escape to expand/collapse
- SPA-like page navigation via HTMX boost — page transitions without full reloads
Changed
- Live dashboard/page updates now use WebSocket push with idiomorph DOM morphing instead of 30-second polling intervals
[0.22.0] - 2026-02-13
Added
- Web UI — server-rendered monitoring dashboard at
/ui/using Jinja2, HTMX, Alpine.js, and Bulma CSS - Dashboard — system health, apps summary, bus metrics, and recent events with WebSocket-driven live updates
- Apps page — shows all configured app manifests with status badges, start/stop/reload controls, and status filter tabs; single-instance apps link directly to instance detail
- App detail (
/ui/apps/{key}) — manifest config, bus listener metrics, scheduled jobs, and filtered log viewer; multi-instance apps show expandable instance table - Instance detail (
/ui/apps/{key}/{index}) — per-instance bus listeners, jobs, and logs - Log viewer (
/ui/logs) — client-side filtering by level/app/text, sortable columns, and real-time WebSocket log streaming - Scheduler page (
/ui/scheduler) — scheduled jobs and execution history, filterable by app - Entity browser (
/ui/entities) — browse entities by domain with text search and pagination - Event Bus page (
/ui/bus) — bus listener metrics, filterable by app run_web_uiconfig option to enable/disable the UI independently from the API- Added section to docs covering the web UI
- FastAPI web backend replacing the standalone
HealthServicewith a full REST API and WebSocket server GET /api/health,GET /api/healthz— system health and container healthchecksGET /api/entities,GET /api/entities/{entity_id},GET /api/entities/domain/{domain}— entity state accessGET /api/apps,GET /api/apps/{app_key},GET /api/apps/manifests— app status and manifestsPOST /api/apps/{app_key}/start|stop|reload— app managementGET /api/scheduler/jobs,GET /api/scheduler/history— scheduled jobs and execution historyGET /api/bus/listeners,GET /api/bus/metrics— per-listener execution metrics and aggregate summaryGET /api/events/recent,GET /api/logs/recent,GET /api/services,GET /api/config— events, logs, HA services, configGET /api/ws— WebSocket endpoint for real-time state/event/log streaming with subscription controlsGET /api/docs— interactive OpenAPI documentation- Event handler execution metrics — per-listener aggregate counters (invocations, successes, failures, DI failures, timing) exposed via REST API and web UI
- Scheduler job execution history — per-job execution records with timing and error details
- Configurable service restart with exponential backoff in
ServiceWatcher service_restart_max_attempts,service_restart_backoff_seconds,service_restart_max_backoff_seconds,service_restart_backoff_multiplierconfig optionsscheduler_behind_schedule_threshold_secondsconfig option (default: 5) — configurable threshold before a "behind schedule" warning is logged for a job (previously hard-coded to 1 second)- Playwright e2e test suite for the web UI (34 tests; run with
pytest -m e2e)
Changed
- Breaking: Replaced
HealthServicewithWebApiServicebacked by FastAPI - Breaking: Config renames:
run_health_service→run_web_api,health_service_port→web_api_port,health_service_log_level→web_api_log_level - New config options:
web_api_host,web_api_cors_origins,web_api_event_buffer_size,web_api_log_buffer_size,web_api_job_history_size Servicebase class now properly sequencesserve()task lifecycle: spawns afteron_initialize(), cancels beforeon_shutdown()
Fixed
- WebSocket disconnect handling no longer produces spurious ERROR logs during normal page navigation
- Scheduler dispatch loop uses single lock acquisition per cycle, reducing scheduling latency
Removed
HealthService(src/hassette/core/health_service.py) — replaced by FastAPI web backend
[0.21.0] - 2026-02-06
Changed
- Refactored
AppHandlerinto four focused components:AppRegistry(state tracking),AppFactory(instance creation),AppLifecycleManager(init/shutdown orchestration), andAppChangeDetector(configuration diffing) - File watcher now batches multiple file change events to prevent race conditions (
changed_file_pathpayload is nowchanged_file_paths: frozenset[Path]) - Renamed
active_apps_configtoactive_manifestsonAppRegistry AppManifest.app_confignow accepts both"config"and"app_config"keys
Added
HassetteAppStateEventemitted when app instances change status (includes app_key, status, previous_status, exception details)- New
Busconvenience methods:on_app_state_changed(),on_app_running(),on_app_stopping() BlockReasonenum and blocked app tracking inAppRegistryto distinguish "enabled but excluded by@only_app" from "not configured"ResourceStatus.STOPPINGenum valueenabled_manifestsproperty onAppRegistryfor querying enabled apps regardless ofonly_appfilterStateManager.get(entity_id)for generic entity access with automatic domain-type resolution andBaseStatefallback
Fixed
- Removing
@only_appdecorator now correctly starts previously-blocked apps during hot reload
[0.20.4] - 2026-02-05
Fixed
- Fixed finding of requirements files in Docker image, thanks @mlsteele!
Added
- Added tests to ensure requirements files are found correctly in Docker image
[0.20.3] - 2026-02-01
Fixed
sourcenow optional inAutomationTriggeredPayload
[0.20.2] - 2026-02-01
Changed
- rename parameter
comparatortoopinComparisoncondition
[0.20.1] - 2026-02-01
Fixed
- add back activation of virtualenv in docker startup script
[0.20.0] - 2026-02-01
Added
- Add --version/-v argument to Hassette to allow displaying the current version
- Add
__iter__,__contains__,keys,values, anditemsmethods to StateManager and StateRegistry - Add functionality to route
state_changeevents to more specific handlers based on domain and/or entity_id - This is done automatically by the
Busby adding the entity_id to the topic when creating the listener - Matched listeners are deduplicated to ensure delivery only happens one time
- Events are dispatched to the most specific route if there are multiple matches
- Add
AnnotationConverterclass andTypeMatcherclass for more robust validation/conversion during DI - Add A, P, C, and D aliases to
hassette.__init__for simpler imports A=hassette.event_handling.accessorsP=hassette.event_handling.predicatesC=hassette.event_handling.conditionsD=hassette.event_handling.dependencies- Add new
Comparisoncondition for basic operators (e.g.==,!=,<,>, etc.) to compare values in state/attribute change listeners - Add new accessors for getting multiple/all attributes at once from state change events
get_attrs_<old|new|old_new>- specify a list of attrsget_all_attrs_<old|new|old_new>- get all attributes as a dict- Add
get_all_changesaccessor that returns a dictionary of all changes, including state and all attributes
Fixed
- Fix AppHandler reporting failed apps as successful by using status attribute
- This is due to some issues with how we're tracking apps, further fixes will need to happen in future releases
- Fix StateManager using
BaseStatewhen we do not find a class in theStateRegistry - This does not work because
BaseStatedoesn't have adomain - Error is now raised instead
- Log level is now used by Apps if set directly in AppConfig in Python code (as opposed to config file)
- Fix HassPayload's context attribute not being a HassContext instance
MediaPlayerStatenow hasattributesusing the correct type
Changed
- BREAKING: Replaced
StateManager.get_stateswith__getitem__that accepts a state class - The error raised in StateManager when a state class is not found in the
StateRegistrynow advises to use this method - Renamed
LOG_LEVELStoLOG_LEVEL_TYPE - Renamed
get_default_dicttoget_defaults_dictto be more clear this is not referring todefaultdictclass - Use same validation for
AppConfiglog level as we do forHassetteconfig log level - Extracted nested Attributes classes for each state out of class definition to make them first class citizens
- e.g.
MediaPlayerState.Attributesis nowMediaPlayerAttributes
Docs
- Remove
Why Hassettepage - Remove docker networking page
- Very large cleanup/reorg/addition of docs
[0.19.2] - 2026-01-25
Fixed
- Change log level for state cache loading message from INFO to DEBUG
[0.19.1] - 2026-01-25
Fixed
- Update
state_manager.pyito fix type hints
[0.19.0] - 2026-01-25
Fixed
- Exit
TypeRegistry.convertearly if already a valid type - Avoid mutating state dicts when accessing via
DomainStates
Added
- Add
__contains__method to DomainStates - Allows us to use
inchecks - Add
to_dict,keys,values, anditemsmethods to DomainStates - Provides convenient access to entity IDs and typed states
- Add
yield_domain_statesto StateProxy - Allows iterating over all states in the proxy
- Handles KeyError when extracting domain
- Update
DomainStatesclass to accept aStateProxyinstance instead of state dictionary to ensure it stays up to date - Add caching to
StateManager, holding on to eachDomainStatesinstance after creation - Add caching to
DomainStates, usingfrozendict.deepfreezeto hash the state dict and avoid recreating the instance if it has not changed
Removed
- BREAKING: Remove
_TypedStateGetterclass and correspondinggetmethod onStateManager- this was never a good idea due to its confusing api - BREAKING: Remove
allproperty onStateManager- this is to avoid calculating all states unnecessarily
[0.18.1] - 2025-12-13
Changed
- Improve docker startup script and dependency handling
- Rewrite docker docs to be more clear about project structure and dependency installation
Fixed
- Fixed a bug in autodetect apps exclusion directories
- Previous commit had mapped the exclusion dirs to Path objects, which broke the set comparison, this has been reverted
[0.18.0.dev3] - 2025-12-13
Changed
- Hardcode UID/GID of 1000 for non-root user in Docker image
[0.18.0.dev2] - 2025-12-13
Changed
- Breaking: Docker image switched to Debian slim
- Breaking: Remove
latesttag, latest tag will now include python version as well
Fixed
- Use correct version of python when pulling base image
- (e.g. image tagged with py-3.12 uses python 3.12)
[0.18.0.dev1] - 2025-12-13
Changed
- Allow Python 3.11 and 3.12 again!
- Breaking: All events now contain untyped payloads instead of typed ones
StateChangeEventis nowRawStateChangeEvent- There is a new DI handler for
TypedStateChangeEventto handle conversion if desired - Breaking: State conversion system now uses dynamic registry instead of hardcoded unions
StateUniontype has been removed - useBaseStatein type hints insteadDomainLiteraltype has been removed - no longer needed with dynamic registration- State classes automatically register their domains via
__init_subclass__hook - Breaking:
try_convert_statenow typed to returnBaseState | Noneinstead ofStateUnion | None - Uses registry lookup instead of Pydantic discriminated unions for conversion
- Falls back to
BaseStatefor unknown/custom domains try_convert_statemoved tohassette.state_registrymodulestates.__init__now only imports/exports classes, no conversion logic- Improved dependency injection system for event handlers, including support for optional dependencies via
Maybe*annotations - Renamed
states.pytostate_manager.py(and renamed the class) to avoid confusion withmodels/statesmodule - Removed defaults from StateT and StateValueT type vars
- Removed type constraints from StateValueT type var to allow custom types to be used
- Moved
accessors,conditions,dependencies, andpredicatesall tohassette.event_handlingfor consistency - Moved DI extraction and injection modules to
hassette.bus
Added
TypeRegistryclass for handling simple value conversion (e.g. converting "off" to False)- Handling of Union types
- Handling of None types
- Handling of type conversion for custom
AnnotatedDI handlers
Removed
- Breaking: Removed
StateUniontype - replaced withBaseStatethroughout codebase - Breaking: Removed
DomainLiteraltype - no longer needed with registry system - Breaking: Removed manual
_StateUniontype definition from states module - Breaking: Removed StateValueOld/New, StateValueOldNew, StateOldNew, MaybeStateOldNew, AttrOld, AttrNew, AttrOldNew DI handlers
- These can be used still by annotating with
Annotated[<type>, A.<function>]using providedaccessorsmodule - They were too difficult to maintain/type properly across the framework
- These can be used still by annotating with
[0.17.0] - 2025-11-22
Changed
- Breaking: - Requires Python 3.13 going forward, Python 3.12 and 3.11 are no longer supported.
- This allows use of
type, defaults for TypeVars, and other new typing features. - Renamed
core_config.pytocore.py - Renamed
servicestocoreand movecore.pyundercoredirectory - Didn't make sense to keep named as
servicessince we have resources in here as well
Added
- Add
diskcachedependency andcacheattribute to all resources - Each resource class has its own cache directory under the Hassette data directory
- Add
statesattribute toApp- provides access to current states in Home Assistant statesis an instance of the newStatesclassStatesprovides domain-based access to entity states, e.g.app.states.light.get("light.my_light")Stateslistens to state change events and keeps an up-to-date cache of states- New states documentation page under core-concepts
- Add
Maybe*DI annotations for optional dependencies in event handlers MaybeStateNew,MaybeStateOld,MaybeEntityId, etc.- These will allow
NoneorMISSING_VALUEto be returned if the value is not available - The original dependency annotations will raise an error if the value is not available
- Add
raise_on_incorrect_dependency_typetoHassetteConfigto control whether to raise an error if a dependency cannot be provided due to type mismatch - Default is
truein production mode,falsein dev mode - When
falsea warning will be logged but the handler will still be called with whatever value was returned
Fixed
- Fixed bug that caused apps to not be re-imported when code changed due to skipping cache check in app handler
- Fixed missing domains in
DomainLiteralinhassette.models.states.base - Add tests to catch this in the future
[0.16.0] - 2025-11-16
Added
- Added
ANY_VALUEsentinel for clearer semantics in predicates - use this to indicate "any value is acceptable" - Dependency Injection for Event Handlers - Handlers can now use
Annotatedtype hints with dependency markers fromhassette.dependenciesto automatically extract and inject event data as parameters. This provides a cleaner, more type-safe alternative to manually accessing event payloads. - Available dependencies include
StateNew,StateOld,AttrNew(name),AttrOld(name),EntityId,Domain,Service,ServiceData,StateValueNew,StateValueOld,EventContext, and more - Handlers can mix DI parameters with custom kwargs
- See
hassette.dependenciesmodule documentation and updated examples for details
Changed
- Breaking: - Event handlers can no longer receive positional only args or variadic positional args
NOT_PROVIDEDpredicate is now used only to indicate that a parameter was not provided to a function
[0.15.5] - 2025-11-14
Changed
- Update
HassetteConfigdefaults to differ if in dev mode - Generally speaking, values are extended (e.g. timeouts) and more permissive (e.g.
allow_startup_if_app_precheck_fails = truein dev mode) - Moved
AppManifestandHassetteTomlConfigSettingsSourcetoclasses.py - Moved
LOG_LEVELStohassette.types.typesinstead ofconst.misc, as this is aLiteral, not a list of constants - Renamed
core_config.pytocore.py - Bumped version of
uvinmise.toml, docker image, and build backend - Converted docs to mkdocs instead of sphinx
Fixed
- Fixed bug in AppHandler where all apps would be lost when
handle_changeswas called, due to improper reloading of configuration - Now uses
HassetteConfig.reload()to reload config instead of re-initializing the class
[0.15.4] - 2025-11-07
Added
- add config setting for continuing startup if app precheck fails
- add config setting for skipping app precheck entirely
- add config setting for loading found .env files into os.environ
- add
entitiesback to public API exports fromhassette
Changed
- Cache app import failures to avoid attempting to load these again if we are continuing startup after precheck failures
- Improve app precheck logging by using
logger.errorand short traceback instead oflogger.exception
[0.15.3] - 2025-11-02
Changed
- Moved more internal log lines to
DEBUGlevel to reduce noise during normal operation. - Moved
only_appwarning to only emit if@only_appis actually being used. - Make
FalseySentinelsubclass to use forNOT_PROVIDEDandMISSING_VALUEto simplify bool checks. - Add
Typeguardmethod toStateChangePayloadto allow type narrowing onold_stateandnew_state. - Implemented as
self.has_state(<self.old_state|self.new_state>)
Documentation
- Improved documentation landing page
- Add logo
- Improve getting-started page
[0.15.2] - 2025-11-02
Fixed
- Fix docker_start.sh to use new entrypoint
[0.15.0] - 2025-11-02
Added
ComparisonConditions for comparing old and new values in state and attribute change listeners.IncreasedandDecreasedconditions added for numeric comparisons.- Added
IsNoneandIsNotNoneconditions for checking if a value isNoneor not. - Hassette will now attempt to automatically detect apps and register them without any configuration being required.
- This can be disabled by setting
auto_detect_apps = falsein the config. - Manually configured apps will still be loaded as normal and take precedence over auto-detected apps.
- You cannot use auto-detect apps if you have a configuration with required values (unless they are being populated from environment variables or secrets).
- In this case, you must manually configure the app to provide the required values.
Fixed
- Fixed missing tzdata in Alpine-based Docker image causing timezone issues.
- Cli parsing working now, so any/all settings can be passed to override config file or env vars, using
--helpworks correctly, etc.
Removed
- Setting sources custom tracking removed, so debug level logging will no longer show where each config value was set from.
- This was originally added due to my own confusion around config precedence, but maintaining it is not worth the extra complexity.
- Secrets can no longer be set in
hassette.tomlto be accessible in app config - This never actually made much sense, I just didn't actually think about that when adding the feature
Changed
- You can now pass
ComparisonConditions to thechangedparameter onon_state_changeandon_attribute_changemethods. - This allows for comparing the old and new values to each other, rather than checking each independently.
- You are now able to register event handlers that take no arguments, for events where you don't care about the event data.
- The handler will simply be called without any parameters when the event is fired.
- This works for all bus listener methods, e.g.
on_event,on_entity,on_status_change, etc. - When you do require the event to be passed, you only need to ensure it is the first parameter and the name is
event. - No longer export anything through
predicatesmodule - Recommendation now is to import like
from hassette import predicates as Pfrom hassette import conditions as Cfrom hassette import accessors as A
- Breaking: -
base_urlnow requires an explicit schema (http:// or https://) - If no schema is provided, a
SchemeRequiredInBaseUrlErrorwill be raised during config validation - This is to avoid having to guess the intended scheme, which can lead to confusion and errors
- Breaking: -
base_urlmust haveportincluded if your instance requires the port - Previously, we would default to port 8123 if no port was provided
- This is not always correct, as some instances may be running on a different port, be behind a reverse proxy, or use nabu casa and not require a port at all
Internal
- Refactor listener and add adapter to handle debounce, throttle, and variadic/no-arg handlers more cleanly.
- Rename
Hasette._websockettoHassette._websocket_serviceto match naming conventions. - Refactor handler types and move types into
typesmodule instead of single file for better organization. - Remove extra wrappers around
pydantic-settings, made some improvements so these are no longer necessary. - Flattened whole package structure for simpler imports and better organization.
[0.14.0] - 2025-10-19
Added
- Add validation for filename extension in AppManifest - add
.pyif no suffix, raise error if not.py - Bus handlers can now accept args and kwargs to be passed to the callback when the event is fired
tasks.pyrenamed totask_bucket.pyto follow naming conventionspost_to_loopmethod added toTaskBucketto allow posting callables to the event loop from other threads
Changed
- Breaking: - Renamed
async_utils.pytofunc_utils.py, addedcallable_nameandcallable_short_nameutility functions - Breaking: - Upgrade to
whenever==0.9.*which removedSystemDateTimein favor ofZonedDateTime- all references in the code base have been updated
Internal
- New type for handlers,
HandlerType, as we now have additional protocols for variadic handlers
Fixed
- Correct scheduler helpers
run_minutely,run_hourly, andrun_dailyto not start immediately if nostartwas provided, but to start on the next interval instead.
Bus/Predicate Refactor
- Refactored predicates to use composable
Predicates,Conditions, andAccessors Predicateis a callable that takes an event and returns a bool- E.g.
AttrFrom,AttrTo,DomainMatches,EntityMatches,ValueIs, etc.
- E.g.
Conditionis a callable that takes a value and returns a bool- E.g.
Glob,Contains,IsIn,IsOrContains,Intersects, etc.
- E.g.
Accessoris a callable that takes an event and returns a value- E.g.
get_domain,get_entity_id,get_service_data,get_path, etc.
- E.g.
- Updated Bus methods to use new predicate system
- Only implementation changes, public API remains the same
- Updated tests to use new predicate system
- Add/update types for predicates, conditions, and accessors
- Updated documentation for predicates and bus event listening to reflect new system
[0.13.0] - 2025-10-14
Added
Subscriptionnow hascancelmethod to unsubscribe from events, to be consistent withScheduledJob.App.send_event_syncmethod added for synchronous event sending.Bus.on_status_change,Bus.on_attribute_change,Bus.on_service_callall take sync callables for comparison parameters.- For example, you can pass a lambda to
changed_fromthat does a custom comparison. Busnow exposeson_homeassistant_stopandon_homeassistant_startconvenience methods for listening to these common events.Busstatus/attribute change entity_id parameters now accept glob patterns.
Changed
- Breaking:
Scheduler.run_oncehas been updated to usestartinstead ofrun_atto be consistent with other helpers. - Breaking:
cleanupmethod is now marked as final and cannot be overridden in subclasses. - Breaking:
Bus.on_entityrenamed toBus.on_status_changeto match naming conventions across the codebase. - Breaking:
Bus.on_status_changeentityparameter renamed toentity_idfor clarity. - Breaking:
Bus.on_attributerenamed toBus.on_attribute_changeto match naming conventions across the codebase. - Breaking:
Bus.on_attribute_changeentityparameter renamed toentity_idfor clarity.
Removed
- Breaking: Removed deprecated
set_logger_to_debugandset_logger_to_levelResource methods. - Breaking: Removed deprecated
run_sync,run_on_loop_thread, andcreate_taskmethods from Hassette. - Breaking: Removed
run_atalias forrun_oncein Scheduler.
Internal
- Remove scheduled jobs that are cancelled or do not repeat, instead of just marking them as cancelled and leaving them in the job queue.
- Reworked predicates to make more sense and be more composable.
- Added types for
PredicateCallable,KnownTypes, andChangeType.PredicateCallableis a callable that takes a single argument of any known type and returns a bool.KnownTypesis a union of all types that can be passed to predicates.ChangeTypeis a union of all types that can be passed to change parameters.
- Use
Sentinelfromtyping_extensionsfor default values. - Rename
SENTINELtoNOT_PROVIDEDfor clarity. - Moved
is_async_callabletohassette.utils.async_utils, now being used in more places. - Moved glob logic from
Routertohassette.utils.glob_utils, now being used in more places.
Documentation
- Updated Apps and Scheduler documentation to reflect new features and changes.
- Improved reference docs created with autodoc.
[0.12.1] - 2025-10-11
Fixed
- Fixed
run_minutely/run_hourly/run_dailyscheduler helpers to run every N minutes/hours/days, not every minute/hour/day at 0th second/minute.
[0.12.0] - 2025-10-11
Added
- Lifecycle:
- Lifecycle hooks
on/before/after_initializeandon/before/after_shutdownadded toResourceandServicefor more granular control over startup and shutdown sequences. - Breaking:
App.initializeandApp.shutdownare now final methods that call the new hooks; attempting to override them will raise aCannotOverrideFinalError. - Developer Experience:
- Hassette now performs a pre-check of all apps before starting, exiting early if any apps raise an exception during import.
- This allows earlier iteration for exceptions that can be caught at class definition/module import time.
- Scheduler now includes convenience helpers
run_at,run_minutely,run_hourly, andrun_dailyfor common cadence patterns. - Add
humanizeto support human-friendly duration strings in log messages.
- Dev Mode:
- Reintroduced
dev_modeconfiguration flag (also auto-enabled when running under a debugger orpython -X dev) to turn on asyncio debug logging and richer task diagnostics. - Only reload apps when in
dev_mode, to avoid unexpected reloads in production, overridable withalways_reload_appsconfig flag. - Only respect
@only_appdecorator when indev_mode, to avoid accidentally running only one app in production - overridable withallow_only_app_in_prodconfig flag. - The event loop automatically switches to debug mode when
dev_modeis enabled. - Task Buckets:
- Task buckets gained context helpers and
run_sync/run_on_loop_threadwrappers so work spawned from worker threads is still tracked and can be cancelled cleanly. - Task buckets now expose
make_async_adapter, replacing the old helper inhassette.utils.async_utilsso sync callables are wrapped with the owning bucket's executor. - App-owned
Api,Bus, andSchedulerinstances share the app's task bucket and derive unique name prefixes, giving per-instance loggers and consistent task accounting. - All Apps (and all resources/services) should use
self.task_bucketto spawn background tasks and to run synchronous code, to ensure proper tracking and cancellation.- Using
self.hassette.run_syncorself.hassette.run_on_loop_threadis still supported, but will not track tasks in the app's task bucket.
- Using
- Configuration:
- Resolve all paths in
HassetteConfigto absolute paths. - Individual service log levels can be set via config, with the overall
log_levelbeing used if not specified. - New config options for individual service log levels:
bus_service_log_levelscheduler_service_log_levelapp_handler_log_levelhealth_service_log_levelwebsocket_log_levelservice_watcher_log_levelfile_watcher_log_leveltask_bucket_log_levelapps_log_level- Add
log_leveltoAppConfigso apps can set their own log levels.
- Add new configuration options for logging events on the Bus when at DEBUG level:
log_all_events- log every event that is fired.log_all_hass_events- log every event from Home Assistant - will fall back tolog_all_eventsif not set.log_all_hassette_events- log every event from Hassette apps and core - will fall back tolog_all_eventsif not set.
- Add
app_startup_timeout_secondsandapp_shutdown_timeout_secondstoHassetteConfigto control how long to wait for apps to start and stop before giving up. - Allow having the Bus skip entities/domains altogether via
bus_excluded_domainsandbus_excluded_domainsconfig options.- These take a tuple of strings and accept glob patterns.
- Any events matching an excluded domain or entity_id will not be delivered to listeners or logged.
Changed
- Breaking: Public imports now come from the root
hassettepackage; the oldhassette.corepaths have been moved underhassette.core.resources/hassette.core.services, so update any directhassette.core...imports to use the re-exported names onhassette. - Breaking:
App.initializeandApp.shutdownhave been replaced withApp.on_initializeandApp.on_shutdownhooks that do not need to callsuper(). - Attempting to override these methods will now raise a
CannotOverrideFinalError. - The Scheduler will now spawn tasks to run a job and reschedule a job, so jobs that take longer than their interval will not block subsequent runs.
- Resources now build a parent/child graph via
Resource.add_childand harmonizedcreate()factory methods, so services and sub-resources inherit owners and task buckets automatically. Api.call_serviceand the sync facade default toreturn_response=False, and theturn_on/turn_off/toggleare corrected to not passreturn_responsesince this is not supported.- Deprecated
set_logger_to_level- loggers are finally working properly now so the standardlogger.setLevel(...)should be used instead.
Fixed
- Event bus and scheduler loops respect
shutdown_event, allowing them to exit promptly during shutdown. - WebSocket reconnects treat
CouldNotFindHomeAssistantErroras retryable and properly apply the retry policy, improving cold-start resilience. Api.call_servicenow includesreturn_responsein the payload when requested, andServiceResponsecorrectly models the returned data.
Internal
- Improved documentation:
- Switched to RTD theme for better readability and navigation.
- Improved formatting of comparison guides.
- Fixed some references.
- Reorganized most of the core code into
resourcesandservices - Use
contextvarsinstead of class variables to track global instance ofHassetteandHassetteConfig SchedulerServicenow delegates scheduling to_ScheduledJobQueue, which uses a fair async lock to coordinate concurrent writers before dispatching due jobs.Hassette.run_sync/run_on_loop_threadnow route through the global task bucket.- Breaking: The
run_forevermethod of theServiceclass has been replaced withserve. The new lifecycle hooks are valid forServiceas well.
[0.11.0] - 2025-10-05
Added
hassette.event.app_reload_completednow fires after reload cycles, andHassetteEmptyPayloadprovides a helper for simple internal events.- Add
TaskBucketclass for tracking and cancelling related async tasks. - Add
Hassette.task_bucketfor global task tracking, andResource.task_bucketfor per-resource task tracking. - Introduced
TaskBucketinstances for Hassette, services, and apps; configure shutdown grace periods via the newHassetteConfig.task_cancellation_timeout_secondssetting. - Added
Hassette.wait_for_readyandhassette.utils.wait_for_readyhelpers so resources can block on dependencies (for example, the API now waits for the WebSocket). - Add
ResourceNotReadyErrorexception to indicate that a resource is not ready for use. - Expanded Home Assistant tuning knobs with
websocket_connection_timeout_seconds,websocket_total_timeout_seconds,websocket_response_timeout_seconds,websocket_heartbeat_interval_seconds, andscheduler_min/default/max_delay_seconds. - Add individual log level settings for core services.
- Add
cleanuplifecycle method toResourceandServicefor async cleanup tasks during shutdown. This generally will not need to be overridden, but is available if needed.
Changed
- Breaking: Per-owner buses replace the global
hassette.bus; listener removal must go throughBusService, which now tracks listeners by owner under a fair async lock for atomic cleanup. - Breaking:
@onlybecomes@only_app, apps must expose a non-emptyinstance_name, and each app now owns itsBusandSchedulerhandles. - Breaking: The
hassette.core.appspackage moved underhassette.core.classes.app, and the service singletons are nowBusServiceandSchedulerService; import apps fromhassette.core/hassette.core.classesand treat the underscored services as private. - Deprecated:
set_logger_to_debughas been renamed toset_logger_to_level, and all core services now default toINFOlevel logging.set_logger_to_debugis still available but will be removed in a future release. - App handlers now mark apps as ready after
initializecompletes. - The API now waits for WebSocket readiness before creating its session, and classifies common client errors as non-retryable.
Fixed
- App reloads clean up owned listeners and jobs, preventing leaked callbacks between reload cycles.
- Startup failures now emit the list of resources that never became ready, making it easier to diagnose configuration mistakes.
Internal
- Test harness integrates TaskBucket support, adds a
hassette_with_nothingfixture, and continues to provision mock services so CI can run without a Home Assistant container. - Tightened local tooling: expanded
pyrightconfig.json, enabled Ruff'sTID252, and taught the nox test session to runpytestwith-W error. - Scheduler coordination now flows through
SchedulerService, which reads min/default/max delays from config, waits for Hassette readiness, and tags spawned jobs in the task bucket for easier cancellation. - Lifecycle helpers extend
Resource/Servicewith explicit readiness flags (mark_ready,mark_not_ready,is_ready); Hassette spins up a global task bucket, names every background task, and blocks startup until all registered resources report ready, logging holdouts before shutting down. - WebSocket connection handling uses Tenacity-driven retries with dedicated connect/auth/response timeouts, and the API now waits for WebSocket readiness before creating its session while classifying common client errors as non-retryable.
- Add asyncio task factory to register all tasks in the global task bucket with meaningful names to make cleanup easier.
[0.10.0] - 2025-09-27
Added
- Added utility functions for datetime conversion in
src/hassette/utils.py
Changed
- Updated state models to use
SystemDateTimeconsistently instead ofInstantor mixed types - Replaced deprecated
InstantBaseStatewithDateTimeBaseStatefor better type handling - Remove
repr=Falseforlast_changed,last_updated, andlast_reportedinBaseStateto improve logging and debugging output
Fixed
- Fixed incorrect datetime conversion in
InputDateTimeStateto ensure proper timezone handling
[0.9.0] - 2025-09-26
Added
- Added ability to provide args and kwargs to scheduled jobs via scheduler helpers
argsandkwargskeyword-only parameters added to all scheduler helper functions- These will be passed to the scheduled callable when it is run
- See Scheduler documentation for details
Changed
- Narrow date/time types accepted by
get_history,get_logbook,get_camera_imageandget_calendar_eventsto excludedatetime,date, andZonedDateTime- usePlainDateTime,SystemDateTime, orDateinstead
Documentation
- Updated scheduler documentation to include new args/kwargs parameters for scheduling helpers
- Updated Readme to change roadmap reference to point to Github project board
- Removed roadmap.md file, using project board for tracking now
[0.8.1] - 2025-09-23
Fixed
- Remove opengraph sphinx extension from docs dependencies - it was causing issues with building the docs and isn't necessary for our use case
[0.8.0] - 2025-09-23
Added
- hot-reloading support for apps using
watchfileslibrary - watches app files, hassette.toml, and .env files for changes
- reloads apps on change, removes orphans, reimports apps if source files change
- can be disabled with
watch_files = falsein config - add a few new configuration values to control file watcher behavior
- add utility function to wait for resources to be running with shutdown support
wait_for_resources_runningfunction added toHassetteclass- also available as standalone utility function in
hassette.utils @onlydecorator to allow marking a single app to run without changinghassette.toml- importable from
hassette.core.apps - useful for development when you want to only run a single app without modifying config file
- will raise an error if multiple apps are marked with
@only - add
app_keytoAppManifest- reflects the key used to identify the app in config
Changed
- move service watching logic to it's own service
- refactor app_handler to handle reloading apps, re-importing, removing orphans, etc.
Fixed
- update
api.call_servicetarget typing to also allow lists of ids - thanks @zlangbert!
[0.7.0] - 2025-09-14
Changed
- rename
cancelonSubscriptiontounsubscribefor clarity
Fixed
- improved docstrings across
Apimethods
Added
- Documentation!
[0.6.2] - 2025-09-14
Fixed
- Fix logging on
Appsubclasses to usehassette.<AppClassName>logger
[0.6.1] - 2025-09-14
Fixed
- Fixed
HassetteConfigusing single underscore when checking for app_dir, config_dir, and data_dir manually - Now checks both single and double underscore (with double underscore taking precedence) just to be safe
- Fixed
HassetteConfigincorrectly prioritizingHASSETTE_LOG_LEVELoverHASSETTE__LOG_LEVEL(double underscore should take precedence)
[0.6.0] - 2025-09-14
Removed
- Removed
DEFAULT_CONFIGconstant for app config, not necessary
Fixed
- Fixed
HassetteConfigto properly handleenv_fileandconfig_fileparameters passed in programmatically or via CLI args - These are now passed to the appropriate settings sources correctly
- Fixed
HassetteConfigincorrectly prioritizing TomlConfig over environment variables and dotenv files (Pydantic docs are confusing on this point)
Changed
Configuration
- Add back ability to set top level
[hassette]section in config file using customTomlConfigSettingsSource - Update examples to show top level
[hassette]section usage - Update README with new config usage and Docker instructions
- Update README with example of using
docker-compose.ymlfile - Update README with example of setting app config inline (.e.g
config = {send_alert = true}) - Added relative
./configpath for config and .env files
App Handler
- Improved app handler logic, apps should now be able to import other modules from the same app directory
- Known Issue: Using
isinstancedoes not work consistently, will be providing recommendation in docs on how to make this work better
- Known Issue: Using
Hassette
- Update imports to be relative, same as other modules
Apps
- Rename
app_manifest_clstoapp_manifest- was always an instance, not a class
Added
HassetteConfig
- Add
secretsattribute toHassetteConfigto allow specifying secret names that will be filled from config sources- Secrets can be listed in the config file like
secrets = ["my_secret", "another_secret"] - Secrets will be filled from config sources in order or will attempt to pull from environment variables if not found
- Secrets are available in config as a dict, e.g.
config.secrets["my_secret"]
- Secrets can be listed in the config file like
- Add
HassetteBaseSettingsto add tracking of final settings sources for all config attributesHassetteConfig.FINAL_SETTINGS_SOURCESwill show where each config attribute was set from- Useful for debugging config issues
- Add
HassetteTomlConfigSettingsSourceto load config from a TOML file, supports top level[hassette]section - Add
get_configclass method toHassetteConfigto get global configuration without needing to accessHassettedirectly- E.g.
HassetteConfig.get_config()will return the current config instance
- E.g.
- Check for app required keys prior to loading apps, will skip any apps missing required keys and log a warning
- Particularly useful if you have config values for the app in environment variables but have the app removed/disabled
Hassette
- Surface
get_apponHassetteclass to allow getting an app instance by name and index (if necessary)- E.g.
hassette.get_app("MyApp")orhassette.get_app("MyApp", 1)
- E.g.
[0.5.0] - 2025-09-12
Changed
- BREAKING: Remove logic to pop top level
[hassette]section from config file, this has the unfortunate side effect of potentially overriding values set in environment variables - Update examples to remove references to top level
[hassette]section - Add warning if we detect this section in the config file
- Add TODO to get this working by implementing a custom
TomlConfigSettingsSourcethat handles this - BREAKING: Switch back to
__double underscore for environment variable prefixes, prevents issues with app config that uses single underscore - Add
env_fileto AppConfig default class config to load environment variables from/config/.envand.envfiles automatically - Add examples of using
SettingsConfigDictto set a customenv_prefixon AppConfig subclasses
[0.4.2] - 2025-09-12
Fixed
- Fixed permissions for /app and /data in Dockerfile
- Update example docker-compose.yml to use named volume for /data
[0.4.1] - 2025-09-11
Fixed
- Fixed Dockerfile to build for both amd64 and arm64
[0.4.0] - 2025-09-10
Added
Docker Support
- Dockerfile with Python 3.12-alpine base image for lightweight deployment
- Docker start script to set up virtual environment, install dependencies, and run Hassette
- /apps that contain a pyproject.toml or uv.lock will be installed as a project
- /config and /apps will be scanned for requirements.txt or hassette-requirements.txt files and merged for installation
- Example docker-compose.yml file for easy setup
- uv cache directory at /uv_cache to speed up dependency installation
Configuration
- New
app_dirconfiguration option to specify the directory containing user apps (default: ./apps) - Top level
[hassette]can be used - previously had to be at the root of the file, with no header HealthServiceconfig - allow setting port and allow disabling health servicehealth_service_port(default: 8126)run_health_service(default: true)
Changed
- BREAKING: Moved all event models from
hassette.models.eventstohassette.core.eventsfor better organization - BREAKING: Updated configuration structure - flattened Hass configuration properties directly into main config
config.hass.token→config.tokenconfig.hass.ws_url→config.ws_urlconfig.hass.base_url→config.base_url- BREAKING: Changed environment variable prefix from
hassette__*tohassette_*(double underscore to single) - Change resource status prior to sending event, to ensure consistency
- Improve retry logic in
_Apiand_Websocketclasses
Fixed
- Improved App constructor with better parameter formatting and documentation
- Added
indexparameter documentation to App__init__method - Fixed logging initialization to handle missing handlers gracefully using
contextlib.suppress - Enhanced state conversion with better discriminated union handling using Pydantic's
discriminatorfield - Improved error handling in
try_convert_statefunction - Updated AppConfig to allow arbitrary types (
arbitrary_types_allowed=True) - Handle bug in
HealthServiceconfig - sometimesweb.AppKeyraises anUnboundLocalError(only seen in testing so far), fallback to string in this case
Removed
- Removed unused
_make_unique_namemethod from App class - Removed
KNOWN_TOPICSconstant that was no longer used - Removed
hass_configproperty from Hassette class (configuration is now flattened) - Cleaned up unused imports and redundant code
ResourceSyncstopmethod on Resource__init__onServicethat was the same as the parent class
Internal
- Simplified configuration test files to use new flattened structure
- Updated all import statements throughout the codebase to reflect new module structure
- Simplified app handler path resolution by using
full_pathproperty directly - Updated test configuration and example files to match new config structure
- Enhanced state model discriminator logic for better type resolution
- Consolidated configuration access patterns for cleaner code
[0.3.3] - 2025-09-07
Fixed
- Filter pydantic args correctly in
get_app_config_classutility function so we don't attempt to usetyping.TypeVaras a config class.
[0.3.2] - 2025-09-07
Fixed
- Removed incorrect
__init__override inAppSyncthat was causing issues with app instantiation
[0.3.1] - 2025-09-07
Fixed
- Fixed timestamp conversion return types in
InputDateTimeStateattributes - Removed custom attributes from input number states
- Get AppSync working using anyio.to_thread and
hassette.loop.create_taskto ensure we're on the right event loop
Internal
- Consolidated input entity states into unified
input.pymodule BinarySensorStatenow inherits fromBoolBaseState- Fixed inheritance issues in
SceneState,ZoneState, andNumberState - Update health service to use a
web.AppKeyinstead of a string
Tests
- get tests against HA instance working in Github Actions
- updated tests for fixed synchronous app handling
[0.3.0] - 2025-09-04
Changed
- Update exports to remove long lists of states, events, and predicates
- Still export StateChangeEvent
- Other exports are now under
states,events,predicatesexports - E.g.
from hassette import AttrChangedbecomesfrom hassette import predicatesandpredicates.AttrChanged
[0.2.1] - 2025-09-04
Added
- New examples directory with comprehensive automation examples
- Battery monitoring example app with sync/async variants
- Presence detection example with complex scene management
- Sensor notification example
- Example
hassette.tomlconfiguration file - New
on_hassette_service_startedevent handler - Additional sync API methods for better synchronous app support
Changed
- Improved README with comprehensive documentation and examples
- Updated pyproject.toml with better PyPI metadata and project URLs
- Enhanced notify service examples and API calls
- Updated roadmap with current development priorities
Fixed
- Fixed notify service call examples in battery and presence apps
- Fixed
HassetteServiceEventannotation
[0.2.0] - 2025-09-04
Added
- Full typing support for Home Assistant entities and events
- Custom scheduler replacing APScheduler dependency
- Comprehensive state model system with typed attributes
- Event bus with powerful filtering capabilities
- Testing utilities and mock server support
Changed
- BREAKING: Significant changes to state/sensor structure for better type safety
- Made sensor attributes required and always present
- Simplified state management by moving simple states to dedicated module
- Reduced complexity in state handling while maintaining full functionality
- Updated authentication and HTTP handling
Fixed
- API parity between sync and async methods
- Sensor attribute handling and device class support
- Configuration scope and core initialization
Internal
- Moved sensor literals into constants module
- Reorganized state models for better maintainability
- Added comprehensive test coverage for API parity
- Improved development tooling and testing setup
[0.1.1] - 2025-09-02
Added
- Initial public release of Hassette framework
- Basic async-first Home Assistant automation support
- Type-safe entity and event handling
- TOML-based configuration system
- Pydantic model validation for app configs
Features
- Event-driven architecture with asyncio
- Home Assistant WebSocket API integration
- Structured logging with coloredlogs
- Scheduler for cron and interval-based tasks
- App lifecycle management (initialize/shutdown)