python-library-automation 0.1.16__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 (42) hide show
  1. automation/__init__.py +25 -0
  2. automation/assistant.py +141 -0
  3. automation/builtins/__init__.py +27 -0
  4. automation/builtins/action/__init__.py +0 -0
  5. automation/builtins/action/call_entity_method.py +35 -0
  6. automation/builtins/action/delay.py +18 -0
  7. automation/builtins/action/log.py +20 -0
  8. automation/builtins/action/set_attribute.py +27 -0
  9. automation/builtins/entity/__init__.py +0 -0
  10. automation/builtins/entity/time.py +29 -0
  11. automation/builtins/entity/variable.py +81 -0
  12. automation/builtins/event/__init__.py +0 -0
  13. automation/builtins/event/_scheduled.py +34 -0
  14. automation/builtins/event/at.py +27 -0
  15. automation/builtins/event/callback.py +65 -0
  16. automation/builtins/event/every.py +32 -0
  17. automation/builtins/event/state_changed.py +58 -0
  18. automation/core/__init__.py +13 -0
  19. automation/core/action.py +22 -0
  20. automation/core/base.py +59 -0
  21. automation/core/composite_action.py +47 -0
  22. automation/core/entity.py +178 -0
  23. automation/core/event.py +87 -0
  24. automation/core/event_context.py +10 -0
  25. automation/core/trigger.py +151 -0
  26. automation/errors.py +42 -0
  27. automation/executor.py +46 -0
  28. automation/hub.py +52 -0
  29. automation/listeners/__init__.py +17 -0
  30. automation/listeners/base.py +23 -0
  31. automation/listeners/console.py +82 -0
  32. automation/listeners/instance_schema.py +26 -0
  33. automation/listeners/record.py +29 -0
  34. automation/listeners/trace.py +70 -0
  35. automation/listeners/type_schema.py +26 -0
  36. automation/loader.py +131 -0
  37. automation/renderer.py +311 -0
  38. automation/schema.py +142 -0
  39. automation/updater.py +84 -0
  40. python_library_automation-0.1.16.dist-info/METADATA +8 -0
  41. python_library_automation-0.1.16.dist-info/RECORD +42 -0
  42. python_library_automation-0.1.16.dist-info/WHEEL +4 -0
