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.
redzed/cron_service.py CHANGED
@@ -3,8 +3,8 @@ Call the .rz_cron_event() method of all registered blocks at given times of day.
3
3
 
4
4
  Blocks acting on given time, date and weekdays are implemented
5
5
  on top of this low-level service.
6
-
7
6
  - - - - - -
7
+ Part of the redzed package.
8
8
  Docs: https://redzed.readthedocs.io/en/latest/
9
9
  Home: https://github.com/xitop/redzed/
10
10
  """
@@ -12,23 +12,41 @@ from __future__ import annotations
12
12
 
13
13
  import asyncio
14
14
  import bisect
15
+ from collections import deque
15
16
  from collections.abc import Collection
16
17
  import datetime as dt
17
- import time
18
18
  import typing as t
19
19
 
20
20
  from .block import Block, EventData
21
21
  from .debug import get_debug_level
22
22
  from .utils import SEC_PER_HOUR, SEC_PER_MIN, SEC_PER_DAY
23
23
 
24
- # time tracking accuracy (in seconds)
25
- _TT_OK = 0.001 # desired accuracy
26
- _TT_WARNING = 0.1 # log a warning when exceeded
27
- _TT_ERROR = 2.5 # do a reset when exceeded
28
- _SYNC_SLEEP = 0.000_2 # sleeps <= 200 µs can be blocking for sake of accuracy
24
+ # time tracking settings (in seconds)
25
+ _TT_OVERHEAD = 0.001 # asyncio sleep overhead estimation; real value will be measured
26
+ _TT_WARNING = 0.2 # timing difference threshold for a warning message
27
+ _TT_ERROR = 2.5 # timing difference threshold for a reset
28
+
29
+ # hourly wake ups for precise time tracking and early detection of DST changes
30
+ _SET24H = frozenset(dt.time(hour, 0, 0) for hour in range(24))
29
31
 
30
- # hourly wake-ups for precise time tracking and early detection of DST changes
31
- _SET24 = frozenset(dt.time(hour, 0, 0) for hour in range(24))
32
+
33
+ def _wait_time(t1: dt.time, t2: dt.time) -> float:
34
+ """
35
+ Return seconds from *t1* to *t2* on a 24 hour clock.
36
+
37
+ The result is always between -1 and 23 hours (in seconds).
38
+ Positive values are normal wait times (when *t2* is after *t1*).
39
+ Negative values correspond to delays after a missed event
40
+ (when *t2* is less than 1 hour before *t1*).
41
+ """
42
+ # datetime.time does not support time arithmetic
43
+ diff = (SEC_PER_HOUR*(t2.hour - t1.hour)
44
+ + SEC_PER_MIN*(t2.minute - t1.minute)
45
+ + (t2.second - t1.second)
46
+ + (t2.microsecond - t1.microsecond) / 1_000_000.0)
47
+ if -SEC_PER_HOUR < diff <= 23 * SEC_PER_HOUR:
48
+ return diff
49
+ return diff + SEC_PER_DAY if diff < 0 else diff - SEC_PER_DAY
32
50
 
33
51
 
34
52
  class Cron(Block):
@@ -44,7 +62,10 @@ class Cron(Block):
44
62
  def __init__(self, *args, utc: bool, **kwargs) -> None:
45
63
  super().__init__(*args, **kwargs)
46
64
  self._utc = bool(utc)
47
- self._alarms: dict[dt.time, set[Block]] = {}
65
+ self._alarms: dict[dt.time, set[Block]] = {tod: set() for tod in _SET24H}
66
+ self._timetable: list[dt.time] = sorted(_SET24H)
67
+ # timetable = sorted list (for bisection) of wake-up times (i.e. _alarms keys)
68
+ self._tt_len = 24
48
69
  self._reversed: dict[Block, set[dt.time]] = {}
49
70
  self._do_reload = asyncio.Event()
50
71
 
@@ -69,144 +90,137 @@ class Cron(Block):
69
90
  raise ValueError("time_of_day must not contain timezone data")
70
91
 
71
92
  def set_schedule(self, blk: Block, times_of_day: Collection[dt.time]) -> None:
72
- """
73
- Add a block to be activated at given times or update its schedule.
74
-
75
- The block's 'rz_cron_event' method will be called at given time
76
- and also when this service is started, reset or reloaded.
77
-
78
- A datetime.datetime object will be passed to the blk as its
79
- only argument.
80
- """
93
+ """Add a block to be activated at given times or update its schedule."""
81
94
  if not hasattr(blk, 'rz_cron_event'):
82
95
  raise TypeError(f"{blk} is not compatible with the cron service")
96
+ for tod in times_of_day:
97
+ self._check_tz(tod)
98
+ self.log_debug2("Got a new schedule for %s", blk)
99
+ times_of_day = set(times_of_day)
83
100
 
84
- times_of_day = set(times_of_day) # make a set, make a copy
85
- # remove old times of day
101
+ # remove old times of day; it is not necessary to notify the main loop
86
102
  old_times = self._reversed.get(blk, set())
87
103
  for tod in old_times - times_of_day:
88
104
  self._alarms[tod].discard(blk)
89
-
90
- do_reload = False
91
105
  # add new times of day
106
+ do_reload = False
92
107
  for tod in times_of_day - old_times:
93
- self._check_tz(tod)
94
108
  if tod in self._alarms:
95
109
  self._alarms[tod].add(blk)
96
110
  else:
97
111
  self._alarms[tod] = {blk}
98
- if tod not in _SET24:
99
- do_reload = True # new entry added to timetable
112
+ # new entry added to timetable; the main loop must be notified
113
+ do_reload = True
100
114
  self._reversed[blk] = times_of_day
101
-
102
- # cleanup
103
- unused = [tod for tod, blkset in self._alarms.items() if not blkset]
104
- for tod in unused:
105
- del self._alarms[tod]
106
- if tod not in _SET24:
107
- do_reload = True # empty entry removed from the timetable
108
115
  if do_reload:
109
116
  self._do_reload.set()
117
+ # cleanup
118
+ for tod in [
119
+ tod for tod, blks in self._alarms.items() if tod not in _SET24H and not blks]:
120
+ del self._alarms[tod]
121
+
122
+ self._timetable = sorted(self._alarms)
123
+ self._tt_len = len(self._timetable)
124
+
110
125
 
111
126
  async def _cron_daemon(self) -> t.NoReturn:
112
127
  """Recalculate registered blocks according to the schedule."""
113
- overhead = _TT_OK # initial value, will be adjusted
114
- # the sleeptime is reduced by this value
115
128
  reset_flag = False
116
- reload_flag = True # reload will also initialize the index
117
- short_sleep = False # alternative sleep function used => do not compute overhead
118
- while True:
119
- if reload_flag:
120
- timetable = sorted(_SET24.union(self._alarms))
121
- tlen = len(timetable)
122
- self.log_debug1("time schedule reloaded")
123
- index = None
124
- reload_flag = False
129
+ overhead = _TT_OVERHEAD # an estimate to start with
130
+ measured_overheads: deque[float] = deque(maxlen=8)
131
+ prev_sleeptime: float
132
+ long_sleep: bool
133
+ wakeup: dt.time|None = None
125
134
 
135
+ while True:
126
136
  nowdt = self.dtnow()
127
137
  nowt = nowdt.time()
128
- if index is None:
129
- # reload is set before entering the loop -> "tlen" gets initialized
130
- # pylint: disable-next=possibly-used-before-assignment
131
- index = bisect.bisect_left(timetable, nowt) % tlen
132
- wakeup = timetable[index]
133
- self.log_debug1("wakeup time: %s", wakeup)
134
-
135
- # sleep until the wakeup time:
136
- # step 0 - compute the delay until wakeup time
137
- # - sleep
138
- # step 1 - check the current time, adjust overhead estimate,
139
- # A: finish if the time is correct, or
140
- # B: add a tiny sleep if woken up too early, because
141
- # continuing before wakeup time is not acceptable
142
- # C: do a reset if the time is way off
143
- # step 2 - check time after 1B,
144
- # A: finish if the time is correct
145
- # B: do a reset otherwise
146
- for step in range(3):
147
- # datetime.time does not support time arithmetic
148
- sleeptime = (SEC_PER_HOUR*(wakeup.hour - nowt.hour)
149
- + SEC_PER_MIN*(wakeup.minute - nowt.minute)
150
- + (wakeup.second - nowt.second)
151
- + (wakeup.microsecond - nowt.microsecond)/ 1_000_000.0)
152
- if nowt.hour == 23 and wakeup.hour == 0:
153
- # wrap around midnight (relying on hourly wakeups in SET24)
154
- sleeptime += SEC_PER_DAY
155
- # sleeptime: negative = after the alarm time; positive = before the alarm time
138
+
139
+ if wakeup is None or self._do_reload.is_set():
140
+ index = bisect.bisect_left(self._timetable, nowt)
141
+ next_wakeup = self._timetable[index % self._tt_len]
142
+ # next_wakeup is new and wakeup was not processed yet,
143
+ # all we need is to select which one comes first.
144
+ if wakeup is None \
145
+ or next_wakeup != wakeup and _wait_time(next_wakeup, wakeup) > 0:
146
+ wakeup = next_wakeup
147
+ # else: the current wakeup is confirmed
148
+ self._do_reload.clear()
149
+ self.log_debug1("wake-up time after reload: %s", wakeup)
150
+ else:
151
+ # return the next entry (in circular manner)
152
+ index = bisect.bisect_right(self._timetable, wakeup)
153
+ wakeup = self._timetable[index % self._tt_len]
154
+ self.log_debug1("wake-up time: %s", wakeup)
155
+
156
+ # sleep until the wake-up time:
157
+ # step 0: main sleep
158
+ # compute the delay until wake-up time and sleep
159
+ # steps 1 and 2: fine adjustment
160
+ # check the current time, adjust overhead estimate and
161
+ # - finish if the time is correct, or
162
+ # - add a tiny sleep if woken up too early, because
163
+ # that is not acceptable, or
164
+ # - do a reset if the time is way off
165
+ # steps 3 and 4: safety net
166
+ # like above, just for the case the computer clock
167
+ # does something unexpected
168
+ for step in range(5):
169
+ sleeptime = _wait_time(nowt, wakeup) # negative = we are late
170
+ debug2 = get_debug_level() >= 2
156
171
  if step == 0:
157
- self.log_debug2("sleep until wakeup: %.3f sec", sleeptime)
158
- if step >= 1 or sleeptime < 0:
172
+ if debug2:
173
+ self.log_debug("sleep until wake up: %.3f sec", sleeptime)
174
+ else:
159
175
  diff = abs(sleeptime)
160
- if get_debug_level() >= 2:
161
- msg = 'BEFORE' if sleeptime > 0 else 'after'
162
- self.log_debug(
163
- "step %d, diff %.2f ms %s, estimated overhead: %.2f ms",
164
- step, 1000*diff, msg, 1000*overhead)
176
+ after = sleeptime <= 0.0
165
177
  if diff > _TT_WARNING:
166
178
  self.log_warning(
167
- "expected time: %s, current time: %s, diff: %.2f ms.",
168
- wakeup, nowt, 1000*diff)
169
- if (step == 2 and sleeptime > 0) or diff > _TT_ERROR:
179
+ "expected time: %s, current time: %s, diff: %.1f sec %s",
180
+ wakeup, nowt, diff, 'after' if after else 'BEFORE')
181
+ elif debug2:
182
+ self.log_debug(
183
+ "iteration %d, diff %.2f ms %s",
184
+ step, 1000*diff, 'after' if after else 'BEFORE')
185
+ # prev_sleeptime and long_sleep has been set in previous step
186
+ # pylint: disable=used-before-assignment
187
+ if diff > _TT_ERROR or sleeptime > prev_sleeptime or step == 4:
188
+ # something is wrong with the computer clock
170
189
  reset_flag = True
171
- if reset_flag:
172
190
  break
173
- if step == 1 and not short_sleep and not -_TT_OK <= sleeptime <= 0:
174
- overhead -= (sleeptime + _TT_OK/2) / 2 # average of new and old
175
- if sleeptime <= 0:
191
+ if long_sleep:
192
+ saved = overhead
193
+ measured_overheads.append(overhead - sleeptime)
194
+ # Using smallest overhead recently measured, because to be late
195
+ # by a tiny amount is much better than to wake up early by a tiny
196
+ # amount. The latter case must be corrected by another sleep.
197
+ overhead = min(measured_overheads)
198
+ if debug2 and overhead != saved:
199
+ self.log_debug("estimated overhead >= %.2f ms", 1000*overhead)
200
+ if after:
176
201
  break
177
- if get_debug_level() >= 2:
202
+ if debug2:
178
203
  self.log_debug("additional sleep %.2f ms", 1000*sleeptime)
204
+ prev_sleeptime = sleeptime
179
205
 
180
- if sleeptime == 0.0:
181
- pass # how likely is this?
182
- elif sleeptime <= _SYNC_SLEEP:
183
- short_sleep = True
184
- # breaking the asyncio rules for max time tracking accuracy:
185
- # doing a blocking sleep, but only for a fraction of a millisecond
186
- time.sleep(sleeptime)
187
- elif sleeptime <= overhead:
188
- short_sleep = True
189
- await asyncio.sleep(sleeptime)
190
- else:
191
- short_sleep = False
206
+ if (long_sleep := sleeptime > overhead):
192
207
  try:
193
208
  async with asyncio.timeout(sleeptime - overhead):
194
209
  await self._do_reload.wait()
195
210
  except TimeoutError:
196
- pass
211
+ pass # no reload request
197
212
  else:
198
- self._do_reload.clear()
199
- reload_flag = True
200
- break
213
+ break # reload request arrived
214
+ elif sleeptime > 0.0:
215
+ await asyncio.sleep(sleeptime)
201
216
  nowdt = self.dtnow()
202
217
  nowt = nowdt.time()
218
+ # --- end for loop ---
203
219
 
204
220
  if reset_flag:
205
221
  # DST begin/end or other computer clock related reason
206
- if (not self._utc
207
- and nowdt.isoweekday() >= 6
208
- and abs(diff - SEC_PER_HOUR) <= _TT_ERROR
209
- ):
222
+ if (not self._utc and nowdt.isoweekday() >= 6
223
+ and abs(diff - SEC_PER_HOUR) <= _TT_ERROR):
210
224
  self.log_warning("Apparently a DST (summer time) clock change has occured.")
211
225
  self.log_warning(
212
226
  "Resetting due to a time tracking problem. "
@@ -215,29 +229,29 @@ class Cron(Block):
215
229
  for blk in list(self._reversed): # all blocks
216
230
  assert hasattr(blk, 'rz_cron_event')
217
231
  blk.rz_cron_event(nowdt)
218
- index = None
219
232
  reset_flag = False
233
+ wakeup = None
220
234
  continue
221
- if reload_flag:
235
+ if self._do_reload.is_set():
222
236
  continue
223
237
 
224
- if wakeup in self._alarms:
225
- # .rz_cron_event() may alter the set we are iterating over
226
- block_list = list(self._alarms[wakeup])
227
- if get_debug_level() >= 1:
228
- self.log_debug(
229
- "Notifying blocks: %s", ", ".join(blk.name for blk in block_list))
230
- for blk in block_list:
231
- assert hasattr(blk, 'rz_cron_event')
232
- blk.rz_cron_event(nowdt)
233
- index = (index + 1) % tlen
238
+ if wakeup not in self._alarms:
239
+ continue # entry removed in the meantime
240
+ # .rz_cron_event() may alter the set we are iterating over
241
+ block_list = list(self._alarms[wakeup])
242
+ if get_debug_level() >= 1:
243
+ self.log_debug(
244
+ "Notifying block(s): %s", ", ".join(blk.name for blk in block_list))
245
+ for blk in block_list:
246
+ assert hasattr(blk, 'rz_cron_event')
247
+ blk.rz_cron_event(nowdt)
234
248
 
235
249
  def _event__get_config(self, _edata: EventData) -> dict[str, dict[str, list[str]]]:
236
250
  """Return the internal scheduling data for debugging or monitoring."""
237
251
  return {
238
252
  'alarms': {
239
- str(recalc_time): sorted(blk.name for blk in blk_set)
240
- for recalc_time, blk_set in self._alarms.items()},
241
- 'blocks': {blk.name: sorted(str(recalc_time) for recalc_time in times_set)
242
- for blk, times_set in self._reversed.items()},
253
+ str(tod): sorted(blk.name for blk in blks)
254
+ for tod, blks in self._alarms.items() if blks},
255
+ 'blocks': {blk.name: sorted(str(tod) for tod in tods)
256
+ for blk, tods in self._reversed.items()},
243
257
  }
redzed/debug.py CHANGED
@@ -1,26 +1,37 @@
1
1
  """
