Skip to content

Commit e82b9fd

Browse files
NodeJSmithCopilot
andauthored
Feature/predicate rewrite; Bus/Predicate documentation (#147)
* fix all examples/docs to use on_state_change and on_attribute_change * remove some mypy style type ignore comments from copilot/codex * rename test_tasks to test_task_bucket, move a test from core to it * improve some websocket tests * cleanup core tests * fix test logic after splitting it into two * be smarter about testing run_sync * clean up predicate structure and predicate tests structure a bit * predicate must be not be async * should be frozen * get rid of all async stuff * totally rework the predicates system * add collection membership check * add back handling of NOT_PROVIDED, do not accept typing.Any * correct return type and logic of get_service_data * use ServiceMatches to handle glob logic * fix init imports * have codex rewrite tests, not yet reviewed * Update tests/test_websocket_service.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update examples/apps/presence.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/hassette/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * remove old import * fix a few more duplicate entries * last duplicates? * update known types to include sequence/mapping * add another test method * only export predicates module * update docs * remove duplicate * improve bus documentation * fix some more references to SystemDateTime * refactor(types.py, utils.py): rename PredicateCallable to Condition and update related type aliases for clarity and consistency refactor(utils.py): remove unnecessary checks for PredicateCallable to simplify the code and improve readability * rename _KnownTypes to KnownTypeScalar * add code conventions * fix type by using TypeVar instead of union * new tests + improve tets * fix a type import * update to use frozen=true like others * do not check IsInstance for predicate, just callable. move imports to type check block * fix an import * add tests for get_path * handle standard glom error without logging, logging for other errors * remove no longer valid rule * update docstring for ChangeType * fix order of aguments to compare_value, fix all tests and usage * add return type annotation, rename From/To to StateFrom/StateTo * feat(predicates): add new conditions IsIn, NotIn, IsOrContains, and Intersects to enhance predicate functionality refactor(predicates): reorganize imports and update existing predicates to utilize new conditions fix(predicates): improve type checking and handling of collections in utility functions for better reliability test(predicates): add unit tests for new conditions to ensure correct behavior and integration refactor(tests): remove redundant tests and improve test coverage for predicates feat(tests): add new predicates conditions to enhance functionality in tests fix(tests): update service data where tests to use IsOrContains for better matching logic * add return type to get_path * remove immediately * correct name * switch these to classes instead of functions * fix name * clean up a few items in the docs * add all the new modules to docs * add blank line after complaint from sphinx * change to TypeAliasType * remove duplicate type guard function * Update examples/apps/office_button_app.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * raise error if string passed to a collection condition, fix tests * Update docs/bus.rst Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix docstrings to include args/kwargs, remove rendant import * fix IsOrContains - i'd had that mixed up in my head * fix docstring in compare_value * remove unnecessary bit of bus docs * fix examples in docstring * update changelog * pre-compile regex pattern * add back log line for initialize/shutdown * bump version * add version to changelog --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 75c192f commit e82b9fd

43 files changed

Lines changed: 2927 additions & 821 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/copilot-instructions.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ These notes make AI coding agents productive quickly in this repo. Focus on the
2323

2424
- Base classes: `src/hassette/core/resources/app/app.py` exposes `App[AppConfigT]` (async) and `AppSync`. Override lifecycle hooks (`before_initialize`, `on_initialize`, `after_initialize`, and matching shutdown hooks); `initialize()` / `shutdown()` are final.
2525
- Example async app:
26+
2627
```python
2728
from hassette import App, AppConfig
2829

@@ -31,7 +32,7 @@ These notes make AI coding agents productive quickly in this repo. Focus on the
3132

3233
class MyApp(App[MyConfig]):
3334
async def on_initialize(self):
34-
self.bus.on_entity(
35+
self.bus.on_state_change(
3536
self.app_config.light,
3637
handler=self.on_light_change,
3738
changed_to="on",
@@ -40,6 +41,7 @@ These notes make AI coding agents productive quickly in this repo. Focus on the
4041
async def on_light_change(self, event):
4142
await self.api.call_service("notify", "mobile_app_me", message="Light turned on")
4243
```
44+
4345
- Sync apps inherit `AppSync` and implement `on_initialize_sync` / `on_shutdown_sync`; use `self.api.sync.*` for blocking calls. `self.task_bucket` offers helpers (`spawn`, `run_in_thread`, `run_sync`) for background work.
4446

4547
## Event Bus Usage
@@ -102,3 +104,11 @@ These notes make AI coding agents productive quickly in this repo. Focus on the
102104
- Config: `src/hassette/config/{core_config.py,app_manifest.py,sources_helper.py}`
103105
- Events & models: `src/hassette/events/**`, `src/hassette/models/**`, `src/hassette/topics.py`
104106
- Examples & tests: `examples/`, `tests/` (fixtures in `tests/conftest.py`)
107+
108+
## Code Conventions
109+
110+
- Type hints: Strong typing is a first-class citizen. Use `typing` and `typing_extensions` features liberally (e.g. `TypeVar`, `Generic`, `Protocol`, `runtime_checkable`, `final`, `Literal`, `TypedDict`, etc.). Leverage existing types in `src/hassette/types.py` and domain-specific models in `src/hassette/models/`.
111+
- Pyrigh and Ruff: Type checking is enforced via Pyright (`pyrightconfig.json`) and linting via Ruff (`ruff.toml`). Follow their rules and fix violations promptly. Do not use mypy style type comments.
112+
- Docstrings: Document all public classes, methods, and functions with clear docstrings. Follow the Google style guide for consistency.
113+
- Examples: Include usage examples in docstrings where applicable, especially for complex methods or classes.
114+
- Documentation is written in reStructuredText (`.rst`) format and lives in the `docs/` directory. Update docs alongside code changes to keep them in sync. Docstring examples should be formatted for inclusion in the docs.

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
## [0.14.0] - 2025-10-19
11+
1012
### Added
1113
- Add validation for filename extension in AppManifest - add `.py` if no suffix, raise error if not `.py`
1214
- Bus handlers can now accept args and kwargs to be passed to the callback when the event is fired
@@ -23,6 +25,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2325
### Fixed
2426
- Correct scheduler helpers `run_minutely`, `run_hourly`, and `run_daily` to not start immediately if no `start` was provided, but to start on the next interval instead.
2527

28+
### Bus/Predicate Refactor
29+
- Refactored predicates to use composable `Predicates`, `Conditions`, and `Accessors`
30+
- `Predicate` is a callable that takes an event and returns a bool
31+
- E.g. `AttrFrom`, `AttrTo`, `DomainMatches`, `EntityMatches`, `ValueIs`, etc.
32+
- `Condition` is a callable that takes a value and returns a bool
33+
- E.g. `Glob`, `Contains`, `IsIn`, `IsOrContains`, `Intersects`, etc.
34+
- `Accessor` is a callable that takes an event and returns a value
35+
- E.g. `get_domain`, `get_entity_id`, `get_service_data`, `get_path`, etc.
36+
- Updated Bus methods to use new predicate system
37+
- Only implementation changes, public API remains the same
38+
- Updated tests to use new predicate system
39+
- Add/update types for predicates, conditions, and accessors
40+
- Updated documentation for predicates and bus event listening to reflect new system
2641

2742
## [0.13.0] - 2025-10-14
2843

docs/apps.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Example
2929
3030
class MyApp(App[MyAppConfig]):
3131
async def on_initialize(self):
32-
self.on_change_listener = self.bus.on_entity(self.app_config.light, handler=self.on_change)
32+
self.on_change_listener = self.bus.on_state_change(self.app_config.light, handler=self.on_change)
3333
self.minutely_logger = self.scheduler.run_minutely(self.log_every_minute)
3434
3535
async def on_change(self, event: StateChangeEvent[states.LightState]):
@@ -54,7 +54,7 @@ App Capabilities
5454
- ``self.app_config``: The parsed config for this app instance, typed as the app's ``AppConfig`` class.
5555
- ``self.index``: The index of this app instance in the config list, or 0 if only a single instance.
5656
- ``self.instance_name``: The name of this app instance as set in the config, or ``<ClassName>.[index]`` if not set.
57-
- ``self.now()``: Get the current time as ``SystemDateTime``.
57+
- ``self.now()``: Get the current time as ``ZonedDateTime``.
5858

5959
Advanced Capabilities
6060
---------------------
@@ -136,7 +136,7 @@ The base AppConfig class includes two fields by default:
136136
137137
class Presence(App[PresenceConfig]):
138138
async def on_initialize(self):
139-
self.bus.on_entity(self.app_config.motion_sensor, handler=self.on_motion, changed_to="on")
139+
self.bus.on_state_change(self.app_config.motion_sensor, handler=self.on_motion, changed_to="on")
140140
141141
async def on_motion(self, event):
142142
for light in self.app_config.lights:

0 commit comments

Comments
 (0)