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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: asynkit
3
- Version: 0.9.1
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() # could be an async callback
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
- # may return an awaitable
158
- def get_processed_data(datagetter):
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 function
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 = get_processed_data(datagetter)
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 = get_processed_data(datagetter)
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 methods.
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
- `coro_sync()` can also be applied as a decorator:
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.coro_sync
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 `ensure_corofunc()` utility can be used when passing callbacks to async
209
- code, to ensure that the callbacks are async. This, with `coro_sync()`, can help integrate
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
- def sync_client(sync_callback):
214
- middleware = AsyncMiddleware(asynkit.ensure_corofunc(sync_callback))
215
- return asynkit.coro_sync(middleware.run())
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 avoid writing special synchronous versions of middleware, or having
219
- _hybrid_ methods which _optionally_ return awaitables:
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() # could be an async callback
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
- # may return an awaitable
134
- def get_processed_data(datagetter):
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 function
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 = get_processed_data(datagetter)
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 = get_processed_data(datagetter)
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 methods.
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
- `coro_sync()` can also be applied as a decorator:
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.coro_sync
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 `ensure_corofunc()` utility can be used when passing callbacks to async
185
- code, to ensure that the callbacks are async. This, with `coro_sync()`, can help integrate
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
- def sync_client(sync_callback):
190
- middleware = AsyncMiddleware(asynkit.ensure_corofunc(sync_callback))
191
- return asynkit.coro_sync(middleware.run())
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 avoid writing special synchronous versions of middleware, or having
195
- _hybrid_ methods which _optionally_ return awaitables:
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.1"
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 ensure_corofunc(
560
- callable: Union[Callable[P, T], Callable[P, Coroutine[Any, Any, T]]]
561
- ) -> Callable[P, Coroutine[Any, Any, T]]:
562
- """Make a callable async, so that the result needs to be awaited.
563
- Useful for adding synchronous callbacs to async code.
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
- callable2 = cast(Callable[P, T], callable)
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