2
- Debug level.
2
+ Debug levels.
3
+ - - - - - -
4
+ Part of the redzed package.
5
+ Docs: https://redzed.readthedocs.io/en/latest/
6
+ Home: https://github.com/xitop/redzed/
3
7
  """
4
8
  from __future__ import annotations
5
9
 
6
- __all__ = ['get_debug_level', 'set_debug_level']
7
-
8
10
  import logging
9
11
  import os
10
12
 
11
13
  from . import circuit
12
14
 
15
+ __all__ = ['get_debug_level', 'set_debug_level']
16
+
13
17
  _logger = logging.getLogger(__package__)
14
- _debug_handler: logging.StreamHandler|None = None
15
18
 
16
19
 
17
- class _CircuitTimeFormatter(logging.Formatter):
18
- """Formatter for debug level 3."""
19
- def format(self, record: logging.LogRecord) -> str:
20
- msg = super().format(record)
21
- if get_debug_level() >= 3:
22
- msg = f"{circuit.get_circuit().runtime():.03f} {msg}"
23
- return msg
20
+ def get_level_from_env() -> int|None:
21
+ if (env_level := os.environ.get('REDZED_DEBUG', '')) == '':
22
+ return None
23
+ try:
24
+ level = int(env_level)
25
+ except Exception:
26
+ pass
27
+ else:
28
+ if 0 <= level <= 3:
29
+ return level
30
+ _logger.warning(
31
+ "Envvar 'REDZED_DEBUG' should be: 0 (disabled), 1 (normal), "
32
+ + "2 (verbose) or 3 (verbose with circuit timestamps)")
33
+ _logger.error("Ignoring REDZED_DEBUG='%s'. Please use a correct value.", env_level)
34
+ return None
24
35
 
25
36
 
26
37
  class _DebugLevel:
@@ -31,56 +42,62 @@ class _DebugLevel:
31
42
  """
