Skip to content

Commit 01f02cf

Browse files
authored
Incorporated EP2025 sprint feedback and added a new section (#955)
Also fixed a number of broken documentation links.
1 parent d896480 commit 01f02cf

18 files changed

+750
-48
lines changed

README.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
:alt: Gitter chat
1313

1414
AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or
15-
trio_. It implements trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
16-
with the native SC of trio itself.
15+
Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
16+
with the native SC of Trio itself.
1717

1818
Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or
19-
trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
19+
Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
2020
refactoring necessary. It will blend in with the native libraries of your chosen backend.
2121

22+
To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it
23+
`here <https://anyio.readthedocs.io/en/stable/why.html>`_.
24+
2225
Documentation
2326
-------------
2427

@@ -49,7 +52,7 @@ AnyIO also comes with its own pytest_ plugin which also supports asynchronous fi
4952
It even works with the popular Hypothesis_ library.
5053

5154
.. _asyncio: https://docs.python.org/3/library/asyncio.html
52-
.. _trio: https://github.com/python-trio/trio
55+
.. _Trio: https://github.com/python-trio/trio
5356
.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
5457
.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning
5558
.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs

docs/api.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ Streams and stream wrappers
131131
.. autodata:: anyio.abc.AnyByteStreamConnectable
132132

133133
.. autoclass:: anyio.streams.buffered.BufferedByteReceiveStream
134+
.. autoclass:: anyio.streams.buffered.BufferedByteStream
134135
.. autoclass:: anyio.streams.file.FileStreamAttribute
135136
.. autoclass:: anyio.streams.file.FileReadStream
136137
.. autoclass:: anyio.streams.file.FileWriteStream
@@ -172,6 +173,8 @@ Sockets and networking
172173
.. autoclass:: anyio.abc.UDPSocket()
173174
.. autoclass:: anyio.abc.ConnectedUDPSocket()
174175
.. autoclass:: anyio.abc.UNIXSocketStream()
176+
.. autoclass:: anyio.abc.UNIXDatagramSocket()
177+
.. autoclass:: anyio.abc.ConnectedUNIXDatagramSocket()
175178
.. autoclass:: anyio.TCPConnectable
176179
.. autoclass:: anyio.UNIXConnectable
177180

docs/basics.rst

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ The simplest possible AnyIO program looks like this::
3434

3535
run(main)
3636

37-
This will run the program above on the default backend (asyncio). To run it on another
38-
supported backend, say Trio_, you can use the ``backend`` argument, like so::
37+
This will run the program above on the default backend (asyncio). To explicitly specify
38+
which backend to run on, you can use the ``backend`` argument, like so::
3939

4040
run(main, backend='trio')
41+
run(main, backend='asyncio')
4142

4243
But AnyIO code is not required to be run via :func:`run`. You can just as well use the
4344
native ``run()`` function of the backend library::
@@ -63,6 +64,14 @@ native ``run()`` function of the backend library::
6364
Backend specific options
6465
------------------------
6566

67+
Any options exclusive to a specific backend can be passed as keyword arguments to
68+
:func:`run`::
69+
70+
run(main, backend="asyncio", debug=True)
71+
run(main, backend="trio", restrict_keyboard_interrupt_to_checkpoints=True)
72+
73+
Here is the list of supported options for each backend:
74+
6675
**Asyncio**:
6776

6877
* options covered in the documentation of :class:`asyncio.Runner`
@@ -95,5 +104,7 @@ asynchronous framework of your choice. There are a few rules to keep in mind how
95104
* Threads spawned outside of AnyIO cannot use :func:`.from_thread.run` to call
96105
asynchronous code
97106

107+
.. seealso:: :ref:`asyncio cancellation`
108+
98109
.. _virtualenv: https://docs.python-guide.org/dev/virtualenvs/
99110
.. _Trio: https://github.com/python-trio/trio

docs/cancellation.rst

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,34 @@ If the task is just starting, it will run until it first tries to run an operati
1515
requiring waiting, such as :func:`~sleep`.
1616

1717
A task group contains its own cancel scope. The entire task group can be cancelled by
18-
cancelling this scope.
18+
cancelling this scope::
19+
20+
from anyio import create_task_group, get_cancelled_exc_class, sleep, run
21+
22+
23+
async def waiter(index: int):
24+
try:
25+
await sleep(1)
26+
except get_cancelled_exc_class():
27+
print(f"Waiter {index} cancelled")
28+
raise
29+
30+
31+
async def taskfunc():
32+
async with create_task_group() as tg:
33+
# Start a couple tasks and wait until they are blocked
34+
tg.start_soon(waiter, 1)
35+
tg.start_soon(waiter, 2)
36+
await sleep(0.1)
37+
38+
# Cancel the scope and exit the task group
39+
tg.cancel_scope.cancel()
40+
41+
run(taskfunc)
42+
43+
# Output:
44+
# Waiter 1 cancelled
45+
# Waiter 2 cancelled
1946

2047
.. _Trio: https://trio.readthedocs.io/en/latest/reference-core.html
2148
#cancellation-and-timeouts
@@ -126,6 +153,7 @@ itself is being cancelled. Shielding a cancel scope is often best combined with
126153

127154
run(main)
128155

156+
.. _finalization:
129157

130158
Finalization
131159
------------

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ The manual
2626
testing
2727
api
2828
migration
29+
why
2930
faq
3031
support
3132
contributing

docs/networking.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -255,12 +255,14 @@ AnyIO stream or socket listener. For that, various class methods exist:
255255
* :meth:`.abc.UNIXDatagramSocket.from_socket`
256256
* :meth:`.abc.ConnectedUNIXDatagramSocket.from_socket`
257257

258+
.. _connectables:
259+
258260
Abstracting remote connections using Connectables
259261
-------------------------------------------------
260262

261263
AnyIO offers a hierarchy of classes implementing either the
262-
:class:`ObjectStreamConnectable` or :class:`ByteStreamConnectable` interfaces which
263-
lets developers abstract out the connection mechanism for network clients.
264+
:class:`.abc.ObjectStreamConnectable` or :class:`.abc.ByteStreamConnectable` interfaces
265+
which lets developers abstract out the connection mechanism for network clients.
264266
For example, you could create a network client class like this::
265267

266268
from os import PathLike
@@ -289,7 +291,7 @@ For example, you could create a network client class like this::
289291

290292
Here's a dissection of the type annotation for ``connectable``:
291293

292-
* :class:`ByteStreamConnectable`: allows for any arbitrary bytestream connectable
294+
* :class:`.abc.ByteStreamConnectable`: allows for any arbitrary bytestream connectable
293295
* ``tuple[str, int]``: TCP host/port
294296
* ``str | bytes | PathLike[str]``: file system path to a UNIX socket
295297

docs/subinterpreters.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Running functions in a worker interpreter makes sense when:
2424
If the code you're trying to run only does blocking network I/O, or file I/O, then
2525
you're better off using :doc:`worker thread <threads>` instead.
2626

27-
This is done by using :func:`.interpreter.run_sync`::
27+
This is done by using :func:`.to_interpreter.run_sync`::
2828

2929
import time
3030

docs/synchronization.rst

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ notifying other tasks and guarding access to shared resources.
1313
Events
1414
------
1515

16-
Events are used to notify tasks that something they've been waiting to happen has
17-
happened. An event object can have multiple listeners and they are all notified when the
18-
event is triggered.
16+
Events (:class:`Event`) are used to notify tasks that something they've been waiting to
17+
happen has happened. An event object can have multiple listeners and they are all
18+
notified when the event is triggered.
1919

2020
Example::
2121

@@ -35,17 +35,20 @@ Example::
3535

3636
run(main)
3737

38+
# Output:
39+
# Received notification!
40+
3841
.. note:: Unlike standard library Events, AnyIO events cannot be reused, and must be
3942
replaced instead. This practice prevents a class of race conditions, and matches the
4043
semantics of the Trio library.
4144

4245
Semaphores
4346
----------
4447

45-
Semaphores are used for limiting access to a shared resource. A semaphore starts with a
46-
maximum value, which is decremented each time the semaphore is acquired by a task and
47-
incremented when it is released. If the value drops to zero, any attempt to acquire the
48-
semaphore will block until another task frees it.
48+
Semaphores (:class:`Semaphore`) are used for limiting access to a shared resource. A
49+
semaphore starts with a maximum value, which is decremented each time the semaphore is
50+
acquired by a task and incremented when it is released. If the value drops to zero, any
51+
attempt to acquire the semaphore will block until another task frees it.
4952

5053
Example::
5154

@@ -54,7 +57,7 @@ Example::
5457

5558
async def use_resource(tasknum, semaphore):
5659
async with semaphore:
57-
print('Task number', tasknum, 'is now working with the shared resource')
60+
print(f"Task number {tasknum} is now working with the shared resource")
5861
await sleep(1)
5962

6063

@@ -66,6 +69,18 @@ Example::
6669

6770
run(main)
6871

72+
# Output:
73+
# Task number 0 is now working with the shared resource
74+
# Task number 1 is now working with the shared resource
75+
# Task number 2 is now working with the shared resource
76+
# Task number 3 is now working with the shared resource
77+
# Task number 4 is now working with the shared resource
78+
# Task number 5 is now working with the shared resource
79+
# Task number 6 is now working with the shared resource
80+
# Task number 7 is now working with the shared resource
81+
# Task number 8 is now working with the shared resource
82+
# Task number 9 is now working with the shared resource
83+
6984
.. tip:: If the performance of semaphores is critical for you, you could pass
7085
``fast_acquire=True`` to :class:`Semaphore`. This has the effect of skipping the
7186
:func:`~.lowlevel.cancel_shielded_checkpoint` call in :meth:`Semaphore.acquire` if
@@ -76,9 +91,9 @@ Example::
7691
Locks
7792
-----
7893

79-
Locks are used to guard shared resources to ensure sole access to a single task at once.
80-
They function much like semaphores with a maximum value of 1, except that only the task
81-
that acquired the lock is allowed to release it.
94+
Locks (:class:`Lock`) are used to guard shared resources to ensure sole access to a
95+
single task at once. They function much like semaphores with a maximum value of 1,
96+
except that only the task that acquired the lock is allowed to release it.
8297

8398
Example::
8499

@@ -99,6 +114,12 @@ Example::
99114

100115
run(main)
101116

117+
# Output:
118+
# Task number 0 is now working with the shared resource
119+
# Task number 1 is now working with the shared resource
120+
# Task number 2 is now working with the shared resource
121+
# Task number 3 is now working with the shared resource
122+
102123
.. tip:: If the performance of locks is critical for you, you could pass
103124
``fast_acquire=True`` to :class:`Lock`. This has the effect of skipping the
104125
:func:`~.lowlevel.cancel_shielded_checkpoint` call in :meth:`Lock.acquire` if there
@@ -148,12 +169,31 @@ Example::
148169

149170
run(main)
150171

172+
# Output:
173+
# Woke up task number 0
174+
# Woke up task number 1
175+
# Woke up task number 2
176+
# Woke up task number 3
177+
# Woke up task number 4
178+
# Woke up task number 5
179+
180+
.. _capacity-limiters:
181+
151182
Capacity limiters
152183
-----------------
153184

154-
Capacity limiters are like semaphores except that a single borrower (the current task by
155-
default) can only hold a single token at a time. It is also possible to borrow a token
156-
on behalf of any arbitrary object, so long as that object is hashable.
185+
Capacity limiters (:class:`CapacityLimiter`) are like semaphores except that a single
186+
borrower (the current task by default) can only hold a single token at a time. It is
187+
also possible to borrow a token on behalf of any arbitrary object, so long as that object
188+
is hashable.
189+
190+
It is recommended to use capacity limiters instead of semaphores unless you intend to
191+
allow a task to acquire multiple tokens from the same object. AnyIO uses capacity
192+
limiters to limit the number of threads spawned.
193+
194+
The number of total tokens available for tasks to acquire can be adjusted by assigning
195+
the desired value to the ``total_tokens`` property. If the value is higher than the
196+
previous one, it will automatically wake up the appropriate number of waiting tasks.
157197

158198
Example::
159199

@@ -162,7 +202,7 @@ Example::
162202

163203
async def use_resource(tasknum, limiter):
164204
async with limiter:
165-
print('Task number', tasknum, 'is now working with the shared resource')
205+
print(f"Task number {tasknum} is now working with the shared resource")
166206
await sleep(1)
167207

168208

@@ -174,8 +214,17 @@ Example::
174214

175215
run(main)
176216

177-
You can adjust the total number of tokens by setting a different value on the limiter's
178-
``total_tokens`` property.
217+
# Output:
218+
# Task number 0 is now working with the shared resource
219+
# Task number 1 is now working with the shared resource
220+
# Task number 2 is now working with the shared resource
221+
# Task number 3 is now working with the shared resource
222+
# Task number 4 is now working with the shared resource
223+
# Task number 5 is now working with the shared resource
224+
# Task number 6 is now working with the shared resource
225+
# Task number 7 is now working with the shared resource
226+
# Task number 8 is now working with the shared resource
227+
# Task number 9 is now working with the shared resource
179228

180229
Resource guards
181230
---------------

docs/tasks.rst

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Here's a demonstration::
4141
.. _Trio: https://trio.readthedocs.io/en/latest/reference-core.html
4242
#tasks-let-you-do-multiple-things-at-once
4343

44+
.. _start_initialize:
45+
4446
Starting and initializing tasks
4547
-------------------------------
4648

@@ -145,9 +147,22 @@ function from the exceptiongroup_ package::
145147

146148
If you need to set local variables in the handlers, declare them as ``nonlocal``::
147149

148-
def handle_valueerror(exc):
149-
nonlocal somevariable
150-
somevariable = 'whatever'
150+
async def yourfunc():
151+
somevariable: str | None = None
152+
153+
def handle_valueerror(exc):
154+
nonlocal somevariable
155+
somevariable = 'whatever'
156+
157+
with catch({
158+
ValueError: handle_valueerror,
159+
KeyError: handle_keyerror
160+
}):
161+
async with create_task_group() as tg:
162+
tg.start_soon(some_task)
163+
tg.start_soon(another_task)
164+
165+
print(f"{somevariable=}")
151166

152167
.. _exceptiongroup: https://pypi.org/project/exceptiongroup/
153168

docs/testing.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ Testing with AnyIO
22
==================
33

44
AnyIO provides built-in support for testing your library or application in the form of a
5-
pytest_ plugin.
5+
pytest_ plugin. This plugin is part of the AnyIO distribution, so nothing extra needs to
6+
be installed to use it.
67

78
.. _pytest: https://docs.pytest.org/en/latest/
89

@@ -146,9 +147,9 @@ Built-in utility fixtures
146147
Some useful pytest fixtures are provided to make testing network services easier:
147148

148149
* ``free_tcp_port_factory``: session scoped fixture returning a callable
149-
(:class:`~pytest_plugin.FreePortFactory`) that generates unused TCP port numbers
150+
(:class:`~.pytest_plugin.FreePortFactory`) that generates unused TCP port numbers
150151
* ``free_udp_port_factory``: session scoped fixture returning a callable
151-
(:class:`~pytest_plugin.FreePortFactory`) that generates unused UDP port numbers
152+
(:class:`~.pytest_plugin.FreePortFactory`) that generates unused UDP port numbers
152153
* ``free_tcp_port``: function level fixture that invokes the ``free_tcp_port_factory``
153154
fixture to generate a free TCP port number
154155
* ``free_udp_port``: function level fixture that invokes the ``free_udp_port_factory``

0 commit comments

Comments
 (0)