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)

 

posted @ 2024-04-12 20:07  奋斗终生  Views(630)  Comments(0Edit  收藏  举报