11import asyncio
22import contextlib
33import datetime
4+ import functools
45import logging
56from collections .abc import AsyncIterator , Awaitable , Callable
6- from typing import Final
7-
8- from tenacity import TryAgain , retry_always , retry_if_exception_type
7+ from typing import Final , ParamSpec , TypeVar
8+
9+ from tenacity import (
10+ TryAgain ,
11+ before_sleep_log ,
12+ retry ,
13+ retry_always ,
14+ retry_if_exception_type ,
15+ )
916from tenacity .asyncio import AsyncRetrying
1017from tenacity .wait import wait_fixed
1118
@@ -30,6 +37,42 @@ async def __call__(self, delay: float | None) -> None:
3037 self .event .clear ()
3138
3239
40+ P = ParamSpec ("P" )
41+ R = TypeVar ("R" )
42+
43+
44+ def periodic (
45+ * ,
46+ interval : datetime .timedelta ,
47+ raise_on_error : bool = False ,
48+ early_wake_up_event : asyncio .Event | None = None ,
49+ ) -> Callable [[Callable [P , Awaitable [None ]]], Callable [P , Awaitable [None ]]]:
50+ def _decorator (func : Callable [P , Awaitable [None ]]) -> Callable [P , Awaitable [None ]]:
51+ nap = (
52+ asyncio .sleep
53+ if early_wake_up_event is None
54+ else SleepUsingAsyncioEvent (early_wake_up_event )
55+ )
56+
57+ @retry (
58+ sleep = nap ,
59+ wait = wait_fixed (interval .total_seconds ()),
60+ reraise = True ,
61+ retry = retry_if_exception_type (TryAgain )
62+ if raise_on_error
63+ else retry_if_exception_type (),
64+ before_sleep = before_sleep_log (_logger , logging .DEBUG ),
65+ )
66+ @functools .wraps (func )
67+ async def _wrapper (* args : P .args , ** kwargs : P .kwargs ) -> None :
68+ await func (* args , ** kwargs )
69+ raise TryAgain
70+
71+ return _wrapper
72+
73+ return _decorator
74+
75+
3376async def _periodic_scheduled_task (
3477 task : Callable [..., Awaitable [None ]],
3578 * ,
0 commit comments