Skip to content

Commit 00070cf

Browse files
committed
Be more permissive
1 parent 924531f commit 00070cf

File tree

3 files changed

+29
-27
lines changed

3 files changed

+29
-27
lines changed

async_utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
# limitations under the License.
1414

1515

16-
__version__ = "7.0.3"
16+
__version__ = "7.0.4"

async_utils/corofunc_cache.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from __future__ import annotations
1616

1717
import asyncio
18-
from collections.abc import Callable, Coroutine, Hashable
18+
from collections.abc import Awaitable, Callable, Coroutine, Hashable
1919
from functools import partial, wraps
2020
from typing import Any, ParamSpec, TypeVar
2121

@@ -26,11 +26,10 @@
2626

2727
P = ParamSpec("P")
2828
T = TypeVar("T")
29-
K = TypeVar("K")
30-
V = TypeVar("V")
3129

3230

3331
type CoroFunc[**P, T] = Callable[P, Coroutine[Any, Any, T]]
32+
type CoroLike[**P, T] = Callable[P, Awaitable[T]]
3433

3534

3635
class LRU[K, V]:
@@ -59,7 +58,7 @@ def remove(self, key: K) -> None:
5958

6059
def corocache(
6160
ttl: float | None = None,
62-
) -> Callable[[CoroFunc[P, T]], CoroFunc[P, T]]:
61+
) -> Callable[[CoroLike[P, T]], CoroFunc[P, T]]:
6362
"""Decorator to cache coroutine functions.
6463
6564
This is less powerful than the version in task_cache.py but may work better for
@@ -71,37 +70,37 @@ def corocache(
7170
7271
The ordering of args and kwargs matters."""
7372

74-
def wrapper(coro: CoroFunc[P, T]) -> CoroFunc[P, T]:
75-
internal_cache: dict[Hashable, asyncio.Task[T]] = {}
73+
def wrapper(coro: CoroLike[P, T]) -> CoroFunc[P, T]:
74+
internal_cache: dict[Hashable, asyncio.Future[T]] = {}
7675

7776
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
7877
key = make_key(args, kwargs)
7978
try:
8079
return await internal_cache[key]
8180
except KeyError:
82-
internal_cache[key] = task = asyncio.create_task(coro(*args, **kwargs))
81+
internal_cache[key] = fut = asyncio.ensure_future(coro(*args, **kwargs))
8382
if ttl is not None:
84-
# This results in internal_cache.pop(key, task) later
83+
# This results in internal_cache.pop(key, fut) later
8584
# while avoiding a late binding issue with a lambda instead
8685
call_after_ttl = partial(
8786
asyncio.get_running_loop().call_later,
8887
ttl,
8988
internal_cache.pop,
9089
key,
9190
)
92-
task.add_done_callback(call_after_ttl)
93-
return await task
91+
fut.add_done_callback(call_after_ttl)
92+
return await fut
9493

9594
return wrapped
9695

9796
return wrapper
9897

9998

100-
def _lru_evict(ttl: float, cache: LRU[Hashable, Any], key: Hashable, _ignored_task: object) -> None:
99+
def _lru_evict(ttl: float, cache: LRU[Hashable, Any], key: Hashable, _ignored_fut: object) -> None:
101100
asyncio.get_running_loop().call_later(ttl, cache.remove, key)
102101

103102

104-
def lrucorocache(ttl: float | None = None, maxsize: int = 1024) -> Callable[[CoroFunc[P, T]], CoroFunc[P, T]]:
103+
def lrucorocache(ttl: float | None = None, maxsize: int = 1024) -> Callable[[CoroLike[P, T]], CoroFunc[P, T]]:
105104
"""Decorator to cache coroutine functions.
106105
107106
This is less powerful than the version in task_cache.py but may work better for
@@ -113,22 +112,22 @@ def lrucorocache(ttl: float | None = None, maxsize: int = 1024) -> Callable[[Cor
113112
114113
The ordering of args and kwargs matters.
115114
116-
tasks are evicted by LRU and ttl.
115+
futs are evicted by LRU and ttl.
117116
"""
118117

119-
def wrapper(coro: CoroFunc[P, T]) -> CoroFunc[P, T]:
120-
internal_cache: LRU[Hashable, asyncio.Task[T]] = LRU(maxsize)
118+
def wrapper(coro: CoroLike[P, T]) -> CoroFunc[P, T]:
119+
internal_cache: LRU[Hashable, asyncio.Future[T]] = LRU(maxsize)
121120

