Lifespan Events¶
You can define logic (code) that should be executed before the applicationstarts up. This means that this code will be executedonce,before the applicationstarts receiving requests.
The same way, you can define logic (code) that should be executed when the application isshutting down. In this case, this code will be executedonce,after having handled possiblymany requests.
Because this code is executed before the applicationstarts taking requests, and right after itfinishes handling requests, it covers the whole applicationlifespan (the word "lifespan" will be important in a second 😉).
This can be very useful for setting upresources that you need to use for the whole app, and that areshared among requests, and/or that you need toclean up afterwards. For example, a database connection pool, or loading a shared machine learning model.
Use Case¶
Let's start with an exampleuse case and then see how to solve it with this.
Let's imagine that you have somemachine learning models that you want to use to handle requests. 🤖
The same models are shared among requests, so, it's not one model per request, or one per user or something similar.
Let's imagine that loading the model cantake quite some time, because it has to read a lot ofdata from disk. So you don't want to do it for every request.
You could load it at the top level of the module/file, but that would also mean that it wouldload the model even if you are just running a simple automated test, then that test would beslow because it would have to wait for the model to load before being able to run an independent part of the code.
That's what we'll solve, let's load the model before the requests are handled, but only right before the application starts receiving requests, not while the code is being loaded.
Lifespan¶
You can define thisstartup andshutdown logic using thelifespan parameter of theFastAPI app, and a "context manager" (I'll show you what that is in a second).
Let's start with an example and then see it in detail.
We create an async functionlifespan() withyield like this:
fromcontextlibimportasynccontextmanagerfromfastapiimportFastAPIdeffake_answer_to_everything_ml_model(x:float):returnx*42ml_models={}@asynccontextmanagerasyncdeflifespan(app:FastAPI):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelyield# Clean up the ML models and release the resourcesml_models.clear()app=FastAPI(lifespan=lifespan)@app.get("/predict")asyncdefpredict(x:float):result=ml_models["answer_to_everything"](x)return{"result":result}Here we are simulating the expensivestartup operation of loading the model by putting the (fake) model function in the dictionary with machine learning models before theyield. This code will be executedbefore the applicationstarts taking requests, during thestartup.
And then, right after theyield, we unload the model. This code will be executedafter the applicationfinishes handling requests, right before theshutdown. This could, for example, release resources like memory or a GPU.
Tip
Theshutdown would happen when you arestopping the application.
Maybe you need to start a new version, or you just got tired of running it. 🤷
Lifespan function¶
The first thing to notice, is that we are defining an async function withyield. This is very similar to Dependencies withyield.
fromcontextlibimportasynccontextmanagerfromfastapiimportFastAPIdeffake_answer_to_everything_ml_model(x:float):returnx*42ml_models={}@asynccontextmanagerasyncdeflifespan(app:FastAPI):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelyield# Clean up the ML models and release the resourcesml_models.clear()app=FastAPI(lifespan=lifespan)@app.get("/predict")asyncdefpredict(x:float):result=ml_models["answer_to_everything"](x)return{"result":result}The first part of the function, before theyield, will be executedbefore the application starts.
And the part after theyield will be executedafter the application has finished.
Async Context Manager¶
If you check, the function is decorated with an@asynccontextmanager.
That converts the function into something called an "async context manager".
fromcontextlibimportasynccontextmanagerfromfastapiimportFastAPIdeffake_answer_to_everything_ml_model(x:float):returnx*42ml_models={}@asynccontextmanagerasyncdeflifespan(app:FastAPI):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelyield# Clean up the ML models and release the resourcesml_models.clear()app=FastAPI(lifespan=lifespan)@app.get("/predict")asyncdefpredict(x:float):result=ml_models["answer_to_everything"](x)return{"result":result}Acontext manager in Python is something that you can use in awith statement, for example,open() can be used as a context manager:
withopen("file.txt")asfile:file.read()In recent versions of Python, there's also anasync context manager. You would use it withasync with:
asyncwithlifespan(app):awaitdo_stuff()When you create a context manager or an async context manager like above, what it does is that, before entering thewith block, it will execute the code before theyield, and after exiting thewith block, it will execute the code after theyield.
In our code example above, we don't use it directly, but we pass it to FastAPI for it to use it.
Thelifespan parameter of theFastAPI app takes anasync context manager, so we can pass our newlifespan async context manager to it.
fromcontextlibimportasynccontextmanagerfromfastapiimportFastAPIdeffake_answer_to_everything_ml_model(x:float):returnx*42ml_models={}@asynccontextmanagerasyncdeflifespan(app:FastAPI):# Load the ML modelml_models["answer_to_everything"]=fake_answer_to_everything_ml_modelyield# Clean up the ML models and release the resourcesml_models.clear()app=FastAPI(lifespan=lifespan)@app.get("/predict")asyncdefpredict(x:float):result=ml_models["answer_to_everything"](x)return{"result":result}Alternative Events (deprecated)¶
Warning
The recommended way to handle thestartup andshutdown is using thelifespan parameter of theFastAPI app as described above. If you provide alifespan parameter,startup andshutdown event handlers will no longer be called. It's alllifespan or all events, not both.
You can probably skip this part.
There's an alternative way to define this logic to be executed duringstartup and duringshutdown.
You can define event handlers (functions) that need to be executed before the application starts up, or when the application is shutting down.
These functions can be declared withasync def or normaldef.
startup event¶
To add a function that should be run before the application starts, declare it with the event"startup":
fromfastapiimportFastAPIapp=FastAPI()items={}@app.on_event("startup")asyncdefstartup_event():items["foo"]={"name":"Fighters"}items["bar"]={"name":"Tenders"}@app.get("/items/{item_id}")asyncdefread_items(item_id:str):returnitems[item_id]In this case, thestartup event handler function will initialize the items "database" (just adict) with some values.
You can add more than one event handler function.
And your application won't start receiving requests until all thestartup event handlers have completed.
shutdown event¶
To add a function that should be run when the application is shutting down, declare it with the event"shutdown":
fromfastapiimportFastAPIapp=FastAPI()@app.on_event("shutdown")defshutdown_event():withopen("log.txt",mode="a")aslog:log.write("Application shutdown")@app.get("/items/")asyncdefread_items():return[{"name":"Foo"}]Here, theshutdown event handler function will write a text line"Application shutdown" to a filelog.txt.
Info
In theopen() function, themode="a" means "append", so, the line will be added after whatever is on that file, without overwriting the previous contents.
Tip
Notice that in this case we are using a standard Pythonopen() function that interacts with a file.
So, it involves I/O (input/output), that requires "waiting" for things to be written to disk.
Butopen() doesn't useasync andawait.
So, we declare the event handler function with standarddef instead ofasync def.
startup andshutdown together¶
There's a high chance that the logic for yourstartup andshutdown is connected, you might want to start something and then finish it, acquire a resource and then release it, etc.
Doing that in separated functions that don't share logic or variables together is more difficult as you would need to store values in global variables or similar tricks.
Because of that, it's now recommended to instead use thelifespan as explained above.
Technical Details¶
Just a technical detail for the curious nerds. 🤓
Underneath, in the ASGI technical specification, this is part of theLifespan Protocol, and it defines events calledstartup andshutdown.
Info
You can read more about the Starlettelifespan handlers inStarlette's Lifespan' docs.
Including how to handle lifespan state that can be used in other areas of your code.
Sub Applications¶
🚨 Keep in mind that these lifespan events (startup and shutdown) will only be executed for the main application, not forSub Applications - Mounts.







