38
38
NewType ,
39
39
Optional ,
40
40
overload ,
41
+ ParamSpec ,
41
42
Protocol ,
42
43
Sequence ,
43
44
Tuple ,
53
54
FixerType = Callable [[asyncio .Task ], Union [asyncio .Task , Awaitable [asyncio .Task ]]]
54
55
logger : logging .Logger = logging .getLogger (__name__ )
55
56
T = TypeVar ("T" )
57
+ TParams = ParamSpec ("TParams" )
56
58
57
59
__all__ : Sequence [str ] = [
58
60
"Watcher" ,
@@ -125,15 +127,15 @@ async def cancel(fut: asyncio.Future) -> None:
125
127
126
128
127
129
def as_task (
128
- func : Callable [... , Coroutine [object , object , T ]]
129
- ) -> Callable [... , asyncio .Task [T ]]:
130
+ func : Callable [TParams , Coroutine [object , object , T ]]
131
+ ) -> Callable [TParams , asyncio .Task [T ]]:
130
132
"""
131
133
Decorate a function, So that when called it is wrapped in a task
132
134
on the running loop.
133
135
"""
134
136
135
137
@wraps (func )
136
- def create_task (* args : Any , ** kws : Mapping [ str , Any ] ) -> asyncio .Task [T ]:
138
+ def create_task (* args : TParams . args , ** kws : TParams . kwargs ) -> asyncio .Task [T ]:
137
139
loop = asyncio .get_running_loop ()
138
140
return loop .create_task (func (* args , ** kws ))
139
141
@@ -161,7 +163,7 @@ class Watcher:
161
163
_cancel_timeout : float
162
164
_preexit_callbacks : List [Callable [[], None ]]
163
165
_shielded_tasks : Dict [asyncio .Task , asyncio .Future ]
164
- # pyre-fixme [13]: Attribute ` loop` is never initialized.
166
+ # pyre-ignore [13]: loop is initialized in __aenter__
165
167
loop : asyncio .AbstractEventLoop
166
168
running : bool
167
169
done_ok : bool
@@ -424,7 +426,7 @@ async def _handle_cancel(self) -> None:
424
426
)
425
427
426
428
427
- CacheKey = NewType ("CacheKey" , Sequence [Hashable ])
429
+ CacheKey = NewType ("CacheKey" , tuple [Hashable , ... ])
428
430
ArgID = Union [int , str ]
429
431
430
432
@@ -457,11 +459,9 @@ def _build_key(
457
459
Allow for not including certain fields from args or kwargs
458
460
"""
459
461
if not ignored_args :
460
- # pyre-fixme[45]: Cannot instantiate abstract class `CacheKey`.
461
462
return CacheKey ((args , tuple (sorted (kwargs .items ()))))
462
463
463
464
# If we do want to ignore something then do so
464
- # pyre-fixme[45]: Cannot instantiate abstract class `CacheKey`.
465
465
return CacheKey (
466
466
(
467
467
tuple ((value for idx , value in enumerate (args ) if idx not in ignored_args )),
@@ -474,37 +474,40 @@ def _build_key(
474
474
475
475
class AsyncCallable (Protocol ):
476
476
def __call__ (
477
- self , fn : Callable [... , Coroutine [object , object , T ]]
478
- ) -> Callable [... , Coroutine [object , object , T ]]: # pragma: nocover
477
+ self , fn : Callable [TParams , Coroutine [object , object , T ]]
478
+ ) -> Callable [TParams , Coroutine [object , object , T ]]: # pragma: nocover
479
479
...
480
480
481
481
482
- FuncType = Callable [..., Coroutine [object , object , T ]]
483
-
484
-
485
- @overload # noqa: 811
482
+ @overload
486
483
def herd (
487
- fn : FuncType [T ], * , ignored_args : Optional [AbstractSet [ArgID ]] = None
488
- ) -> FuncType [T ]: # pragma: nocover
484
+ fn : Callable [TParams , Coroutine [object , object , T ]],
485
+ * ,
486
+ ignored_args : Optional [AbstractSet [ArgID ]] = None ,
487
+ ) -> Callable [TParams , Coroutine [object , object , T ]]: # pragma: nocover
489
488
...
490
489
491
490
492
- @overload # noqa: 811
491
+ @overload
493
492
def herd (
494
- fn : Optional [ AsyncCallable ] = None ,
493
+ fn : None = None ,
495
494
* ,
496
495
ignored_args : Optional [AbstractSet [ArgID ]] = None ,
497
496
) -> AsyncCallable : # pragma: nocover
498
497
...
499
498
500
499
501
- # pyre-ignore[3]: Defining the return type is pointless here
502
500
def herd (
503
- # pyre-ignore[2]: This is fine, we don't need types the overloads cover it
504
- fn = None ,
501
+ fn : Callable [TParams , Coroutine [object , object , T ]] | None = None ,
505
502
* ,
506
503
ignored_args : Optional [AbstractSet [ArgID ]] = None ,
507
- ): # noqa: 811
504
+ ) -> (
505
+ Callable [TParams , Coroutine [object , object , T ]]
506
+ | Callable [
507
+ [Callable [TParams , Coroutine [object , object , T ]]],
508
+ Callable [TParams , Coroutine [object , object , T ]],
509
+ ]
510
+ ):
508
511
"""
509
512
Provide a simple thundering herd protection as a decorator.
510
513
if requests comes in while and existing request with those same args is pending,
@@ -518,11 +521,13 @@ def herd(
518
521
Each member of the herd is "shielded" from cancellation effecting other herd members
519
522
"""
520
523
521
- def decorator (fn : FuncType [T ]) -> T :
524
+ def decorator (
525
+ fn : Callable [TParams , Coroutine [object , object , T ]]
526
+ ) -> Callable [TParams , Coroutine [object , object , T ]]:
522
527
local : threading .local = threading .local ()
523
528
524
529
@functools .wraps (fn )
525
- async def wrapped (* args : Any , ** kwargs : Any ) -> T :
530
+ async def wrapped (* args : TParams . args , ** kwargs : TParams . kwargs ) -> T :
526
531
pending = cast (Dict [CacheKey , _CountTask ], _get_local (local , "pending" ))
527
532
request = _build_key (tuple (args ), kwargs , ignored_args )
528
533
count_task = pending .setdefault (request , _CountTask ())
@@ -548,7 +553,6 @@ async def wrapped(*args: Any, **kwargs: Any) -> T:
548
553
return wrapped
549
554
550
555
if fn and callable (fn ):
551
- # pyre-fixme[6]: For 1st param expected `F` but got `(...) -> object`.
552
556
return decorator (fn )
553
557
554
558
return decorator
0 commit comments