From 67d0c99de7edc5249c5e1bffdfb4af6af9f39f46 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Tue, 25 Jul 2023 17:40:38 +0100 Subject: [PATCH 1/5] Use ParamSpec for wrapped signatures --- .mypy.ini | 24 ++++++++++++++++++++++ async_lru/__init__.py | 48 ++++++++++++++++++++++++------------------- setup.cfg | 6 ------ tests/conftest.py | 11 ++++++---- 4 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 .mypy.ini diff --git a/.mypy.ini b/.mypy.ini new file mode 100644 index 0000000..71ff0a8 --- /dev/null +++ b/.mypy.ini @@ -0,0 +1,24 @@ +[mypy] +files = async_lru, tests +check_untyped_defs = True +follow_imports_for_stubs = True +disallow_any_decorated = True +disallow_any_generics = True +disallow_any_unimported = True +disallow_incomplete_defs = True +disallow_subclassing_any = True +disallow_untyped_calls = True +disallow_untyped_decorators = True +disallow_untyped_defs = True +enable_error_code = ignore-without-code, possibly-undefined, redundant-expr, redundant-self, truthy-bool, truthy-iterable, unused-awaitable +implicit_reexport = False +no_implicit_optional = True +pretty = True +show_column_numbers = True +show_error_codes = True +strict_equality = True +warn_incomplete_stub = True +warn_redundant_casts = True +warn_return_any = True +warn_unreachable = True +warn_unused_ignores = True diff --git a/async_lru/__init__.py b/async_lru/__init__.py index 0bdffc7..2728dd8 100644 --- a/async_lru/__init__.py +++ b/async_lru/__init__.py @@ -21,6 +21,11 @@ overload, ) +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + if sys.version_info >= (3, 11): from typing import Self @@ -35,9 +40,10 @@ _T = TypeVar("_T") _R = TypeVar("_R") +_P = ParamSpec("_P") _Coro = Coroutine[Any, Any, _R] -_CB = Callable[..., _Coro[_R]] -_CBP = Union[_CB[_R], "partial[_Coro[_R]]", "partialmethod[_Coro[_R]]"] +_CB = Callable[_P, _Coro[_R]] +_CBP = Union[_CB[_P, _R], "partial[_Coro[_R]]", "partialmethod[_Coro[_R]]"] @final @@ -61,10 +67,10 @@ def cancel(self) -> None: @final -class _LRUCacheWrapper(Generic[_R]): +class _LRUCacheWrapper(Generic[_P, _R]): def __init__( self, - fn: _CB[_R], + fn: _CB[_P, _R], maxsize: Optional[int], typed: bool, ttl: Optional[float], @@ -186,7 +192,7 @@ def _task_done_callback( fut.set_result(task.result()) - async def __call__(self, /, *fn_args: Any, **fn_kwargs: Any) -> _R: + async def __call__(self, /, *fn_args: _P.args, **fn_kwargs: _P.kwargs) -> _R: if self.__closed: raise RuntimeError(f"alru_cache is closed for {self}") @@ -204,7 +210,7 @@ async def __call__(self, /, *fn_args: Any, **fn_kwargs: Any) -> _R: exc = cache_item.fut._exception if exc is None: - self._cache_hit(key) + self._cache_hit(key) # type: ignore[unreachable] # github.com/python/typeshed/pull/10502 return cache_item.fut.result() else: # exception here @@ -213,7 +219,7 @@ async def __call__(self, /, *fn_args: Any, **fn_kwargs: Any) -> _R: fut = loop.create_future() coro = self.__wrapped__(*fn_args, **fn_kwargs) - task: asyncio.Task[_R] = loop.create_task(coro) + task = loop.create_task(coro) self.__tasks.add(task) task.add_done_callback(partial(self._task_done_callback, fut, key)) @@ -228,7 +234,7 @@ async def __call__(self, /, *fn_args: Any, **fn_kwargs: Any) -> _R: def __get__( self, instance: _T, owner: Optional[Type[_T]] - ) -> Union[Self, "_LRUCacheWrapperInstanceMethod[_R, _T]"]: + ) -> Union[Self, "_LRUCacheWrapperInstanceMethod[_P, _R, _T]"]: if owner is None: return self else: @@ -236,10 +242,10 @@ def __get__( @final -class _LRUCacheWrapperInstanceMethod(Generic[_R, _T]): +class _LRUCacheWrapperInstanceMethod(Generic[_P, _R, _T]): def __init__( self, - wrapper: _LRUCacheWrapper[_R], + wrapper: _LRUCacheWrapper[_P, _R], instance: _T, ) -> None: try: @@ -290,16 +296,16 @@ def cache_info(self) -> _CacheInfo: def cache_parameters(self) -> _CacheParameters: return self.__wrapper.cache_parameters() - async def __call__(self, /, *fn_args: Any, **fn_kwargs: Any) -> _R: - return await self.__wrapper(self.__instance, *fn_args, **fn_kwargs) + async def __call__(self, /, *fn_args: _P.args, **fn_kwargs: _P.kwargs) -> _R: + return await self.__wrapper(self.__instance, *fn_args, **fn_kwargs) # type: ignore[arg-type] def _make_wrapper( maxsize: Optional[int], typed: bool, ttl: Optional[float] = None, -) -> Callable[[_CBP[_R]], _LRUCacheWrapper[_R]]: - def wrapper(fn: _CBP[_R]) -> _LRUCacheWrapper[_R]: +) -> Callable[[_CBP[_P, _R]], _LRUCacheWrapper[_P, _R]]: + def wrapper(fn: _CBP[_P, _R]) -> _LRUCacheWrapper[_P, _R]: origin = fn while isinstance(origin, (partial, partialmethod)): @@ -312,7 +318,7 @@ def wrapper(fn: _CBP[_R]) -> _LRUCacheWrapper[_R]: if hasattr(fn, "_make_unbound_method"): fn = fn._make_unbound_method() - return _LRUCacheWrapper(cast(_CB[_R], fn), maxsize, typed, ttl) + return _LRUCacheWrapper(cast(_CB[_P, _R], fn), maxsize, typed, ttl) return wrapper @@ -323,28 +329,28 @@ def alru_cache( typed: bool = False, *, ttl: Optional[float] = None, -) -> Callable[[_CBP[_R]], _LRUCacheWrapper[_R]]: +) -> Callable[[_CBP[_P, _R]], _LRUCacheWrapper[_P, _R]]: ... @overload def alru_cache( - maxsize: _CBP[_R], + maxsize: _CBP[_P, _R], /, -) -> _LRUCacheWrapper[_R]: +) -> _LRUCacheWrapper[_P, _R]: ... def alru_cache( - maxsize: Union[Optional[int], _CBP[_R]] = 128, + maxsize: Union[Optional[int], _CBP[_P, _R]] = 128, typed: bool = False, *, ttl: Optional[float] = None, -) -> Union[Callable[[_CBP[_R]], _LRUCacheWrapper[_R]], _LRUCacheWrapper[_R]]: +) -> Union[Callable[[_CBP[_P, _R]], _LRUCacheWrapper[_P, _R]], _LRUCacheWrapper[_P, _R]]: if maxsize is None or isinstance(maxsize, int): return _make_wrapper(maxsize, typed, ttl) else: - fn = cast(_CB[_R], maxsize) + fn = maxsize if callable(fn) or hasattr(fn, "_make_unbound_method"): return _make_wrapper(128, False, None)(fn) diff --git a/setup.cfg b/setup.cfg index e7b500e..f7a0128 100644 --- a/setup.cfg +++ b/setup.cfg @@ -78,9 +78,3 @@ testpaths = tests/ junit_family=xunit2 asyncio_mode=auto timeout=15 - - -[mypy] -strict=True -pretty=True -packages=async_lru, tests diff --git a/tests/conftest.py b/tests/conftest.py index 36147d4..0ffb814 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,18 @@ from functools import _CacheInfo -from typing import Callable +from typing import Callable, ParamSpec, TypeVar import pytest -from async_lru import _R, _LRUCacheWrapper +from async_lru import _LRUCacheWrapper + +_T = TypeVar("_T") +_P = ParamSpec("_P") @pytest.fixture -def check_lru() -> Callable[..., None]: +def check_lru() -> Callable[..., None]: # type: ignore[misc] def _check_lru( - wrapped: _LRUCacheWrapper[_R], + wrapped: _LRUCacheWrapper[_P, _T], *, hits: int, misses: int, From 79cc7e072b1f4b2e868c656265c6ff25564df82e Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Tue, 25 Jul 2023 17:47:53 +0100 Subject: [PATCH 2/5] Lint --- async_lru/__init__.py | 5 ++++- tests/conftest.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/async_lru/__init__.py b/async_lru/__init__.py index 2728dd8..0e5c67c 100644 --- a/async_lru/__init__.py +++ b/async_lru/__init__.py @@ -21,6 +21,7 @@ overload, ) + if sys.version_info >= (3, 10): from typing import ParamSpec else: @@ -346,7 +347,9 @@ def alru_cache( typed: bool = False, *, ttl: Optional[float] = None, -) -> Union[Callable[[_CBP[_P, _R]], _LRUCacheWrapper[_P, _R]], _LRUCacheWrapper[_P, _R]]: +) -> Union[ + Callable[[_CBP[_P, _R]], _LRUCacheWrapper[_P, _R]], _LRUCacheWrapper[_P, _R] +]: if maxsize is None or isinstance(maxsize, int): return _make_wrapper(maxsize, typed, ttl) else: diff --git a/tests/conftest.py b/tests/conftest.py index 0ffb814..fd55ee6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,6 +5,7 @@ from async_lru import _LRUCacheWrapper + _T = TypeVar("_T") _P = ParamSpec("_P") From 30c5aa687242b49ea25012245470a8e8e22eda8c Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Tue, 25 Jul 2023 17:48:56 +0100 Subject: [PATCH 3/5] Lint --- tests/conftest.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index fd55ee6..281ca84 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,18 @@ +import sys from functools import _CacheInfo -from typing import Callable, ParamSpec, TypeVar +from typing import Callable, TypeVar import pytest from async_lru import _LRUCacheWrapper +if sys.version_info >= (3, 10): + from typing import ParamSpec +else: + from typing_extensions import ParamSpec + + _T = TypeVar("_T") _P = ParamSpec("_P") From 58cf7f0fce4d7b761c807d7250f781e48dd88c49 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 26 Oct 2023 19:36:03 +0100 Subject: [PATCH 4/5] Update setup.cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 45682fc..344abfe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -78,4 +78,4 @@ testpaths = tests/ junit_family=xunit2 asyncio_mode=auto timeout=15 -xfail_strict = true \ No newline at end of file +xfail_strict = true From 0e56d889bd45fa1792e1d781be2905ff25f5fd60 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Thu, 26 Oct 2023 19:52:43 +0100 Subject: [PATCH 5/5] Update __init__.py --- async_lru/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/async_lru/__init__.py b/async_lru/__init__.py index f7f5d8c..202bf33 100644 --- a/async_lru/__init__.py +++ b/async_lru/__init__.py @@ -211,7 +211,7 @@ async def __call__(self, /, *fn_args: _P.args, **fn_kwargs: _P.kwargs) -> _R: exc = cache_item.fut._exception if exc is None: - self._cache_hit(key) # type: ignore[unreachable] # github.com/python/typeshed/pull/10502 + self._cache_hit(key) return cache_item.fut.result() else: # exception here