Skip to content

Commit c70de02

Browse files
committed
Preserve type hints in decorator (aio-libs#512)
The @cached decorator did not preserve type hints, breaking static analysis.
1 parent 768763b commit c70de02

File tree

2 files changed

+29
-4
lines changed

2 files changed

+29
-4
lines changed

aiocache/decorators.py

+14-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22
import functools
33
import inspect
44
import logging
5+
from typing import Callable, TypeVar
6+
try:
7+
from typing import ParamSpec # Python 3.10+
8+
except ImportError:
9+
from typing_extensions import ParamSpec
10+
11+
P = ParamSpec("P")
12+
R = TypeVar("R")
513

614
from aiocache.base import SENTINEL
715
from aiocache.lock import RedLock
@@ -43,9 +51,10 @@ def __init__(
4351
self.noself = noself
4452
self.cache = cache
4553

46-
def __call__(self, f):
54+
# Use ParamSpec and TypeVar to preserve the decorated function's type signature for static type checkers.
55+
def __call__(self, f: Callable[P, R]) -> Callable[P, R]:
4756
@functools.wraps(f)
48-
async def wrapper(*args, **kwargs):
57+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
4958
return await self.decorator(f, *args, **kwargs)
5059

5160
wrapper.cache = self.cache
@@ -228,9 +237,10 @@ def __init__(
228237
self.skip_cache_func = skip_cache_func
229238
self.ttl = ttl
230239

231-
def __call__(self, f):
240+
# Use ParamSpec and TypeVar to preserve the decorated function's type signature for static type checkers.
241+
def __call__(self, f: Callable[P, R]) -> Callable[P, R]:
232242
@functools.wraps(f)
233-
async def wrapper(*args, **kwargs):
243+
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
234244
return await self.decorator(f, *args, **kwargs)
235245

236246
wrapper.cache = self.cache

tests/ut/test_decorators.py

+15
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,21 @@ async def what(self, a, b):
177177
assert str(inspect.signature(what)) == "(self, a, b)"
178178
assert inspect.getfullargspec(what.__wrapped__).args == ["self", "a", "b"]
179179

180+
async def test_cached_preserves_type_hints(self, mock_cache):
181+
@cached(cache=mock_cache)
182+
async def add(x: int, y: int) -> int:
183+
return x + y
184+
185+
try:
186+
from typing import get_type_hints
187+
except ImportError:
188+
from typing_extensions import get_type_hints
189+
190+
hints = get_type_hints(add)
191+
assert hints['x'] is int
192+
assert hints['y'] is int
193+
assert hints['return'] is int
194+
180195
async def test_reuses_cache_instance(self, mock_cache):
181196
@cached(cache=mock_cache)
182197
async def what():

0 commit comments

Comments
 (0)