32
43
 
33
44
  def __init__(self) -> None:
34
- self._level = -1 # make sure the set_level's core will run
35
- self.set_level(self._get_level_from_env())
36
-
37
- def _get_level_from_env(self) -> int:
38
- try:
39
- level = os.environ['REDZED_DEBUG']
40
- except KeyError:
41
- return 0
42
- level = level.strip()
43
- if not level:
44
- return 0
45
- if level in {'0', '1', '2', '3'}:
46
- return int(level)
47
- _logger.warning(
48
- "Envvar 'REDZED_DEBUG' should be: 0 (disabled), 1 (normal), "
49
- + "2 (verbose) or 3 (verbose with circuit timestamps)")
50
- _logger.warning("Ignoring REDZED_DEBUG='%s'. Please use a correct value.", level)
51
- return 0
52
-
53
- def get_level(self) -> int:
45
+ if (level := get_level_from_env()) is None:
46
+ level = 0
47
+ self._level = level
48
+ _logger.debug("[Logging] Debug level: %d", level)
49
+
50
+ @property
51
+ def level(self) -> int:
54
52
  return self._level
55
53
 
56
- def set_level(self, level: int) -> None:
54
+ @level.setter
55
+ def level(self, level: int) -> None:
57
56
  """Set debug level."""
