Jeśli chcesz wykonać operacje które nie muszą zostać wykonane przed zwróceniem odpowiedzi HTTP (wysłać email, zapisać bitmapę na blob storage), z rozwiązaniem przychodzi BackgroundTask
. Jeśli proces biznesowy tego nie wymaga, to dlaczego klient serwera ma czekać kilka sekund na odpowiedź? Wystarczy że zwrócisz status HTTP 202 (zaakceptowane) i wykonasz zadanie w tle.
Dodanie i wykonanie BackgroundTask
FastAPI wspiera wstrzykiwanie zależności – instancję BackgroundTasks
do której dodamy nasze zadania definiujemy jako parametr funkcji która jest dekorowana przez endpoint .
O to właśnie nam chodzi – FastAPI zajmie się orkiestracją (wykonaniem funkcji którą rejestrujemy) po obsłużeniu żadania HTTP.
Rejestrujemy zadania poprzez metodę .add_task
która przyjmuje callable
i opcjonalne argumenty które mają być przekazane podczas wykonania przekazanego callable
. Innymi słowy sygnaturę którą można wywołać: czyli referencję do funkcji lub obiektu który implementuje __call__
.
Dostajemy instancję BackgroundTasks która wykona zarejestrowane zadanie po zwróceniu odpowiedzi – oto właśnie nam chodzi – FastAPI zajmie się orkiestracją (wykonaniem funkcji którą rejestrujemy) po obsłużeniu żądania HTTP.
import asyncio
import uvicorn
from fastapi import FastAPI, BackgroundTasks
app = FastAPI()
async def task(request_id: int):
# Ta funkcja uruchomi się po zwróceniu odpowiedzi, kiedy zarejestrujemy ją jako background task
await asyncio.sleep(2)
print(f'Handling background task for request id:{request_id}')
counter = 0
@app.get("/")
async def root(background_task: BackgroundTasks):
global counter
counter += 1
background_task.add_task(task, data=counter)
if counter % 2:
raise Exception()
print(f'Request id:{counter}')
return counter
if __name__ == '__main__':
uvicorn.run("main:app", reload=True)
Jak widzisz, w związku z naturą async
, pierwszy background task wykonał się dopiero po zwróceniu odpowiedzi z czwartego żądania HTTP.
Przykład z nieobsłużonym wyjątkiem
Nawet jeśli nieobsłużony wyjątek nastąpi po zarejestrowaniu zadania, zarejestrowane zadanie nie zostanie wykonane
async def task(request_id: int):
await asyncio.sleep(2)
print(f'Handling background task for request id:{request_id}')
counter = 0
@app.get("/")
async def root(background_task: BackgroundTasks):
global counter
counter += 1
background_task.add_task(task, request_id=counter)
if counter % 2:
raise Exception()
print(f'Request id:{counter}')
return counter
Szczegóły techniczne
- Klasa BackgroundTasks pochodzi bezpośrednio z
starlette.background
, ale nie importuj jej stamtąd. Została zaimportowana/włączona bezpośrednio do FastAPI. - BackgroundTask jest uruchamiane po wysłaniu odpowiedzi. Nie ma więc możliwości zgłoszenia wyjątku dla klienta żądania kiedy coś pójdzie nie tak.
Źródła
Udostępnij ten wpis
Dobrnąłeś do końca. Jeśli ten artykuł był dla Ciebie wartościowy i chcesz otrzymywać informacje o kolejnych, to zapraszam Cię do zapisania się do listy mailingowej. Gwarantuję zero spamu.
Radek.