Skip to content

Commit 18adcdb

Browse files
committed
Move protocol declarations
1 parent 63a40fa commit 18adcdb

File tree

4 files changed

+110
-69
lines changed

4 files changed

+110
-69
lines changed

src/async_utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
__author__ = "Michael Hall"
1010
__license__ = "Apache-2.0"
1111
__copyright__ = "Copyright 2020-Present Michael Hall"
12-
__version__ = "2025.04.24b"
12+
__version__ = "2025.04.25b"
1313

1414
import os
1515
import sys

src/async_utils/_internal_types.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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.
14+
15+
# This used to include CPython code, some minor performance losses have been
16+
# taken to not tightly include upstream code
17+
18+
19+
from __future__ import annotations
20+
21+
import asyncio
22+
from collections.abc import Awaitable, Callable, Coroutine
23+
24+
from . import _typings as t
25+
26+
type CoroFunc[**P, R] = Callable[P, Coroutine[t.Any, t.Any, R]]
27+
type CoroLike[**P, R] = Callable[P, Awaitable[R]]
28+
type TaskFunc[**P, R] = Callable[P, asyncio.Task[R]]
29+
type TaskCoroFunc[**P, R] = CoroFunc[P, R] | TaskFunc[P, R]
30+
31+
type _CT_RET = tuple[tuple[t.Any, ...], dict[str, t.Any]]
32+
#: Note CacheTransformers recieve a tuple (args) and dict(kwargs)
33+
#: rather than a ParamSpec of the decorated function.
34+
#: Warning: Mutations will impact callsite, return new objects as needed.
35+
type CacheTransformer = Callable[[tuple[t.Any, ...], dict[str, t.Any]], _CT_RET]
36+
37+
38+
_proto_cache: t.Any = {}
39+
40+
TYPE_CHECKING = False
41+
if TYPE_CHECKING:
42+
from typing import Protocol
43+
44+
class CoroCacheDeco(Protocol):
45+
def __call__[**P, R](self, c: CoroLike[P, R], /) -> CoroFunc[P, R]: ...
46+
47+
class TaskCacheDeco(Protocol):
48+
def __call__[**P, R](self, c: TaskCoroFunc[P, R], /) -> TaskFunc[P, R]: ...
49+
50+
51+
else:
52+
53+
def __getattr__(name: str):
54+
if name in {"CoroCacheDeco", "TaskCacheDeco"}:
55+
if p := _proto_cache.get(name):
56+
return p
57+
58+
if name == "CoroCacheDeco":
59+
from typing import Protocol
60+
61+
class CoroCacheDeco(Protocol):
62+
def __call__[**P, R](
63+
self, c: CoroLike[P, R], /
64+
) -> CoroFunc[P, R]: ...
65+
66+
_proto_cache[name] = CoroCacheDeco
67+
return CoroCacheDeco
68+
69+
if name == "TaskCacheDeco":
70+
from typing import Protocol
71+
72+
class TaskCacheDeco(Protocol):
73+
def __call__[**P, R](
74+
self, c: TaskCoroFunc[P, R], /
75+
) -> TaskFunc[P, R]: ...
76+
77+
_proto_cache[name] = TaskCacheDeco
78+
return TaskCacheDeco
79+
80+
msg = f"module {__name__!r} has no attribute {name!r}"
81+
raise AttributeError(msg)
82+
83+
84+
__all__ = (
85+
"CacheTransformer",
86+
"CoroCacheDeco",
87+
"CoroFunc",
88+
"CoroLike",
89+
"TaskCacheDeco",
90+
"TaskCoroFunc",
91+
"TaskFunc",
92+
)

src/async_utils/corofunc_cache.py

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,42 +16,17 @@
1616

1717
import asyncio
1818
import concurrent.futures as cf
19-
from collections.abc import Awaitable, Callable, Coroutine, Hashable
19+
from collections.abc import Hashable
2020
from functools import partial, wraps
2121

22+
from . import _internal_types as i_types
2223
from . import _typings as t
2324
from ._paramkey import make_key
2425
from .lru import LRU
2526

2627
__all__ = ("corocache", "lrucorocache")
2728

2829

