Skip to content

Commit 330f34d

Browse files
update docs and types
1 parent ce2d0d4 commit 330f34d

File tree

1 file changed

+52
-12
lines changed

1 file changed

+52
-12
lines changed

unittests/utility.py

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import (
22
Callable,
33
Coroutine,
4+
Generator,
45
Iterable,
56
AsyncIterator,
67
TypeVar,
@@ -17,32 +18,52 @@
1718

1819

1920
async 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

2532
def 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

3247
class 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

7193
class 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

81110
class 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

88124
class 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

Comments
 (0)