Skip to content

Commit 204f558

Browse files
Documentation Maintenance 072022 (#97)
* documented reusing sync primitives for async * reworded async neutral explanation * expanded async neutral glossary
1 parent 7329c50 commit 204f558

File tree

4 files changed

+80
-8
lines changed

4 files changed

+80
-8
lines changed

docs/index.rst

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ The missing ``async`` toolbox
4848
:hidden:
4949

5050
source/notes/iter_scope
51+
source/notes/compatible
5152
source/glossary
5253

5354
.. toctree::
@@ -120,12 +121,12 @@ Async Neutral Arguments
120121

121122
Many objects of :py:mod:`asyncstdlib` are :term:`async neutral` -- they accept
122123
*both* regular and async arguments.
123-
Type annotations use *(async)* to denote async neutral objects.
124-
For example, the annotation *(int, ...) → (async) bool* denotes a call that takes an
125-
:py:class:`int` and either returns a boolean directly *or* requires ``await`` to
126-
return a boolean.
124+
Type annotations use parentheses to denote this;
125+
for example, "*(async) iter T*" in the signature **zip(**\ *\*iterables: (async) iter T*\ **)**
126+
means that :py:mod:`asyncstdlib`'s :py:func:`~builtins.zip`
127+
can handle both synchronous and asynchronous iterables.
127128

128-
Whether a call is regular or async is determined by inspecting its
129+
Whether a callable is regular or async is determined by inspecting its
129130
return type at runtime.
130131
This supports async-producing factories, such as an ``async def``
131132
function wrapped in :py:class:`functools.partial`.

docs/source/glossary.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ Glossary of Terms
1010
.. glossary::
1111

1212
async neutral
13-
An object that may provide either of a regular or asynchronous implementation.
14-
For example, an async neutral iterable may support either regular
13+
Types that support either of a regular or asynchronous implementation.
14+
For example, an async neutral iterable may provide either regular
1515
``for _ in iterable`` or asynchronous ``async for _ in iterable`` iteration.
16+
Commonly, callables have async neutral parameters to simplify using them
17+
with a mixture of synchronous and regular arguments.
1618

1719
borrowing
1820
borrowed object

docs/source/notes/compatible.rst

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
.. _guide_compatible:
2+
3+
================
4+
Sync/Async Reuse
5+
================
6+
7+
The :py:mod:`asyncstdlib` only re-implements functions and classes
8+
that benefit from an async implementation.
9+
In some cases, a synchronous implementation is already
10+
sufficient to cover the async case as well.
11+
12+
Example: async property
13+
=======================
14+
15+
A prominent example is an "``async`` ``property``":
16+
a computed attribute that allows to run ``async`` code as well.
17+
This is useful for example to fetch data for the attribute
18+
from a remote database or server.
19+
20+
As it turns out, we can directly use the builtin :py:class:`property` for this!
21+
22+
.. code-block:: python3
23+
24+
# python3 -m asyncio
25+
class Remote:
26+
_count = 0
27+
@property # <== builtin @property ...
28+
async def attribute(self): # ... around an async method
29+
await asyncio.sleep(1) # let's pretend to do some work...
30+
self._count += 1
31+
return "Na" * self._count
32+
33+
instance = Remote()
34+
print(await instance.attribute) # waits 1 second, prints Na
35+
print(await instance.attribute) # waits 1 second, prints NaNa
36+
37+
In principle, we could also define setters and deleters
38+
– however, Python has no syntax for async assignment or deletion
39+
which limits the advantage of using a :py:class:`property` in the first place. [1]_
40+
41+
Identifying reusability
42+
=======================
43+
44+
In general, a utility is sync/async compatible when it takes a callable but does not
45+
depend on the concrete result.
46+
For example, a `property` getter just prepares some attribute value
47+
– which may as well be an awaitable.
48+
In contrast, the similar :py:func:`~asyncstdlib.functools.cached_property` must access
49+
the concrete result to store it – this requires async capabilities for the async case.
50+
51+
Some examples for async compatible parts of the standard library include:
52+
53+
* Factory descriptors such as :py:class:`property`, :py:class:`classmethod` and :py:class:`staticmethod`
54+
* Factories such as :py:func:`functools.partial` and :py:func:`functools.partialmethod`
55+
* Selectors such as :py:func:`functools.singledispatch` and :py:func:`functools.singledispatchmethod`
56+
* Modifiers such as :py:func:`functools.wraps` and :py:func:`functools.update_wrapper`
57+
* Special method operators not enforcing result types such as :py:func:`reversed` and :py:func:`~operator.__add__`
58+
59+
Most of these merely wrap a callable to either modify it directly
60+
(such as :py:func:`functools.wraps`)
61+
or call it regardless of the return type
62+
(such as :py:func:`functools.partial`).
63+
Note that some functions such as :py:func:`~operator.__add__` *usually* work for the
64+
`async` case, but may fail in some subtle edge case – such as not being able to see
65+
a :py:data:`NotImplemented` return value.
66+
67+
.. [1] Using `setattr` and `delattr` one can asynchronously run a setter/getter,
68+
for example `await setattr(instance, "attribute")`. However, with the lack
69+
of specific syntax this offers little to no advantage over using a method.

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[flake8]
22
statistics = True
33
max-line-length = 80
4-
ignore = E501, B008, B011, W503
4+
ignore = E501, B008, B011, B905, W503
55
select = C,E,F,W,B,B9
66
exclude = docs,.svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg

0 commit comments

Comments
 (0)