haanim 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- haanim-0.2.0/.gitignore +113 -0
- haanim-0.2.0/LICENSE +21 -0
- haanim-0.2.0/PKG-INFO +220 -0
- haanim-0.2.0/README.md +185 -0
- haanim-0.2.0/pyproject.toml +199 -0
- haanim-0.2.0/src/haanim/__init__.py +166 -0
- haanim-0.2.0/src/haanim/const.py +141 -0
- haanim-0.2.0/src/haanim/engine/__init__.py +34 -0
- haanim-0.2.0/src/haanim/engine/action_dispatcher.py +590 -0
- haanim-0.2.0/src/haanim/engine/action_pool.py +319 -0
- haanim-0.2.0/src/haanim/engine/assets.py +143 -0
- haanim-0.2.0/src/haanim/engine/ast_evaluator.py +977 -0
- haanim-0.2.0/src/haanim/engine/automation_context.py +671 -0
- haanim-0.2.0/src/haanim/engine/automation_ids.py +91 -0
- haanim-0.2.0/src/haanim/engine/automation_module.py +130 -0
- haanim-0.2.0/src/haanim/engine/automation_status.py +185 -0
- haanim-0.2.0/src/haanim/engine/callables.py +112 -0
- haanim-0.2.0/src/haanim/engine/card.py +551 -0
- haanim-0.2.0/src/haanim/engine/card_checks.py +264 -0
- haanim-0.2.0/src/haanim/engine/card_elements.py +748 -0
- haanim-0.2.0/src/haanim/engine/card_layout.py +276 -0
- haanim-0.2.0/src/haanim/engine/constraints/__init__.py +5 -0
- haanim-0.2.0/src/haanim/engine/constraints/rules.py +235 -0
- haanim-0.2.0/src/haanim/engine/control.py +222 -0
- haanim-0.2.0/src/haanim/engine/cron_schedule.py +98 -0
- haanim-0.2.0/src/haanim/engine/decorators.py +584 -0
- haanim-0.2.0/src/haanim/engine/discovery.py +196 -0
- haanim-0.2.0/src/haanim/engine/durations.py +62 -0
- haanim-0.2.0/src/haanim/engine/errors.py +347 -0
- haanim-0.2.0/src/haanim/engine/eval_function.py +344 -0
- haanim-0.2.0/src/haanim/engine/expression_eval.py +545 -0
- haanim-0.2.0/src/haanim/engine/guards.py +115 -0
- haanim-0.2.0/src/haanim/engine/haanim_api.py +674 -0
- haanim-0.2.0/src/haanim/engine/haanim_module.py +153 -0
- haanim-0.2.0/src/haanim/engine/hot_reload.py +166 -0
- haanim-0.2.0/src/haanim/engine/import_controller.py +90 -0
- haanim-0.2.0/src/haanim/engine/lifecycle.py +601 -0
- haanim-0.2.0/src/haanim/engine/logging_wrapper.py +83 -0
- haanim-0.2.0/src/haanim/engine/metadata.py +126 -0
- haanim-0.2.0/src/haanim/engine/operators.py +48 -0
- haanim-0.2.0/src/haanim/engine/safe_builtins.py +48 -0
- haanim-0.2.0/src/haanim/engine/symbol_table.py +199 -0
- haanim-0.2.0/src/haanim/engine/time_expr.py +343 -0
- haanim-0.2.0/src/haanim/engine/time_schedule.py +190 -0
- haanim-0.2.0/src/haanim/engine/triggers/__init__.py +24 -0
- haanim-0.2.0/src/haanim/engine/triggers/base.py +122 -0
- haanim-0.2.0/src/haanim/engine/triggers/cron_trigger.py +57 -0
- haanim-0.2.0/src/haanim/engine/triggers/event_trigger.py +111 -0
- haanim-0.2.0/src/haanim/engine/triggers/interval_trigger.py +115 -0
- haanim-0.2.0/src/haanim/engine/triggers/manager.py +156 -0
- haanim-0.2.0/src/haanim/engine/triggers/scheduled.py +93 -0
- haanim-0.2.0/src/haanim/engine/triggers/state_trigger.py +173 -0
- haanim-0.2.0/src/haanim/engine/triggers/time_trigger.py +72 -0
- haanim-0.2.0/src/haanim/engine/validation.py +304 -0
- haanim-0.2.0/src/haanim/engine/variables.py +147 -0
- haanim-0.2.0/src/haanim/engine/waiting.py +82 -0
- haanim-0.2.0/src/haanim/entity.py +227 -0
- haanim-0.2.0/src/haanim/events.py +136 -0
- haanim-0.2.0/src/haanim/interfaces.py +447 -0
- haanim-0.2.0/src/haanim/py.typed +0 -0
- haanim-0.2.0/src/haanim/testing/__init__.py +24 -0
- haanim-0.2.0/src/haanim/testing/fakes.py +953 -0
- haanim-0.2.0/src/haanim/testing/harness.py +819 -0
- haanim-0.2.0/src/haanim/types.py +293 -0
haanim-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
*.manifest
|
|
32
|
+
*.spec
|
|
33
|
+
|
|
34
|
+
# Unit test / coverage reports
|
|
35
|
+
htmlcov/
|
|
36
|
+
.tox/
|
|
37
|
+
.nox/
|
|
38
|
+
.coverage
|
|
39
|
+
.coverage.*
|
|
40
|
+
.cache
|
|
41
|
+
nosetests.xml
|
|
42
|
+
coverage.xml
|
|
43
|
+
*.cover
|
|
44
|
+
*.py,cover
|
|
45
|
+
.hypothesis/
|
|
46
|
+
.pytest_cache/
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# pipenv
|
|
50
|
+
Pipfile.lock
|
|
51
|
+
|
|
52
|
+
# UV
|
|
53
|
+
.uv/
|
|
54
|
+
uv.lock
|
|
55
|
+
|
|
56
|
+
# PEP 582
|
|
57
|
+
__pypackages__/
|
|
58
|
+
|
|
59
|
+
# Environments
|
|
60
|
+
.env
|
|
61
|
+
.venv
|
|
62
|
+
env/
|
|
63
|
+
venv/
|
|
64
|
+
ENV/
|
|
65
|
+
env.bak/
|
|
66
|
+
venv.bak/
|
|
67
|
+
|
|
68
|
+
# mkdocs documentation
|
|
69
|
+
/site
|
|
70
|
+
|
|
71
|
+
# mypy
|
|
72
|
+
.mypy_cache/
|
|
73
|
+
.dmypy.json
|
|
74
|
+
dmypy.json
|
|
75
|
+
|
|
76
|
+
# Pyre type checker
|
|
77
|
+
.pyre/
|
|
78
|
+
|
|
79
|
+
# IDE
|
|
80
|
+
.idea/
|
|
81
|
+
*.swp
|
|
82
|
+
*.swo
|
|
83
|
+
*~
|
|
84
|
+
/out
|
|
85
|
+
|
|
86
|
+
# OS
|
|
87
|
+
.DS_Store
|
|
88
|
+
Thumbs.db
|
|
89
|
+
|
|
90
|
+
# Home Assistant
|
|
91
|
+
home-assistant.log
|
|
92
|
+
home-assistant_v2.db
|
|
93
|
+
*.db-shm
|
|
94
|
+
*.db-wal
|
|
95
|
+
|
|
96
|
+
# Development environment
|
|
97
|
+
dev/config/.storage/
|
|
98
|
+
dev/config/home-assistant.log
|
|
99
|
+
dev/config/home-assistant.log.*
|
|
100
|
+
dev/config/home-assistant_v2.db
|
|
101
|
+
dev/config/*.db-shm
|
|
102
|
+
dev/config/*.db-wal
|
|
103
|
+
dev/config/.cloud/
|
|
104
|
+
dev/config/.HA_VERSION
|
|
105
|
+
dev/config/deps/
|
|
106
|
+
dev/config/tts/
|
|
107
|
+
dev/config/blueprints/
|
|
108
|
+
|
|
109
|
+
# Misc
|
|
110
|
+
_*.md
|
|
111
|
+
|
|
112
|
+
# A copy of the engine inside the integration is only made for a release (scripts/build-integration.py)
|
|
113
|
+
custom_components/haanim/bundled/
|
haanim-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 HAAnim Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
haanim-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: haanim
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Python automations for Home Assistant: the HAAnim engine and its test harness
|
|
5
|
+
Project-URL: Homepage, https://github.com/valsr/haanim
|
|
6
|
+
Project-URL: Documentation, https://haanim.readthedocs.io/
|
|
7
|
+
Project-URL: Repository, https://github.com/valsr/haanim
|
|
8
|
+
Project-URL: Issues, https://github.com/valsr/haanim/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/valsr/haanim/blob/main/CHANGELOG.md
|
|
10
|
+
Author: valsr
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: automation,hacs,home-assistant,homeassistant
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Home Automation
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.14.2
|
|
21
|
+
Requires-Dist: cronsim>=2.6
|
|
22
|
+
Requires-Dist: python-slugify>=8.0.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: black>=24.0.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: flake8>=7.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: homeassistant<2026.10,>=2026.9.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.8.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pylint>=3.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pyright>=1.1.380; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-homeassistant-custom-component>=0.13.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: pytest-xdist>=3.5.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# HAAnim
|
|
37
|
+
|
|
38
|
+
[](https://github.com/valsr/haanim/actions/workflows/ci.yml)
|
|
39
|
+
[](https://github.com/custom-components/hacs)
|
|
40
|
+
[](https://pypi.org/project/haanim/)
|
|
41
|
+
[](https://haanim.readthedocs.io/)
|
|
42
|
+
[](https://github.com/valsr/haanim/blob/main/LICENSE)
|
|
43
|
+
|
|
44
|
+
Write Home Assistant automations in Python. An automation is a folder with a `main.py`; decorators say when
|
|
45
|
+
its functions run, and the `haa` object reaches entities, services, storage and the automation's own
|
|
46
|
+
dashboard card.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from haanim import StateEvent, TimeEvent, haa, on_state, on_time
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@on_state("sensor.temperature > 30")
|
|
53
|
+
async def high_temperature(event: StateEvent):
|
|
54
|
+
await haa.service.notify.mobile_app(message=f"It is {float(haa.entity.sensor.temperature)}°C")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@on_time("09:00", day_of_week="weekdays", when="person.john == 'home'")
|
|
58
|
+
async def morning(event: TimeEvent):
|
|
59
|
+
await haa.service.light.turn_on(entity_id="light.bedroom", brightness=150)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## What you get
|
|
63
|
+
|
|
64
|
+
- **Triggers**: time (including sunrise and sunset), interval, cron, Home Assistant events and state
|
|
65
|
+
expressions, each with optional constraints (time of day, date range, day of week, state).
|
|
66
|
+
- **Actions** that can be run by hand, by a service, by another automation or by a trigger, with `DROP`,
|
|
67
|
+
`QUEUE` and `CANCEL` execution modes, timeouts and a concurrency limit.
|
|
68
|
+
- **`haa`**: entity access, service calls, `sleep` and `wait_for`, persistent variables, assets, calling and
|
|
69
|
+
controlling other automations.
|
|
70
|
+
- **A sensor per automation** (`sensor.haanim_<id>`: `on`, `off`, `error`) with status attributes.
|
|
71
|
+
- **A dashboard card** (`custom:haanim-card`) that the automation titles and fills with text, images,
|
|
72
|
+
values, live entities and buttons, plus a management panel in the sidebar with each automation's
|
|
73
|
+
controls, actions and log.
|
|
74
|
+
- **Hot reload**: edit a file and the automation is reloaded.
|
|
75
|
+
- **A test harness**: test an automation with `pytest`, with no Home Assistant running.
|
|
76
|
+
|
|
77
|
+
The interpreter keeps automations from blocking Home Assistant by accident (no blocking I/O, an import
|
|
78
|
+
allowlist, loops that yield). It is a guard rail, not a sandbox: an automation can do whatever Home Assistant
|
|
79
|
+
can. Only install automations you trust.
|
|
80
|
+
|
|
81
|
+
## Installation
|
|
82
|
+
|
|
83
|
+
HAAnim needs Home Assistant 2026.9 or newer.
|
|
84
|
+
|
|
85
|
+
1. Install the integration with HACS: **HACS → ⋮ → Custom repositories**, add
|
|
86
|
+
`https://github.com/valsr/haanim` as an **Integration**, then download HAAnim.
|
|
87
|
+
2. Restart Home Assistant.
|
|
88
|
+
3. **Settings → Devices & services → Add integration → HAAnim**.
|
|
89
|
+
|
|
90
|
+
That is all: a release of the integration has the HAAnim engine in it, so no Python package has to be
|
|
91
|
+
installed into Home Assistant.
|
|
92
|
+
|
|
93
|
+
To install by hand, download `haanim.zip` from a
|
|
94
|
+
[release](https://github.com/valsr/haanim/releases) and unpack it into
|
|
95
|
+
`<config>/custom_components/haanim`. Copying `custom_components/haanim` out of a checkout of the repository is
|
|
96
|
+
not enough, because the engine is not in that folder there; build the folder to copy with
|
|
97
|
+
`python scripts/build-integration.py --folder OUT`.
|
|
98
|
+
|
|
99
|
+
Automations live in `/config/haanim/automations/` by default. The folder, the rescan interval, the limits and
|
|
100
|
+
the import options are set under **Configure** on the integration.
|
|
101
|
+
|
|
102
|
+
## Your first automation
|
|
103
|
+
|
|
104
|
+
Create `/config/haanim/automations/hello/main.py`:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from haanim import ActionEvent, action, haa, startup
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@startup
|
|
111
|
+
def ready(event: ActionEvent):
|
|
112
|
+
haa.card.add_element(haa.card.create_text("hello", "## Hello\nPress the button."))
|
|
113
|
+
haa.card.add_element(haa.card.create_button("greet", label="Greet", action="greet"))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@action
|
|
117
|
+
async def greet(event: ActionEvent):
|
|
118
|
+
await haa.service.persistent_notification.create(message="Hello from HAAnim")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Within the rescan interval the automation is running: `sensor.haanim_hello` is `on`, and the action can be run
|
|
122
|
+
from the HAAnim panel, with the `haanim.run_action` service, or from the automation's card:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
type: custom:haanim-card
|
|
126
|
+
automation_id: hello
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The [automation guide](https://haanim.readthedocs.io/en/latest/AUTOMATIONS/) covers everything an automation can do, and
|
|
130
|
+
[`examples/`](https://github.com/valsr/haanim/tree/main/examples) has complete automations with tests, among them a demo for each part of
|
|
131
|
+
HAAnim. The same documentation is built for Read the Docs from `docs/` (`mkdocs.yml`).
|
|
132
|
+
|
|
133
|
+
## Testing an automation
|
|
134
|
+
|
|
135
|
+
The engine is also a Python package, `haanim`, for your own machine: it gives your editor completion and
|
|
136
|
+
types for `from haanim import ...`, and the test harness. It is not needed in Home Assistant.
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
pip install haanim pytest pytest-asyncio
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The package needs Python 3.14, the Python that Home Assistant 2026.9 runs on.
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
from haanim.testing import AutomationHarness
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
async def test_greeting():
|
|
149
|
+
async with AutomationHarness("automations/hello") as automation:
|
|
150
|
+
await automation.press("greet")
|
|
151
|
+
assert automation.service_calls("persistent_notification.create")[0].data == {
|
|
152
|
+
"message": "Hello from HAAnim"
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The harness runs the automation with the same interpreter and triggers as Home Assistant, against a fake Home
|
|
157
|
+
Assistant whose clock only moves when the test moves it. Installing the package does not install Home
|
|
158
|
+
Assistant.
|
|
159
|
+
|
|
160
|
+
## Services
|
|
161
|
+
|
|
162
|
+
| Service | Data | What it does |
|
|
163
|
+
| ------------------------- | --------------------------------- | -------------------------------------------------------- |
|
|
164
|
+
| `haanim.run_action` | `automation_id`, `action`, `data` | Runs an action; returns its result as response data |
|
|
165
|
+
| `haanim.enable` | `automation_id` | Enables and starts the automation |
|
|
166
|
+
| `haanim.disable` | `automation_id` | Stops and disables the automation |
|
|
167
|
+
| `haanim.start` | `automation_id` | Starts the automation |
|
|
168
|
+
| `haanim.stop` | `automation_id` | Stops the automation |
|
|
169
|
+
| `haanim.restart` | `automation_id` | Restarts the automation |
|
|
170
|
+
| `haanim.reload` | `automation_id` (optional) | Rescans now and reloads one automation, or all |
|
|
171
|
+
| `haanim.list_automations` | - | Returns ID, name, state and enabled flag of every one |
|
|
172
|
+
| `haanim.list_actions` | `automation_id` | Returns the automation's actions |
|
|
173
|
+
| `haanim.clear_log` | `automation_id` (optional) | Empties the log HAAnim keeps for one automation, or all |
|
|
174
|
+
| `haanim.set_log_level` | `automation_id`, `level` | Sets the automation's log level; `default` takes it away |
|
|
175
|
+
|
|
176
|
+
## Development
|
|
177
|
+
|
|
178
|
+
```sh
|
|
179
|
+
git clone https://github.com/valsr/haanim.git
|
|
180
|
+
cd haanim
|
|
181
|
+
uv sync --all-extras
|
|
182
|
+
|
|
183
|
+
uv run pytest # Python tests, frontend tests and the coverage gate
|
|
184
|
+
uv run python scripts/check-public-api-coverage.py # the automation-facing API must be at 100%
|
|
185
|
+
scripts/test-frontend.sh # only the JavaScript tests (needs node 22+)
|
|
186
|
+
uv run black --check . && uv run pylint custom_components src
|
|
187
|
+
uv run mypy custom_components src && uv run pyright src custom_components examples
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
A Home Assistant with the integration, the package and the examples, in a container:
|
|
191
|
+
|
|
192
|
+
```sh
|
|
193
|
+
./build-and-run.sh # http://localhost:8123, user admin, password admin
|
|
194
|
+
uv run python scripts/e2e-smoke.py # end-to-end checks against that container
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The engine has one source, `src/haanim`. In a checkout the integration imports it as the installed `haanim`
|
|
198
|
+
package (`uv sync` installs it in place). A release carries a copy of it inside the integration:
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
uv run python scripts/build-integration.py # dist/haanim.zip: what a GitHub release attaches and HACS installs
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The repository:
|
|
205
|
+
|
|
206
|
+
| Path | What is there |
|
|
207
|
+
| --------------------------- | -------------------------------------------------------------------------- |
|
|
208
|
+
| `src/haanim/` | The engine: interpreter, triggers, dispatcher, `haa`. No Home Assistant imports |
|
|
209
|
+
| `src/haanim/testing/` | The test harness and the fakes it is built on |
|
|
210
|
+
| `custom_components/haanim/` | The integration: entity, services, options, websocket commands, frontend |
|
|
211
|
+
| `examples/` | Example automations and their harness tests |
|
|
212
|
+
| `tests/` | `engine/` (no Home Assistant), `integration/`, `frontend/` |
|
|
213
|
+
| `docs/` | The documentation (built with MkDocs) and notes on the development environment |
|
|
214
|
+
|
|
215
|
+
See [CONTRIBUTING.md](https://github.com/valsr/haanim/blob/main/CONTRIBUTING.md) before sending a pull request.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT, see [LICENSE](https://github.com/valsr/haanim/blob/main/LICENSE). HAAnim is a custom integration and is not supported by the Home Assistant
|
|
220
|
+
project.
|
haanim-0.2.0/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# HAAnim
|
|
2
|
+
|
|
3
|
+
[](https://github.com/valsr/haanim/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/custom-components/hacs)
|
|
5
|
+
[](https://pypi.org/project/haanim/)
|
|
6
|
+
[](https://haanim.readthedocs.io/)
|
|
7
|
+
[](https://github.com/valsr/haanim/blob/main/LICENSE)
|
|
8
|
+
|
|
9
|
+
Write Home Assistant automations in Python. An automation is a folder with a `main.py`; decorators say when
|
|
10
|
+
its functions run, and the `haa` object reaches entities, services, storage and the automation's own
|
|
11
|
+
dashboard card.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from haanim import StateEvent, TimeEvent, haa, on_state, on_time
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@on_state("sensor.temperature > 30")
|
|
18
|
+
async def high_temperature(event: StateEvent):
|
|
19
|
+
await haa.service.notify.mobile_app(message=f"It is {float(haa.entity.sensor.temperature)}°C")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@on_time("09:00", day_of_week="weekdays", when="person.john == 'home'")
|
|
23
|
+
async def morning(event: TimeEvent):
|
|
24
|
+
await haa.service.light.turn_on(entity_id="light.bedroom", brightness=150)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## What you get
|
|
28
|
+
|
|
29
|
+
- **Triggers**: time (including sunrise and sunset), interval, cron, Home Assistant events and state
|
|
30
|
+
expressions, each with optional constraints (time of day, date range, day of week, state).
|
|
31
|
+
- **Actions** that can be run by hand, by a service, by another automation or by a trigger, with `DROP`,
|
|
32
|
+
`QUEUE` and `CANCEL` execution modes, timeouts and a concurrency limit.
|
|
33
|
+
- **`haa`**: entity access, service calls, `sleep` and `wait_for`, persistent variables, assets, calling and
|
|
34
|
+
controlling other automations.
|
|
35
|
+
- **A sensor per automation** (`sensor.haanim_<id>`: `on`, `off`, `error`) with status attributes.
|
|
36
|
+
- **A dashboard card** (`custom:haanim-card`) that the automation titles and fills with text, images,
|
|
37
|
+
values, live entities and buttons, plus a management panel in the sidebar with each automation's
|
|
38
|
+
controls, actions and log.
|
|
39
|
+
- **Hot reload**: edit a file and the automation is reloaded.
|
|
40
|
+
- **A test harness**: test an automation with `pytest`, with no Home Assistant running.
|
|
41
|
+
|
|
42
|
+
The interpreter keeps automations from blocking Home Assistant by accident (no blocking I/O, an import
|
|
43
|
+
allowlist, loops that yield). It is a guard rail, not a sandbox: an automation can do whatever Home Assistant
|
|
44
|
+
can. Only install automations you trust.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
HAAnim needs Home Assistant 2026.9 or newer.
|
|
49
|
+
|
|
50
|
+
1. Install the integration with HACS: **HACS → ⋮ → Custom repositories**, add
|
|
51
|
+
`https://github.com/valsr/haanim` as an **Integration**, then download HAAnim.
|
|
52
|
+
2. Restart Home Assistant.
|
|
53
|
+
3. **Settings → Devices & services → Add integration → HAAnim**.
|
|
54
|
+
|
|
55
|
+
That is all: a release of the integration has the HAAnim engine in it, so no Python package has to be
|
|
56
|
+
installed into Home Assistant.
|
|
57
|
+
|
|
58
|
+
To install by hand, download `haanim.zip` from a
|
|
59
|
+
[release](https://github.com/valsr/haanim/releases) and unpack it into
|
|
60
|
+
`<config>/custom_components/haanim`. Copying `custom_components/haanim` out of a checkout of the repository is
|
|
61
|
+
not enough, because the engine is not in that folder there; build the folder to copy with
|
|
62
|
+
`python scripts/build-integration.py --folder OUT`.
|
|
63
|
+
|
|
64
|
+
Automations live in `/config/haanim/automations/` by default. The folder, the rescan interval, the limits and
|
|
65
|
+
the import options are set under **Configure** on the integration.
|
|
66
|
+
|
|
67
|
+
## Your first automation
|
|
68
|
+
|
|
69
|
+
Create `/config/haanim/automations/hello/main.py`:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from haanim import ActionEvent, action, haa, startup
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@startup
|
|
76
|
+
def ready(event: ActionEvent):
|
|
77
|
+
haa.card.add_element(haa.card.create_text("hello", "## Hello\nPress the button."))
|
|
78
|
+
haa.card.add_element(haa.card.create_button("greet", label="Greet", action="greet"))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@action
|
|
82
|
+
async def greet(event: ActionEvent):
|
|
83
|
+
await haa.service.persistent_notification.create(message="Hello from HAAnim")
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Within the rescan interval the automation is running: `sensor.haanim_hello` is `on`, and the action can be run
|
|
87
|
+
from the HAAnim panel, with the `haanim.run_action` service, or from the automation's card:
|
|
88
|
+
|
|
89
|
+
```yaml
|
|
90
|
+
type: custom:haanim-card
|
|
91
|
+
automation_id: hello
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The [automation guide](https://haanim.readthedocs.io/en/latest/AUTOMATIONS/) covers everything an automation can do, and
|
|
95
|
+
[`examples/`](https://github.com/valsr/haanim/tree/main/examples) has complete automations with tests, among them a demo for each part of
|
|
96
|
+
HAAnim. The same documentation is built for Read the Docs from `docs/` (`mkdocs.yml`).
|
|
97
|
+
|
|
98
|
+
## Testing an automation
|
|
99
|
+
|
|
100
|
+
The engine is also a Python package, `haanim`, for your own machine: it gives your editor completion and
|
|
101
|
+
types for `from haanim import ...`, and the test harness. It is not needed in Home Assistant.
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
pip install haanim pytest pytest-asyncio
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The package needs Python 3.14, the Python that Home Assistant 2026.9 runs on.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from haanim.testing import AutomationHarness
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def test_greeting():
|
|
114
|
+
async with AutomationHarness("automations/hello") as automation:
|
|
115
|
+
await automation.press("greet")
|
|
116
|
+
assert automation.service_calls("persistent_notification.create")[0].data == {
|
|
117
|
+
"message": "Hello from HAAnim"
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The harness runs the automation with the same interpreter and triggers as Home Assistant, against a fake Home
|
|
122
|
+
Assistant whose clock only moves when the test moves it. Installing the package does not install Home
|
|
123
|
+
Assistant.
|
|
124
|
+
|
|
125
|
+
## Services
|
|
126
|
+
|
|
127
|
+
| Service | Data | What it does |
|
|
128
|
+
| ------------------------- | --------------------------------- | -------------------------------------------------------- |
|
|
129
|
+
| `haanim.run_action` | `automation_id`, `action`, `data` | Runs an action; returns its result as response data |
|
|
130
|
+
| `haanim.enable` | `automation_id` | Enables and starts the automation |
|
|
131
|
+
| `haanim.disable` | `automation_id` | Stops and disables the automation |
|
|
132
|
+
| `haanim.start` | `automation_id` | Starts the automation |
|
|
133
|
+
| `haanim.stop` | `automation_id` | Stops the automation |
|
|
134
|
+
| `haanim.restart` | `automation_id` | Restarts the automation |
|
|
135
|
+
| `haanim.reload` | `automation_id` (optional) | Rescans now and reloads one automation, or all |
|
|
136
|
+
| `haanim.list_automations` | - | Returns ID, name, state and enabled flag of every one |
|
|
137
|
+
| `haanim.list_actions` | `automation_id` | Returns the automation's actions |
|
|
138
|
+
| `haanim.clear_log` | `automation_id` (optional) | Empties the log HAAnim keeps for one automation, or all |
|
|
139
|
+
| `haanim.set_log_level` | `automation_id`, `level` | Sets the automation's log level; `default` takes it away |
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
git clone https://github.com/valsr/haanim.git
|
|
145
|
+
cd haanim
|
|
146
|
+
uv sync --all-extras
|
|
147
|
+
|
|
148
|
+
uv run pytest # Python tests, frontend tests and the coverage gate
|
|
149
|
+
uv run python scripts/check-public-api-coverage.py # the automation-facing API must be at 100%
|
|
150
|
+
scripts/test-frontend.sh # only the JavaScript tests (needs node 22+)
|
|
151
|
+
uv run black --check . && uv run pylint custom_components src
|
|
152
|
+
uv run mypy custom_components src && uv run pyright src custom_components examples
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A Home Assistant with the integration, the package and the examples, in a container:
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
./build-and-run.sh # http://localhost:8123, user admin, password admin
|
|
159
|
+
uv run python scripts/e2e-smoke.py # end-to-end checks against that container
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The engine has one source, `src/haanim`. In a checkout the integration imports it as the installed `haanim`
|
|
163
|
+
package (`uv sync` installs it in place). A release carries a copy of it inside the integration:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
uv run python scripts/build-integration.py # dist/haanim.zip: what a GitHub release attaches and HACS installs
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
The repository:
|
|
170
|
+
|
|
171
|
+
| Path | What is there |
|
|
172
|
+
| --------------------------- | -------------------------------------------------------------------------- |
|
|
173
|
+
| `src/haanim/` | The engine: interpreter, triggers, dispatcher, `haa`. No Home Assistant imports |
|
|
174
|
+
| `src/haanim/testing/` | The test harness and the fakes it is built on |
|
|
175
|
+
| `custom_components/haanim/` | The integration: entity, services, options, websocket commands, frontend |
|
|
176
|
+
| `examples/` | Example automations and their harness tests |
|
|
177
|
+
| `tests/` | `engine/` (no Home Assistant), `integration/`, `frontend/` |
|
|
178
|
+
| `docs/` | The documentation (built with MkDocs) and notes on the development environment |
|
|
179
|
+
|
|
180
|
+
See [CONTRIBUTING.md](https://github.com/valsr/haanim/blob/main/CONTRIBUTING.md) before sending a pull request.
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT, see [LICENSE](https://github.com/valsr/haanim/blob/main/LICENSE). HAAnim is a custom integration and is not supported by the Home Assistant
|
|
185
|
+
project.
|