redzed 25.12.30__py3-none-any.whl → 26.2.4__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.
- redzed/__init__.py +4 -4
- redzed/base_block.py +2 -2
- redzed/block.py +18 -19
- redzed/blocklib/counter.py +4 -0
- redzed/blocklib/fsm.py +81 -52
- redzed/blocklib/inputs.py +38 -66
- redzed/blocklib/outputs.py +109 -77
- redzed/blocklib/repeat.py +29 -13
- redzed/blocklib/timedate.py +3 -3
- redzed/blocklib/timeinterval.py +4 -0
- redzed/blocklib/timer.py +3 -4
- redzed/blocklib/validator.py +46 -0
- redzed/circuit.py +65 -40
- redzed/cron_service.py +137 -123
- redzed/debug.py +70 -53
- redzed/formula_trigger.py +4 -8
- redzed/initializers.py +6 -7
- redzed/py.typed +0 -0
- redzed/signal_shutdown.py +3 -3
- redzed/undef.py +2 -2
- redzed/utils/async_utils.py +1 -100
- redzed/utils/data_utils.py +20 -44
- {redzed-25.12.30.dist-info → redzed-26.2.4.dist-info}/METADATA +2 -2
- redzed-26.2.4.dist-info/RECORD +30 -0
- {redzed-25.12.30.dist-info → redzed-26.2.4.dist-info}/WHEEL +1 -1
- {redzed-25.12.30.dist-info → redzed-26.2.4.dist-info}/licenses/LICENSE.txt +1 -1
- redzed-25.12.30.dist-info/RECORD +0 -28
- {redzed-25.12.30.dist-info → redzed-26.2.4.dist-info}/top_level.txt +0 -0
redzed/blocklib/outputs.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Output blocks.
|
|
3
3
|
- - - - - -
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Part of the redzed package.
|
|
5
|
+
Docs: https://redzed.readthedocs.io/en/latest/
|
|
6
|
+
Home: https://github.com/xitop/redzed/
|
|
6
7
|
"""
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
|
|
@@ -13,7 +14,8 @@ from collections.abc import Callable, Awaitable
|
|
|
13
14
|
import typing as t
|
|
14
15
|
|
|
15
16
|
import redzed
|
|
16
|
-
from redzed.utils import BufferShutDown, cancel_shield, func_call_string,
|
|
17
|
+
from redzed.utils import BufferShutDown, cancel_shield, func_call_string, time_period
|
|
18
|
+
from .validator import _Validate
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
class OutputFunc(redzed.Block):
|
|
@@ -28,22 +30,27 @@ class OutputFunc(redzed.Block):
|
|
|
28
30
|
triggered_by: str|redzed.Block|redzed.Formula|redzed.UndefType = redzed.UNDEF,
|
|
29
31
|
**kwargs) -> None:
|
|
30
32
|
super().__init__(*args, **kwargs)
|
|
31
|
-
self._stop_value = stop_value
|
|
32
33
|
self._func = func
|
|
33
|
-
self.
|
|
34
|
+
self._shutdown = False
|
|
35
|
+
if stop_value is not redzed.UNDEF:
|
|
36
|
+
@redzed.stop_function
|
|
37
|
+
def stop_function_():
|
|
38
|
+
self._event_put({'evalue': stop_value})
|
|
39
|
+
stop_function_.__qualname__ += self.name
|
|
40
|
+
stop_function_.__name__ += self.name
|
|
34
41
|
if triggered_by is not redzed.UNDEF:
|
|
35
42
|
@redzed.triggered
|
|
36
43
|
def trigger(value=triggered_by) -> None:
|
|
37
44
|
self.event('put', value)
|
|
38
45
|
|
|
39
46
|
def _event_put(self, edata: redzed.EventData) -> t.Any:
|
|
40
|
-
|
|
47
|
+
evalue = edata['evalue']
|
|
41
48
|
if redzed.get_debug_level() >= 1:
|
|
42
|
-
self.log_debug("Running %s", func_call_string(self._func, (
|
|
49
|
+
self.log_debug("Running %s", func_call_string(self._func, (evalue,)))
|
|
43
50
|
try:
|
|
44
|
-
result = self._func(
|
|
51
|
+
result = self._func(evalue)
|
|
45
52
|
except Exception as err:
|
|
46
|
-
func_args = func_call_string(self._func, (
|
|
53
|
+
func_args = func_call_string(self._func, (evalue,))
|
|
47
54
|
self.log_error("Output function failed; call: %s; error: %r", func_args, err)
|
|
48
55
|
err.add_note(f"Error occurred in function call {func_args}")
|
|
49
56
|
self.circuit.abort(err)
|
|
@@ -51,30 +58,64 @@ class OutputFunc(redzed.Block):
|
|
|
51
58
|
self.log_debug2("output function returned: %r", result)
|
|
52
59
|
return result
|
|
53
60
|
|
|
61
|
+
def rz_is_shut_down(self) -> bool:
|
|
62
|
+
return self._shutdown
|
|
63
|
+
|
|
54
64
|
def rz_stop(self) -> None:
|
|
55
|
-
|
|
56
|
-
self._event_put({'evalue': self._stop_value})
|
|
65
|
+
self._shutdown = True
|
|
57
66
|
|
|
58
67
|
|
|
59
|
-
class _Buffer(redzed.Block):
|
|
68
|
+
class _Buffer(_Validate, redzed.Block):
|
|
60
69
|
def __init__(
|
|
61
70
|
self, *args,
|
|
62
71
|
stop_value: t.Any = redzed.UNDEF,
|
|
63
72
|
triggered_by: str|redzed.Block|redzed.Formula|redzed.UndefType = redzed.UNDEF,
|
|
64
73
|
**kwargs) -> None:
|
|
65
74
|
super().__init__(*args, **kwargs)
|
|
66
|
-
self.
|
|
75
|
+
self._shutdown = False
|
|
76
|
+
if stop_value is not redzed.UNDEF:
|
|
77
|
+
stop_value = self._validate(stop_value)
|
|
78
|
+
@redzed.stop_function
|
|
79
|
+
def stop_function_():
|
|
80
|
+
self.rz_put_value(stop_value)
|
|
81
|
+
stop_function_.__qualname__ += self.name
|
|
82
|
+
stop_function_.__name__ += self.name
|
|
67
83
|
if triggered_by is not redzed.UNDEF:
|
|
68
84
|
@redzed.triggered
|
|
69
85
|
def trigger(value=triggered_by) -> None:
|
|
70
86
|
self.event('put', value)
|
|
71
87
|
|
|
88
|
+
def _event_put(self, edata: redzed.EventData) -> None:
|
|
89
|
+
"""Put an item into the queue."""
|
|
90
|
+
# not aggregating following two lines in order to have a clear traceback
|
|
91
|
+
evalue = edata['evalue']
|
|
92
|
+
evalue = self._validate(evalue)
|
|
93
|
+
self.rz_put_value(evalue)
|
|
94
|
+
|
|
95
|
+
def _event__get_size(self, _edata: redzed.EventData) -> int:
|
|
96
|
+
return self.rz_get_size()
|
|
97
|
+
|
|
98
|
+
def rz_is_shut_down(self) -> bool:
|
|
99
|
+
return self._shutdown
|
|
100
|
+
|
|
101
|
+
def rz_stop(self) -> None:
|
|
102
|
+
self._shutdown = True
|
|
103
|
+
|
|
104
|
+
def rz_put_value(self, value: t.Any) -> None:
|
|
105
|
+
raise NotImplementedError
|
|
106
|
+
|
|
72
107
|
def rz_get_size(self) -> int:
|
|
73
108
|
raise NotImplementedError
|
|
74
109
|
|
|
75
110
|
async def rz_buffer_get(self) -> t.Any:
|
|
76
111
|
raise NotImplementedError
|
|
77
112
|
|
|
113
|
+
def rz_close(self) -> None:
|
|
114
|
+
if (size := self.rz_get_size()) == 1:
|
|
115
|
+
self.log_error("One value was not retrieved from buffer")
|
|
116
|
+
elif size > 1:
|
|
117
|
+
self.log_error("%d values were not retrieved from buffer", size)
|
|
118
|
+
|
|
78
119
|
|
|
79
120
|
class QueueBuffer(_Buffer):
|
|
80
121
|
"""
|
|
@@ -92,26 +133,17 @@ class QueueBuffer(_Buffer):
|
|
|
92
133
|
# Queue.shutdown is available in Python 3.13, but we want to support 3.11+
|
|
93
134
|
self._waiters = 0
|
|
94
135
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
evalue = edata['evalue']
|
|
98
|
-
if evalue is redzed.UNDEF:
|
|
99
|
-
raise ValueError(f"{self}: Cannot put UNDEF into the buffer")
|
|
100
|
-
self._queue.put_nowait(evalue)
|
|
136
|
+
def rz_put_value(self, value: t.Any) -> None:
|
|
137
|
+
self._queue.put_nowait(value)
|
|
101
138
|
|
|
102
139
|
def rz_get_size(self) -> int:
|
|
103
140
|
"""Get the number of items in the buffer."""
|
|
104
141
|
return self._queue.qsize()
|
|
105
142
|
|
|
106
|
-
def _event__get_size(self, _edata: redzed.EventData) -> int:
|
|
107
|
-
return self.rz_get_size()
|
|
108
|
-
|
|
109
143
|
def rz_stop(self) -> None:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
for _ in range(self._waiters - 1):
|
|
114
|
-
self._queue.put_nowait(redzed.UNDEF)
|
|
144
|
+
super().rz_stop()
|
|
145
|
+
for _ in range(self._waiters):
|
|
146
|
+
self.rz_put_value(redzed.UNDEF)
|
|
115
147
|
|
|
116
148
|
async def rz_buffer_get(self) -> t.Any:
|
|
117
149
|
"""
|
|
@@ -121,7 +153,7 @@ class QueueBuffer(_Buffer):
|
|
|
121
153
|
After the shutdown drain the queue
|
|
122
154
|
and then raise BufferShutDown to each caller.
|
|
123
155
|
"""
|
|
124
|
-
if self.
|
|
156
|
+
if self._shutdown and self._queue.qsize() == 0:
|
|
125
157
|
raise BufferShutDown("The buffer was shut down")
|
|
126
158
|
self._waiters += 1
|
|
127
159
|
try:
|
|
@@ -133,10 +165,6 @@ class QueueBuffer(_Buffer):
|
|
|
133
165
|
raise BufferShutDown("The buffer was shut down")
|
|
134
166
|
return value
|
|
135
167
|
|
|
136
|
-
def rz_close(self) -> None:
|
|
137
|
-
if (size := self._queue.qsize()) > 0:
|
|
138
|
-
self.log_warning("%d unprocessed item(s) left in the buffer", size)
|
|
139
|
-
|
|
140
168
|
|
|
141
169
|
class MemoryBuffer(_Buffer):
|
|
142
170
|
"""
|
|
@@ -145,64 +173,65 @@ class MemoryBuffer(_Buffer):
|
|
|
145
173
|
|
|
146
174
|
def __init__(self, *args, **kwargs) -> None:
|
|
147
175
|
super().__init__(*args, **kwargs)
|
|
148
|
-
|
|
149
|
-
|
|
176
|
+
# allowed states:
|
|
177
|
+
# _shutdown value has_value
|
|
178
|
+
# - has value: False, not UNDEF, set
|
|
179
|
+
# - is empty: False, UNDEF, cleared
|
|
180
|
+
# - draining: True, not UNDEF, set
|
|
181
|
+
# - shut down: True, UNDEF, set
|
|
182
|
+
self._value = redzed.UNDEF
|
|
183
|
+
self._has_value = asyncio.Event()
|
|
150
184
|
|
|
151
185
|
def rz_get_size(self) -> int:
|
|
152
186
|
"""Get the number of items (0 or 1) in the buffer."""
|
|
153
|
-
has_data = self.
|
|
187
|
+
has_data = self._value is not redzed.UNDEF
|
|
154
188
|
return 1 if has_data else 0
|
|
155
189
|
|
|
156
|
-
def
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
self._cell.send(edata['evalue'])
|
|
190
|
+
def rz_put_value(self, value: t.Any) -> None:
|
|
191
|
+
if value is redzed.UNDEF:
|
|
192
|
+
raise ValueError(f"{self}: Cannot put UNDEF into the buffer")
|
|
193
|
+
self._value = value
|
|
194
|
+
self._has_value.set()
|
|
162
195
|
|
|
163
196
|
def rz_stop(self) -> None:
|
|
164
197
|
"""Shut down"""
|
|
165
|
-
|
|
166
|
-
|
|
198
|
+
super().rz_stop()
|
|
199
|
+
if not self._has_value.is_set():
|
|
200
|
+
assert self._value is redzed.UNDEF
|
|
201
|
+
self._has_value.set() # unblock reader(s)
|
|
167
202
|
|
|
168
203
|
async def rz_buffer_get(self) -> t.Any:
|
|
169
|
-
"""
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
self._stop_pending = False
|
|
183
|
-
return self._stop_value
|
|
184
|
-
|
|
185
|
-
def rz_close(self) -> None:
|
|
186
|
-
if self._stop_pending:
|
|
187
|
-
self.log_error("The stop_value was not retrieved")
|
|
204
|
+
"""Remove and return an item from the memory cell."""
|
|
205
|
+
while True:
|
|
206
|
+
await self._has_value.wait()
|
|
207
|
+
if self._value is redzed.UNDEF:
|
|
208
|
+
if self._shutdown:
|
|
209
|
+
raise BufferShutDown("The buffer was shut down")
|
|
210
|
+
if not self._has_value.is_set():
|
|
211
|
+
raise RuntimeError("BUG!: busy loop prevented")
|
|
212
|
+
continue
|
|
213
|
+
value, self._value = self._value, redzed.UNDEF
|
|
214
|
+
if not self._shutdown:
|
|
215
|
+
self._has_value.clear()
|
|
216
|
+
return value
|
|
188
217
|
|
|
189
218
|
|
|
190
219
|
class OutputWorker(redzed.Block):
|
|
191
220
|
"""
|
|
192
|
-
Run
|
|
221
|
+
Run an awaitable for each value from a buffer.
|
|
193
222
|
"""
|
|
194
223
|
|
|
195
224
|
def __init__(
|
|
196
225
|
self, *args,
|
|
197
226
|
buffer: str|redzed.Block,
|
|
198
|
-
|
|
227
|
+
aw_func: Callable[[t.Any], Awaitable[t.Any]], # e.g. an async function
|
|
199
228
|
workers: int = 1,
|
|
200
229
|
**kwargs) -> None:
|
|
201
230
|
super().__init__(*args, **kwargs)
|
|
202
231
|
if workers < 1:
|
|
203
232
|
raise ValueError(f"{self}: At least one worker is required")
|
|
204
233
|
self._buffer = buffer
|
|
205
|
-
self.
|
|
234
|
+
self._aw_func = aw_func
|
|
206
235
|
self._workers = workers
|
|
207
236
|
self._worker_tasks: list[asyncio.Task[None]] = []
|
|
208
237
|
|
|
@@ -222,10 +251,9 @@ class OutputWorker(redzed.Block):
|
|
|
222
251
|
try:
|
|
223
252
|
if redzed.get_debug_level() >= 1:
|
|
224
253
|
self.log_debug(
|
|
225
|
-
"%s: Awaiting
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
self.log_debug2("%s: coroutine done", wname)
|
|
254
|
+
"%s: Awaiting %s", wname, func_call_string(self._aw_func, (value,)))
|
|
255
|
+
await self._aw_func(value)
|
|
256
|
+
self.log_debug2("%s: await done", wname)
|
|
229
257
|
except Exception as err:
|
|
230
258
|
err.add_note(f"Processed value was: {value!r}")
|
|
231
259
|
raise
|
|
@@ -241,27 +269,31 @@ class OutputWorker(redzed.Block):
|
|
|
241
269
|
self._worker(wname), auto_cancel=False, name=f"{self}: {wname}"))
|
|
242
270
|
|
|
243
271
|
async def rz_astop(self) -> None:
|
|
272
|
+
worker_tasks = [worker for worker in self._worker_tasks if not worker.done()]
|
|
273
|
+
if not worker_tasks:
|
|
274
|
+
return
|
|
244
275
|
try:
|
|
245
|
-
await asyncio.wait(
|
|
276
|
+
await asyncio.wait(worker_tasks)
|
|
246
277
|
except asyncio.CancelledError:
|
|
247
278
|
# stop_timeout from the circuit runner
|
|
248
|
-
for worker in
|
|
279
|
+
for worker in worker_tasks:
|
|
249
280
|
if not worker.done():
|
|
250
281
|
worker.cancel()
|
|
251
282
|
await asyncio.sleep(0)
|
|
252
|
-
if (running := sum(1 for worker in
|
|
283
|
+
if (running := sum(1 for worker in worker_tasks if not worker.done())) > 0:
|
|
253
284
|
self.log_warning("%d worker(s) did not stop", running)
|
|
254
285
|
raise
|
|
255
286
|
|
|
287
|
+
|
|
256
288
|
class OutputController(redzed.Block):
|
|
257
289
|
"""
|
|
258
|
-
Run
|
|
290
|
+
Run an awaitable for the latest value from a buffer.
|
|
259
291
|
"""
|
|
260
292
|
|
|
261
293
|
def __init__(
|
|
262
294
|
self, *args,
|
|
263
295
|
buffer: str|redzed.Block,
|
|
264
|
-
|
|
296
|
+
aw_func: Callable[[t.Any], Awaitable[t.Any]], # e.g. an async function
|
|
265
297
|
rest_time: float|str = 0.0,
|
|
266
298
|
**kwargs) -> None:
|
|
267
299
|
super().__init__(*args, **kwargs)
|
|
@@ -269,7 +301,7 @@ class OutputController(redzed.Block):
|
|
|
269
301
|
if self._rest_time >= self.rz_stop_timeout:
|
|
270
302
|
raise ValueError(f"{self}: rest_time must be shorter than the stop_timeout")
|
|
271
303
|
self._buffer = buffer
|
|
272
|
-
self.
|
|
304
|
+
self._aw_func = aw_func
|
|
273
305
|
self._main_loop_task: asyncio.Task[None]|None = None
|
|
274
306
|
|
|
275
307
|
def rz_pre_init(self) -> None:
|
|
@@ -284,7 +316,7 @@ class OutputController(redzed.Block):
|
|
|
284
316
|
|
|
285
317
|
async def _run_with_rest_time(self, value: t.Any) -> None:
|
|
286
318
|
try:
|
|
287
|
-
await self.
|
|
319
|
+
await self._aw_func(value)
|
|
288
320
|
except asyncio.CancelledError:
|
|
289
321
|
self.log_debug2("Task cancelled")
|
|
290
322
|
await self._rest_time_delay()
|
|
@@ -331,7 +363,7 @@ class OutputController(redzed.Block):
|
|
|
331
363
|
if redzed.get_debug_level() >= 1:
|
|
332
364
|
self.log_debug(
|
|
333
365
|
"Creating task: %s%s",
|
|
334
|
-
func_call_string(self.
|
|
366
|
+
func_call_string(self._aw_func, (value,)),
|
|
335
367
|
" + rest time" if self._rest_time > 0.0 else "",
|
|
336
368
|
)
|
|
337
369
|
task = asyncio.create_task(self._run_with_rest_time(value))
|
redzed/blocklib/repeat.py
CHANGED
|
@@ -13,7 +13,7 @@ import asyncio
|
|
|
13
13
|
import typing as t
|
|
14
14
|
|
|
15
15
|
import redzed
|
|
16
|
-
from redzed.utils import
|
|
16
|
+
from redzed.utils import time_period
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class Repeat(redzed.Block):
|
|
@@ -27,20 +27,27 @@ class Repeat(redzed.Block):
|
|
|
27
27
|
**kwargs
|
|
28
28
|
) -> None:
|
|
29
29
|
self._dest = dest
|
|
30
|
-
self.
|
|
30
|
+
self._default_interval = time_period(interval)
|
|
31
31
|
if count is not None and count < 0:
|
|
32
32
|
# count = 0 (no repeating) is accepted
|
|
33
33
|
raise ValueError("argument 'count' must not be negative")
|
|
34
|
-
self.
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
34
|
+
self._default_count = count
|
|
35
|
+
self._got_event = False
|
|
36
|
+
self._new_event = asyncio.Event()
|
|
37
|
+
# current event
|
|
38
|
+
self._etype: str
|
|
39
|
+
self._edata: redzed.EventData
|
|
40
|
+
self._interval: float
|
|
41
|
+
self._count: int|None
|
|
37
42
|
super().__init__(*args, **kwargs)
|
|
38
43
|
|
|
39
44
|
def rz_pre_init(self) -> None:
|
|
40
45
|
"""Resolve destination block name."""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
dest = self.circuit.resolve_name(self._dest)
|
|
47
|
+
if isinstance(dest, redzed.Formula):
|
|
48
|
+
raise TypeError(f"{self}: {dest} is a Formula; cannot send events to it.")
|
|
49
|
+
if isinstance(dest, type(self)):
|
|
50
|
+
raise TypeError(f"{self}: {dest} is another Repeat block; this is not allowed")
|
|
44
51
|
self._dest = dest
|
|
45
52
|
self.circuit.create_service(self._repeater(), name=f"Event repeating task at {self}")
|
|
46
53
|
|
|
@@ -52,21 +59,30 @@ class Repeat(redzed.Block):
|
|
|
52
59
|
repeat = 0 # prevent pylint warning
|
|
53
60
|
while True:
|
|
54
61
|
try:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
repeat = 0
|
|
62
|
+
async with asyncio.timeout(self._interval if repeating else None):
|
|
63
|
+
await self._new_event.wait()
|
|
58
64
|
except asyncio.TimeoutError:
|
|
65
|
+
pass
|
|
66
|
+
# getting a timeout does not necessarily mean the event was not set
|
|
67
|
+
if self._new_event.is_set():
|
|
68
|
+
self._new_event.clear()
|
|
69
|
+
repeat = 0
|
|
70
|
+
else:
|
|
59
71
|
repeat += 1
|
|
60
72
|
|
|
61
73
|
if repeat > 0: # skip the original event
|
|
62
74
|
self._set_output(repeat)
|
|
63
75
|
assert isinstance(self._dest, redzed.Block) # @mypy: name resolved
|
|
64
|
-
self._dest.event(
|
|
76
|
+
self._dest.event(self._etype, **(self._edata | {'repeat': repeat}))
|
|
65
77
|
repeating = self._count is None or repeat < self._count
|
|
66
78
|
|
|
67
79
|
def _default_event_handler(self, etype: str, edata: redzed.EventData) -> None:
|
|
68
80
|
# send the original event synchronously
|
|
69
81
|
self._set_output(0)
|
|
70
82
|
assert isinstance(self._dest, redzed.Block) # mypy: name is resolved
|
|
83
|
+
self._etype = etype
|
|
84
|
+
self._edata = edata
|
|
85
|
+
self._interval = edata.pop('repeat_interval', self._default_interval)
|
|
86
|
+
self._count = edata.pop('repeat_count', self._default_count)
|
|
71
87
|
self._dest.event(etype, **(edata | {'repeat': 0}))
|
|
72
|
-
self.
|
|
88
|
+
self._new_event.set()
|
redzed/blocklib/timedate.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Periodic events at fixed time/date.
|
|
3
|
-
|
|
4
3
|
- - - - - -
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Part of the redzed package.
|
|
5
|
+
Docs: https://redzed.readthedocs.io/en/latest/
|
|
6
|
+
Home: https://github.com/xitop/redzed/
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
redzed/blocklib/timeinterval.py
CHANGED
|
@@ -13,6 +13,10 @@ Intervals use those objects as endpoints:
|
|
|
13
13
|
- DateTimeInterval defines non-recurring intervals.
|
|
14
14
|
|
|
15
15
|
All intervals support the operation "value in interval".
|
|
16
|
+
- - - - - -
|
|
17
|
+
Part of the redzed package.
|
|
18
|
+
Docs: https://redzed.readthedocs.io/en/latest/
|
|
19
|
+
Home: https://github.com/xitop/redzed/
|
|
16
20
|
"""
|
|
17
21
|
from __future__ import annotations
|
|
18
22
|
|
redzed/blocklib/timer.py
CHANGED
|
@@ -18,10 +18,9 @@ class Timer(FSM):
|
|
|
18
18
|
A timer.
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
['on', float("inf"), 'off']
|
|
24
|
-
['off', float("inf"), 'on']]
|
|
21
|
+
STATES = [
|
|
22
|
+
['off', float("inf"), 'on'],
|
|
23
|
+
['on', float("inf"), 'off']]
|
|
25
24
|
EVENTS = [
|
|
26
25
|
['start', ..., 'on'],
|
|
27
26
|
['stop', ..., 'off'],
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data validator
|
|
3
|
+
- - - - - -
|
|
4
|
+
Part of the redzed package.
|
|
5
|
+
Docs: https://redzed.readthedocs.io/en/latest/
|
|
6
|
+
Home: https://github.com/xitop/redzed/
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from collections.abc import Callable
|
|
11
|
+
import typing as t
|
|
12
|
+
|
|
13
|
+
class _Validate:
|
|
14
|
+
"""
|
|
15
|
+
Add a value validator.
|
|
16
|
+
|
|
17
|
+
To be used as a redzed.Block mix-in class.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self, *args,
|
|
22
|
+
validator: Callable[[t.Any], t.Any]|None = None,
|
|
23
|
+
**kwargs) -> None:
|
|
24
|
+
self._validator = validator
|
|
25
|
+
super().__init__(*args, **kwargs)
|
|
26
|
+
|
|
27
|
+
# mypy: disable-error-code=attr-defined
|
|
28
|
+
# pylint: disable=no-member
|
|
29
|
+
def _validate(self, value: t.Any) -> t.Any:
|
|
30
|
+
"""
|
|
31
|
+
Return the value processed by the validator.
|
|
32
|
+
|
|
33
|
+
Return the value unchanged if a validator was not configured.
|
|
34
|
+
The validator may raise to reject the value.
|
|
35
|
+
"""
|
|
36
|
+
if self._validator is None:
|
|
37
|
+
return value
|
|
38
|
+
try:
|
|
39
|
+
validated = self._validator(value)
|
|
40
|
+
except Exception as err:
|
|
41
|
+
self.log_debug1(
|
|
42
|
+
"Validator rejected value %r with %s: %s", value, type(err).__name__, err)
|
|
43
|
+
raise
|
|
44
|
+
if validated != value:
|
|
45
|
+
self.log_debug2("Validator has rewritten %r -> %r", value, validated)
|
|
46
|
+
return validated
|