From 3a71d3be5f89d6a042dd4765c75470916e942ad0 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 10 Jun 2021 22:54:59 +1000 Subject: [PATCH 1/3] Use ParamSpec in ext-tasks --- discord/ext/tasks/__init__.py | 40 ++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index 9cd14a92..60789530 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -29,10 +29,12 @@ import datetime from typing import ( Any, Awaitable, - Callable, + Callable, + Coroutine, Generic, List, - Optional, + Optional, + TYPE_CHECKING, Type, TypeVar, Union, @@ -50,6 +52,13 @@ from collections.abc import Sequence from discord.backoff import ExponentialBackoff from discord.utils import MISSING +if TYPE_CHECKING: + from typing_extensions import ParamSpec + + P = ParamSpec("P") +else: + P = TypeVar("P") # hacky runtime fix + log = logging.getLogger(__name__) __all__ = ( @@ -57,8 +66,9 @@ __all__ = ( ) T = TypeVar('T') +OT = TypeVar('OT') +_coro = Coroutine[Any, Any, T] _func = Callable[..., Awaitable[Any]] -LF = TypeVar('LF', bound=_func) FT = TypeVar('FT', bound=_func) ET = TypeVar('ET', bound=Callable[[Any, BaseException], Awaitable[Any]]) LT = TypeVar('LT', bound='Loop') @@ -89,13 +99,13 @@ class SleepHandle: self.future.cancel() -class Loop(Generic[LF]): +class Loop(Generic[P, T]): """A background task helper that abstracts the loop and reconnection logic for you. The main interface to create this is through :func:`loop`. """ def __init__(self, - coro: LF, + coro: Callable[P, _coro[T]], seconds: float, hours: float, minutes: float, @@ -104,7 +114,7 @@ class Loop(Generic[LF]): reconnect: bool, loop: Optional[asyncio.AbstractEventLoop], ) -> None: - self.coro: LF = coro + self.coro: Callable[P, _coro[T]] = coro self.reconnect: bool = reconnect self.loop: Optional[asyncio.AbstractEventLoop] = loop self.count: Optional[int] = count @@ -207,11 +217,11 @@ class Loop(Generic[LF]): self._stop_next_iteration = False self._has_failed = False - def __get__(self, obj: T, objtype: Type[T]) -> Loop[LF]: + def __get__(self, obj: OT, objtype: Type[OT]) -> Loop[P, T]: if obj is None: return self - copy = Loop( + copy = Loop[P, T]( self.coro, seconds=self._seconds, hours=self._hours, @@ -285,7 +295,7 @@ class Loop(Generic[LF]): return None return self._next_iteration - async def __call__(self, *args: Any, **kwargs: Any) -> Any: + async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: r"""|coro| Calls the internal callback that the task holds. @@ -305,7 +315,7 @@ class Loop(Generic[LF]): return await self.coro(*args, **kwargs) - def start(self, *args: Any, **kwargs: Any) -> asyncio.Task: + def start(self, *args: P.args, **kwargs: P.kwargs) -> asyncio.Task: r"""Starts the internal task in the event loop. Parameters @@ -367,7 +377,7 @@ class Loop(Generic[LF]): if self._can_be_cancelled(): self._task.cancel() - def restart(self, *args: Any, **kwargs: Any) -> None: + def restart(self, *args: P.args, **kwargs: P.kwargs) -> None: r"""A convenience method to restart the internal task. .. note:: @@ -692,7 +702,7 @@ def loop( count: Optional[int] = None, reconnect: bool = True, loop: Optional[asyncio.AbstractEventLoop] = None, -) -> Callable[[LF], Loop[LF]]: +) -> Callable[[Callable[P, _coro[T]]], Loop[P, T]]: """A decorator that schedules a task in the background for you with optional reconnect logic. The decorator returns a :class:`Loop`. @@ -736,7 +746,7 @@ def loop( The function was not a coroutine, an invalid value for the ``time`` parameter was passed, or ``time`` parameter was passed in conjunction with relative time parameters. """ - def decorator(func: LF) -> Loop[LF]: + def decorator(func: Callable[P, _coro[T]]) -> Loop[P, T]: kwargs = { 'seconds': seconds, 'minutes': minutes, @@ -746,5 +756,5 @@ def loop( 'reconnect': reconnect, 'loop': loop, } - return Loop(func, **kwargs) - return decorator + return Loop[P, T](func, **kwargs) + return decorator # type: ignore - pyright bug -- 2.47.2 From faa3e84cfb6ca184d74ebd87d0b19d324cd7c24a Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 11 Jun 2021 14:55:27 +1000 Subject: [PATCH 2/3] Remove type-ignore related to Pyright issue. --- discord/ext/tasks/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index 60789530..0d2c112d 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -757,4 +757,4 @@ def loop( 'loop': loop, } return Loop[P, T](func, **kwargs) - return decorator # type: ignore - pyright bug + return decorator -- 2.47.2 From a7c95966acc249ed32098cfc153c0929c45410f7 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 28 Jun 2021 15:45:24 +1000 Subject: [PATCH 3/3] Fix issue when tasks loop used in classes. --- discord/ext/tasks/__init__.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/discord/ext/tasks/__init__.py b/discord/ext/tasks/__init__.py index 0d2c112d..0d0d7322 100644 --- a/discord/ext/tasks/__init__.py +++ b/discord/ext/tasks/__init__.py @@ -53,7 +53,7 @@ from discord.backoff import ExponentialBackoff from discord.utils import MISSING if TYPE_CHECKING: - from typing_extensions import ParamSpec + from typing_extensions import Concatenate, ParamSpec P = ParamSpec("P") else: @@ -65,8 +65,8 @@ __all__ = ( 'loop', ) +C = TypeVar('C') T = TypeVar('T') -OT = TypeVar('OT') _coro = Coroutine[Any, Any, T] _func = Callable[..., Awaitable[Any]] FT = TypeVar('FT', bound=_func) @@ -99,7 +99,7 @@ class SleepHandle: self.future.cancel() -class Loop(Generic[P, T]): +class Loop(Generic[C, P, T]): """A background task helper that abstracts the loop and reconnection logic for you. The main interface to create this is through :func:`loop`. @@ -121,7 +121,7 @@ class Loop(Generic[P, T]): self._current_loop = 0 self._handle = None self._task = None - self._injected = None + self._injected: Optional[C] = None self._valid_exception = ( OSError, discord.GatewayNotFound, @@ -217,11 +217,11 @@ class Loop(Generic[P, T]): self._stop_next_iteration = False self._has_failed = False - def __get__(self, obj: OT, objtype: Type[OT]) -> Loop[P, T]: + def __get__(self, obj: C, objtype: Type[C]) -> Loop[C, P, T]: if obj is None: return self - copy = Loop[P, T]( + copy = Loop[C, P, T]( self.coro, seconds=self._seconds, hours=self._hours, @@ -702,7 +702,7 @@ def loop( count: Optional[int] = None, reconnect: bool = True, loop: Optional[asyncio.AbstractEventLoop] = None, -) -> Callable[[Callable[P, _coro[T]]], Loop[P, T]]: +) -> Callable[[Union[Callable[Concatenate[Type[C], P], _coro[T]], Callable[P, _coro[T]]]], Loop[C, P, T]]: """A decorator that schedules a task in the background for you with optional reconnect logic. The decorator returns a :class:`Loop`. @@ -746,7 +746,7 @@ def loop( The function was not a coroutine, an invalid value for the ``time`` parameter was passed, or ``time`` parameter was passed in conjunction with relative time parameters. """ - def decorator(func: Callable[P, _coro[T]]) -> Loop[P, T]: + def decorator(func: Union[Callable[Concatenate[Type[C], P], _coro[T]], Callable[P, _coro[T]]]) -> Loop[C, P, T]: kwargs = { 'seconds': seconds, 'minutes': minutes, @@ -756,5 +756,5 @@ def loop( 'reconnect': reconnect, 'loop': loop, } - return Loop[P, T](func, **kwargs) + return Loop[C, P, T](func, **kwargs) return decorator -- 2.47.2