redzed 25.12.30__py3-none-any.whl → 26.1.28__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.
@@ -1,8 +1,9 @@
1
1
  """
2
2
  Output blocks.
3
3
  - - - - - -
4
- Docs: https://edzed.readthedocs.io/en/latest/
5
- Home: https://github.com/xitop/edzed/
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
 
@@ -14,6 +15,7 @@ import typing as t
14
15
 
15
16
  import redzed
16
17
  from redzed.utils import BufferShutDown, cancel_shield, func_call_string, MsgSync, time_period
18
+ from .validator import _Validate
17
19
 
18
20
 
19
21
  class OutputFunc(redzed.Block):
@@ -37,13 +39,13 @@ class OutputFunc(redzed.Block):
37
39
  self.event('put', value)
38
40
 
39
41
  def _event_put(self, edata: redzed.EventData) -> t.Any:
40
- arg = edata['evalue']
42
+ evalue = edata['evalue']
41
43
  if redzed.get_debug_level() >= 1:
42
- self.log_debug("Running %s", func_call_string(self._func, (arg,)))
44
+ self.log_debug("Running %s", func_call_string(self._func, (evalue,)))
43
45
  try:
44
- result = self._func(arg)
46
+ result = self._func(evalue)
45
47
  except Exception as err:
46
- func_args = func_call_string(self._func, (arg,))
48
+ func_args = func_call_string(self._func, (evalue,))
47
49
  self.log_error("Output function failed; call: %s; error: %r", func_args, err)
48
50
  err.add_note(f"Error occurred in function call {func_args}")
49
51
  self.circuit.abort(err)
@@ -56,19 +58,31 @@ class OutputFunc(redzed.Block):
56
58
  self._event_put({'evalue': self._stop_value})
57
59
 
58
60
 
59
- class _Buffer(redzed.Block):
61
+ class _Buffer(_Validate, redzed.Block):
60
62
  def __init__(
61
63
  self, *args,
62
64
  stop_value: t.Any = redzed.UNDEF,
63
65
  triggered_by: str|redzed.Block|redzed.Formula|redzed.UndefType = redzed.UNDEF,
64
66
  **kwargs) -> None:
65
67
  super().__init__(*args, **kwargs)
66
- self._stop_value = stop_value
68
+ # the stop_value is stored already validated
69
+ self._stop_value = \
70
+ redzed.UNDEF if stop_value is redzed.UNDEF else self._validate(stop_value)
67
71
  if triggered_by is not redzed.UNDEF:
68
72
  @redzed.triggered
69
73
  def trigger(value=triggered_by) -> None:
70
74
  self.event('put', value)
71
75
 
76
+ def _event_put(self, edata: redzed.EventData) -> None:
77
+ """Put an item into the queue."""
78
+ # not aggregating following two lines in order to have a clear traceback
79
+ evalue = edata['evalue']
80
+ evalue = self._validate(evalue)
81
+ self.rz_put_value(evalue)
82
+
83
+ def rz_put_value(self, value: t.Any) -> None:
84
+ raise NotImplementedError
85
+
72
86
  def rz_get_size(self) -> int:
73
87
  raise NotImplementedError
74
88
 
@@ -91,13 +105,10 @@ class QueueBuffer(_Buffer):
91
105
  self._queue: asyncio.Queue[t.Any] = queue_type(maxsize)
92
106
  # Queue.shutdown is available in Python 3.13, but we want to support 3.11+
93
107
  self._waiters = 0
108
+ self._shutdown = False
94
109
 
95
- def _event_put(self, edata: redzed.EventData) -> None:
96
- """Put an item into the queue."""
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)
110
+ def rz_put_value(self, value: t.Any) -> None:
111
+ self._queue.put_nowait(value)
101
112
 
102
113
  def rz_get_size(self) -> int:
103
114
  """Get the number of items in the buffer."""
@@ -107,11 +118,14 @@ class QueueBuffer(_Buffer):
107
118
  return self.rz_get_size()
108
119
 
109
120
  def rz_stop(self) -> None:
110
- # stop_value might be UNDEF
111
- self._queue.put_nowait(self._stop_value)
112
- # unblock waiters (-1 accounts for the inserted stop value)
113
- for _ in range(self._waiters - 1):
114
- self._queue.put_nowait(redzed.UNDEF)
121
+ waiters = self._waiters
122
+ if self._stop_value is not redzed.UNDEF:
123
+ self.rz_put_value(self._stop_value)
124
+ waiters -= 1
125
+ # unblock waiters
126
+ for _ in range(waiters):
127
+ self.rz_put_value(redzed.UNDEF)
128
+ self._shutdown = True
115
129
 
116
130
  async def rz_buffer_get(self) -> t.Any:
117
131
  """
