1

In FastAPI, I'm trying to understand why the background instance not getting created outside the route function handler and its different behaviors.

Examples:

Standard doc example working as expected:

@app.get('/')
async def index(background_tasks: BackgroundTasks):
    background_tasks.add_task(some_function_reference)
    #Executes non-blocking in the same asyncio loop without any issues
    return "Hello"

It behaves differently when adding the background_tasks outside of the route function:

async def some_logic(background_tasks: BackgroundTasks):
    #Throws a "required positional argument missing" error
    background_tasks.add_task(some_function_reference)


@app.get('/')
async def index():
    await some_logic()
    #Executes non-blocking in the same asyncio loop
    return "Hello"

meanwhile, if we try to init the BackgroundTasks in the some_logic function, the task does not run as following:

async def some_logic():
    #Does not Run
    background_tasks = BackgroundTasks()
    background_tasks.add_task(some_function_reference)


@app.get('/')
async def index(background_tasks: BackgroundTasks):
    await some_logic()
    #Executes non-blocking in the same asyncio loop
    return "Hello"

Why would these three cases be different? Why do i need to pass the background tasks from the route function to the following called function?

STOPIMACODER
  • 822
  • 2
  • 7
  • 19

1 Answers1

2

The BackgroundTasks instance gets populated by the FastAPI framework and injected into the callers. In the first example, you use a route which has it's dependencies injected by FastAPI.

The second example, you are calling some_logic as a normal function. Because it's not being called by FastAPI directly, no dependency injection is happening.

In the third example, you create a new instance of background tasks, but this is a different instance than the one that FastAPI cares about. You create tasks in it, but there is no handler registered to act on them.

In this case, if the goal is to make the background tasks easier to apply to methods, you could add the some_logic function as a dependency of the route. A dependency is called by FastAPI, and therefore the arguments will be injected.

Example:

from fastapi import FastAPI, Depends
from starlette.background import BackgroundTasks
from starlette.testclient import TestClient

app = FastAPI()


def task():
    print("Hello from the Background!")


async def some_logic(background_tasks: BackgroundTasks):
    background_tasks.add_task(task)


@app.get('/', dependencies=[Depends(some_logic)])
async def index():
    return "Hello"


with TestClient(app) as client:
    client.get("/")
Hello from the Background!
flakes
  • 21,558
  • 8
  • 41
  • 88
  • Interesting. So i would assume in this case if an exception is raised after the task is added, it would not execute since its handled by the global error handler? Would that mean that task would be lost and never executed? – STOPIMACODER Aug 06 '23 at 03:35
  • Also, how would this operate if there is a needed argument to be passed to the `some_logic` function? I would assume depend would not be sufficient here and we would require passing background_tasks in the route function body? – STOPIMACODER Aug 06 '23 at 03:37
  • 1
    @STOPIMACODER "if an exception is raised after the task is added, it would not execute" That's correct. The background tasks feel very weak compared to most of the other FastAPI fixtures. It's shoehorned in kind of crudely from the underlying starlette library that acts on them. I avoid them most cases if I can. – flakes Aug 06 '23 at 03:39
  • 1
    @STOPIMACODER Dependencies can be handled in a lot of ways. It really depends on what data you need. If its purely data from the request, there's nothing stopping you from adding these as extra dependencies in `some_logic`. There's also this pattern for more complex cases, where you have a dependency return a callable that can take extra params, but not need to plumb absolutely everything through the route https://fastapi.tiangolo.com/advanced/advanced-dependencies/#a-callable-instance – flakes Aug 06 '23 at 03:41
  • 1
    Understood. I'll check out the doc you linked seems like an interesting read. Thank you for the clarification! – STOPIMACODER Aug 06 '23 at 03:43