automation/renderer.py ADDED
@@ -0,0 +1,311 @@
1
+ import ast
2
+ import re
3
+ import operator
4
+ from typing import Any
5
+
6
+ VARIABLE_RE = re.compile(r"\{([^{}]+)\}")
7
+ _SINGLE_VAR_RE = re.compile(r"^\{([^{}]+)\}$")
8
+
9
+ class Renderer:
10
+ """
11
+ 渲染器 — 统一的变量解析、模板渲染、表达式求值引擎
12
+
13
+ 通过 derive() 创建子渲染器来注入局部作用域,
14
+ 不可变设计,derive 不影响父渲染器。
15
+ """
16
+
17
+ def __init__(self, hub):
18
+ self._hub = hub
19
+ self._scopes: dict[tuple[str, str], dict[str, Any]] = {}
20
+
21
+ def derive(self, type_: str, scope: str, data: dict[str, Any]) -> "Renderer":
22
+ """创建带有新作用域的子渲染器"""
23
+ child = Renderer.__new__(Renderer)
24
+ child._hub = self._hub
25
+ child._scopes = {**self._scopes, (type_, scope): data}
26
+ return child
27
+
28
+ # ── 变量解析 ──
29
+
30
+ def resolve(self, token: str) -> Any:
31
+ """解析 type.scope.attr_path 变量"""
32
+ parts = token.split(".", 2)
33
+
34
+ if len(parts) == 2:
35
+ type_, scope = parts
36
+ if type_ == "entity":
37
+ if scope not in self._hub.entities:
38
+ raise ValueError(f"Entity {scope!r} not found")
39
+ return self._hub.entities[scope]
40
+ key = (type_, scope)
41
+ if key in self._scopes:
42
+ return self._scopes[key]
43
+ raise ValueError(f"Cannot resolve variable: {{{token}}}")
44
+
45
+ if len(parts) < 2:
46
+ raise ValueError(
47
+ f"Invalid variable: {{{token}}}, "
48
+ f"expected {{type.scope.attribute}}"
49
+ )
50
+ type_, scope, attr_path = parts
51
+
52
+ # 局部作用域: event.local.xxx, action.local.xxx
53
+ key = (type_, scope)
54
+ if key in self._scopes:
55
+ return self._deep_get(self._scopes[key], attr_path)
56
+
57
+ # 全局: entity.instance_name.attr_path
58
+ if type_ == "entity":
59
+ return self._resolve_entity(scope, attr_path)
60
+
61
+ raise ValueError(f"Cannot resolve variable: {{{token}}}")
62
+
63
+ def render(self, template: str) -> str:
64
+ """将 {var} 占位符替换为实际值的字符串"""
65
+ def replace(match):
66
+ token = match.group(1).strip()
67
+ return str(self.resolve(token))
68
+ return VARIABLE_RE.sub(replace, template)
69
+
70
+ def render_value(self, value: Any) -> Any:
71
+ """递归解析值:纯变量引用返回原始对象,混合文本做字符串渲染"""
72
+ if isinstance(value, str):
73
+ m = _SINGLE_VAR_RE.match(value.strip())
74
+ if m:
75
+ return self.resolve(m.group(1).strip())
76
+ return self.render(value)
77
+ if isinstance(value, dict):
78
+ return {k: self.render_value(v) for k, v in value.items()}
79
+ if isinstance(value, list):
80
+ return [self.render_value(v) for v in value]
81
+ return value
82
+
83
+ def eval_bool(self, expr: str) -> bool:
84
+ """解析并求值布尔表达式,{var} 占位符会先解析为值"""
85
+ placeholders: dict[str, str] = {}
86
+ def replace(match):
87
+ token = match.group(1).strip()
88
+ name = f"_v{len(placeholders)}"
89
+ placeholders[name] = token
90
+ return name
91
+ parsed = VARIABLE_RE.sub(replace, expr)
92
+ tree = ast.parse(parsed, mode="eval")
93
+ _validate_ast(tree)
94
+ values = {
95
+ name: self.resolve(token)
96
+ for name, token in placeholders.items()
97
+ }
98
+ result = _safe_eval(tree.body, values)
99
+ if not isinstance(result, bool):
100
+ raise ValueError(
101
+ f"Expression must return bool, got {type(result).__name__}"
102
+ )
103
+ return result
104
+
105
+ def validate_token(self, token: str) -> None:
106
+ parts = token.split(".", 2)
107
+ if len(parts) < 2:
108
+ raise ValueError(f"Invalid variable format: {{{token}}}")
109
+
110
+ if len(parts) == 2:
111
+ type_, scope = parts
112
+ if type_ == "entity":
113
+ if scope not in self._hub.entities:
114
+ raise ValueError(f"Entity {scope!r} not found")
115
+ return
116
+ if type_ in ("event", "action") and scope == "local":
117
+ return
118
+ raise ValueError(f"Unknown variable namespace: {type_}.{scope}")
119
+
120
+ type_, scope, attr_path = parts
121
+ if type_ == "entity":
122
+ if scope not in self._hub.entities:
123
+ raise ValueError(f"Entity {scope!r} not found")
124
+ entity = self._hub.entities[scope]
125
+ attr_root = attr_path.split(".")[0]
126
+ known = {a.name for a in entity.get_attributes()}
127
+ if attr_root not in known:
128
+ raise ValueError(
129
+ f"Entity {scope!r} has no attribute {attr_root!r}, "
130
+ f"available: {', '.join(sorted(known))}"
131
+ )
132
+ return
133
+ if type_ in ("event", "action") and scope == "local":
134
+ return
135
+ raise ValueError(f"Unknown variable namespace: {type_}.{scope}")
136
+
137
+ def validate_template(self, template: str) -> None:
138
+ for match in VARIABLE_RE.finditer(template):
139
+ self.validate_token(match.group(1).strip())
140
+
141
+ def validate_expr(self, expr: str) -> None:
142
+ self.validate_template(expr)
143
+
144
+ cleaned = VARIABLE_RE.sub("True", expr)
145
+ try:
146
+ tree = ast.parse(cleaned, mode="eval")
147
+ except SyntaxError as e:
148
+ raise ValueError(f"Syntax error in expression: {expr!r}") from e
149
+ _validate_ast(tree)
150
+
151
+ def _resolve_entity(self, name: str, attr_path: str) -> Any:
152
+ if name not in self._hub.entities:
153
+ raise ValueError(f"Entity {name!r} not found")
154
+ entity = self._hub.entities[name]
155
+ parts = attr_path.split(".")
156
+
157
+ values = entity.get_attribute_values()
158
+ first = parts[0]
159
+ if first not in values:
160
+ raise ValueError(
161
+ f"{entity.__class__.__name__} {name!r} has no attribute path {attr_path!r}"
162
+ )
163
+ obj = values[first]
164
+
165
+ for part in parts[1:]:
166
+ if not hasattr(obj, part):
167
+ raise ValueError(
168
+ f"{entity.__class__.__name__} {name!r} has no attribute path {attr_path!r}"
169
+ )
170
+ obj = getattr(obj, part)
171
+ return obj
172
+
173
+ @staticmethod
174
+ def _deep_get(data: dict, path: str) -> Any:
175
+ """从 dict 中按 . 分隔路径取值"""
176
+ current = data
177
+ for part in path.split("."):
178
+ if isinstance(current, dict):
179
+ if part not in current:
180
+ raise ValueError(f"Key {part!r} not found in scope data")
181
+ current = current[part]
182
+ else:
183
+ if not hasattr(current, part):
184
+ raise ValueError(f"Attribute {part!r} not found")
185
+ current = getattr(current, part)
186
+ return current
187
+
188
+ _SAFE_NODES = (
189
+ ast.Expression, ast.BoolOp, ast.Compare, ast.UnaryOp,
190
+ ast.Constant, ast.Name, ast.Load, ast.Store, ast.And, ast.Or, ast.Not,
191
+ ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE,
192
+ ast.Is, ast.IsNot, ast.In, ast.NotIn,
193
+ ast.List, ast.Tuple,
194
+ ast.Call,
195
+ ast.Attribute,
196
+ ast.ListComp, ast.GeneratorExp, ast.comprehension,
197
+ )
198
+ _SAFE_FUNCTIONS = frozenset({"any", "all"})
199
+
200
+ _CMP_OPS = {
201
+ ast.Eq: operator.eq, ast.NotEq: operator.ne,
202
+ ast.Lt: operator.lt, ast.LtE: operator.le,
203
+ ast.Gt: operator.gt, ast.GtE: operator.ge,
204
+ ast.Is: operator.is_, ast.IsNot: operator.is_not,
205
+ ast.In: lambda a, b: a in b,
206
+ ast.NotIn: lambda a, b: a not in b,
207
+ }
208
+
209
+ def _validate_ast(tree: ast.AST) -> None:
210
+ for node in ast.walk(tree):
211
+ if not isinstance(node, _SAFE_NODES):
212
+ raise ValueError(f"Unsupported expression node: {type(node).__name__}")
213
+ if isinstance(node, ast.Call):
214
+ if (
215
+ not isinstance(node.func, ast.Name)
216
+ or node.func.id not in _SAFE_FUNCTIONS
217
+ ):
218
+ raise ValueError(
219
+ f"Unsupported function: only {', '.join(_SAFE_FUNCTIONS)} allowed"
220
+ )
221
+ if node.keywords:
222
+ raise ValueError("Keyword arguments not supported in expressions")
223
+ if isinstance(node, ast.Attribute):
224
+ if node.attr.startswith("_"):
225
+ raise ValueError(
226
+ f"Access to private attribute {node.attr!r} not allowed"
227
+ )
228
+ if isinstance(node, ast.comprehension):
229
+ if not isinstance(node.target, ast.Name):
230
+ raise ValueError(
231
+ "Only simple variable names allowed in comprehension target"
232
+ )
233
+ if node.is_async:
234
+ raise ValueError("Async comprehensions not supported")
235
+
236
+ def _safe_eval(node: ast.AST, values: dict[str, Any]) -> Any:
237
+ if isinstance(node, ast.BoolOp):
238
+ if isinstance(node.op, ast.And):
239
+ return all(_safe_eval(v, values) for v in node.values)
240
+ return any(_safe_eval(v, values) for v in node.values)
241
+
242
+ if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
243
+ return not _safe_eval(node.operand, values)
244
+
245
+ if isinstance(node, ast.Compare):
246
+ left = _safe_eval(node.left, values)
247
+ for op, comparator in zip(node.ops, node.comparators):
248
+ right = _safe_eval(comparator, values)
249
+ op_func = _CMP_OPS.get(type(op))
250
+ if op_func is None:
251
+ raise ValueError(f"Unsupported compare op: {type(op).__name__}")
252
+ if not op_func(left, right):
253
+ return False
254
+ left = right
255
+ return True
256
+
257
+ if isinstance(node, ast.Constant):
258
+ return node.value
259
+
260
+ if isinstance(node, ast.Name):
261
+ if node.id not in values:
262
+ raise ValueError(f"Unknown variable: {node.id}")
263
+ return values[node.id]
264
+
265
+ if isinstance(node, (ast.List, ast.Tuple)):
266
+ return [_safe_eval(el, values) for el in node.elts]
267
+
268
+ if isinstance(node, ast.Attribute):
269
+ obj = _safe_eval(node.value, values)
270
+ if not hasattr(obj, node.attr):
271
+ raise ValueError(
272
+ f"{type(obj).__name__!r} has no attribute {node.attr!r}"
273
+ )
274
+ return getattr(obj, node.attr)
275
+
276
+ if isinstance(node, (ast.ListComp, ast.GeneratorExp)):
277
+ return _eval_comprehension(node.generators, 0, node.elt, values)
278
+
279
+ if isinstance(node, ast.Call):
280
+ func_name = node.func.id
281
+ args = [_safe_eval(a, values) for a in node.args]
282
+ fn = any if func_name == "any" else all
283
+ if len(args) == 1:
284
+ return fn(args[0])
285
+ if len(args) == 2:
286
+ items, attr = args
287
+ return fn(getattr(item, attr, False) for item in items)
288
+ raise ValueError(f"{func_name}() takes 1 or 2 arguments, got {len(args)}")
289
+
290
+ raise ValueError(f"Cannot evaluate node: {type(node).__name__}")
291
+
292
+ def _eval_comprehension(
293
+ generators: list[ast.comprehension],
294
+ gen_idx: int,
295
+ elt: ast.AST,
296
+ values: dict[str, Any],
297
+ ) -> list:
298
+ """递归求值推导式(支持多层 for 和 if 过滤)"""
299
+ if gen_idx >= len(generators):
300
+ return [_safe_eval(elt, values)]
301
+ gen = generators[gen_idx]
302
+ target_name = gen.target.id
303
+ iterable = _safe_eval(gen.iter, values)
304
+ results = []
305
+ for item in iterable:
306
+ inner = {**values, target_name: item}
307
+ if all(_safe_eval(cond, inner) for cond in gen.ifs):
308
+ results.extend(
309
+ _eval_comprehension(generators, gen_idx + 1, elt, inner)
310
+ )
311
+ return results
automation/schema.py ADDED
@@ -0,0 +1,142 @@
1
+ from __future__ import annotations
2
+ from typing import Any, TYPE_CHECKING
3
+
4
+ from automation.core.entity import (
5
+ entity_registry, introspect_attributes, introspect_methods,
6
+ )
7
+ from automation.core.event import event_registry
8
+ from automation.core.action import action_registry
9
+ from automation.core.trigger import trigger_registry
10
+
11
+ if TYPE_CHECKING:
12
+ from automation.hub import Hub
13
+
14
+ SECTION_REGISTRIES = {
15
+ "entities": entity_registry,
16
+ "events": event_registry,
17
+ "actions": action_registry,
18
+ "triggers": trigger_registry,
19
+ }
20
+
21
+
22
+ def _field_schema(field_info) -> dict[str, Any]:
23
+ result: dict[str, Any] = {}
24
+ if field_info.annotation is not None:
25
+ result["type"] = getattr(field_info.annotation, "__name__", str(field_info.annotation))
26
+ if field_info.description:
27
+ result["description"] = field_info.description
28
+ if field_info.default is not None:
29
+ result["default"] = field_info.default
30
+ result["required"] = field_info.is_required()
31
+ return result
32
+
33
+
34
+ def export_type_schema() -> dict[str, Any]:
35
+ result: dict[str, Any] = {}
36
+
37
+ for section, registry in SECTION_REGISTRIES.items():
38
+ section_types = {}
39
+ for reg_name in registry.get_registered_names():
40
+ short = reg_name.split(".")[-1]
41
+ cls = registry.get(short)
42
+
43
+ config_fields = {}
44
+ for name, field_info in cls.model_fields.items():
45
+ if name == "instance_name":
46
+ continue
47
+ config_fields[name] = _field_schema(field_info)
48
+
49
+ type_info: dict[str, Any] = {"config_fields": config_fields}
50
+
51
+ if section == "entities":
52
+ type_info["attributes"] = [
53
+ {
54
+ "name": a.name, "type": a.type,
55
+ "description": a.description,
56
+ "readonly": a.readonly, "default": a.default,
57
+ }
58
+ for a in introspect_attributes(cls)
59
+ ]
60
+ type_info["methods"] = [
61
+ {
62
+ "name": m.name, "description": m.description,
63
+ "params": m.params, "return_type": m.return_type,
64
+ }
65
+ for m in introspect_methods(cls)
66
+ ]
67
+
68
+ section_types[short] = type_info
69
+ result[section] = section_types
70
+
71
+ result["composite_actions"] = {
72
+ "description": "可复用的命名组合动作",
73
+ "fields": {
74
+ "params": {"type": "object", "description": "参数声明 {名称: 类型}"},
75
+ "conditions": {"type": "array", "description": "条件表达式列表"},
76
+ "actions": {"type": "array", "description": "子动作规格列表"},
77
+ },
78
+ }
79
+ return result
80
+
81
+
82
+ def export_instance_schema(hub: Hub) -> dict[str, Any]:
83
+ from automation.core.entity import Entity
84
+
85
+ result: dict[str, Any] = {}
86
+
87
+ entities = {}
88
+ for name, entity in hub.entities.items():
89
+ info: dict[str, Any] = {"type": entity._type}
90
+ if isinstance(entity, Entity):
91
+ info["attributes"] = {
92
+ attr.name: {
93
+ "type": attr.type,
94
+ "value": getattr(entity, attr.name, None),
95
+ "readonly": attr.readonly,
96
+ "description": attr.description,
97
+ }
98
+ for attr in entity.get_attributes()
99
+ }
100
+ info["methods"] = [
101
+ {
102
+ "name": m.name, "description": m.description,
103
+ "params": m.params, "return_type": m.return_type,
104
+ }
105
+ for m in entity.get_methods()
106
+ ]
107
+ entities[name] = info
108
+ result["entities"] = entities
109
+
110
+ events = {}
111
+ for name, event in hub.events.items():
112
+ event_info: dict[str, Any] = {"type": event._type}
113
+ config = {}
114
+ for fname, finfo in event.model_fields.items():
115
+ if fname == "instance_name":
116
+ continue
117
+ config[fname] = getattr(event, fname)
118
+ event_info["config"] = config
119
+ events[name] = event_info
120
+ result["events"] = events
121
+
122
+ triggers = {}
123
+ for name, trigger in hub.triggers.items():
124
+ triggers[name] = {
125
+ "type": trigger._type,
126
+ "event": trigger.event,
127
+ "mode": trigger.mode,
128
+ "conditions": trigger.conditions,
129
+ "actions_count": len(trigger.actions),
130
+ }
131
+ result["triggers"] = triggers
132
+
133
+ actions = {}
134
+ for name, composite in hub.actions.items():
135
+ actions[name] = {
136
+ "params": composite.params,
137
+ "conditions": composite.conditions,
138
+ "actions_count": len(composite.action_specs),
139
+ }
140
+ result["actions"] = actions
141
+
142
+ return result
automation/updater.py ADDED
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any
5
+ from automation.hub import Hub, State
6
+ from automation.loader import build_section, build_actions
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ async def apply_diff(
12
+ hub: Hub, old: dict[str, Any], new: dict[str, Any]
13
+ ) -> None:
14
+ changed_refs: set[tuple[str, str]] = set()
15
+
16
+ all_sections = (*hub.AUTOMATION_SECTIONS, "actions")
17
+ for section_name in all_sections:
18
+ old_section = old.get(section_name, {})
19
+ new_section = new.get(section_name, {})
20
+ for name in set(old_section) | set(new_section):
21
+ if old_section.get(name) != new_section.get(name):
22
+ changed_refs.add((section_name, name))
23
+
24
+ # --- automation sections (entities, events, triggers) ---
25
+ for section_name in hub.AUTOMATION_SECTIONS:
26
+ old_section = old.get(section_name, {})
27
+ new_section = new.get(section_name, {})
28
+ current = hub.section(section_name)
29
+
30
+ for name in list(current):
31
+ if name not in new_section:
32
+ obj = current.pop(name)
33
+ await obj.on_stop()
34
+
35
+ for name, spec in new_section.items():
36
+ old_spec = old_section.get(name)
37
+ if name in current and old_spec == spec:
38
+ continue
39
+
40
+ if name in current:
41
+ obj = current[name]
42
+ if hub.state == State.RUNNING:
43
+ await obj.on_stop()
44
+ raw_spec = dict(spec)
45
+ raw_spec.pop("type", None)
46
+ await obj.update(hub, raw_spec)
47
+ await obj.on_validate(hub)
48
+ await obj.on_update(hub)
49
+ await obj.on_activate(hub)
50
+ if hub.state == State.RUNNING:
51
+ await obj.on_start()
52
+ else:
53
+ built = build_section(section_name, {name: spec})
54
+ obj = built[name]
55
+ obj._hub = hub
56
+ await obj.on_validate(hub)
57
+ await obj.on_activate(hub)
58
+ current[name] = obj
59
+ if hub.state == State.RUNNING:
60
+ await obj.on_start()
61
+
62
+ # --- actions (CompositeAction) ---
63
+ old_actions_cfg = old.get("actions", {})
64
+ new_actions_cfg = new.get("actions", {})
65
+
66
+ for name in list(hub.actions):
67
+ if name not in new_actions_cfg:
68
+ del hub.actions[name]
69
+
70
+ if new_actions_cfg != old_actions_cfg:
71
+ new_composites = build_actions(new_actions_cfg)
72
+ hub.actions = new_composites
73
+ for composite in hub.actions.values():
74
+ composite.validate(hub)
75
+
76
+ # --- refresh affected triggers ---
77
+ for trigger in hub.triggers.values():
78
+ refs = {("events", trigger.event)}
79
+ for spec in trigger.actions:
80
+ type_name = spec.get("type", "")
81
+ refs.add(("actions", type_name))
82
+ if refs & changed_refs:
83
+ await trigger.on_validate(hub)
84
+ await trigger.on_activate(hub)
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-library-automation
3
+ Version: 0.1.16
4
+ Requires-Python: >=3.10
5
+ Requires-Dist: pydantic>=2.0.0
6
+ Requires-Dist: python-library-registry
7
+ Requires-Dist: python-library-scheduler
8
+ Requires-Dist: python-library-watch-config
@@ -0,0 +1,42 @@
1
+ automation/__init__.py,sha256=gowCERBmUVqZ6aavHlXikw9emojRoa3XRf9SbkcyH2Q,551
2
+ automation/assistant.py,sha256=7mlqPpckFlGm5CGWl_m9pydhK9CID-1Nt-gCuHo2RS0,4870
3
+ automation/errors.py,sha256=ATAR5gMWFme3uqpN-ivMH6of1g7jTcfrdtGxCDxj7cs,1096
4
+ automation/executor.py,sha256=ELThuvCRpN01dfM5xkxcgjfRDNuhlqF3tXMPp3pTqA8,1340
5
+ automation/hub.py,sha256=L1IXxqO2TBmzMi8soSuLVKCoXCQrFgwnBRKOxWw35nI,1799
6
+ automation/loader.py,sha256=RW76kVCfGiUYi8l-zwAVZbrLlGwtywNCSwvCpn8KUX4,4557
7
+ automation/renderer.py,sha256=bM-9tNGzU5jGNZNzA_JxaoggSZBGEZ9cfTfYuLbeYcU,11892
8
+ automation/schema.py,sha256=TJRm5Dij70dYLZ1hjuxt2Hn11pUVwBy97zz181XX03I,4944
9
+ automation/updater.py,sha256=8KRkC4LCZ8SFVhB6HPPW9HxvLfQdk1ea1fmZV5fRhB4,3062
10
+ automation/builtins/__init__.py,sha256=Azwf9VF0SI97Pt9wf-SD1zmedVyAEyQbo1WGliq5qW0,886
11
+ automation/builtins/action/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ automation/builtins/action/call_entity_method.py,sha256=cjz_wal37AUapKPTmtMWVmP0EiF1cjaSSW1XtSXFBf8,1247
13
+ automation/builtins/action/delay.py,sha256=x5EMoG1Hr0y2kuJLawYMHaoYZG5gvuwITUfM-C1zHDk,499
14
+ automation/builtins/action/log.py,sha256=f9ZnLVZ8WcQI-Mj_qpZGY41Vcq7tRX9GUHR1pQxVejg,513
15
+ automation/builtins/action/set_attribute.py,sha256=kpUmCvqDYIno73p3w1YzRMuOEGQ3sKfkO2QWq0bCODw,965
16
+ automation/builtins/entity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ automation/builtins/entity/time.py,sha256=Dh1I8-twm7M5pZFgQXb3CcW6T1m6fCKHPYVW1xS0rnI,723
18
+ automation/builtins/entity/variable.py,sha256=aGfN_AEPlFxcAjkzFFQUtwMreu0GTYmBC-hvKyDsSIQ,2928
19
+ automation/builtins/event/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ automation/builtins/event/_scheduled.py,sha256=EnoIvra4PUx-9aqn0Q9gLoNE_SSMiJEX-_V2ztc3YkI,804
21
+ automation/builtins/event/at.py,sha256=i0Lr0l89J1wvCHP9KG2yS37RSYvBkGC3_WCjSYhBn3A,726
22
+ automation/builtins/event/callback.py,sha256=NsaEnlTTI1PdJ1Za-KXgTO5wqQi49x5e1HAiJ8tD1_Q,2208
23
+ automation/builtins/event/every.py,sha256=Xqwu3Z_nM5EMYu1Vi29zCi2yB7sv1ul0v6oAIRsS0OA,1033
24
+ automation/builtins/event/state_changed.py,sha256=nPVQTuxqtF3zxzY7zu3KO3ddov-QSR9Mp7pJCrTubOM,2000
25
+ automation/core/__init__.py,sha256=YO2W5YwrRKmCG1ODsTX2G9_jLRZgxozIrP95XuUUy24,245
26
+ automation/core/action.py,sha256=MGSnVoPxW2K5WKBaen_vUdHDjcaCLXMmmnJioOJSzJ0,532
27
+ automation/core/base.py,sha256=uSSzsy63AHbZUUbc-2M_Y6jkpbCIVxUr9bJiUc7oeaQ,1872
28
+ automation/core/composite_action.py,sha256=6uO9LKVllfP-FB7nKsZ_Rq3sWVt0MdZ1dk45gjSlCDw,1452
29
+ automation/core/entity.py,sha256=NGXAWHDcJeKpiCOFvHmHm3a__Mi3LX1jgUTlPwAdj0c,5797
30
+ automation/core/event.py,sha256=SRdS5KTNJctiJPNeVnJd6cPCDBjWizbxtrCp84dimQo,2995
31
+ automation/core/event_context.py,sha256=YU1rTG83bJj78C9e-C-iWpnVpfB2n0B62L3we4bAa0s,345
32
+ automation/core/trigger.py,sha256=1wP6cvWvgk05-OpkvirUbTktx4seDtFiHGOHklu4ycw,5416
33
+ automation/listeners/__init__.py,sha256=qot61a5pfvfWpYwL-uKYrikfBPY9WP5MsUwIY4aLuzg,476
34
+ automation/listeners/base.py,sha256=Iqh6kIbgagMWy4qPNx-fccriVl0FwA2NX2g3dqcHZe8,1327
35
+ automation/listeners/console.py,sha256=NNyJWuBYQzHltcglZ1Q4BkY5RWM1dRLyfnGuhIejdoI,3172
36
+ automation/listeners/instance_schema.py,sha256=3_ALaqLUjYv2ubYoLWT_KG4Ru04tfhAZRd7M9iKjyvU,808
37
+ automation/listeners/record.py,sha256=zWgNBBdwg-JRiQjX7PfBczQ4-R4GpySBZUP0HdYAbLQ,887
38
+ automation/listeners/trace.py,sha256=sKGFj1spItSvJdAiVk3mQ4XVRm-lME7X89FAbEivQyk,3199
39
+ automation/listeners/type_schema.py,sha256=hEyGoVADpO8ePCG1LJiuB2yMj08xTa9pdUH0dI_Ilr8,793
40
+ python_library_automation-0.1.16.dist-info/METADATA,sha256=98o5Fwx1sJRsuOt1EBcl8yyljVn_PyEVqcx2ZASQTCU,247
41
+ python_library_automation-0.1.16.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
42
+ python_library_automation-0.1.16.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any