@@ -121,7 +135,7 @@ class QueueBuffer(_Buffer):
121
135
  After the shutdown drain the queue
122
136
  and then raise BufferShutDown to each caller.
123
137
  """
124
- if self.circuit.after_shutdown() and self._queue.qsize() == 0:
138
+ if self._shutdown and self._queue.qsize() == 0:
125
139
  raise BufferShutDown("The buffer was shut down")
126
140
  self._waiters += 1
127
141
  try:
@@ -156,9 +170,8 @@ class MemoryBuffer(_Buffer):
156
170
  def _event__get_size(self, _edata: redzed.EventData) -> int:
157
171
  return self.rz_get_size()
158
172
 
159
- def _event_put(self, edata: redzed.EventData) -> None:
160
- """Put an item into the memory cell. Existing value will be overwritten."""
161
- self._cell.send(edata['evalue'])
173
+ def rz_put_value(self, value: t.Any) -> None:
174
+ self._cell.send(value)
162
175
 
163
176
  def rz_stop(self) -> None:
164
177
  """Shut down"""
@@ -189,20 +202,20 @@ class MemoryBuffer(_Buffer):
189
202
 
190
203
  class OutputWorker(redzed.Block):
191
204
  """
192
- Run a coroutine for each value from a buffer.
205
+ Run an awaitable for each value from a buffer.
193
206
  """
194
207
 
195
208
  def __init__(
196
209
  self, *args,
197
210
  buffer: str|redzed.Block,
198
- coro_func: Callable[[t.Any], Awaitable[t.Any]], # i.e. an async function
211
+ aw_func: Callable[[t.Any], Awaitable[t.Any]], # e.g. an async function
199
212
  workers: int = 1,
200
213
  **kwargs) -> None:
201
214
  super().__init__(*args, **kwargs)
202
215
  if workers < 1:
203
216
  raise ValueError(f"{self}: At least one worker is required")
204
217
  self._buffer = buffer
205
- self._corofunc = coro_func
218
+ self._aw_func = aw_func
206
219
  self._workers = workers
207
220
  self._worker_tasks: list[asyncio.Task[None]] = []
208
221
 
@@ -222,10 +235,9 @@ class OutputWorker(redzed.Block):
222
235
  try:
223
236
  if redzed.get_debug_level() >= 1:
224
237
  self.log_debug(
225
- "%s: Awaiting coroutine %s",
226
- wname, func_call_string(self._corofunc, (value,)))
227
- await self._corofunc(value)
228
- self.log_debug2("%s: coroutine done", wname)
238
+ "%s: Awaiting %s", wname, func_call_string(self._aw_func, (value,)))
239
+ await self._aw_func(value)
240
+ self.log_debug2("%s: await done", wname)
229
241
  except Exception as err:
230
242
  err.add_note(f"Processed value was: {value!r}")
231
243
  raise
@@ -241,27 +253,30 @@ class OutputWorker(redzed.Block):
241
253
  self._worker(wname), auto_cancel=False, name=f"{self}: {wname}"))
242
254
 
243
255
  async def rz_astop(self) -> None:
256
+ worker_tasks = [worker for worker in self._worker_tasks if not worker.done()]
257
+ if not worker_tasks:
258
+ return
244
259
  try:
245
- await asyncio.wait(self._worker_tasks)
260
+ await asyncio.wait(worker_tasks)
246
261
  except asyncio.CancelledError:
247
262
  # stop_timeout from the circuit runner
248
- for worker in self._worker_tasks:
263
+ for worker in worker_tasks:
249
264
  if not worker.done():
250
265
  worker.cancel()
251
266
  await asyncio.sleep(0)
252
- if (running := sum(1 for worker in self._worker_tasks if not worker.done())) > 0:
267
+ if (running := sum(1 for worker in worker_tasks if not worker.done())) > 0:
253
268
  self.log_warning("%d worker(s) did not stop", running)
254
269
  raise
255
270
 
256
271
  class OutputController(redzed.Block):
257
272
  """
