Pass lifespan from envless_env.fastapi and the app loads on startup, in a worker thread so the event loop keeps running, before it accepts a request. A failed load stops the server from starting.
from fastapi import FastAPI
from envless_env.fastapi import lifespan
from envless_env.server import env
app = FastAPI(lifespan=lifespan)
@app.get('/health')
def health() -> dict[str, object]:
return {'region': env.AWS_REGION}Starlette takes the same argument, Starlette(lifespan=lifespan).
With your own lifespan
Wrap an existing lifespan with with_envless. The load runs first, then your lifespan, and whatever state your lifespan yields reaches the app unchanged.
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from fastapi import FastAPI
from envless_env.fastapi import with_envless
from envless_env.server import env
@asynccontextmanager
async def database(app: FastAPI) -> AsyncGenerator[dict[str, str], None]:
url = str(env.DATABASE_URL)
yield {'database_url': url}
app = FastAPI(lifespan=with_envless(database))Values needed at import
The lifespan runs after your modules are imported. If a module reads a variable at import time, to build a client or decide which routers to mount, load before that import instead: make import envless_env.register the first line of the module your server imports, or start the server under envless run.
import envless_env.register # noqa: F401
from fastapi import FastAPI
from envless_env.server import env
app = FastAPI(debug=env.DEBUG is True)Each Uvicorn or Gunicorn worker is its own process and loads on its own. After the first fetch the others read the ciphertext cache, as long as they share a home folder.