Skip to content

Commit f94b5c7

Browse files
committed
Added documentation, improved docstrings and rearranged some code
Fixes nedbat#2.
1 parent ad2d069 commit f94b5c7

20 files changed

+1154
-105
lines changed

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest
2+
:target: https://anyio.readthedocs.io/en/latest/?badge=latest
3+
:alt: Documentation
14
.. image:: https://travis-ci.com/agronholm/anyio.svg?branch=master
25
:target: https://travis-ci.com/agronholm/anyio
36
:alt: Build Status
@@ -15,6 +18,9 @@ It bridges the following functionality:
1518
* Synchronization primitives (locks, conditions, events, semaphores, queues)
1619
* High level networking (TCP, UDP and UNIX sockets)
1720

21+
You can even use it together with native libraries from your selected backend in applications.
22+
Doing this in libraries is not advisable however since it limits the usefulness of your library.
23+
1824
.. _asyncio: https://docs.python.org/3/library/asyncio.html
1925
.. _curio: https://github.com/dabeaz/curio
2026
.. _trio: https://github.com/python-trio/trio

anyio/__init__.py

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,41 @@
2323
_local = threading.local()
2424

2525

26+
#
27+
# Event loop
28+
#
29+
30+
def run(func: Callable[..., Coroutine[Any, Any, T_Retval]], *args,
31+
backend: str = BACKENDS[0], backend_options: Optional[Dict[str, Any]] = None) -> T_Retval:
32+
"""
33+
Run the given coroutine function in an asynchronous event loop.
34+
35+
The current thread must not be already running an event loop.
36+
37+
:param func: a coroutine function
38+
:param args: positional arguments to ``func``
39+
:param backend: name of the asynchronous event loop implementation – one of ``asyncio``,
40+
``curio`` and ``trio``
41+
:param backend_options: keyword arguments to call the backend ``run()`` implementation with
42+
:return: the return value of the coroutine function
43+
:raises RuntimeError: if an asynchronous event loop is already running in this thread
44+
:raises LookupError: if the named backend is not found
45+
46+
"""
47+
asynclib_name = detect_running_asynclib()
48+
if asynclib_name:
49+
raise RuntimeError('Already running {} in this thread'.format(asynclib_name))
50+
51+
try:
52+
asynclib = import_module('{}._backends.{}'.format(__name__, backend))
53+
except ImportError as exc:
54+
raise LookupError('No such backend: {}'.format(backend)) from exc
55+
56+
backend_options = backend_options or {}
57+
with claim_current_thread(asynclib):
58+
return asynclib.run(func, *args, **backend_options)
59+
60+
2661
@contextmanager
2762
def claim_current_thread(asynclib) -> None:
2863
assert ismodule(asynclib)
@@ -33,11 +68,24 @@ def claim_current_thread(asynclib) -> None:
3368
reset_detected_asynclib()
3469

3570

36-
def reset_detected_asynclib():
71+
def reset_detected_asynclib() -> None:
72+
"""
73+
Reset the cached information about the currently running async library.
74+
75+
This is only needed in case you need to run AnyIO code on two or more different async libraries
76+
using their native ``run()`` functions one after another in the same thread.
77+
78+
"""
3779
_local.__dict__.clear()
3880

3981

4082
def detect_running_asynclib() -> Optional[str]:
83+
"""
84+
Return the name of the asynchronous framework running in the current thread.
85+
86+
:return: the name of the framework, or ``None`` if no supported framework is running
87+
88+
"""
4189
if 'trio' in sys.modules:
4290
from trio.hazmat import current_trio_token
4391
try:
@@ -76,37 +124,6 @@ def _get_asynclib():
76124
return _local.asynclib
77125

78126

79-
def run(func: Callable[..., Coroutine[Any, Any, T_Retval]], *args,
80-
backend: str = BACKENDS[0], backend_options: Optional[Dict[str, Any]] = None) -> T_Retval:
81-
"""
82-
Run the given coroutine function in an asynchronous event loop.
83-
84-
The current thread must not be already running an event loop.
85-
86-
:param func: a coroutine function
87-
:param args: positional arguments to ``func``
88-
:param backend: name of the asynchronous event loop implementation – one of ``asyncio``,
89-
``curio`` and ``trio``
90-
:param backend_options: keyword arguments to call the backend ``run()`` implementation with
91-
:return: the return value of the coroutine function
92-
:raises RuntimeError: if an asynchronous event loop is already running in this thread
93-
:raises LookupError: if the named backend is not found
94-
95-
"""
96-
asynclib_name = detect_running_asynclib()
97-
if asynclib_name:
98-
raise RuntimeError('Already running {} in this thread'.format(asynclib_name))
99-
100-
try:
101-
asynclib = import_module('{}._backends.{}'.format(__name__, backend))
102-
except ImportError as exc:
103-
raise LookupError('No such backend: {}'.format(backend)) from exc
104-
105-
backend_options = backend_options or {}
106-
with claim_current_thread(asynclib):
107-
return asynclib.run(func, *args, **backend_options)
108-
109-
110127
def is_in_event_loop_thread() -> bool:
111128
"""
112129
Determine whether the current thread is running a recognized asynchronous event loop.
@@ -117,6 +134,11 @@ def is_in_event_loop_thread() -> bool:
117134
return detect_running_asynclib() is not None
118135

119136

137+
#
138+
# Miscellaneous
139+
#
140+
141+
120142
def finalize(resource: T_Agen) -> 'typing.AsyncContextManager[T_Agen]':
121143
"""
122144
Return a context manager that automatically closes an asynchronous resource on exit.
@@ -130,10 +152,6 @@ def finalize(resource: T_Agen) -> 'typing.AsyncContextManager[T_Agen]':
130152
return _get_asynclib().finalize(resource)
131153

