async-timer 1.1.0__tar.gz → 1.1.1__tar.gz
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.
- {async_timer-1.1.0 → async_timer-1.1.1}/PKG-INFO +1 -1
- {async_timer-1.1.0 → async_timer-1.1.1}/pyproject.toml +1 -1
- async_timer-1.1.1/src/async_timer/__init__.py +2 -0
- {async_timer-1.1.0 → async_timer-1.1.1}/src/async_timer/timer.py +9 -46
- async_timer-1.1.1/src/async_timer/traget_caller.py +70 -0
- async_timer-1.1.0/src/async_timer/__init__.py +0 -2
- {async_timer-1.1.0 → async_timer-1.1.1}/README.md +0 -0
- {async_timer-1.1.0 → async_timer-1.1.1}/src/async_timer/pacemaker.py +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Utility async io functions"""
|
|
2
2
|
import asyncio
|
|
3
|
-
import inspect
|
|
4
3
|
import logging
|
|
5
4
|
import time
|
|
6
5
|
import typing
|
|
@@ -14,6 +13,8 @@ TimerMainTaskT = typing.Union[
|
|
|
14
13
|
typing.Callable[[], typing.Coroutine[typing.Any, typing.Any, T]],
|
|
15
14
|
typing.Callable[[], typing.AsyncGenerator[T, typing.Any]],
|
|
16
15
|
typing.Callable[[], typing.Generator[T, typing.Any, typing.Any]],
|
|
16
|
+
typing.AsyncGenerator[T, typing.Any],
|
|
17
|
+
typing.Generator[T, typing.Any, typing.Any],
|
|
17
18
|
]
|
|
18
19
|
TimerCallbackT = typing.Callable[["Timer[T]", TimerMainTaskT[T]], None]
|
|
19
20
|
|
|
@@ -95,7 +96,7 @@ class Timer(typing.Generic[T]):
|
|
|
95
96
|
one resolving cancels the timer
|
|
96
97
|
"""
|
|
97
98
|
self.iterator = async_timer.pacemaker.TimerPacemaker(delay)
|
|
98
|
-
self.
|
|
99
|
+
self.target_caller = async_timer.traget_caller.Caller(target)
|
|
99
100
|
self.result_fanout = FanoutRv()
|
|
100
101
|
self.exception_callback = exc_cb
|
|
101
102
|
self.cancel_callback = cancel_cb
|
|
@@ -189,63 +190,24 @@ class Timer(typing.Generic[T]):
|
|
|
189
190
|
except asyncio.CancelledError as err:
|
|
190
191
|
raise StopAsyncIteration() from err
|
|
191
192
|
|
|
192
|
-
def _maybe_detect_generator(
|
|
193
|
-
self, target_rv
|
|
194
|
-
) -> typing.Tuple[T, typing.Callable[[], T]]:
|
|
195
|
-
"""Check if the value returned by the `self.target` call is a
|
|
196
|
-
kind of generator (sync or async).
|
|
197
|
-
|
|
198
|
-
Returns a (this_iter_rv, new_callable) tuple
|
|
199
|
-
"""
|
|
200
|
-
if inspect.isgenerator(target_rv):
|
|
201
|
-
|
|
202
|
-
def _lock_sync_gen_ctx(gen_src):
|
|
203
|
-
return lambda: next(gen_src)
|
|
204
|
-
|
|
205
|
-
get_next_val = _lock_sync_gen_ctx(target_rv)
|
|
206
|
-
rv = (get_next_val(), get_next_val)
|
|
207
|
-
elif inspect.isasyncgen(target_rv):
|
|
208
|
-
|
|
209
|
-
def _lock_async_gen_ctx(gen_src):
|
|
210
|
-
return lambda: gen_src.__anext__()
|
|
211
|
-
|
|
212
|
-
get_next_val = _lock_async_gen_ctx(target_rv)
|
|
213
|
-
rv = (get_next_val(), get_next_val)
|
|
214
|
-
else:
|
|
215
|
-
rv = (target_rv, None)
|
|
216
|
-
return rv
|
|
217
|
-
|
|
218
193
|
async def _loop_callback_routine(self):
|
|
219
|
-
get_next_val = self.target
|
|
220
|
-
first_iter = True
|
|
221
194
|
try:
|
|
222
195
|
async for _ in self.iterator:
|
|
223
196
|
try:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
(next_val, updated_get_next_val) = self._maybe_detect_generator(
|
|
227
|
-
next_val
|
|
228
|
-
)
|
|
229
|
-
if updated_get_next_val is not None:
|
|
230
|
-
get_next_val = updated_get_next_val
|
|
231
|
-
if inspect.isawaitable(next_val):
|
|
232
|
-
rv = await next_val
|
|
233
|
-
else:
|
|
234
|
-
rv = next_val
|
|
235
|
-
except (StopIteration, StopAsyncIteration):
|
|
197
|
+
rv = await self.target_caller.next()
|
|
198
|
+
except StopAsyncIteration:
|
|
236
199
|
break
|
|
237
200
|
except Exception as err:
|
|
238
201
|
await self.result_fanout.send_exception(err)
|
|
239
|
-
self.exception_callback(self, self.target)
|
|
202
|
+
self.exception_callback(self, self.target_caller.target)
|
|
240
203
|
break
|
|
241
204
|
else:
|
|
242
205
|
await self.result_fanout.send_result(rv)
|
|
243
|
-
first_iter = False
|
|
244
206
|
self.hit_count += 1
|
|
245
207
|
finally:
|
|
246
208
|
# Main loop finished - cancel all watchers
|
|
247
209
|
await self.result_fanout.cancel()
|
|
248
|
-
self.cancel_callback(self, self.target)
|
|
210
|
+
self.cancel_callback(self, self.target_caller.target)
|
|
249
211
|
|
|
250
212
|
async def cancel(self):
|
|
251
213
|
"""Unshedule the timer"""
|
|
@@ -261,7 +223,8 @@ class Timer(typing.Generic[T]):
|
|
|
261
223
|
|
|
262
224
|
def __repr__(self) -> str:
|
|
263
225
|
return (
|
|
264
|
-
f"<{self.__class__.__name__} target={self.target!r}
|
|
226
|
+
f"<{self.__class__.__name__} target={self.target_caller.target!r}"
|
|
227
|
+
f" delay={self.delay!r}"
|
|
265
228
|
f" hit_count={self.hit_count!r}"
|
|
266
229
|
f" exception_callback={self.exception_callback!r}"
|
|
267
230
|
f" cancel_callback={self.cancel_callback!r}"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""This module is responsible for the magic behaviour calling the `target` function."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from collections.abc import Iterator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Caller:
|
|
8
|
+
target = None
|
|
9
|
+
get_next_val = None
|
|
10
|
+
first_call: bool = True
|
|
11
|
+
|
|
12
|
+
def __init__(self, target):
|
|
13
|
+
self.target = target
|
|
14
|
+
|
|
15
|
+
def _wrap_generator(self, maybe_gen):
|
|
16
|
+
if inspect.isgenerator(maybe_gen):
|
|
17
|
+
|
|
18
|
+
def _lock_sync_gen_ctx():
|
|
19
|
+
return lambda: next(maybe_gen)
|
|
20
|
+
|
|
21
|
+
gen_next_val = _lock_sync_gen_ctx()
|
|
22
|
+
elif inspect.isasyncgen(maybe_gen):
|
|
23
|
+
|
|
24
|
+
def _lock_async_gen_ctx():
|
|
25
|
+
return lambda: maybe_gen.__anext__()
|
|
26
|
+
|
|
27
|
+
gen_next_val = _lock_async_gen_ctx()
|
|
28
|
+
elif isinstance(maybe_gen, Iterator):
|
|
29
|
+
|
|
30
|
+
def _lock_iterator_ctx():
|
|
31
|
+
return next(maybe_gen)
|
|
32
|
+
|
|
33
|
+
gen_next_val = _lock_iterator_ctx
|
|
34
|
+
else:
|
|
35
|
+
gen_next_val = None
|
|
36
|
+
return gen_next_val
|
|
37
|
+
|
|
38
|
+
def _setup(self, target):
|
|
39
|
+
"""Configure `get_next_val` to return next value.
|
|
40
|
+
|
|
41
|
+
Return the first such next value.
|
|
42
|
+
"""
|
|
43
|
+
self.get_next_val = self._wrap_generator(target)
|
|
44
|
+
if self.get_next_val:
|
|
45
|
+
# `target` is a generator and we now have the
|
|
46
|
+
# `get_next_val`
|
|
47
|
+
return self.get_next_val()
|
|
48
|
+
assert callable(target), "Otherwise target must be callable"
|
|
49
|
+
target_rv = target()
|
|
50
|
+
self.get_next_val = self._wrap_generator(target_rv)
|
|
51
|
+
if self.get_next_val:
|
|
52
|
+
# Tartget is a callable that returned a generator.
|
|
53
|
+
return self.get_next_val()
|
|
54
|
+
# Otherwise, target is just a callable that returns values
|
|
55
|
+
self.get_next_val = target
|
|
56
|
+
return target_rv
|
|
57
|
+
|
|
58
|
+
async def next(self):
|
|
59
|
+
"""Call `target` one more time."""
|
|
60
|
+
try:
|
|
61
|
+
if self.first_call:
|
|
62
|
+
rv = self._setup(self.target)
|
|
63
|
+
self.first_call = False
|
|
64
|
+
else:
|
|
65
|
+
rv = self.get_next_val()
|
|
66
|
+
except StopIteration as _err:
|
|
67
|
+
raise StopAsyncIteration() from _err
|
|
68
|
+
if inspect.isawaitable(rv):
|
|
69
|
+
rv = await rv
|
|
70
|
+
return rv
|
|
File without changes
|
|
File without changes
|