diff --git a/docs/user-guide/repeated-tasks.md b/docs/user-guide/repeated-tasks.md index 3d8bd733..968097af 100644 --- a/docs/user-guide/repeated-tasks.md +++ b/docs/user-guide/repeated-tasks.md @@ -51,6 +51,7 @@ Here is a more detailed description of the various keyword arguments for `repeat * `seconds: float` : The number of seconds to wait between successive calls * `wait_first: bool = False` : If `False` (the default), the wrapped function is called immediately when the decorated function is first called. If `True`, the decorated function will wait one period before making the first call to the wrapped function +* `wait_first_seconds: float = 0` : If `wait_first` is `True`, `wait_first_seconds` specifies the duration of the first wait period for the wrapped function to be called. If left as default, it will use `seconds` as the duration. * `logger: Optional[logging.Logger] = None` : If you pass a logger, any exceptions raised in the repeating execution loop will be logged (with a traceback) to the provided logger. * `raise_exceptions: bool = False` diff --git a/fastapi_utils/tasks.py b/fastapi_utils/tasks.py index 0cbe6dd3..8428aa30 100644 --- a/fastapi_utils/tasks.py +++ b/fastapi_utils/tasks.py @@ -16,6 +16,7 @@ def repeat_every( *, seconds: float, wait_first: bool = False, + wait_first_seconds: float = 0.0, logger: Optional[logging.Logger] = None, raise_exceptions: bool = False, max_repetitions: Optional[int] = None, @@ -32,6 +33,8 @@ def repeat_every( The number of seconds to wait between repeated calls wait_first: bool (default False) If True, the function will wait for a single period before the first call + wait_first_seconds: float (default 0.0) + If > 0 and wait_first = True, specifies a duration in seconds before the first call, other than `seconds`. logger: Optional[logging.Logger] (default None) The logger to use to log any exceptions raised by calls to the decorated function. If not provided, exceptions will not be logged by this function (though they may be handled by the event loop). @@ -57,7 +60,10 @@ async def wrapped() -> None: async def loop() -> None: nonlocal repetitions if wait_first: - await asyncio.sleep(seconds) + if wait_first_seconds: + await asyncio.sleep(wait_first_seconds) + else: + await asyncio.sleep(seconds) while max_repetitions is None or repetitions < max_repetitions: try: if is_coroutine: diff --git a/tests/test_tasks.py b/tests/test_tasks.py index a47f0dc4..0ad2d4f4 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -61,6 +61,19 @@ def repeatedly_print_hello() -> None: assert err == "" +@pytest.mark.asyncio +async def test_repeat_print_wait_first_seconds(capsys: CaptureFixture) -> None: + @repeat_every(seconds=0.07, max_repetitions=3, wait_first=True, wait_first_seconds=0.1) + def repeatedly_print_hello() -> None: + print("hello") + + await repeatedly_print_hello() + await asyncio.sleep(0.18) #  wait for 0.07 + 0.1 secs + out, err = capsys.readouterr() + assert out == "hello\n" * 2, "Printed 'hello' twice" + assert err == "" + + @pytest.mark.asyncio async def test_repeat_unlogged_error(caplog: LogCaptureFixture) -> None: @repeat_every(seconds=0.07, max_repetitions=None)