58
- global _debug_handler # pylint: disable=global-statement
59
-
60
- if not isinstance(level, int):
61
- raise TypeError(f"Expected an integer, got {level!r}")
62
57
  if level == self._level:
63
58
  return
59
+ if not isinstance(level, int):
60
+ raise TypeError(f"Expected an integer, got {level!r}")
64
61
  if not 0 <= level <= 3:
65
62
  raise ValueError(f"Debug level must be an integer 0 to 3, but got {level}")
66
- # self._level is -1 on initial call
67
- if self._level >= 0:
68
- _logger.debug("Debug level: %d -> %d", self._level, level)
69
- elif level > 0:
70
- _logger.debug("Debug level: %d", level)
63
+ _logger.debug("[Logging] Debug level: %d -> %d", self._level, level)
71
64
  self._level = level
72
- if level == 0:
73
- if _debug_handler is not None:
74
- _logger.removeHandler(_debug_handler)
75
- _debug_handler = None
76
- _logger.setLevel(logging.WARNING)
77
- elif not _logger.hasHandlers() and _debug_handler is None:
78
- _logger.addHandler(_debug_handler := logging.StreamHandler())
79
- _debug_handler.setFormatter(_CircuitTimeFormatter("%(levelname)s - %(message)s"))
80
- _logger.setLevel(logging.DEBUG)
81
65
 
