haanim 0.2.0__py3-none-any.whl
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/__init__.py +166 -0
- haanim/const.py +141 -0
- haanim/engine/__init__.py +34 -0
- haanim/engine/action_dispatcher.py +590 -0
- haanim/engine/action_pool.py +319 -0
- haanim/engine/assets.py +143 -0
- haanim/engine/ast_evaluator.py +977 -0
- haanim/engine/automation_context.py +671 -0
- haanim/engine/automation_ids.py +91 -0
- haanim/engine/automation_module.py +130 -0
- haanim/engine/automation_status.py +185 -0
- haanim/engine/callables.py +112 -0
- haanim/engine/card.py +551 -0
- haanim/engine/card_checks.py +264 -0
- haanim/engine/card_elements.py +748 -0
- haanim/engine/card_layout.py +276 -0
- haanim/engine/constraints/__init__.py +5 -0
- haanim/engine/constraints/rules.py +235 -0
- haanim/engine/control.py +222 -0
- haanim/engine/cron_schedule.py +98 -0
- haanim/engine/decorators.py +584 -0
- haanim/engine/discovery.py +196 -0
- haanim/engine/durations.py +62 -0
- haanim/engine/errors.py +347 -0
- haanim/engine/eval_function.py +344 -0
- haanim/engine/expression_eval.py +545 -0
- haanim/engine/guards.py +115 -0
- haanim/engine/haanim_api.py +674 -0
- haanim/engine/haanim_module.py +153 -0
- haanim/engine/hot_reload.py +166 -0
- haanim/engine/import_controller.py +90 -0
- haanim/engine/lifecycle.py +601 -0
- haanim/engine/logging_wrapper.py +83 -0
- haanim/engine/metadata.py +126 -0
- haanim/engine/operators.py +48 -0
- haanim/engine/safe_builtins.py +48 -0
- haanim/engine/symbol_table.py +199 -0
- haanim/engine/time_expr.py +343 -0
- haanim/engine/time_schedule.py +190 -0
- haanim/engine/triggers/__init__.py +24 -0
- haanim/engine/triggers/base.py +122 -0
- haanim/engine/triggers/cron_trigger.py +57 -0
- haanim/engine/triggers/event_trigger.py +111 -0
- haanim/engine/triggers/interval_trigger.py +115 -0
- haanim/engine/triggers/manager.py +156 -0
- haanim/engine/triggers/scheduled.py +93 -0
- haanim/engine/triggers/state_trigger.py +173 -0
- haanim/engine/triggers/time_trigger.py +72 -0
- haanim/engine/validation.py +304 -0
- haanim/engine/variables.py +147 -0
- haanim/engine/waiting.py +82 -0
- haanim/entity.py +227 -0
- haanim/events.py +136 -0
- haanim/interfaces.py +447 -0
- haanim/py.typed +0 -0
- haanim/testing/__init__.py +24 -0
- haanim/testing/fakes.py +953 -0
- haanim/testing/harness.py +819 -0
- haanim/types.py +293 -0
- haanim-0.2.0.dist-info/METADATA +220 -0
- haanim-0.2.0.dist-info/RECORD +63 -0
- haanim-0.2.0.dist-info/WHEEL +4 -0
- haanim-0.2.0.dist-info/licenses/LICENSE +21 -0
haanim/__init__.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""HAAnim: Python automations for Home Assistant.
|
|
2
|
+
|
|
3
|
+
This package holds the automation engine and the test harness
|
|
4
|
+
(``haanim.testing``). The Home Assistant integration in
|
|
5
|
+
``custom_components/haanim`` is the glue that connects the engine to a running
|
|
6
|
+
instance. Importing this package does not import Home Assistant.
|
|
7
|
+
|
|
8
|
+
It also carries the type information for what an automation imports::
|
|
9
|
+
|
|
10
|
+
from haanim import haa, action, on_state, StateEvent
|
|
11
|
+
|
|
12
|
+
Inside Home Assistant, and inside the test harness, each automation gets these
|
|
13
|
+
names from the interpreter, bound to that automation. Here they are the same
|
|
14
|
+
classes and decorators, so editors and type checkers see the real signatures.
|
|
15
|
+
``haa``, ``hass`` and ``logging`` belong to a running automation and exist here
|
|
16
|
+
for type checking only.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from typing import TYPE_CHECKING, Any
|
|
20
|
+
|
|
21
|
+
from haanim.const import ActionMode
|
|
22
|
+
from haanim.engine.card import HAAnimCard
|
|
23
|
+
from haanim.engine.card_elements import (
|
|
24
|
+
BadgeElement,
|
|
25
|
+
ButtonElement,
|
|
26
|
+
CardElement,
|
|
27
|
+
CardLayout,
|
|
28
|
+
CardRow,
|
|
29
|
+
EntityElement,
|
|
30
|
+
GaugeElement,
|
|
31
|
+
GraphElement,
|
|
32
|
+
HtmlElement,
|
|
33
|
+
IconElement,
|
|
34
|
+
ImageElement,
|
|
35
|
+
TextElement,
|
|
36
|
+
ValueElement,
|
|
37
|
+
)
|
|
38
|
+
from haanim.engine.decorators import (
|
|
39
|
+
action,
|
|
40
|
+
on_cron,
|
|
41
|
+
on_event,
|
|
42
|
+
on_interval,
|
|
43
|
+
on_state,
|
|
44
|
+
on_time,
|
|
45
|
+
shutdown,
|
|
46
|
+
startup,
|
|
47
|
+
)
|
|
48
|
+
from haanim.engine.errors import (
|
|
49
|
+
PUBLIC_ERRORS,
|
|
50
|
+
ActionCancelledError,
|
|
51
|
+
ActionDroppedError,
|
|
52
|
+
ActionNotFoundError,
|
|
53
|
+
ActionTimeOutError,
|
|
54
|
+
AutomationAlreadyRunningError,
|
|
55
|
+
AutomationDisabledError,
|
|
56
|
+
AutomationNotLoadedError,
|
|
57
|
+
AutomationNotRunningError,
|
|
58
|
+
AutomationSecurityError,
|
|
59
|
+
AutomationSyntaxError,
|
|
60
|
+
HAAnimError,
|
|
61
|
+
NonExistingAutomationError,
|
|
62
|
+
NonExistingEntityError,
|
|
63
|
+
NonExistingServiceError,
|
|
64
|
+
PoolExhaustedError,
|
|
65
|
+
QueueFullError,
|
|
66
|
+
)
|
|
67
|
+
from haanim.engine.haanim_api import HAAnim, HAAnimAutomationProxy, HAAnimServiceCall, HAAnimServiceProxy
|
|
68
|
+
from haanim.engine.logging_wrapper import LoggerWrapper
|
|
69
|
+
from haanim.entity import HAAnimEntity
|
|
70
|
+
from haanim.events import (
|
|
71
|
+
ActionEvent,
|
|
72
|
+
AutomationEvent,
|
|
73
|
+
CronEvent,
|
|
74
|
+
EventTriggerEvent,
|
|
75
|
+
IntervalEvent,
|
|
76
|
+
ManualEvent,
|
|
77
|
+
StateEvent,
|
|
78
|
+
TimeEvent,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
__version__ = "0.2.0"
|
|
82
|
+
|
|
83
|
+
RUNTIME_ONLY = ("haa", "hass", "logging")
|
|
84
|
+
"""Names an automation imports that exist only while it runs: the interpreter supplies them."""
|
|
85
|
+
|
|
86
|
+
if TYPE_CHECKING:
|
|
87
|
+
haa: HAAnim
|
|
88
|
+
hass: Any
|
|
89
|
+
logging: LoggerWrapper
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def __getattr__(name: str) -> Any:
|
|
93
|
+
"""Explain why a name of a running automation is not here."""
|
|
94
|
+
if name in RUNTIME_ONLY:
|
|
95
|
+
raise AttributeError(
|
|
96
|
+
f"'{name}' exists only inside a running automation: Home Assistant and the test harness "
|
|
97
|
+
"(haanim.testing.AutomationHarness) supply it to each automation they load"
|
|
98
|
+
)
|
|
99
|
+
raise AttributeError(f"module 'haanim' has no attribute '{name}'")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
__all__ = [
|
|
103
|
+
"__version__",
|
|
104
|
+
# The API object and what it hands out
|
|
105
|
+
"haa",
|
|
106
|
+
"hass",
|
|
107
|
+
"logging",
|
|
108
|
+
"HAAnim",
|
|
109
|
+
"HAAnimAutomationProxy",
|
|
110
|
+
"HAAnimCard",
|
|
111
|
+
"CardElement",
|
|
112
|
+
"CardLayout",
|
|
113
|
+
"CardRow",
|
|
114
|
+
"TextElement",
|
|
115
|
+
"HtmlElement",
|
|
116
|
+
"ImageElement",
|
|
117
|
+
"ValueElement",
|
|
118
|
+
"EntityElement",
|
|
119
|
+
"IconElement",
|
|
120
|
+
"GaugeElement",
|
|
121
|
+
"BadgeElement",
|
|
122
|
+
"GraphElement",
|
|
123
|
+
"ButtonElement",
|
|
124
|
+
"HAAnimEntity",
|
|
125
|
+
"HAAnimServiceCall",
|
|
126
|
+
"HAAnimServiceProxy",
|
|
127
|
+
"LoggerWrapper",
|
|
128
|
+
"RUNTIME_ONLY",
|
|
129
|
+
# Decorators
|
|
130
|
+
"action",
|
|
131
|
+
"startup",
|
|
132
|
+
"shutdown",
|
|
133
|
+
"on_time",
|
|
134
|
+
"on_interval",
|
|
135
|
+
"on_cron",
|
|
136
|
+
"on_event",
|
|
137
|
+
"on_state",
|
|
138
|
+
"ActionMode",
|
|
139
|
+
# Events
|
|
140
|
+
"ActionEvent",
|
|
141
|
+
"TimeEvent",
|
|
142
|
+
"IntervalEvent",
|
|
143
|
+
"CronEvent",
|
|
144
|
+
"StateEvent",
|
|
145
|
+
"EventTriggerEvent",
|
|
146
|
+
"ManualEvent",
|
|
147
|
+
"AutomationEvent",
|
|
148
|
+
# Errors
|
|
149
|
+
"PUBLIC_ERRORS",
|
|
150
|
+
"HAAnimError",
|
|
151
|
+
"AutomationSyntaxError",
|
|
152
|
+
"AutomationSecurityError",
|
|
153
|
+
"NonExistingAutomationError",
|
|
154
|
+
"AutomationNotLoadedError",
|
|
155
|
+
"AutomationNotRunningError",
|
|
156
|
+
"AutomationAlreadyRunningError",
|
|
157
|
+
"AutomationDisabledError",
|
|
158
|
+
"ActionNotFoundError",
|
|
159
|
+
"ActionDroppedError",
|
|
160
|
+
"QueueFullError",
|
|
161
|
+
"PoolExhaustedError",
|
|
162
|
+
"ActionTimeOutError",
|
|
163
|
+
"ActionCancelledError",
|
|
164
|
+
"NonExistingEntityError",
|
|
165
|
+
"NonExistingServiceError",
|
|
166
|
+
]
|
haanim/const.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Constants for the HAAnim engine."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Final
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
# Enums
|
|
8
|
+
"ActionMode",
|
|
9
|
+
# Trigger kinds
|
|
10
|
+
"TRIGGER_TIME",
|
|
11
|
+
"TRIGGER_INTERVAL",
|
|
12
|
+
"TRIGGER_CRON",
|
|
13
|
+
"TRIGGER_EVENT",
|
|
14
|
+
"TRIGGER_STATE",
|
|
15
|
+
# Constraint kinds
|
|
16
|
+
"CONSTRAINT_TIME",
|
|
17
|
+
"CONSTRAINT_STATE",
|
|
18
|
+
# Host events
|
|
19
|
+
"EVENT_HOST_STARTED",
|
|
20
|
+
"EVENT_ACTION_ERROR",
|
|
21
|
+
# Defaults
|
|
22
|
+
"DEFAULT_ACTION_QUEUE_SIZE",
|
|
23
|
+
"DEFAULT_ACTION_TIMEOUT",
|
|
24
|
+
"DEFAULT_MAX_CONCURRENT_ACTIONS",
|
|
25
|
+
"DEFAULT_WORKER_SHUTDOWN_TIMEOUT",
|
|
26
|
+
"DEFAULT_STARTUP_TIMEOUT",
|
|
27
|
+
"DEFAULT_SHUTDOWN_TIMEOUT",
|
|
28
|
+
"DEFAULT_STOP_GRACE_PERIOD",
|
|
29
|
+
"DEFAULT_IMPORT_ALLOWLIST",
|
|
30
|
+
# Security
|
|
31
|
+
"RESTRICTED_BUILTINS",
|
|
32
|
+
"DISABLED_MODULE_MEMBERS",
|
|
33
|
+
"DISABLED_LOOP_MEMBERS",
|
|
34
|
+
"LOOP_GETTERS",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ActionMode(Enum):
|
|
39
|
+
"""Action execution modes for handling concurrent calls.
|
|
40
|
+
|
|
41
|
+
DROP: Drop new action request if already executing.
|
|
42
|
+
QUEUE: Queue action request to execute sequentially.
|
|
43
|
+
CANCEL: Cancel current action and start new execution.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
DROP = "drop"
|
|
47
|
+
QUEUE = "queue"
|
|
48
|
+
CANCEL = "cancel"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# The kinds of trigger; each is attached with the decorator ``on_<kind>``
|
|
52
|
+
TRIGGER_TIME: Final = "time"
|
|
53
|
+
TRIGGER_INTERVAL: Final = "interval"
|
|
54
|
+
TRIGGER_CRON: Final = "cron"
|
|
55
|
+
TRIGGER_EVENT: Final = "event"
|
|
56
|
+
TRIGGER_STATE: Final = "state"
|
|
57
|
+
|
|
58
|
+
# The kinds of constraint a trigger can carry
|
|
59
|
+
CONSTRAINT_TIME: Final = "time"
|
|
60
|
+
CONSTRAINT_STATE: Final = "state"
|
|
61
|
+
|
|
62
|
+
# Event the host fires once it has finished starting
|
|
63
|
+
EVENT_HOST_STARTED: Final = "homeassistant_started"
|
|
64
|
+
|
|
65
|
+
# Fired when an action called by a trigger fails (see "Trigger Functions Are Actions" in the design)
|
|
66
|
+
EVENT_ACTION_ERROR: Final = "haanim_action_error"
|
|
67
|
+
|
|
68
|
+
# Default action settings
|
|
69
|
+
DEFAULT_ACTION_QUEUE_SIZE: Final = 100
|
|
70
|
+
DEFAULT_ACTION_TIMEOUT: Final = 0 # 0 = no timeout
|
|
71
|
+
DEFAULT_MAX_CONCURRENT_ACTIONS: Final = 20
|
|
72
|
+
DEFAULT_WORKER_SHUTDOWN_TIMEOUT: Final = 0.2 # 200ms
|
|
73
|
+
|
|
74
|
+
# Modules an automation can import by default (see "Imports" in the design).
|
|
75
|
+
# "hass" and "haanim" are supplied by the engine; the rest are standard modules.
|
|
76
|
+
DEFAULT_IMPORT_ALLOWLIST: Final[list[str]] = [
|
|
77
|
+
"asyncio",
|
|
78
|
+
"datetime",
|
|
79
|
+
"json",
|
|
80
|
+
"logging",
|
|
81
|
+
"math",
|
|
82
|
+
"random",
|
|
83
|
+
"re",
|
|
84
|
+
"time",
|
|
85
|
+
"typing",
|
|
86
|
+
"collections",
|
|
87
|
+
"functools",
|
|
88
|
+
"itertools",
|
|
89
|
+
"operator",
|
|
90
|
+
"statistics",
|
|
91
|
+
"decimal",
|
|
92
|
+
"fractions",
|
|
93
|
+
"enum",
|
|
94
|
+
"dataclasses",
|
|
95
|
+
"html",
|
|
96
|
+
"hass",
|
|
97
|
+
"haanim",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# Builtins that are not available in automations
|
|
101
|
+
RESTRICTED_BUILTINS: Final[set[str]] = {
|
|
102
|
+
"eval",
|
|
103
|
+
"exec",
|
|
104
|
+
"compile",
|
|
105
|
+
"__import__",
|
|
106
|
+
"open",
|
|
107
|
+
"input",
|
|
108
|
+
"breakpoint",
|
|
109
|
+
"globals",
|
|
110
|
+
"locals",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Members of allowed modules that block the event loop or leave it, with what to use instead
|
|
114
|
+
DISABLED_MODULE_MEMBERS: Final[dict[str, dict[str, str]]] = {
|
|
115
|
+
"time": {
|
|
116
|
+
"sleep": "use 'await haa.sleep()'",
|
|
117
|
+
},
|
|
118
|
+
"asyncio": {
|
|
119
|
+
"run": "automations already run on the event loop",
|
|
120
|
+
"new_event_loop": "automations run on Home Assistant's event loop",
|
|
121
|
+
"set_event_loop": "automations run on Home Assistant's event loop",
|
|
122
|
+
"to_thread": "automations do not use threads",
|
|
123
|
+
"create_subprocess_exec": "automations cannot start processes",
|
|
124
|
+
"create_subprocess_shell": "automations cannot start processes",
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Methods of the event loop that are disabled for the same reason
|
|
129
|
+
DISABLED_LOOP_MEMBERS: Final[dict[str, str]] = {
|
|
130
|
+
"run_forever": "the event loop is already running",
|
|
131
|
+
"run_until_complete": "the event loop is already running; use 'await'",
|
|
132
|
+
"run_in_executor": "automations do not use threads",
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Functions of asyncio that return the event loop
|
|
136
|
+
LOOP_GETTERS: Final[set[str]] = {"get_event_loop", "get_running_loop"}
|
|
137
|
+
|
|
138
|
+
# Lifecycle time limits, in seconds (see "Automation Lifecycle" in the design)
|
|
139
|
+
DEFAULT_STARTUP_TIMEOUT: Final = 30.0
|
|
140
|
+
DEFAULT_SHUTDOWN_TIMEOUT: Final = 10.0
|
|
141
|
+
DEFAULT_STOP_GRACE_PERIOD: Final = 0.5
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""AST-based Python automation execution engine for HAAnim.
|
|
2
|
+
|
|
3
|
+
This package provides a restricted Python execution environment using
|
|
4
|
+
AST (Abstract Syntax Tree) evaluation instead of exec/eval.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from haanim.engine.ast_evaluator import AstEvaluator
|
|
10
|
+
from haanim.engine.errors import (
|
|
11
|
+
HAAnimError,
|
|
12
|
+
AutomationRuntimeError,
|
|
13
|
+
AutomationSecurityError,
|
|
14
|
+
AutomationSyntaxError,
|
|
15
|
+
)
|
|
16
|
+
from haanim.engine.eval_function import EvalFunction, ReturnValue
|
|
17
|
+
from haanim.engine.import_controller import ImportController
|
|
18
|
+
from haanim.engine.safe_builtins import SafeBuiltins
|
|
19
|
+
from haanim.engine.symbol_table import SymbolTable
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# Error types
|
|
23
|
+
"HAAnimError",
|
|
24
|
+
"AutomationRuntimeError",
|
|
25
|
+
"AutomationSecurityError",
|
|
26
|
+
"AutomationSyntaxError",
|
|
27
|
+
# Core components
|
|
28
|
+
"AstEvaluator",
|
|
29
|
+
"EvalFunction",
|
|
30
|
+
"ImportController",
|
|
31
|
+
"ReturnValue",
|
|
32
|
+
"SafeBuiltins",
|
|
33
|
+
"SymbolTable",
|
|
34
|
+
]
|