async-timer 1.0.2__tar.gz → 1.0.3__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.0.2 → async_timer-1.0.3}/PKG-INFO +49 -3
- {async_timer-1.0.2 → async_timer-1.0.3}/README.md +48 -2
- {async_timer-1.0.2 → async_timer-1.0.3}/pyproject.toml +2 -2
- {async_timer-1.0.2 → async_timer-1.0.3}/src/async_timer/timer.py +25 -0
- {async_timer-1.0.2 → async_timer-1.0.3}/src/async_timer/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: async-timer
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.3
|
|
4
4
|
Summary: The missing Python async timer.
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: async,timer
|
|
@@ -48,16 +48,62 @@ This package is particularly useful for tasks like automatically updating caches
|
|
|
48
48
|
|
|
49
49
|
## Example Usage
|
|
50
50
|
|
|
51
|
+
### FastAPI
|
|
52
|
+
|
|
53
|
+
This snippet starts fastapi webserver with the `refresh_db` function being executed every 5 seconds, refresing a shared `DB_CACHE` object.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
|
|
57
|
+
import contextlib
|
|
58
|
+
import time
|
|
59
|
+
|
|
60
|
+
import uvicorn
|
|
61
|
+
from fastapi import FastAPI
|
|
62
|
+
|
|
63
|
+
import async_timer
|
|
64
|
+
|
|
65
|
+
DB_CACHE = {"initialised": False}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def refresh_db():
|
|
69
|
+
global DB_CACHE
|
|
70
|
+
DB_CACHE |= {"initialised": True, "cur_value": time.time()}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@contextlib.asynccontextmanager
|
|
74
|
+
async def lifespan(_app: FastAPI):
|
|
75
|
+
async with async_timer.Timer(delay=5, target=refresh_db) as timer:
|
|
76
|
+
await timer.wait(hit_count=1) # block until the timer triggers at least once
|
|
77
|
+
yield
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
app = FastAPI(lifespan=lifespan)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.get("/")
|
|
84
|
+
async def root():
|
|
85
|
+
return {"message": "Hello World", "db_cache": DB_CACHE}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
if __name__ == "__main__":
|
|
89
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### join()
|
|
51
94
|
```python
|
|
52
95
|
|
|
53
96
|
import async_timer
|
|
54
97
|
|
|
55
|
-
# Simple timer example
|
|
56
98
|
timer = async_timer.Timer(12, target=lambda: 42)
|
|
57
99
|
timer.start()
|
|
58
|
-
val = await timer.join() # `val` will be 42 after 12 seconds
|
|
100
|
+
val = await timer.join() # `val` will be set to 42 after 12 seconds
|
|
101
|
+
```
|
|
59
102
|
|
|
103
|
+
### for loop
|
|
104
|
+
```python
|
|
60
105
|
# Async for loop example
|
|
106
|
+
import async_timer
|
|
61
107
|
import time
|
|
62
108
|
with async_timer.Timer(14, target=time.time) as timer:
|
|
63
109
|
async for time_rv in timer:
|
|
@@ -26,16 +26,62 @@ This package is particularly useful for tasks like automatically updating caches
|
|
|
26
26
|
|
|
27
27
|
## Example Usage
|
|
28
28
|
|
|
29
|
+
### FastAPI
|
|
30
|
+
|
|
31
|
+
This snippet starts fastapi webserver with the `refresh_db` function being executed every 5 seconds, refresing a shared `DB_CACHE` object.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
|
|
35
|
+
import contextlib
|
|
36
|
+
import time
|
|
37
|
+
|
|
38
|
+
import uvicorn
|
|
39
|
+
from fastapi import FastAPI
|
|
40
|
+
|
|
41
|
+
import async_timer
|
|
42
|
+
|
|
43
|
+
DB_CACHE = {"initialised": False}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def refresh_db():
|
|
47
|
+
global DB_CACHE
|
|
48
|
+
DB_CACHE |= {"initialised": True, "cur_value": time.time()}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@contextlib.asynccontextmanager
|
|
52
|
+
async def lifespan(_app: FastAPI):
|
|
53
|
+
async with async_timer.Timer(delay=5, target=refresh_db) as timer:
|
|
54
|
+
await timer.wait(hit_count=1) # block until the timer triggers at least once
|
|
55
|
+
yield
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
app = FastAPI(lifespan=lifespan)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.get("/")
|
|
62
|
+
async def root():
|
|
63
|
+
return {"message": "Hello World", "db_cache": DB_CACHE}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### join()
|
|
29
72
|
```python
|
|
30
73
|
|
|
31
74
|
import async_timer
|
|
32
75
|
|
|
33
|
-
# Simple timer example
|
|
34
76
|
timer = async_timer.Timer(12, target=lambda: 42)
|
|
35
77
|
timer.start()
|
|
36
|
-
val = await timer.join() # `val` will be 42 after 12 seconds
|
|
78
|
+
val = await timer.join() # `val` will be set to 42 after 12 seconds
|
|
79
|
+
```
|
|
37
80
|
|
|
81
|
+
### for loop
|
|
82
|
+
```python
|
|
38
83
|
# Async for loop example
|
|
84
|
+
import async_timer
|
|
39
85
|
import time
|
|
40
86
|
with async_timer.Timer(14, target=time.time) as timer:
|
|
41
87
|
async for time_rv in timer:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "async-timer"
|
|
3
|
-
version = "v1.0.
|
|
3
|
+
version = "v1.0.3"
|
|
4
4
|
description = "The missing Python async timer."
|
|
5
5
|
authors = ["Ilya O. <vrghost@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -66,7 +66,7 @@ line-length = 88
|
|
|
66
66
|
|
|
67
67
|
[tool.ruff.isort]
|
|
68
68
|
order-by-type = true
|
|
69
|
-
known-first-party = ["
|
|
69
|
+
known-first-party = ["async_timer", ]
|
|
70
70
|
forced-separate = ["tests"]
|
|
71
71
|
|
|
72
72
|
[tool.black]
|
|
@@ -61,6 +61,7 @@ def _default_main_loop_exception_callback(*_, **__):
|
|
|
61
61
|
|
|
62
62
|
class Timer(typing.Generic[T]):
|
|
63
63
|
delay: float
|
|
64
|
+
hit_count: int = 0 # Number of times the timer has run so far
|
|
64
65
|
target: TimerMainTaskT[T]
|
|
65
66
|
|
|
66
67
|
result_fanout: FanoutRv[T]
|
|
@@ -111,6 +112,29 @@ class Timer(typing.Generic[T]):
|
|
|
111
112
|
await self.result_fanout.wait()
|
|
112
113
|
) # this can raise `asyncio.CancelledError`
|
|
113
114
|
|
|
115
|
+
async def wait(
|
|
116
|
+
self, /, hit_count: int = None, hits: int = None
|
|
117
|
+
) -> typing.Optional[T]:
|
|
118
|
+
"""
|
|
119
|
+
Wait for the timer to reach certain hit count
|
|
120
|
+
or wait for a certain number of hits.
|
|
121
|
+
|
|
122
|
+
Returns the last generated result IF there was a need to wait.
|
|
123
|
+
Returns `None` otherwise.
|
|
124
|
+
"""
|
|
125
|
+
if hit_count is not None:
|
|
126
|
+
target_hit_count = max(0, hit_count)
|
|
127
|
+
elif hits is not None:
|
|
128
|
+
target_hit_count = self.hit_count + max(0, hits)
|
|
129
|
+
else:
|
|
130
|
+
raise RuntimeError("Please provide either `hits` or `hit_count`")
|
|
131
|
+
need_to_wait_for = target_hit_count - self.hit_count
|
|
132
|
+
last_rv = None
|
|
133
|
+
while need_to_wait_for > 0:
|
|
134
|
+
last_rv = await self.join()
|
|
135
|
+
need_to_wait_for -= 1
|
|
136
|
+
return last_rv
|
|
137
|
+
|
|
114
138
|
async def __anext__(self) -> T:
|
|
115
139
|
try:
|
|
116
140
|
return await self.join()
|
|
@@ -169,6 +193,7 @@ class Timer(typing.Generic[T]):
|
|
|
169
193
|
else:
|
|
170
194
|
await self.result_fanout.send_result(rv)
|
|
171
195
|
first_iter = False
|
|
196
|
+
self.hit_count += 1
|
|
172
197
|
await asyncio.sleep(self.delay)
|
|
173
198
|
finally:
|
|
174
199
|
# Main loop finished - cancel all watchers
|
|
File without changes
|