82
66
 
83
- _global_debug_level = _DebugLevel()
67
+ _debug_level = _DebugLevel()
68
+
69
+
70
+ def get_debug_level():
71
+ """Get the debug level."""
72
+ return _debug_level.level
84
73
 
85
- get_debug_level = _global_debug_level.get_level
86
- set_debug_level = _global_debug_level.set_level
74
+
75
+ def set_debug_level(level):
76
+ """
77
+ Set the debug level.
78
+
79
+ Also make sure debug messages will be logged or printed.
80
+ For this purpose, if there is no handler, add an own stream handler.
81
+ """
82
+ _debug_level.level = level
83
+ if circuit.get_circuit().get_state() in [
84
+ circuit.CircuitState.UNDER_CONSTRUCTION, circuit.CircuitState.CLOSED]:
85
+ return
86
+ if level > 0:
87
+ if not _logger.hasHandlers():
88
+ _logger.addHandler(logging.StreamHandler())
89
+ _logger.propagate = False
90
+ _logger.setLevel(logging.DEBUG)
91
+ else:
92
+ _logger.setLevel(logging.NOTSET if _logger.propagate else logging.INFO)
93
+
94
+
95
+ class _CircuitTimeFilter(logging.Filter):
96
+ """Filter adding timestamsps in debug level 3."""
97
+ def filter(self, record: logging.LogRecord) -> bool:
98
+ if _debug_level.level >= 3 and isinstance(record.msg, str):
99
+ record.msg = f"{circuit.get_circuit().runtime():.03f} {record.msg}"
100
+ return True
101
+
102
+
103
+ _logger.addFilter(_CircuitTimeFilter())
redzed/formula_trigger.py CHANGED
@@ -194,12 +194,8 @@ class Formula(base_block.BlockOrFormula):
194
194
  self._evaluate_active = False