29-
type CoroFunc[**P, R] = Callable[P, Coroutine[t.Any, t.Any, R]]
30-
type CoroLike[**P, R] = Callable[P, Awaitable[R]]
31-
32-
type _CT_RET = tuple[tuple[t.Any, ...], dict[str, t.Any]]
33-
34-
#: Note CacheTransformers recieve a tuple (args) and dict(kwargs)
35-
#: rather than a ParamSpec of the decorated function.
36-
#: Warning: Mutations will impact callsite, return new objects as needed.
37-
type CacheTransformer = Callable[[tuple[t.Any, ...], dict[str, t.Any]], _CT_RET]
38-
39-
# This is the only way to return a generic function not dependent on
40-
# type-checker specific behavior where the generic is deferred until application
41-
# of the function.
42-
TYPE_CHECKING = False
43-
if TYPE_CHECKING:
44-
from typing import Protocol
45-
46-
class Deco(Protocol):
47-
def __call__[**P, R](self, c: CoroLike[P, R], /) -> CoroFunc[P, R]: ...
48-
49-
else:
50-
# This branch is here for something reasonable to exist at runtime
51-
# Not importing typing at runtime
52-
type Deco[**P, R] = Callable[[CoroLike[P, R]], CoroFunc[P, R]]
53-
54-
5530
def _chain_fut[R](c_fut: cf.Future[R], a_fut: asyncio.Future[R]) -> None:
5631
if a_fut.cancelled():
5732
c_fut.cancel()
@@ -64,8 +39,8 @@ def _chain_fut[R](c_fut: cf.Future[R], a_fut: asyncio.Future[R]) -> None:
6439
def corocache(
6540
ttl: float | None = None,
6641
*,
67-
cache_transform: CacheTransformer | None = None,
68-
) -> Deco:
42+
cache_transform: i_types.CacheTransformer | None = None,
43+
) -> i_types.CoroCacheDeco:
6944
"""Cache the results of the decorated coroutine.
7045
7146
This is less powerful than the version in task_cache.py but may work better
@@ -98,7 +73,7 @@ def corocache(
9873
def key_func(args: tuple[t.Any, ...], kwds: dict[t.Any, t.Any], /) -> Hashable:
9974
return make_key(*cache_transform(args, kwds))
10075

101-
def wrapper[**P, R](coro: CoroLike[P, R], /) -> CoroFunc[P, R]:
76+
def wrapper[**P, R](coro: i_types.CoroLike[P, R], /) -> i_types.CoroFunc[P, R]:
10277
internal_cache: dict[Hashable, cf.Future[R]] = {}
10378
internal_taskset: set[asyncio.Task[R]] = set()
10479

@@ -137,8 +112,8 @@ def lrucorocache(
137112
ttl: float | None = None,
138113
maxsize: int = 1024,
139114
*,
140-
cache_transform: CacheTransformer | None = None,
141-
) -> Deco:
115+
cache_transform: i_types.CacheTransformer | None = None,
116+
) -> i_types.CoroCacheDeco:
142117
"""Cache the results of the decorated coroutine.
143118
144119
This is less powerful than the version in task_cache.py but may work better
@@ -177,7 +152,7 @@ def lrucorocache(
177152
def key_func(args: tuple[t.Any, ...], kwds: dict[t.Any, t.Any], /) -> Hashable:
178153
return make_key(*cache_transform(args, kwds))
179154

180-
def wrapper[**P, R](coro: CoroLike[P, R], /) -> CoroFunc[P, R]:
155+
def wrapper[**P, R](coro: i_types.CoroLike[P, R], /) -> i_types.CoroFunc[P, R]:
181156
internal_cache: LRU[Hashable, cf.Future[R]] = LRU(maxsize)
182157
internal_taskset: set[asyncio.Task[R]] = set()
183158

src/async_utils/task_cache.py

Lines changed: 9 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,17 @@
1616

1717
import asyncio
1818
import concurrent.futures as cf
19-
from collections.abc import Callable, Coroutine, Hashable
19+
from collections.abc import Callable, Hashable
2020
from functools import partial, wraps
2121

22+
from . import _internal_types as i_types
2223
from . import _typings as t
2324
from ._paramkey import make_key
2425
from .lru import LRU
2526

2627
__all__ = ("lrutaskcache", "taskcache")
2728

2829

29-
# Use below doesn't accept non-task Futures, so can't accept general awaitables
30-
type CoroFunc[**P, R] = Callable[P, Coroutine[t.Any, t.Any, R]]
31-
type TaskFunc[**P, R] = Callable[P, asyncio.Task[R]]
32-
type TaskCoroFunc[**P, R] = CoroFunc[P, R] | TaskFunc[P, R]
33-
34-
type _CT_RET = tuple[tuple[t.Any, ...], dict[str, t.Any]]
35-
36-
#: Note CacheTransformers recieve a tuple (args) and dict(kwargs)
37-
#: rather than a ParamSpec of the decorated function.
38-
#: Warning: Mutations will impact callsite, return new objects as needed.
39-
type CacheTransformer = Callable[[tuple[t.Any, ...], dict[str, t.Any]], _CT_RET]
40-
41-
# This is the only way to return a generic function not dependent on
42-
# type-checker specific behavior where the generic is deferred until application
43-
# of the function.
44-
TYPE_CHECKING = False
45-
if TYPE_CHECKING:
46-
from typing import Protocol
47-
48-
class Deco(Protocol):
49-
def __call__[**P, R](self, c: TaskCoroFunc[P, R], /) -> TaskFunc[P, R]: ...
50-
51-
else:
52-
# This branch is here for something reasonable to exist at runtime
53-
# Not importing typing at runtime
54-
type Deco[**P, R] = Callable[[TaskCoroFunc[P, R]], TaskFunc[P, R]]
55-
5630
# Non-annotation assignments for transformed functions
5731
_WRAP_ASSIGN = ("__module__", "__name__", "__qualname__", "__doc__")
5832

@@ -70,7 +44,7 @@ class _WrappedSignature[**P, R]:
7044
#: PYUPGRADE: Ensure inspect.signature still accepts this
7145
# as func.__signature__
7246
# Known working: py 3.12.0 - py3.14a6 range inclusive
73-
def __init__(self, f: TaskCoroFunc[P, R], w: TaskFunc[P, R]) -> None:
47+
def __init__(self, f: i_types.TaskCoroFunc[P, R], w: i_types.TaskFunc[P, R]) -> None:
7448
self._f: Callable[..., t.Any] = f # anotation needed for inspect use below....
7549
self._w = w
7650
self._sig: t.Any | None = None
@@ -118,8 +92,8 @@ async def _await[R](fut: asyncio.Future[R]) -> R:
11892
def taskcache(
11993
ttl: float | None = None,
12094
*,
121-
cache_transform: CacheTransformer | None = None,
122-
) -> Deco:
95+
cache_transform: i_types.CacheTransformer | None = None,
96+
) -> i_types.TaskCacheDeco:
12397
"""Cache the results of the decorated coroutine.
12498
12599
Decorator to modify coroutine functions to instead act as functions
@@ -158,7 +132,7 @@ def taskcache(
158132
def key_func(args: tuple[t.Any, ...], kwds: dict[t.Any, t.Any], /) -> Hashable:
159133
return make_key(*cache_transform(args, kwds))
160134

161-
def wrapper[**P, R](coro: TaskCoroFunc[P, R], /) -> TaskFunc[P, R]:
135+
def wrapper[**P, R](coro: i_types.TaskCoroFunc[P, R], /) -> i_types.TaskFunc[P, R]:
162136
internal_cache: dict[Hashable, cf.Future[R]] = {}
163137

164138
def _internal_cache_evict(key: Hashable, _ignored_task: object) -> None:
@@ -197,8 +171,8 @@ def lrutaskcache(
197171
ttl: float | None = None,
198172
maxsize: int = 1024,
199173
*,
200-
cache_transform: CacheTransformer | None = None,
201-
) -> Deco:
174+
cache_transform: i_types.CacheTransformer | None = None,
175+
) -> i_types.TaskCacheDeco:
202176
"""Cache the results of the decorated coroutine.
203177
204178
Decorator to modify coroutine functions to instead act as functions
@@ -244,7 +218,7 @@ def lrutaskcache(
244218
def key_func(args: tuple[t.Any, ...], kwds: dict[t.Any, t.Any], /) -> Hashable:
245219
return make_key(*cache_transform(args, kwds))
246220

247-
def wrapper[**P, R](coro: TaskCoroFunc[P, R], /) -> TaskFunc[P, R]:
221+
def wrapper[**P, R](coro: i_types.TaskCoroFunc[P, R], /) -> i_types.TaskFunc[P, R]:
248222
internal_cache: LRU[Hashable, cf.Future[R]] = LRU(maxsize)
249223

250224
def _internal_cache_evict(key: Hashable, _ignored_task: object) -> None:

0 commit comments

Comments
 (0)