132154

133-
#
134-
# Timeouts and cancellation
135-
#
136-
137155
def sleep(delay: float) -> Awaitable[None]:
138156
"""
139157
Pause the current task for the specified duration.
@@ -144,6 +162,11 @@ def sleep(delay: float) -> Awaitable[None]:
144162
return _get_asynclib().sleep(delay)
145163

146164

165+
#
166+
# Timeouts and cancellation
167+
#
168+
169+
147170
def open_cancel_scope(*, shield: bool = False) -> 'typing.AsyncContextManager[CancelScope]':
148171
"""
149172
Open a cancel scope.
@@ -190,7 +213,15 @@ def move_on_after(delay: Optional[float], *,
190213
return _get_asynclib().move_on_after(delay, shield=shield)
191214

192215

193-
def current_effective_deadline() -> Awaitable[float]:
216+
def current_effective_deadline() -> Coroutine[Any, Any, float]:
217+
"""
218+
Return the nearest deadline among all the cancel scopes effective for the current task.
219+
220+
:return: a clock value from the event loop's internal clock (``float('inf')`` if there is no
221+
deadline in effect)
222+
:rtype: float
223+
224+
"""
194225
return _get_asynclib().current_effective_deadline()
195226

196227

@@ -252,6 +283,7 @@ def aopen(file: Union[str, Path, int], mode: str = 'r', buffering: int = -1,
252283
The arguments are exactly the same as for the builtin :func:`open`.
253284
254285
:return: an asynchronous file object
286+
:rtype: AsyncFile
255287
256288
"""
257289
if isinstance(file, Path):
@@ -261,7 +293,7 @@ def aopen(file: Union[str, Path, int], mode: str = 'r', buffering: int = -1,
261293

262294

263295
#
264-
# Networking
296+
# Sockets and networking
265297
#
266298

267299
def wait_socket_readable(sock: Union[socket.SocketType, ssl.SSLSocket]) -> Awaitable[None]:
@@ -512,7 +544,7 @@ def create_queue(capacity: int) -> Queue:
512544

513545

514546
#
515-
# Signal handling
547+
# Operating system signals
516548
#
517549

518550
def receive_signals(*signals: int) -> 'typing.ContextManager[typing.AsyncIterator[int]]':

anyio/_backends/asyncio.py

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def get_running_loop():
103103

104104

105105
#
106-
# Main entry point
106+
# Event loop
107107
#
108108

109109
def run(func: Callable[..., T_Retval], *args, debug: bool = False,
@@ -126,6 +126,18 @@ async def wrapper():
126126
return retval
127127

128128

129+
#
130+
# Miscellaneous
131+
#
132+
133+
finalize = aclosing
134+
135+
136+
async def sleep(delay: float) -> None:
137+
check_cancelled()
138+
await asyncio.sleep(delay)
139+
140+
129141
#
130142
# Timeouts and cancellation
131143
#
@@ -441,7 +453,7 @@ async def aopen(*args, **kwargs):
441453

442454

443455
#
444-
# Networking
456+
# Sockets and networking
445457
#
446458

447459

@@ -577,7 +589,7 @@ def put(self, item):
577589

578590

579591
#
580-
# Signal handling
592+
# Operating system signals
581593
#
582594

583595
@asynccontextmanager
@@ -605,17 +617,6 @@ async def process_signal_queue():
605617
loop.remove_signal_handler(sig)
606618

607619

608-
#
609-
# Miscellaneous functions
610-
#
611-
612-
async def sleep(delay: float) -> None:
613-
check_cancelled()
614-
await asyncio.sleep(delay)
615-
616-
finalize = aclosing
617-
618-
619620
#
620621
# Testing and debugging
621622
#

anyio/_backends/curio.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717
#
18-
# Main entry point
18+
# Event loop
1919
#
2020

2121
def run(func: Callable[..., T_Retval], *args, **curio_options) -> T_Retval:
@@ -34,6 +34,18 @@ async def wrapper():
3434
return retval
3535

3636

37+
#
38+
# Miscellaneous functions
39+
#
40+
41+
finalize = curio.meta.finalize
42+
43+
44+
async def sleep(seconds: int):
45+
await check_cancelled()
46+
await curio.sleep(seconds)
47+
48+
3749
#
3850
# Timeouts and cancellation
3951
#
@@ -274,7 +286,7 @@ async def aopen(*args, **kwargs):
274286

275287

276288
#
277-
# Networking
289+
# Sockets and networking
278290
#
279291

280292
class Socket(BaseSocket):
@@ -381,7 +393,7 @@ async def put(self, item):
381393

382394

383395
#
384-
# Signal handling
396+
# Operating system signals
385397
#
386398

387399
@asynccontextmanager
@@ -391,18 +403,6 @@ async def receive_signals(*signals: int):
391403
await yield_(queue)
392404

393405

394-
#
395-
# Miscellaneous functions
396-
#
397-
398-
async def sleep(seconds: int):
399-
await check_cancelled()
400-
await curio.sleep(seconds)
401-
402-
403-
finalize = curio.meta.finalize
404-
405-
406406
#
407407
# Testing and debugging
408408
#

0 commit comments

Comments
 (0)