195
195
  return triggers
196
196
 
197
- def formula(name: str, *args, **kwargs) -> Callable[[_FUNC], _FUNC]:
197
+ def formula(func: _FUNC) -> _FUNC:
198
198
  """@formula() creates a Formula block with the decorated function."""
199
- if 'func' in kwargs:
200
- # Argument func=... will be supplied by us
201
- raise TypeError("@formula() got an unexpected keyword argument 'func='")
202
- def decorator(func: _FUNC) -> _FUNC:
203
- Formula(name, *args, func=func, **kwargs)
204
- return func
205
- return decorator
199
+ comment = '' if func.__doc__ is None else inspect.cleandoc(func.__doc__).partition('\n')[0]
200
+ Formula(func.__name__, func=func, comment=comment)
201
+ return func
redzed/initializers.py CHANGED
@@ -2,8 +2,8 @@
2
2
  Block initializers.
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
 
@@ -18,7 +18,7 @@ import typing as t
18
18
 
19
19
  from . import block
20
20
  from .undef import UNDEF, UndefType
21
- from .utils import check_async_func, time_period
21
+ from .utils import time_period
22
22
 
23
23
 
24
24
  _LONG_TIMEOUT = 60 # threshold for a "long timeout" debug message
@@ -225,19 +225,18 @@ class InitTask(AsyncInitializer):
225
225
 
226
226
  def __init__(
227
227
  self,
228
- coro_func: Callable[..., Awaitable[t.Any]],
228
+ aw_func: Callable[..., Awaitable[t.Any]],
229
229
  *args: t.Any,
230
230
  timeout: float|str = 10.0
231
231
  ) -> None:
232
232
  """Similar to InitFunction, but asynchonous."""
233
- check_async_func(coro_func)
234
233
  super().__init__(timeout)
235
- self._corofunc = coro_func
234
+ self._aw_func = aw_func
236
235
  self._args = args
237
236
 
238
237
  async def _async_get_init(self) -> t.Any:
239
238
  async with asyncio.timeout(self._timeout):
240
- return await self._corofunc(*self._args)
239
+ return await self._aw_func(*self._args)
241
240
 
242
241
 
243
242
  class InitWait(AsyncInitializer):
redzed/py.typed ADDED
File without changes
redzed/signal_shutdown.py CHANGED
@@ -2,8 +2,8 @@
2
2
  Stop the runner with a signal.
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
 
@@ -55,7 +55,7 @@ class TerminatingSignal:
55
55
  """A signal handler."""
56
56
  # - we need the _threadsafe variant of call_soon
57
57
  # - get_running loop() and get_circuit() will succeed,
58
- # because this handler is active only during edzed.run()
58
+ # because this handler is active only during redzed.run()
59
59
  msg = f"Signal {self._signame!r} caught"
60
60
  call_soon = asyncio.get_running_loop().call_soon_threadsafe
61
61
  call_soon(_logger.warning, "%s", msg)
redzed/undef.py CHANGED
@@ -2,8 +2,8 @@
2
2
  The UNDEF singleton constant.
3
3
  - - - - - -
4
4
  Part of the redzed package.
5
- # Docs: https://redzed.readthedocs.io/en/latest/
6
- # Home: https://github.com/xitop/redzed/
5
+ Docs: https://redzed.readthedocs.io/en/latest/
6
+ Home: https://github.com/xitop/redzed/
7
7
  """
8
8
  from __future__ import annotations
9
9