|
1 | | -Coming soon! |
| 1 | +Startup and shutdown events are a great way to trigger actions related to the server lifecycle. |
| 2 | + |
| 3 | +However, sometimes you want a task to trigger not just when the server starts, but also |
| 4 | +on a periodic basis. For example, you might want to regularly reset an internal cache, or delete |
| 5 | +expired tokens from a database. |
| 6 | + |
| 7 | +You can accomplish this by triggering a loop inside a start-up event, but there are a few |
| 8 | +challenges to overcome: |
| 9 | + |
| 10 | +1. You finish the startup event before the periodic loop ends (so the server can start!) |
| 11 | +2. If the repeated tasks performs blocking IO, it shouldn't block the event loop |
| 12 | +3. Exceptions raised by the periodic task shouldn't just be silently swallowed |
| 13 | + |
| 14 | +The `fastapi_utils.tasks.repeat_every` decorator handles all of these issues and adds some other conveniences as well. |
| 15 | + |
| 16 | +## The `@repeat_every` decorator |
| 17 | + |
| 18 | +When a function decorated with the `@repeat_every(...)` decorator is called, a loop is started, |
| 19 | +and the function is called periodically with a delay determined by the `seconds` argument provided to the decorator. |
| 20 | + |
| 21 | +If you *also* apply the `@app.event("startup")` decorator, FastAPI will call the function during server startup, |
| 22 | +and the function will then be called repeatedly while the server is still running. |
| 23 | + |
| 24 | +Here's a hypothetical example that could be used to periodically clean up expired tokens: |
| 25 | + |
| 26 | +```python hl_lines="5 18" |
| 27 | +{!./src/repeated_tasks1.py!} |
| 28 | +``` |
| 29 | + |
| 30 | +(You may want to reference the [sessions docs](sessions.md){.internal-link target=_blank} for more |
| 31 | +information about `FastAPISessionMaker`.) |
| 32 | + |
| 33 | +By passing `seconds=60 * 60`, we ensure that the decorated function is called once every hour. |
| 34 | + |
| 35 | +Some other notes: |
| 36 | + |
| 37 | +* The wrapped function should not take any required arguments. |
| 38 | +* `repeat_every` function works right with both `async def` and `def` functions. |
| 39 | +* `repeat_every` is safe to use with `def` functions that perform blocking IO -- they are executed in a threadpool |
| 40 | +(just like `def` endpoints). |
| 41 | + |
| 42 | + |
| 43 | +## Keyword arguments |
| 44 | + |
| 45 | +Here is a more detailed description of the various keyword arguments for `repeat_every`: |
| 46 | + |
| 47 | +* `seconds: float` : The number of seconds to wait between successive calls |
| 48 | +* `wait_first: bool = False` : If `False` (the default), the wrapped function is called immediately when the decorated |
| 49 | +function is first called. If `True`, the decorated function will wait one period before making the first call to the wrapped function |
| 50 | +* `logger: Optional[logging.Logger] = None` : If you pass a logger, any exceptions raised in the repeating execution loop will be logged (with a traceback) |
| 51 | + to the provided logger. |
| 52 | +* `raise_exceptions: bool = False` |
| 53 | + * If `False` (the default), exceptions are caught in the repeating execution loop, but are not raised. |
| 54 | + If you leave this argument `False`, you'll probably want to provide a `logger` to ensure your repeated events |
| 55 | + don't just fail silently. |
| 56 | + * If `True`, an exception will be raised. |
| 57 | + In order to handle this exception, you'll need to register an exception handler that is able to catch it |
| 58 | + For example, you could use `asyncio.get_running_loop().set_exception_handler(...)`, as documented |
| 59 | + [here](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.set_exception_handler). |
| 60 | +* `max_repetitions: Optional[int] = None` : If `None` (the default), the decorated function will keep repeating forever. |
| 61 | +Otherwise, it will stop repeated execution after the specified number of calls |
0 commit comments