11from typing import (
22 Callable ,
33 Coroutine ,
4+ Generator ,
45 Iterable ,
56 AsyncIterator ,
67 TypeVar ,
1718
1819
1920async def asyncify (iterable : Iterable [T ]) -> AsyncIterator [T ]:
20- """Convert an iterable to async iterable"""
21+ """
22+ Convert an iterable into an async iterable
23+
24+ This is intended to sequence literals like lists to `async` iterators
25+ in order to force usage of `async` code paths. There is no functional
26+ or other advantage otherwise.
27+ """
2128 for value in iterable :
2229 yield value
2330
2431
2532def awaitify (call : Callable [..., T ]) -> Callable [..., Awaitable [T ]]:
26- async def await_wrapper (* args , ** kwargs ):
33+ """
34+ Convert a callable (`foo()`) into an async callable (`await foo()`)
35+
36+ This is intended to convert `lambda` expressions to `async` functions
37+ in order to force usage of `async` code paths. There is no functional
38+ or other advantage otherwise.
39+ """
40+
41+ async def await_wrapper (* args : Any , ** kwargs : Any ) -> T :
2742 return call (* args , ** kwargs )
2843
2944 return await_wrapper
3045
3146
3247class PingPong :
33- """Signal to the event loop which gets returned unchanged"""
48+ """
49+ Signal to the event loop which gets returned unchanged
3450
35- def __await__ (self ):
51+ The coroutine yields to the event loop but is resumed
52+ immediately, without running others in the meantime.
53+ This is mainly useful for debugging the event loop.
54+ """
55+
56+ def __await__ (self ) -> "Generator[PingPong, Any, Any]" :
3657 return (yield self )
3758
3859
39- async def inside_loop ():
60+ async def inside_loop () -> bool :
4061 """Test whether there is an active event loop available"""
4162 signal = PingPong ()
4263 return await signal is signal
4364
4465
45- def sync (test_case : Callable [..., Coroutine [T , Any , Any ]]) -> Callable [..., T ]:
66+ def sync (test_case : Callable [..., Coroutine [None , Any , Any ]]) -> Callable [..., None ]:
4667 """
4768 Mark an ``async def`` test case to be run synchronously
4869
@@ -51,7 +72,7 @@ def sync(test_case: Callable[..., Coroutine[T, Any, Any]]) -> Callable[..., T]:
5172 """
5273
5374 @wraps (test_case )
54- def run_sync (* args : Any , ** kwargs : Any ) -> T :
75+ def run_sync (* args : Any , ** kwargs : Any ) -> None :
5576 coro = test_case (* args , ** kwargs )
5677 try :
5778 event = None
@@ -63,13 +84,21 @@ def run_sync(*args: Any, **kwargs: Any) -> T:
6384 )
6485 except StopIteration as e :
6586 result = e .args [0 ] if e .args else None
87+ assert result is None , f"got '{ result !r} ' expected 'None'"
6688 return result
6789
6890 return run_sync
6991
7092
7193class Schedule :
72- """Signal to the event loop to adopt and run a new coroutine"""
94+ r"""
95+ Signal to the event loop to adopt and run new coroutines
96+
97+ :param coros: The coroutines to start running
98+
99+ In order to communicate with the event loop and start the coroutines,
100+ the :py:class:`Schedule` must be `await`\ ed.
101+ """
73102
74103 def __init__ (self , * coros : Coroutine [Any , Any , Any ]):
75104 self .coros = coros
@@ -79,13 +108,22 @@ def __await__(self):
79108
80109
81110class Switch :
82- """Signal to the event loop to run another coroutine"""
111+ """
112+ Signal to the event loop to run another coroutine
113+
114+ Pauses the coroutine but immediately continues after
115+ all other runnable coroutines of the event loop.
116+ This is similar to the common ``sleep(0)`` function
117+ of regular event loop frameworks.
118+ """
83119
84120 def __await__ (self ):
85121 yield self
86122
87123
88124class Lock :
125+ """Simple lock for exclusive access"""
126+
89127 def __init__ (self ):
90128 self ._owned = False
91129 self ._waiting : list [object ] = []
@@ -95,18 +133,20 @@ async def __aenter__(self):
95133 # wait until it is our turn to take the lock
96134 token = object ()
97135 self ._waiting .append (token )
136+ # a spin-lock should be fine since tests are short anyways
98137 while self ._owned or self ._waiting [0 ] is not token :
99138 await Switch ()
100- # take the lock and remove our wait claim
101- self ._owned = True
139+ # we will take the lock now, remove our wait claim
102140 self ._waiting .pop (0 )
103141 self ._owned = True
104142
105143 async def __aexit__ (self , exc_type : Any , exc_val : Any , exc_tb : Any ):
106144 self ._owned = False
107145
108146
109- def multi_sync (test_case : Callable [..., Coroutine [T , Any , Any ]]) -> Callable [..., T ]:
147+ def multi_sync (
148+ test_case : Callable [..., Coroutine [None , Any , Any ]]
149+ ) -> Callable [..., None ]:
110150 """
111151 Mark an ``async def`` test case to be run synchronously with children
112152
0 commit comments