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.
Files changed (63) hide show
  1. haanim/__init__.py +166 -0
  2. haanim/const.py +141 -0
  3. haanim/engine/__init__.py +34 -0
  4. haanim/engine/action_dispatcher.py +590 -0
  5. haanim/engine/action_pool.py +319 -0
  6. haanim/engine/assets.py +143 -0
  7. haanim/engine/ast_evaluator.py +977 -0
  8. haanim/engine/automation_context.py +671 -0
  9. haanim/engine/automation_ids.py +91 -0
  10. haanim/engine/automation_module.py +130 -0
  11. haanim/engine/automation_status.py +185 -0
  12. haanim/engine/callables.py +112 -0
  13. haanim/engine/card.py +551 -0
  14. haanim/engine/card_checks.py +264 -0
  15. haanim/engine/card_elements.py +748 -0
  16. haanim/engine/card_layout.py +276 -0
  17. haanim/engine/constraints/__init__.py +5 -0
  18. haanim/engine/constraints/rules.py +235 -0
  19. haanim/engine/control.py +222 -0
  20. haanim/engine/cron_schedule.py +98 -0
  21. haanim/engine/decorators.py +584 -0
  22. haanim/engine/discovery.py +196 -0
  23. haanim/engine/durations.py +62 -0
  24. haanim/engine/errors.py +347 -0
  25. haanim/engine/eval_function.py +344 -0
  26. haanim/engine/expression_eval.py +545 -0
  27. haanim/engine/guards.py +115 -0
  28. haanim/engine/haanim_api.py +674 -0
  29. haanim/engine/haanim_module.py +153 -0
  30. haanim/engine/hot_reload.py +166 -0
  31. haanim/engine/import_controller.py +90 -0
  32. haanim/engine/lifecycle.py +601 -0
  33. haanim/engine/logging_wrapper.py +83 -0
  34. haanim/engine/metadata.py +126 -0
  35. haanim/engine/operators.py +48 -0
  36. haanim/engine/safe_builtins.py +48 -0
  37. haanim/engine/symbol_table.py +199 -0
  38. haanim/engine/time_expr.py +343 -0
  39. haanim/engine/time_schedule.py +190 -0
  40. haanim/engine/triggers/__init__.py +24 -0
  41. haanim/engine/triggers/base.py +122 -0
  42. haanim/engine/triggers/cron_trigger.py +57 -0
  43. haanim/engine/triggers/event_trigger.py +111 -0
  44. haanim/engine/triggers/interval_trigger.py +115 -0
  45. haanim/engine/triggers/manager.py +156 -0
  46. haanim/engine/triggers/scheduled.py +93 -0
  47. haanim/engine/triggers/state_trigger.py +173 -0
  48. haanim/engine/triggers/time_trigger.py +72 -0
  49. haanim/engine/validation.py +304 -0
  50. haanim/engine/variables.py +147 -0
  51. haanim/engine/waiting.py +82 -0
  52. haanim/entity.py +227 -0
  53. haanim/events.py +136 -0
  54. haanim/interfaces.py +447 -0
  55. haanim/py.typed +0 -0
  56. haanim/testing/__init__.py +24 -0
  57. haanim/testing/fakes.py +953 -0
  58. haanim/testing/harness.py +819 -0
  59. haanim/types.py +293 -0
  60. haanim-0.2.0.dist-info/METADATA +220 -0
  61. haanim-0.2.0.dist-info/RECORD +63 -0
  62. haanim-0.2.0.dist-info/WHEEL +4 -0
  63. 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
+ ]