Skip to content

Commit d9ca435

Browse files
committed
initial documentation effort
1 parent 78bd56a commit d9ca435

File tree

14 files changed

+365
-99
lines changed

14 files changed

+365
-99
lines changed

async_utils/__init__.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
# Copyright 2020-present Michael Hall
2-
#
3-
# Licensed under the Apache License, Version 2.0 (the "License");
4-
# you may not use this file except in compliance with the License.
5-
# You may obtain a copy of the License at
6-
#
7-
# http://www.apache.org/licenses/LICENSE-2.0
8-
#
9-
# Unless required by applicable law or agreed to in writing, software
10-
# distributed under the License is distributed on an "AS IS" BASIS,
11-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12-
# See the License for the specific language governing permissions and
13-
# limitations under the License.
1+
"""A Collection of async and other concurrency utilities.
142
3+
:copyright: (c) 2020-present Michael Hall
4+
:license: Apache License, Version 2.0, see LICENSE for more details.
155
6+
"""
7+
8+
__title__ = "async_utils"
9+
__author__ = "Michael Hall"
10+
__license__ = "Apache-2.0"
11+
__copyright__ = "Copyright 2020-Present Michael Hall"
1612
__version__ = "2024.11.23"

async_utils/_cpython_stuff.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(
2525
self,
2626
tup: tuple[Any, ...],
2727
hash: Callable[[object], int] = hash, # noqa: A002
28-
):
28+
) -> None:
2929
self[:] = tup
3030
self.hashvalue: int = hash(tup)
3131

async_utils/_lru.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,58 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
"""LRU Implementation.
16+
17+
This is currently re-exported in task_cache.py
18+
"""
19+
1520
from __future__ import annotations
1621

1722
__all__ = ("LRU",)
1823

1924

2025
class LRU[K, V]:
21-
def __init__(self, maxsize: int, /):
22-
self.cache: dict[K, V] = {}
23-
self.maxsize = maxsize
26+
"""An LRU implementation.
27+
28+
Parameters
29+
----------
30+
maxsize: int
31+
The maximum number of items to retain
32+
"""
33+
34+
def __init__(self, maxsize: int, /) -> None:
35+
self._cache: dict[K, V] = {}
36+
self._maxsize = maxsize
2437

2538
def get[T](self, key: K, default: T, /) -> V | T:
39+
"""Get a value by key or default value.
40+
41+
You should only use this when you have a default.
42+
Otherwise, use index into the LRU by key.
43+
44+
Args:
45+
key: The key to lookup a value for
46+
default: A default value
47+
48+
Returns
49+
-------
50+
Either the value associated to a key-value pair in the LRU
51+
or the specified default
52+
53+
"""
2654
try:
2755
return self[key]
2856
except KeyError:
2957
return default
3058

3159
def __getitem__(self, key: K, /) -> V:
32-
val = self.cache[key] = self.cache.pop(key)
60+
val = self._cache[key] = self._cache.pop(key)
3361
return val
3462

35-
def __setitem__(self, key: K, value: V, /):
36-
self.cache[key] = value
37-
if len(self.cache) > self.maxsize:
38-
self.cache.pop(next(iter(self.cache)))
63+
def __setitem__(self, key: K, value: V, /) -> None:
64+
self._cache[key] = value
65+
if len(self._cache) > self._maxsize:
66+
self._cache.pop(next(iter(self._cache)))
3967

4068
def remove(self, key: K, /) -> None:
41-
self.cache.pop(key, None)
69+
self._cache.pop(key, None)

async_utils/bg_loop.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
"""Background loop management."""
16+
1517
from __future__ import annotations
1618

1719
import asyncio
@@ -21,26 +23,44 @@
2123
from contextlib import contextmanager
2224
from typing import Any, TypeVar
2325

24-
_T = TypeVar("_T")
26+
T = TypeVar("T")
2527

26-
type _FutureLike[_T] = asyncio.Future[_T] | Awaitable[_T]
28+
type _FutureLike[T] = asyncio.Future[T] | Awaitable[T]
2729

2830
__all__ = ["threaded_loop"]
2931

3032

3133
class LoopWrapper:
32-
def __init__(self, loop: asyncio.AbstractEventLoop):
34+
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
3335
self._loop = loop
3436

35-
def stop(self) -> None:
36-
self._loop.call_soon_threadsafe(self._loop.stop)
37+
def schedule(self, coro: _FutureLike[T], /) -> Future[T]:
38+
"""Schedule a coroutine to run on the wrapped event loop.
39+
40+
Parameters
41+
----------
42+
coro:
43+
A thread-safe coroutine-like object
3744
38-
def schedule(self, coro: _FutureLike[_T]) -> Future[_T]:
39-
"""Schedule a coroutine to run on the wrapped event loop."""
45+
Returns
46+
-------
47+
asyncio.Future:
48+
A Future wrapping the result.
49+
"""
4050
return asyncio.run_coroutine_threadsafe(coro, self._loop)
4151