122121
@wraps(coro)
123122
async def wrapped(*args: P.args, **kwargs: P.kwargs) -> T:
124123
key = make_key(args, kwargs)
125124
try:
126125
return await internal_cache[key]
127126
except KeyError:
128-
internal_cache[key] = task = asyncio.create_task(coro(*args, **kwargs))
127+
internal_cache[key] = fut = asyncio.ensure_future(coro(*args, **kwargs))
129128
if ttl is not None:
130-
task.add_done_callback(partial(_lru_evict, ttl, internal_cache, key))
131-
return await task
129+
fut.add_done_callback(partial(_lru_evict, ttl, internal_cache, key))
130+
return await fut
132131

133132
return wrapped
134133

async_utils/task_cache.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
P = ParamSpec("P")
2828
T = TypeVar("T")
2929

30+
# Use below doesn't accept non-task Futures, so can't accept general awaitables
31+
type CoroFunc[**P, T] = Callable[P, Coroutine[Any, Any, T]]
32+
type TaskFunc[**P, T] = CoroFunc[P, T] | Callable[P, asyncio.Task[T]]
33+
type TaskCoroFunc[**P, T] = CoroFunc[P, T] | TaskFunc[P, T]
34+
3035

3136
class LRU[K, V]:
3237
def __init__(self, maxsize: int, /):
@@ -54,7 +59,7 @@ def remove(self, key: K) -> None:
5459

5560
def taskcache(
5661
ttl: float | None = None,
57-
) -> Callable[[Callable[P, Coroutine[Any, Any, T]]], Callable[P, asyncio.Task[T]]]:
62+
) -> Callable[[TaskCoroFunc[P, T]], TaskFunc[P, T]]:
5863
"""Decorator to modify coroutine functions to instead act as functions returning cached tasks.
5964
6065
For general use, this leaves the end user API largely the same,
@@ -66,7 +71,7 @@ def taskcache(
6671
6772
The ordering of args and kwargs matters."""
6873

69-
def wrapper(coro: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, asyncio.Task[T]]:
74+
def wrapper(coro: TaskCoroFunc[P, T]) -> TaskFunc[P, T]:
7075
internal_cache: dict[Hashable, asyncio.Task[T]] = {}
7176

7277
@wraps(coro, assigned=("__module__", "__name__", "__qualname__", "__doc__"))
@@ -75,7 +80,7 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> asyncio.Task[T]:
7580
try:
7681
return internal_cache[key]
7782
except KeyError:
78-
internal_cache[key] = task = asyncio.create_task(coro(*args, **kwargs))
83+
internal_cache[key] = task = asyncio.ensure_future(coro(*args, **kwargs))
7984
if ttl is not None:
8085
# This results in internal_cache.pop(key, task) later
8186
# while avoiding a late binding issue with a lambda instead
@@ -97,9 +102,7 @@ def _lru_evict(ttl: float, cache: LRU[Hashable, Any], key: Hashable, _ignored_ta
97102
asyncio.get_running_loop().call_later(ttl, cache.remove, key)
98103

99104

100-
def lrutaskcache(
101-
ttl: float | None = None, maxsize: int = 1024
102-
) -> Callable[[Callable[P, Coroutine[Any, Any, T]]], Callable[P, asyncio.Task[T]]]:
105+
def lrutaskcache(ttl: float | None = None, maxsize: int = 1024) -> Callable[[TaskCoroFunc[P, T]], TaskFunc[P, T]]:
103106
"""Decorator to modify coroutine functions to instead act as functions returning cached tasks.
104107
105108
For general use, this leaves the end user API largely the same,
@@ -114,7 +117,7 @@ def lrutaskcache(
114117
tasks are evicted by LRU and ttl.
115118
"""
116119

117-
def wrapper(coro: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, asyncio.Task[T]]:
120+
def wrapper(coro: TaskCoroFunc[P, T]) -> TaskFunc[P, T]:
118121
internal_cache: LRU[Hashable, asyncio.Task[T]] = LRU(maxsize)
119122

120123
@wraps(coro, assigned=("__module__", "__name__", "__qualname__", "__doc__"))
@@ -123,7 +126,7 @@ def wrapped(*args: P.args, **kwargs: P.kwargs) -> asyncio.Task[T]:
123126
try:
124127
return internal_cache[key]
125128
except KeyError:
126-
internal_cache[key] = task = asyncio.create_task(coro(*args, **kwargs))
129+
internal_cache[key] = task = asyncio.ensure_future(coro(*args, **kwargs))
127130
if ttl is not None:
128131
task.add_done_callback(partial(_lru_evict, ttl, internal_cache, key))
129132
return task

0 commit comments

Comments
 (0)