Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork8.3k
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
First Check
Commit to Help
Example Codefromcollections.abcimportAsyncGenerator,GeneratorfromtypingimportAnnotated,AnyfromfastapiimportFastAPI,Depends,BackgroundTasksimporttimefromdatetimeimportdatetimeapp=FastAPI()defnow()->str:returndatetime.now().strftime("%H:%M:%S.%f")[:-3]defbackground_task(task_name:str,delay:int=1)->None:print(f"[{now()}|background-task] Started{task_name}")t0=time.monotonic()time.sleep(delay)print(f"[{now()}|background-task] Finished{task_name}. Elapsed:{time.monotonic()-t0:.3f} seconds" )defsync_dependency(background_tasks:BackgroundTasks)->Generator[dict[str,Any]]:print(f"[{now()}|sync-dependency] Started")t0=time.monotonic()background_tasks.add_task(background_task,"sync:pre")yield {"timestamp":time.time()}# The following background task is never executedbackground_tasks.add_task(background_task,"sync:post")# This statement is executed correctlyprint(f"[{now()}|sync-dependency] Finished. Elapsed:{time.monotonic()-t0:.3f} seconds" )asyncdefasync_dependency(background_tasks:BackgroundTasks,)->AsyncGenerator[dict[str,Any]]:print(f"[{now()}|async-dependency] Started")t0=time.monotonic()background_tasks.add_task(background_task,"async:pre")yield {"timestamp":time.time()}# The following background task is never executedbackground_tasks.add_task(background_task,"async:post")# This statement is executed correctlyprint(f"[{now()}|async-dependency] Finished. Elapsed:{time.monotonic()-t0:.3f} seconds" )@app.get("/sync")defsync(data:Annotated[dict[str,Any],Depends(sync_dependency)],background_tasks:BackgroundTasks,)->dict[str,Any]:print(f"[{now()}|GET /sync] Handling request")background_tasks.add_task(background_task,"sync:handler",2)return {"message":"Hello from sync endpoint!","data":data}@app.get("/async")asyncdefasync_endpoint(data:Annotated[dict[str,Any],Depends(async_dependency)],background_tasks:BackgroundTasks,)->dict[str,Any]:print(f"[{now()}|GET /async] Handling request")background_tasks.add_task(background_task,"async:handler",2)return {"message":"Hello from async endpoint!","data":data}if__name__=="__main__":importuvicornuvicorn.run(app,host="localhost",port=8000,log_level="info") DescriptionRunning the above FastAPI app (e.g., through Here are some logs, while I perform Observe that the Operating SystemmacOS, Linux Operating System DetailsNo response FastAPI Version0.118.0 Pydantic Version2.11.9 Python VersionPython 3.13.7 Additional ContextThe Running the same code against |
BetaWas this translation helpful?Give feedback.
All reactions
It seems you were depending on dependencies exiting early, before the response cycle, background tasks come directly from Starlette and are run as part of the response execution, that's why adding background tasks afteryield was working before.
Now that dependencies withyield are (again) by default closed after the response is sent, the code afteryield by default doesn't have a way to add background tasks, as that code is executed after the entire response cycle, which includes the background tasks.
But now, since FastAPI 0.121.0 you can opt-in to early exit, just like before, with the newscope="function":https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/#ear…
Replies: 2 comments 2 replies
-
I have created a PR to fix this in#14192, I'm hoping it can be reviewed soonish 🤞 |
BetaWas this translation helpful?Give feedback.
All reactions
-
It seems you were depending on dependencies exiting early, before the response cycle, background tasks come directly from Starlette and are run as part of the response execution, that's why adding background tasks after Now that dependencies with But now, since FastAPI 0.121.0 you can opt-in to early exit, just like before, with the new Here's your example code with the new fromcollections.abcimportAsyncGenerator,GeneratorfromtypingimportAnnotated,AnyfromfastapiimportFastAPI,Depends,BackgroundTasksimporttimefromdatetimeimportdatetimeapp=FastAPI()defnow()->str:returndatetime.now().strftime("%H:%M:%S.%f")[:-3]defbackground_task(task_name:str,delay:int=1)->None:print(f"[{now()}|background-task] Started{task_name}")t0=time.monotonic()time.sleep(delay)print(f"[{now()}|background-task] Finished{task_name}. Elapsed:{time.monotonic()-t0:.3f} seconds" )defsync_dependency(background_tasks:BackgroundTasks)->Generator[dict[str,Any]]:print(f"[{now()}|sync-dependency] Started")t0=time.monotonic()background_tasks.add_task(background_task,"sync:pre")yield {"timestamp":time.time()}# The following background task is never executedbackground_tasks.add_task(background_task,"sync:post")# This statement is executed correctlyprint(f"[{now()}|sync-dependency] Finished. Elapsed:{time.monotonic()-t0:.3f} seconds" )asyncdefasync_dependency(background_tasks:BackgroundTasks,)->AsyncGenerator[dict[str,Any]]:print(f"[{now()}|async-dependency] Started")t0=time.monotonic()background_tasks.add_task(background_task,"async:pre")yield {"timestamp":time.time()}# The following background task is never executedbackground_tasks.add_task(background_task,"async:post")# This statement is executed correctlyprint(f"[{now()}|async-dependency] Finished. Elapsed:{time.monotonic()-t0:.3f} seconds" )@app.get("/sync")defsync(data:Annotated[dict[str,Any],Depends(sync_dependency,scope="function")],background_tasks:BackgroundTasks,)->dict[str,Any]:print(f"[{now()}|GET /sync] Handling request")background_tasks.add_task(background_task,"sync:handler",2)return {"message":"Hello from sync endpoint!","data":data}@app.get("/async")asyncdefasync_endpoint(data:Annotated[dict[str,Any],Depends(async_dependency,scope="function")],background_tasks:BackgroundTasks,)->dict[str,Any]:print(f"[{now()}|GET /async] Handling request")background_tasks.add_task(background_task,"async:handler",2)return {"message":"Hello from async endpoint!","data":data}if__name__=="__main__":importuvicornuvicorn.run(app,host="localhost",port=8000,log_level="info") On the other hand, I think that there's a chance to improve further the idea of background tasks in a way that could go beyond what Starlette can offer by default, but that's something to explore in the future. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I’d respectfully disagree with that. The trivial example above intentionally hides some real-world complexities just to make the underlying issue more apparent. What I’m actually depending on is the guarantee that a function runs in its entirety. According to thedocs (emphasis mine):
In our actual use case, we use background tasks both for setup/teardown of contexts and for dispatching events to queues once those contexts are finished (the latter depend on context completion and callbacks within the main function). These events are optional and don’t affect what’s returned in the response; they just require that processing is finished. For example: deftimed(background_tasks:BackgroundTasks)->Generator[None]:background_tasks.add_task(send_to_queue({"event_started_at":now()}))withtimer:yield# Possibly time-consuming computation herebackground_tasks.add_task(send_to_queue({"processing_time":timer.elapsed}))@app.post("/process")defprocess(_timed:Annotated[None,Depends(timed)]):# Expensive computation# In some cases, we may do: `dependency.some_method(...)`returnresponse I do realise that code after deftimed(background_tasks:BackgroundTasks)->Generator[None]:background_tasks.add_task(send_to_queue({"event_started_at":now()}))withtimer:yield# Possibly time-consuming computation heresend_to_queue({"processing_time":timer.elapsed})# No background task here But my main concern is that FastAPI treats background tasks added before In my opinion, FastAPI should ideally do one of:
Personally, I opted to implement this last approach in my PR since it seemed most in line with what users would expect. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Thanks for the feedback, I'll update all the docs around dependencies to make things more clear. Does the example I gave you using As the title says "0.118 Regression", I would assume it used to work before, with the previous behavior. And the I think it might make sense to provide a better system and interface for background tasks, but for now, I prefer not to touch the default behavior of Starlette in a way that could affect many other unknown use cases. |
BetaWas this translation helpful?Give feedback.