Repository navigation
v0.56.0
Breaking Changes
App code
AppConfig.app_keyremoved — the field was never populated, soself.app_config.app_keywas always"". Read the app's key withself.app_keyinstead. Anapp_keyleft under an app's config inhassette.tomlstill loads, but is now an ignored extra field. (#2470)ResourceStatusandExecutionStatusimport paths moved —hassette.types.ResourceStatus,hassette.types.enums.ResourceStatusandhassette.types.types.ExecutionStatusno longer resolve. Import them from the package root:from hassette import ResourceStatus, ExecutionStatus.ExecutionModeandBackpressurePolicyare also exported from the root. TheLOG_LEVEL_TYPEalias is gone; usehassette_wire.LogLevel. (#2449)AppConfig.log_leveldefaults toNone— code that readsself.app_config.log_leveldirectly now getsNonefor apps that don't set it, instead of"INFO". Useself.config_log_levelfor the resolved level. (#2571)
Web API, CLI JSON and hassette_wire
These affect scripts and dashboards that call the HTTP/WebSocket API, parse --json CLI output, or import hassette_wire models. App code is not affected.
- Errors are RFC 9457 problem details — every non-2xx response under
/apiis nowapplication/problem+jsonwithtype,title,status,detailand a stablecode(see the new API errors page). Status codes anddetailstrings are unchanged. Validation 422s now carry a stringdetailsummary instead of a list, and with the SPA served, a non-GET request to an unknown/apipath returns 404 instead of 405. (#2480) - Telemetry outages return a problem body — data routes (telemetry, logs, executions, listeners, jobs, apps) now answer a database outage with a 503
telemetry_unavailableproblem body instead of a 503 with an empty or default body./api/health/readyand/api/telemetry/statusare unchanged. (#2491) - Apps endpoints reshaped — update callers as follows (#2558):
GET /api/apps/manifests→GET /api/apps; the list fieldmanifests→appsGET /api/apps/{app_key}/manifest→GET /api/apps/{app_key}- The old
GET /api/appslive-instance view (AppStatusResponse) is removed GET /api/telemetry/dashboard/app-grid→GET /api/telemetry/app-grid, with rows shaped{app, activity}instead of flat- WebSocket event
app_manifests_changed→apps_changed hassette app --jsonandhassette dashboard --jsonrows are also{app, activity};recent_invocations_1hleaves the app summary- Activity that wasn't computed is now
nullper part (stats,activity_buckets,last_error,blocking_event_count) instead of zeros — handlenull - A non-finite
since(nan,inf) now returns 422
- One
AppHealthmodel —GET /api/telemetry/app/{app_key}/healthreturnsAppHealth, with averages renamed tohandler_avg_duration_ms/job_avg_duration_ms(nullwhen nothing of that kind ran). Grid rows nesterror_rate,error_rate_class,health_statusandlast_activity_tsunderhealthand dropavg_duration_ms.hassette app health --jsonrenameshandler_avg_duration/job_avg_durationto*_ms. Health now counts executions of handlers and jobs removed mid-window. (#2520) - Typed vocabularies —
role,schedule_status,schedule_status_reasonand app action fields are now enums in the OpenAPI and WebSocket schemas.ServiceInfo.roleandJobSummary.schedule_statusare now required. (#2527) hassette_wiremodel renames — names now follow one rule (records are bare nouns, HTTP collections are*Response, WS payloads are*Data). Wire bytes are unchanged; only Python and schema component names change (#2584, #2558, #2520):AppInstanceResponse→AppInstance,BootIssueResponse→BootIssue,ServiceInfoResponse→ServiceInfo,LogEntryResponse→LogEntry,AppSourceResponse→AppSource,ConnectedPayload→ConnectedData,ListenerWithSummary→ListenerSummaryAppManifestResponse→AppSummary,AppManifestListResponse→AppListResponse,DashboardAppGrid*→AppGrid*,ManifestStatus→AppStatusAppHealthResponse→AppHealth;AppStatusResponseis removed
For App Authors: You Can Now Delete
await super().before_initialize()added only to keepself.cacheworking — the cache is now initialized by the framework before your hook runs, so an override no longer needs thesuper()call. (#2572)cast()aroundself.cache.get(key, default=...)—get()with a non-Nonedefault is now typed as the default's type, socast(list[float], ...)wrappers andor 0fallbacks can go. (#2528)- Per-app
log_levelset only to match[hassette.logging] apps— apps without their ownlog_levelnow inherit the global apps level. (#2571) deep_detection_enabled = trueset alongsideallow_deep_detection_in_prod = true— the allow flag now enables Tier 2 blocking-IO detection in production on its own. (#2472)try/exceptinside predicates onduration=listeners to catch your own errors — a raising duration-hold predicate is now recorded as an error execution and routed to the listener'son_errorhandler, like any other predicate. (#2588)- Test code that rebuilds a simulated event to compare context IDs —
AppTestHarness.simulate_*()helpers now return the event they dispatched. (#2593)
Python Client
hassette-clientnow shipsHassetteClient: typed async methods over anaiohttp.ClientSessionyou own, returninghassette_wiremodels. Every network or server failure raises aHassetteClientErrorsubclass (connection, timeout, HTTP status families, app-not-found, bootstrap-not-released), so callers branch on exception type instead of parsing response bodies. See the new Python client page. (#2596)- The server now reports
api_schema_versionin its health payload, andcheck_server_version()raisesUnsupportedServerVersionErroragainst a server too old for the client (#2596) - Client responses parse leniently: an enum or
Literalvalue a newer server adds arrives asUnknownValue(astrsubclass that keeps the raw value) instead of failing validation. Opt in on your own parses withhassette_wire.LENIENT_CONTEXT. (#2502) hassette-wirenow holds every model, enum and Literal served over the HTTP/WebSocket API, depending only on pydantic, so tools can use the API contract without installing the framework (#2449)
Configuration
- New
hassette.local.tomloverlay for per-machine settings you don't want to commit (a devbase_url, a debug log level). Each configured TOML file gets its own<name>.local.tomlsibling, which is deep-merged on top key by key, so it only needs the keys it changes. Env vars and CLI flags still win. Overlays are watched for hot reload. (#2561) - New per-app
cache_shared = truesilences thecache_keycollision warning for apps that share a cache on purpose. The warning still fires, and names the missing apps, unless every app sharing the key sets it. (#2570)
Web UI & CLI
- Blocking-IO findings are now visible: a "blocking calls" section on the app Overview tab, an "N blocking" badge on the Apps list, a "loop stalls" panel on Diagnostics for stalls not tied to an app, and a new
hassette blockingcommand. Findings group by the app call site that blocked, with exact counts. Stalls recorded before upgrading show as "call site not captured". (#2495) - The Apps page and the
hassette app/hassette dashboardtables show "—" for activity that wasn't computed, instead of zeros and a healthy-looking status. The CLI prints a one-line warning on stderr naming the missing parts. (#2558) - App health is computed the same way everywhere. The app detail health strip now reads it from the server and shows Handlers · Error Rate · Handler Avg · Job Avg; the CLI shows handler and job averages instead of one duration column. (#2520)
- Config sections on the Config page and an app's Config tab can be collapsed and expanded (#2577)
- An open dashboard tab prompts you to reload when the server is upgraded underneath it. The CLI keeps working against newer servers. (#2527)
- CLI tables use the same widths and full-word headers across
hassette app,hassette app activityandhassette status(App Key→App,Invoc/1h→Invocations/1h,Errs→Errors) (#2544)
Testing
AppTestHarness.simulate_*()helpers return the typed event they dispatched (for exampleRawStateChangeEventfromsimulate_state_change()), after the triggered handlers have drained. Existing tests that ignore the return value are unaffected. (#2593)
Bug Fixes
- WebSocket writes are no longer re-sent after a response timeout, which could apply a command twice.
fire_event,call_service(return_response=True)withoutwait_for_ack, and helpercreate/update/deletenow send exactly once and raiseResponseTimeoutError, orResponseLostErroron disconnect. Both subclass the newOutcomeUnknownError: if you relied on the old retry, catch it and check whether the command took effect before retrying. Reads still retry. (#2468) - Reloading or starting an app from the web API or CLI no longer takes the app down with
HassetteNotInitializedErroruntil a process restart (#2490) - Startup no longer times out when
file_watcher.watch_files = false(#2501; also shipped in 0.55.1) - Apps without an explicit
log_levelnow inherit[hassette.logging] appsinstead of always usingINFO(#2571) self.cacheworks when an app overridesbefore_initializewithout callingsuper(), instead of raisingAsyncCache is not initialized(#2572)allow_deep_detection_in_prod = trueenables Tier 2 blocking-IO detection on its own. Note: an explicitdeep_detection_enabled = truein production now turns Tier 2 on too;deep_detection_enabled = falsealways wins. (#2472)- A predicate that raises on a
duration=listener is now recorded as an error execution and sent toon_error, instead of only being logged (#2588) - App keys containing
/,\, an absolute path or a..segment are rejected at config validation, so an app's derived cache directory can't escapedata_diror alias another app's (#2579) - WebSocket send and dispatch-failure logs and
FailedMessageErrormessages no longer include payload values, which could contain secrets such as a password-modeinput_textvalue. The payload is still onFailedMessageError.original_data. (#2565, #2567) - Shutdown logs carry the app's real key instead of
app_key=None(#2470) self.cache.get(key, default=x)is typed asx's type instead ofT | None(#2528)- Invalid handler and
where=predicate signatures (*args, positional-only parameters) no longer report a dependency injection error when the handler doesn't use DI (#2575) - Keyboard-focused execution table rows show a focus ring (#2583)
- The OpenAPI and WebSocket schemas now include field descriptions for telemetry, blocking, health, WebSocket and auth models (#2538, #2541)
Documentation
- New pages: API errors (every problem
code) and Python client (#2480, #2596) - Accuracy pass across the core-concepts and testing pages: listener naming errors, state conversion rules,
simulate_app_*signatures,enabled = falsebehavior, and config file discovery (everyhassette.tomlis merged) (#2465) - The bus methods page states correctly that omitting
name=raisesTypeError, and that every handler-taking method acceptson_error(#2441) HelperClient.update()anddelete()take the helper's storage id ("vacation_mode"), not itsentity_id; the docs and docstrings now say so (#2450)- Blocking-IO guidance points to
self.task_bucket.run_in_threadinstead ofasyncio.to_thread(#2523) /api/healthreportsokonly once the WebSocket is connected and app bootstrap is released (#2471)- Fixed the
A.get_pathexample path in the predicate reference (#2435) - The lifecycle internals page says who it's for and defines its terms (#2436)