Spaces:
Sleeping
Sleeping
| # Background Tasks | |
| <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! --> | |
| Useful for operations where the user gets a response quickly but doesn’t | |
| need to wait for the operation to finish. Typical scenarios include: | |
| - User setup in complex systems where you can inform the user and other | |
| people later in email that their account is complete | |
| - Batch processes that can take a significant amount of time (bulk email | |
| or API calls) | |
| - Any other process where the user can be notified later by email, | |
| websocket, webhook, or pop-up | |
| <div> | |
| > **Note** | |
| > | |
| > Background tasks in FastHTML are built on Starlette’s background | |
| > tasks, with added sugar. Starlette’s background task design is an | |
| > easy-to-use wrapper around Python’s async and threading libraries. | |
| > Background tasks make apps snappier to the end user and generally | |
| > improve an app’s speed. | |
| </div> | |
| ## A simple background task example | |
| In this example we are attaching a task to FtResponse by assigning it | |
| via the background argument. When the page is visited, it will display | |
| ‘Simple Background Task Example’ almost instantly, while in the terminal | |
| it will slowly count upward from 0. | |
| <div class="code-with-filename"> | |
| **main.py** | |
| ``` python | |
| from fasthtml.common import * | |
| from starlette.background import BackgroundTask | |
| from time import sleep | |
| app, rt = fast_app() | |
| def counter(loops:int): | |
| "Slowly print integers to the terminal" | |
| for i in range(loops): | |
| print(i) | |
| sleep(i) | |
| @rt | |
| def index(): | |
| task = BackgroundTask(counter, loops=5) | |
| return Titled('Simple Background Task Example'), task | |
| serve() | |
| ``` | |
| </div> | |
| Line 7 | |
| `counter` is our task function. There is nothing special about it, | |
| although it is a good practice for its arguments to be serializable as | |
| JSON | |
| Line 15 | |
| We use `starlette.background.BackgroundTask` to turn `counter()` into a | |
| background task | |
| Line 16 | |
| To add a background task to a handler, we add it to the return values at | |
| the top level of the response. | |
| ## A more realistic example | |
| Let’s imagine that we are accessing a slow-to-process critical service. | |
| We don’t want our users to have to wait. While we could set up SSE to | |
| notify on completion, instead we decide to periodically check to see if | |
| the status of their record has changed. | |
| ### Simulated Slow API Service | |
| First, create a very simple slow timestamp API. All it does is stall | |
| requests for a few seconds before returning JSON containing timestamps. | |
| ``` python | |
| # slow_api.py | |
| from fasthtml.common import * | |
| from time import sleep, time | |
| app, rt = fast_app() | |
| @rt('/slow') | |
| def slow(ts: int): | |
| sleep(3) | |
| return dict(request_time=ts, response_time=int(time())) | |
| serve(port=8123) | |
| ``` | |
| Line 9 | |
| This represents slow processing. | |
| Line 10 | |
| Returns both the task’s original timestamp and the time after completion | |
| ### Main FastHTML app | |
| Now let’s create a user-facing app that uses this API to fetch the | |
| timestamp from the glacially slow service. | |
| ``` python | |
| # main.py | |
| from fasthtml.common import * | |
| from starlette.background import BackgroundTask | |
| import time | |
| import httpx | |
| app, rt = fast_app() | |
| db = database(':memory:') | |
| class TStamp: request_time: int; response_time: int | |
| tstamps = db.create(TStamp, pk='request_time') | |
| def task_submit(request_time: int): | |
| client = httpx.Client() | |
| response = client.post(f'http://127.0.0.1:8123/slow?ts={request_time}') | |
| tstamps.insert(**response.json()) | |
| @rt | |
| def submit(): | |
| "Route that initiates a background task and returns immediately." | |
| request_time = int(time.time()) | |
| task = BackgroundTask(task_submit, request_time=request_time) | |
| return P(f'Request submitted at: {request_time}'), task | |
| @rt | |
| def show_tstamps(): return Ul(map(Li, tstamps())) | |
| @rt | |
| def index(): | |
| return Titled('Background Task Dashboard', | |
| P(Button('Press to call slow service', | |
| hx_post=submit, hx_target='#res')), | |
| H2('Responses from Tasks'), | |
| P('', id='res'), | |
| Div(Ul(map(Li, tstamps())), | |
| hx_get=show_tstamps, hx_trigger='every 5s'), | |
| ) | |
| serve() | |
| ``` | |
| Line 11 | |
| Tracks when requests are sent and responses received | |
| Line 15 | |
| Task function calling slow service to be run in the background of a | |
| route handler. It is common but not necessary to prefix task functions | |
| with ‘task\_’ | |
| Line 17 | |
| Call the slow API service (simulating a time-consuming operation) | |
| Line 18 | |
| Store both timestamps in our database | |
| Line 24 | |
| Create a background task by passing in the function to a BackgroundTask | |
| object, followed by any arguments. | |
| Line 25 | |
| In FtResponse, use the background keyword argument to set the task to be | |
| run after the HTTP response is generated. | |
| Line 28 | |
| Endpoint that displays all recorded timestamp pairs. | |
| Line 33 | |
| When this button is pressed, the ‘submit’ handler will respond | |
| instantly. The task_submit function will insert the slow API response | |
| into the db later. | |
| Line 38 | |
| Every 5 seconds get the tstamps stored in the DB. | |
| <div> | |
| > **Tip** | |
| > | |
| > In the example above we use a synchronous background task function set | |
| > in the | |
| > [`FtResponse`](https://www.fastht.ml/docs/api/core.html#ftresponse) of | |
| > a synchronous handler. However, we can also use asynchronous functions | |
| > and handlers. | |
| </div> | |
| ## Multiple background tasks in a handler | |
| It is possible to add multiple background tasks to an FtResponse. | |
| <div> | |
| > **Warning** | |
| > | |
| > Multiple background tasks on a background task are executed in order. | |
| > In the case a task raises an exception, following tasks will not get | |
| > the opportunity to be executed. | |
| </div> | |
| ``` python | |
| from starlette.background import BackgroundTasks | |
| @rt | |
| async def signup(email, username): | |
| tasks = BackgroundTasks() | |
| tasks.add_task(send_welcome_email, to_address=email) | |
| tasks.add_task(send_admin_notification, username=username) | |
| return Titled('Signup successful!'), tasks | |
| async def send_welcome_email(to_address): | |
| ... | |
| async def send_admin_notification(username): | |
| ... | |
| ``` | |
| ## Background tasks at scale | |
| Background tasks enhance application performance both for users and apps | |
| by handling blocking processes asynchronously, even when defined as | |
| synchronous functions. | |
| When FastHTML’s background tasks aren’t enough and your app runs slow on | |
| a server, manually offloading processes to the `multiprocessing` library | |
| is an option. By doing so you can leverage multiple cores and bypass the | |
| GIL, significantly improving speed and performance at the cost of added | |
| complexity. | |
| Sometimes a server reaches its processing limits, and this is where | |
| distributed task queue systems like Celery and Dramatiq come into play. | |
| They are designed to distribute tasks across multiple servers, offering | |
| improved observability, retry mechanisms, and persistence, at the cost | |
| of substantially increased complexity. | |
| However most applications work well with built-in background tasks like | |
| those in FastHTML, which we recommend trying first. Writing these | |
| functions with JSON-serializable arguments ensures straightforward | |
| conversion to other concurrency methods if needed. | |