'FastApi create background task in a custom APIRoute

According to this tutorial you can create BackgroundTasks from the route function as follow:

@app.post("/send-notification/{email}")
async def send_notification(email: str, background_tasks: BackgroundTasks):
    background_tasks.add_task(write_notification, email, message="some notification")
    return {"message": "Notification sent in the background"}

but in my case, I have a custom APIRoute that should create a background task after every endpoint-call (this is a simplified example):

# core/users.py
def process(email):
    #processing
    #create a background task here

# router.py
class MyRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            response: Response = await original_route_handler(request)
            return response

    return custom_route_handler

# app.py
from core import users
@app.post("/send-notification/{email}")
async def send_notification(email: str):
    #processing


Solution 1:[1]

Thank you everyone for the ideas; but according to my use case (the background task is created in a custom APIRoute, after the response is created) I ended up with something like this:

# core/users.py
def process(response: Response):
    background_task: BackgroundTask = BackgroundTask(async_process, MY_ARG1)
    if response.background is None:
        response.background = background_task
    else:
        response.background.add_task(background_task.func, *background_task.args, *background_task.kwargs)

# router.py
class MyRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Response:
            response: Response = await original_route_handler(request)
            users.process(response=response)
            return response

    return custom_route_handler

# app.py
app = APIRouter(route_class=MyRoute)
@app.post("/send-notification/{email}")
async def send_notification(email: str):
    #processing

PS: These code snippets are a simplified version of what I used in creating a "trace-module" for the OpenReplay project.

Solution 2:[2]

You can do that, but you should attach the background object to the response. You can check starlette official doc at Background Tasks

# core/users.py
from fastapi import BackgroundTasks

def send_email(email):
    print("sending email to {} as background task!".format(email))

def process(email):
    background_tasks = BackgroundTasks()
    background_tasks.add_task(send_email, email)
    return background_tasks


#app.py
from core import users
from fastapi.responses import JSONResponse

@app.post("/send-notification/{email}")
async def send_notification(email: str):
    background_tasks = users.process(email)
    return JSONResponse("Email sent!", background=background_tasks)

Solution 3:[3]

BackgroundTasks is just a helper, you can run it in thread/loop by yourself:

def run_in_background(func, *args):
    if asyncio.iscoroutinefunction(func):
        loop = asyncio.get_event_loop()
        loop.create_task(func("param"))
    else:
        # Starlette uses anyio library here under the hood
        t = threading.Thread(target=func, args=("param",))
        t.start()

Solution 4:[4]

background task come from Starlette and it is attached to a response.

As fastapi doc said:

"It's still possible to use BackgroundTask alone in FastAPI, but you have to create the object in your code and return a Starlette Response including it."

you may want to take a look at runing your function in a threadpool from tiangolo (creator of fastapi)

from fastapi.concurrency import run_in_threadpool

@api.get('/handler')
async def handler():
    ...
    # Slow async function
    await my_async_function()
    ....
    # Slow running sync function
    await run_in_threadpool(sync_function)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 kosciej16
Solution 4 Bastien B