FastAPI: 测试lifespan特性(转)
add by zhj:实践出真知,文章写得真不错,自己测试这些条件
原文:FastAPI: experiment lifespan feature
Init
In FastAPI, one of ways creating a shared resource and living as long as application is up is using lifespan feature in FastAPI.
This lifespan feature can do
- creating resources before App starts getting requests.
- cleaning up resources when App is terminating
The feature used to be supported with startup/shutdown, but it has been deprecated.
Why startup/shutdown is deprecated?
In FastAPI, the startup and shutdown are two different functions. As you know, the shutdown function is used to clean up ( OR free up the resources ). To do that, some instances have to be kept in Global area.
The lifespan feature ( new ) tries to simplify ( OR remove ) those unencessity keeping them in Global area by utilizing “contextmanager”.
I’d like to try how it works today.
Start: Base Code
Here is the starting code. It uses “asynccontextmanager” in contextlib.
from contextlib import asynccontextmanager from fastapi import FastAPI, Request resource = {} @asynccontextmanager async def app_lifespan(app: FastAPI): print("init lifespan") resource["msg"] = "Hello, it's beautiful day!!" yield resource.clear() print("clean up lifespan") app = FastAPI(lifespan=app_lifespan) @app.get("/", include_in_schema=False) async def root(request: Request): resource["msg"] print("completed app init.")
Loading Application
- The interesting thing was that the execution of the code wasn’t blocking code. The message “completed app int” is printed before the “init lifespan”.
- And, one obvious thing was that “clean up lifespan” wasn’t printed when application is loaded. ( coroutine ).
Experiment 1: can contextmanager be used?
I got curious if `contextmanager` instead of `asynccontextmanager` can be used for the lifespan function.
So, I tried it. And, I got an error.
( maybe using sync contextmanager for async function didn’t make a sense from the beggining )
The error is from Starlette that FastAPI depends on.
Based on the code, the lifespan has to be wrapped ( or supported ) with async context manager. ( required )
Experiment 2: Is app positional argument required for lifespan function?
Here is the base code. I got curious if the app
argument is required.
So, I tried after removing the app
argument from the app_lifespan function.
@asynccontextmanager async def app_lifespan(app: FastAPI): print("init lifespan") resource["msg"] = "Hello, it's beautiful day!!" yield resource.clear() print("clean up lifespan")
And, I got this error. The error came from the same code that I looked into when I tried contextmanager
instead asynccontextmanager
.
Basically, the Starlette lifespan implementation that FastAPI uses requires
- asynccontextmanager
- taking a positional argument
async with self.lifespan_context(app) as maybe_state:
Experiment 3: Does lifespan has to be set to FastAPI?
The `def app_lifespan` is assigned ( or set ) to FastAPI in the base code like below.
app = FastAPI(lifespan=app_lifespan)
What if the `app_lifespan` is not assigned into FastAPI during the initialization, will the lifespan be executed?
Well, based on the code I saw in previous experimentations, I can say that it won’t work. And, the below result confirmed that.
Experiment 4: Is `yield` required in lifespan?
What if `yield` is removed? Does it still work?
Is the `yield` required in the lifespan?
@asynccontextmanager async def app_lifespan(app: FastAPI): print("init lifespan") resource["msg"] = "Hello, it's beautiful day!!" # yield resource.clear() print("clean up lifespan")
Yes. The `yield` is required. It’s because the lifespan uses async function + asynccontextmanger. It needs `yield`
Experiment 5: what if lifespan is implemented as sync function?
What if the lifspan is implemented fully as sync function like below?
@contextmanager def app_lifespan(app: FastAPI): print("init lifespan") resource["msg"] = "Hello, it's beautiful day!!" # yield resource.clear() print("clean up lifespan")
As seen in the previous experimentations, since the Starlette uses ( or expect ) asynccontextmanger, it doesn’t work.
Summary
Basically, the lifespan feature requires
- async implemented function
- yield has to be set in the function
- asynccontextmanager has to be used
- async implemented function lifespan has to be set to FastAPI
Here is the final format how FastAPI lifespan has to be implemented.
@asynccontextmanager async def app_lifespan(app: FastAPI): # code to execute when app is loading yield # code to execute when app is shutting down app = FastAPI(lifespan=app_lifespan)