42-
async def run(self, coro: _FutureLike[_T]) -> _T:
43-
"""Schedule and await a coroutine to run on the background loop."""
52+
async def run(self, coro: _FutureLike[T], /) -> T:
53+
"""Schedule and await a coroutine to run on the background loop.
54+
55+
Parameters
56+
----------
57+
coro:
58+
A thread-safe coroutine-like object
59+
60+
Returns
61+
-------
62+
The returned value of the coroutine run in the background
63+
"""
4464
future = asyncio.run_coroutine_threadsafe(coro, self._loop)
4565
return await asyncio.wrap_future(future)
4666

@@ -92,7 +112,12 @@ def threaded_loop(
92112
and yields an object with scheduling methods for interacting with
93113
the loop.
94114
95-
loop is scheduled for shutdown, and thread is joined at contextmanager exit
115+
Loop is scheduled for shutdown, and thread is joined at contextmanager exit
116+
117+
Yields
118+
------
119+
LoopWrapper
120+
A wrapper with methods for interacting with the background loop.
96121
"""
97122
loop = asyncio.new_event_loop()
98123
thread = None

async_utils/bg_tasks.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,17 @@
2727

2828

2929
class BGTasks:
30-
"""An intentionally dumber task group."""
30+
"""An intentionally dumber task group.
31+
32+
Parameters
33+
----------
34+
exit_timeout: int | None
35+
Optionally, the number of seconds to wait before timing out tasks.
36+
In applications that care about graceful shutdown, this should
37+
usually not be set. When not providedd, the context manager
38+
will not exit until all tasks have ended.
39+
40+
"""
3141

3242
def __init__(self, exit_timeout: float | None) -> None:
3343
self._tasks: set[asyncio.Task[Any]] = set()
@@ -40,15 +50,21 @@ def create_task(
4050
name: str | None = None,
4151
context: Context | None = None,
4252
) -> asyncio.Task[_T]:
43-
t = asyncio.create_task(coro)
53+
"""Create a task bound managed by this context manager.
54+
55+
Returns
56+
-------
57+
asyncio.Task: The task that was created.
58+
"""
59+
t = asyncio.create_task(coro, name=name, context=context)
4460
self._tasks.add(t)
4561
t.add_done_callback(self._tasks.discard)
4662
return t
4763

4864
async def __aenter__(self: Self) -> Self:
4965
return self
5066

51-
async def __aexit__(self, *_dont_care: object):
67+
async def __aexit__(self, *_dont_care: object) -> None:
5268
while tsks := self._tasks.copy():
5369
_done, _pending = await asyncio.wait(tsks, timeout=self._etime)
5470
for task in _pending:

async_utils/corofunc_cache.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
def corocache(
3737
ttl: float | None = None,
3838
) -> Callable[[CoroLike[P, R]], CoroFunc[P, R]]:
39-
"""Decorator to cache coroutine functions.
39+
"""Cache the results of the decorated coroutine.
4040
4141
This is less powerful than the version in task_cache.py but may work better
4242
for some cases where typing of libraries this interacts with is too
@@ -48,6 +48,15 @@ def corocache(
4848
feasible in cases where this may matter.
4949
5050
The ordering of args and kwargs matters.
51+
52+
Parameters
53+
----------
54+
ttl: float | None
55+
The time to live in seconds for cached results. Defaults to None (forever)
56+
57+
Returns
58+
-------
59+
A decorator which wraps coroutine-like functions with preemptive caching.
5160
"""
5261

5362
def wrapper(coro: CoroLike[P, R]) -> CoroFunc[P, R]:
@@ -87,7 +96,7 @@ def _lru_evict(
8796
def lrucorocache(
8897
ttl: float | None = None, maxsize: int = 1024
8998
) -> Callable[[CoroLike[P, R]], CoroFunc[P, R]]:
90-
"""Decorator to cache coroutine functions.
99+
"""Cache the results of the decorated coroutine.
91100
92101
This is less powerful than the version in task_cache.py but may work better
93102
for some cases where typing of libraries this interacts with is too
@@ -101,6 +110,19 @@ def lrucorocache(
101110
The ordering of args and kwargs matters.
102111
103112
Cached results are evicted by LRU and ttl.
113+
114+
Parameters
115+
----------
116+
ttl: float | None
117+
The time to live in seconds for cached results. Defaults to None (forever)
118+
maxsize: int
119+
The maximum number of items to retain no matter if they have reached
120+
expiration by ttl or not.
121+
Items evicted by this policy are evicted by least recent use.
122+
123+
Returns
124+
-------
125+
A decorator which wraps coroutine-like functions with preemptive caching.
104126
"""
105127

106128
def wrapper(coro: CoroLike[P, R]) -> CoroFunc[P, R]:

async_utils/gen_transform.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,19 @@ def sync_to_async_gen(
8484
If your generator is actually a synchronous coroutine, that's super cool,
8585
but rewrite is as a native coroutine or use it directly then, you don't need
8686
what this function does.
87+
88+
Parameters
89+
----------
90+
f:
91+
The synchronous generator function to wrap.
92+
*args:
93+
The positional args to pass to the generator construction.
94+
**kwargs:
95+
The keyword arguments to pass to the generator construction.
96+
97+
Returns
98+
-------
99+
An asynchronous iterator which yields the results of the wrapped generator.
87100
"""
88101
# Provides backpressure, ensuring the underlying sync generator in a thread
89102
# is lazy If the user doesn't want laziness, then using this method makes

0 commit comments

Comments
 (0)