asynkit 0.9.1__tar.gz → 0.9.2__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.
- {asynkit-0.9.1 → asynkit-0.9.2}/PKG-INFO +20 -35
- {asynkit-0.9.1 → asynkit-0.9.2}/README.md +19 -34
- {asynkit-0.9.1 → asynkit-0.9.2}/pyproject.toml +2 -1
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/coroutine.py +19 -37
- {asynkit-0.9.1 → asynkit-0.9.2}/LICENSE +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/__init__.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/compat.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/experimental/__init__.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/experimental/anyio.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/loop/__init__.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/loop/default.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/loop/eventloop.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/loop/extensions.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/loop/schedulingloop.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/loop/types.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/monitor.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/py.typed +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/scheduling.py +0 -0
- {asynkit-0.9.1 → asynkit-0.9.2}/src/asynkit/tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: asynkit
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.2
|
|
4
4
|
Summary: Async toolkit for advanced scheduling
|
|
5
5
|
Home-page: https://github.com/kristjanvalur/py-asynkit
|
|
6
6
|
License: MIT
|
|
@@ -141,7 +141,7 @@ of an event loop. As long as the code doesn't _block_ (await unfinished _future
|
|
|
141
141
|
|
|
142
142
|
```python
|
|
143
143
|
async def async_get_processed_data(datagetter):
|
|
144
|
-
data = datagetter() #
|
|
144
|
+
data = datagetter() # an optionally async callback
|
|
145
145
|
data = await data if isawaitable(data) else data
|
|
146
146
|
return process_data(data)
|
|
147
147
|
|
|
@@ -154,11 +154,11 @@ def sync_get_processed_data(datagetter):
|
|
|
154
154
|
This sort of code might previously have been written thus:
|
|
155
155
|
|
|
156
156
|
```python
|
|
157
|
-
#
|
|
158
|
-
def
|
|
157
|
+
# A hybrid function, _may_ return an _awaitable_
|
|
158
|
+
def hybrid_get_processed_data(datagetter):
|
|
159
159
|
data = datagetter()
|
|
160
160
|
if isawaitable(data):
|
|
161
|
-
# return an awaitable helper
|
|
161
|
+
# return an awaitable helper closure
|
|
162
162
|
async def helper():
|
|
163
163
|
data = await data
|
|
164
164
|
return process_data(data)
|
|
@@ -168,12 +168,12 @@ def get_processed_data(datagetter):
|
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
async def async_get_processed_data(datagetter):
|
|
171
|
-
r =
|
|
171
|
+
r = hybrid_get_processed_data(datagetter)
|
|
172
172
|
return await r if isawaitable(r) else r
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
def sync_get_processed_data(datagetter):
|
|
176
|
-
r =
|
|
176
|
+
r = hybrid_get_processed_data(datagetter)
|
|
177
177
|
if isawaitable(r):
|
|
178
178
|
raise RuntimeError("callbacks failed to run synchronously")
|
|
179
179
|
return r
|
|
@@ -182,7 +182,7 @@ def sync_get_processed_data(datagetter):
|
|
|
182
182
|
The above pattern, writing async methods as sync and returning async helpers,
|
|
183
183
|
is common in library code which needs to work both in synchronous and asynchronous
|
|
184
184
|
context. Needless to say, it is very convoluted, hard to debug and contains a lot
|
|
185
|
-
of code duplication where the same logic is repeated inside async helper
|
|
185
|
+
of code duplication where the same logic is repeated inside async helper closures.
|
|
186
186
|
|
|
187
187
|
Using `coro_sync()` it is possible to write the entire logic as `async` methods and
|
|
188
188
|
then simply fail if the code tries to invoke any truly async operations.
|
|
@@ -190,11 +190,11 @@ If the invoked coroutine blocks, a `SynchronousError` is raised _from_ a `Synchr
|
|
|
190
190
|
contains a traceback. This makes it easy to pinpoint the location in the code where the
|
|
191
191
|
async code blocked. If the code tries to access the event loop, e.g. by creating a `Task`, a `RuntimeError` will be raised.
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
The `syncfunction()` decorator can be used to automatically wrap an async function
|
|
194
|
+
so that it is executed using `coro_sync()`:
|
|
195
195
|
|
|
196
196
|
```pycon
|
|
197
|
-
>>> @asynkit.
|
|
197
|
+
>>> @asynkit.syncfunction
|
|
198
198
|
... async def sync_function():
|
|
199
199
|
... async def async_function():
|
|
200
200
|
... return "look, no async!"
|
|
@@ -205,34 +205,19 @@ async code blocked. If the code tries to access the event loop, e.g. by creatin
|
|
|
205
205
|
>>>
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
-
the `
|
|
209
|
-
code, to
|
|
210
|
-
synchronous code with async middleware:
|
|
208
|
+
the `asyncfunction()` utility can be used when passing synchronous callbacks to async
|
|
209
|
+
code, to make them async. This, along with `syncfunction()` and `coro_sync()`,
|
|
210
|
+
can be used to integrate synchronous code with async middleware:
|
|
211
211
|
|
|
212
212
|
```python
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
@asynkit.syncfunction
|
|
214
|
+
async def sync_client(sync_callback):
|
|
215
|
+
middleware = AsyncMiddleware(asynkit.asyncfunction(sync_callback))
|
|
216
|
+
return await middleware.run()
|
|
216
217
|
```
|
|
217
218
|
|
|
218
|
-
Using this pattern, one can
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
```python
|
|
222
|
-
# the hybrid method antipattern
|
|
223
|
-
def hybrid_method(callable):
|
|
224
|
-
"""A hybrid method, possibly returning awaitable"""
|
|
225
|
-
data = callable()
|
|
226
|
-
if isawaitable(data):
|
|
227
|
-
# inner async method with duplicate code
|
|
228
|
-
async def async_helper(data):
|
|
229
|
-
data2 = await data
|
|
230
|
-
return process_data(data2)
|
|
231
|
-
|
|
232
|
-
return async_helper
|
|
233
|
-
else:
|
|
234
|
-
return process_data(data) # duplicate code
|
|
235
|
-
```
|
|
219
|
+
Using this pattern, one can write the middleware completely async, make it also work
|
|
220
|
+
for synchronous code, while avoiding the hybrid function _antipattern._
|
|
236
221
|
|
|
237
222
|
## `CoroStart`
|
|
238
223
|
|
|
@@ -117,7 +117,7 @@ of an event loop. As long as the code doesn't _block_ (await unfinished _future
|
|
|
117
117
|
|
|
118
118
|
```python
|
|
119
119
|
async def async_get_processed_data(datagetter):
|
|
120
|
-
data = datagetter() #
|
|
120
|
+
data = datagetter() # an optionally async callback
|
|
121
121
|
data = await data if isawaitable(data) else data
|
|
122
122
|
return process_data(data)
|
|
123
123
|
|
|
@@ -130,11 +130,11 @@ def sync_get_processed_data(datagetter):
|
|
|
130
130
|
This sort of code might previously have been written thus:
|
|
131
131
|
|
|
132
132
|
```python
|
|
133
|
-
#
|
|
134
|
-
def
|
|
133
|
+
# A hybrid function, _may_ return an _awaitable_
|
|
134
|
+
def hybrid_get_processed_data(datagetter):
|
|
135
135
|
data = datagetter()
|
|
136
136
|
if isawaitable(data):
|
|
137
|
-
# return an awaitable helper
|
|
137
|
+
# return an awaitable helper closure
|
|
138
138
|
async def helper():
|
|
139
139
|
data = await data
|
|
140
140
|
return process_data(data)
|
|
@@ -144,12 +144,12 @@ def get_processed_data(datagetter):
|
|
|
144
144
|
|
|
145
145
|
|
|
146
146
|
async def async_get_processed_data(datagetter):
|
|
147
|
-
r =
|
|
147
|
+
r = hybrid_get_processed_data(datagetter)
|
|
148
148
|
return await r if isawaitable(r) else r
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
def sync_get_processed_data(datagetter):
|
|
152
|
-
r =
|
|
152
|
+
r = hybrid_get_processed_data(datagetter)
|
|
153
153
|
if isawaitable(r):
|
|
154
154
|
raise RuntimeError("callbacks failed to run synchronously")
|
|
155
155
|
return r
|
|
@@ -158,7 +158,7 @@ def sync_get_processed_data(datagetter):
|
|
|
158
158
|
The above pattern, writing async methods as sync and returning async helpers,
|
|
159
159
|
is common in library code which needs to work both in synchronous and asynchronous
|
|
160
160
|
context. Needless to say, it is very convoluted, hard to debug and contains a lot
|
|
161
|
-
of code duplication where the same logic is repeated inside async helper
|
|
161
|
+
of code duplication where the same logic is repeated inside async helper closures.
|
|
162
162
|
|
|
163
163
|
Using `coro_sync()` it is possible to write the entire logic as `async` methods and
|
|
164
164
|
then simply fail if the code tries to invoke any truly async operations.
|
|
@@ -166,11 +166,11 @@ If the invoked coroutine blocks, a `SynchronousError` is raised _from_ a `Synchr
|
|
|
166
166
|
contains a traceback. This makes it easy to pinpoint the location in the code where the
|
|
167
167
|
async code blocked. If the code tries to access the event loop, e.g. by creating a `Task`, a `RuntimeError` will be raised.
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
The `syncfunction()` decorator can be used to automatically wrap an async function
|
|
170
|
+
so that it is executed using `coro_sync()`:
|
|
171
171
|
|
|
172
172
|
```pycon
|
|
173
|
-
>>> @asynkit.
|
|
173
|
+
>>> @asynkit.syncfunction
|
|
174
174
|
... async def sync_function():
|
|
175
175
|
... async def async_function():
|
|
176
176
|
... return "look, no async!"
|
|
@@ -181,34 +181,19 @@ async code blocked. If the code tries to access the event loop, e.g. by creatin
|
|
|
181
181
|
>>>
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
the `
|
|
185
|
-
code, to
|
|
186
|
-
synchronous code with async middleware:
|
|
184
|
+
the `asyncfunction()` utility can be used when passing synchronous callbacks to async
|
|
185
|
+
code, to make them async. This, along with `syncfunction()` and `coro_sync()`,
|
|
186
|
+
can be used to integrate synchronous code with async middleware:
|
|
187
187
|
|
|
188
188
|
```python
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
@asynkit.syncfunction
|
|
190
|
+
async def sync_client(sync_callback):
|
|
191
|
+
middleware = AsyncMiddleware(asynkit.asyncfunction(sync_callback))
|
|
192
|
+
return await middleware.run()
|
|
192
193
|
```
|
|
193
194
|
|
|
194
|
-
Using this pattern, one can
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
```python
|
|
198
|
-
# the hybrid method antipattern
|
|
199
|
-
def hybrid_method(callable):
|
|
200
|
-
"""A hybrid method, possibly returning awaitable"""
|
|
201
|
-
data = callable()
|
|
202
|
-
if isawaitable(data):
|
|
203
|
-
# inner async method with duplicate code
|
|
204
|
-
async def async_helper(data):
|
|
205
|
-
data2 = await data
|
|
206
|
-
return process_data(data2)
|
|
207
|
-
|
|
208
|
-
return async_helper
|
|
209
|
-
else:
|
|
210
|
-
return process_data(data) # duplicate code
|
|
211
|
-
```
|
|
195
|
+
Using this pattern, one can write the middleware completely async, make it also work
|
|
196
|
+
for synchronous code, while avoiding the hybrid function _antipattern._
|
|
212
197
|
|
|
213
198
|
## `CoroStart`
|
|
214
199
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "asynkit"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.2"
|
|
4
4
|
description = "Async toolkit for advanced scheduling"
|
|
5
5
|
authors = ["Kristján Valur Jónsson <sweskman@gmail.com>"]
|
|
6
6
|
repository = "https://github.com/kristjanvalur/py-asynkit"
|
|
@@ -75,6 +75,7 @@ module = [
|
|
|
75
75
|
"asynkit.loop.eventloop",
|
|
76
76
|
"asynkit.compat",
|
|
77
77
|
"asynkit.tools",
|
|
78
|
+
"tests.test_coro",
|
|
78
79
|
]
|
|
79
80
|
warn_unused_ignores=false
|
|
80
81
|
|
|
@@ -4,7 +4,6 @@ import inspect
|
|
|
4
4
|
import sys
|
|
5
5
|
import types
|
|
6
6
|
from contextvars import Context, copy_context
|
|
7
|
-
from inspect import iscoroutinefunction
|
|
8
7
|
from types import FrameType
|
|
9
8
|
from typing import (
|
|
10
9
|
Any,
|
|
@@ -34,7 +33,6 @@ __all__ = [
|
|
|
34
33
|
"coro_eager",
|
|
35
34
|
"func_eager",
|
|
36
35
|
"eager",
|
|
37
|
-
"ensure_corofunc",
|
|
38
36
|
"coro_get_frame",
|
|
39
37
|
"coro_is_new",
|
|
40
38
|
"coro_is_suspended",
|
|
@@ -43,6 +41,8 @@ __all__ = [
|
|
|
43
41
|
"coro_sync",
|
|
44
42
|
"SynchronousError",
|
|
45
43
|
"SynchronousAbort",
|
|
44
|
+
"asyncfunction",
|
|
45
|
+
"syncfunction",
|
|
46
46
|
]
|
|
47
47
|
|
|
48
48
|
PYTHON_37 = sys.version_info.major == 3 and sys.version_info.minor == 7
|
|
@@ -505,36 +505,10 @@ def awaitmethod(
|
|
|
505
505
|
return wrapper
|
|
506
506
|
|
|
507
507
|
|
|
508
|
-
@overload
|
|
509
508
|
def coro_sync(coro: Coroutine[Any, Any, T]) -> T:
|
|
510
|
-
...
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
@overload
|
|
514
|
-
def coro_sync(coro: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, T]:
|
|
515
|
-
...
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
def coro_sync(
|
|
519
|
-
coro: Union[Coroutine[Any, Any, T], Callable[P, Coroutine[Any, Any, T]]]
|
|
520
|
-
) -> Union[T, Callable[P, T]]:
|
|
521
|
-
|
|
522
509
|
"""Runs a corouting synchronlously. If the coroutine blocks, a
|
|
523
510
|
SynchronousError is raised.
|
|
524
|
-
|
|
525
|
-
Can also be used as a decorator for an async function.
|
|
526
511
|
"""
|
|
527
|
-
if iscoroutinefunction(coro):
|
|
528
|
-
# we are a decorator
|
|
529
|
-
coro2 = cast(Callable[..., Coroutine[Any, Any, T]], coro)
|
|
530
|
-
|
|
531
|
-
@functools.wraps(coro)
|
|
532
|
-
def helper(*args: Any, **kwargs: Any) -> T:
|
|
533
|
-
return coro_sync(coro2(*args, **kwargs))
|
|
534
|
-
|
|
535
|
-
return helper
|
|
536
|
-
coro = cast(Coroutine[Any, Any, T], coro)
|
|
537
|
-
# We are running a coroutine synchronously
|
|
538
512
|
start = CoroStart[T](coro)
|
|
539
513
|
if start.done():
|
|
540
514
|
return start.result()
|
|
@@ -556,17 +530,25 @@ def coro_sync(
|
|
|
556
530
|
pass
|
|
557
531
|
|
|
558
532
|
|
|
559
|
-
def
|
|
560
|
-
|
|
561
|
-
)
|
|
562
|
-
"""
|
|
563
|
-
|
|
533
|
+
def syncfunction(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, T]:
|
|
534
|
+
"""Make an async function synchronous, by invoking
|
|
535
|
+
`coro_sync()` on its coroutine. Useful as a decorator.
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
@functools.wraps(func)
|
|
539
|
+
def helper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
540
|
+
return coro_sync(func(*args, **kwargs))
|
|
541
|
+
|
|
542
|
+
return helper
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def asyncfunction(func: Callable[P, T]) -> Callable[P, Coroutine[Any, Any, T]]:
|
|
546
|
+
"""Make a regular function async, so that its result needs to be awaited.
|
|
547
|
+
Useful when providing synchronous callbacks to async code.
|
|
564
548
|
"""
|
|
565
|
-
if inspect.iscoroutinefunction(callable):
|
|
566
|
-
return callable
|
|
567
549
|
|
|
550
|
+
@functools.wraps(func)
|
|
568
551
|
async def helper(*args: P.args, **kwargs: P.kwargs) -> T:
|
|
569
|
-
|
|
570
|
-
return callable2(*args, **kwargs)
|
|
552
|
+
return func(*args, **kwargs)
|
|
571
553
|
|
|
572
554
|
return helper
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|