@app.on_event("startup") and @app.on_event("shutdown") are deprecated. The replacement is a single async context manager passed as app = FastAPI(lifespan=lifespan), where everything before yield runs at startup and everything after runs at shutdown.
The practical advantage is not style. Code before and after the yield share a scope, so a connection pool created at startup is simply a local variable the shutdown half can close, instead of a module-level global. State meant for request handlers goes on app.state or is yielded as a dict that appears in request.state.
The migration trap is ordering with sub-applications: a lifespan defined on a mounted sub-app does not run when the parent app starts, so a mounted API that opens its own pool will silently never initialise it. Either lift the lifespan to the parent or run the sub-app's lifespan explicitly. Also note that lifespan does not execute under TestClient unless the client is used as a context manager, which is why fixtures that call endpoints directly can see uninitialised state.