258
- Run a coroutine for the latest value from a buffer.
273
+ Run an awaitable for the latest value from a buffer.
259
274
  """
260
275
 
261
276
  def __init__(
262
277
  self, *args,
263
278
  buffer: str|redzed.Block,
264
- coro_func: Callable[[t.Any], Awaitable[t.Any]], # i.e. an async function
279
+ aw_func: Callable[[t.Any], Awaitable[t.Any]], # e.g. an async function
265
280
  rest_time: float|str = 0.0,
266
281
  **kwargs) -> None:
267
282
  super().__init__(*args, **kwargs)
@@ -269,7 +284,7 @@ class OutputController(redzed.Block):
269
284
  if self._rest_time >= self.rz_stop_timeout:
270
285
  raise ValueError(f"{self}: rest_time must be shorter than the stop_timeout")
271
286
  self._buffer = buffer
272
- self._corofunc = coro_func
287
+ self._aw_func = aw_func
273
288
  self._main_loop_task: asyncio.Task[None]|None = None
274
289
 
275
290
  def rz_pre_init(self) -> None:
@@ -284,7 +299,7 @@ class OutputController(redzed.Block):
284
299
 
285
300
  async def _run_with_rest_time(self, value: t.Any) -> None:
286
301
  try:
287
- await self._corofunc(value)
302
+ await self._aw_func(value)
288
303
  except asyncio.CancelledError:
289
304
  self.log_debug2("Task cancelled")
290
305
  await self._rest_time_delay()
@@ -331,7 +346,7 @@ class OutputController(redzed.Block):
331
346
  if redzed.get_debug_level() >= 1:
332
347
  self.log_debug(
333
348
  "Creating task: %s%s",
334
- func_call_string(self._corofunc, (value,)),
349
+ func_call_string(self._aw_func, (value,)),
335
350
  " + rest time" if self._rest_time > 0.0 else "",
336
351
  )
337
352
  task = asyncio.create_task(self._run_with_rest_time(value))
@@ -1,9 +1,9 @@
1
1
  """
2
2
  Periodic events at fixed time/date.
3
-
4
3
  - - - - - -
5
- Docs: https://edzed.readthedocs.io/en/latest/
6
- Home: https://github.com/xitop/edzed/
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
@@ -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
- ALL_STATES = ['off', 'on']
22
- TIMED_STATES = [
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
redzed/circuit.py CHANGED
@@ -2,8 +2,8 @@
2
2
  The circuit runner.
3
3
  - - - - - -
4
4
  Part of the redzed package.
5
- # Docs: https://redzed.readthedocs.io/en/latest/
6
- # Project home: https://github.com/xitop/redzed/
5
+ Docs: https://redzed.readthedocs.io/en/latest/
6
+ Project home: https://github.com/xitop/redzed/
7
7
  """
8
8
  from __future__ import annotations
9
9
 
@@ -21,12 +21,12 @@ import typing as t
21
21
 
22
22
  from .block import Block, PersistenceFlags
23
23
  from .cron_service import Cron
24
- from .debug import get_debug_level
24
+ from .debug import get_debug_level, set_debug_level
25
25
  from .initializers import AsyncInitializer
26
26
  from .formula_trigger import Formula, Trigger
27
27
  from .signal_shutdown import TerminatingSignal
28
28
  from .undef import UNDEF
29
- from .utils import check_async_coro, check_identifier, tasks_are_eager, time_period
29
+ from .utils import check_identifier, tasks_are_eager, time_period
30
30
 
31
31
  _logger = logging.getLogger(__package__)
32
32
  _current_circuit: Circuit|None = None
@@ -448,6 +448,7 @@ class Circuit:
448
448
  if self.after_shutdown():
449
449
  # It looks like a supporting task has failed immediately after the start
450
450
  return
451
+ set_debug_level(get_debug_level()) # will check the existence of a logging handler
451
452
 
452
453
  pe_blocks = [blk for blk in self.get_items(Block) if blk.has_method('rz_pre_init')]
453
454
  pe_formulas = list(self.get_items(Formula))
@@ -681,7 +682,6 @@ class Circuit:
681
682
  """Create a service task for the circuit."""
682
683
  if self.after_shutdown():
683
684
  raise RuntimeError("Cannot create a service after shutdown")
684
- check_async_coro(coro)
685
685
  # Python 3.12 and 3.13 only: Eager tasks start to run before their name is set.
686
686
  # As a workaround we tell the watchdog wrapper the name.
687
687
  task = asyncio.create_task(