Python

FastAPI and Starlette

Load Envless variables in FastAPI or Starlette with the envless-env lifespan, before the first request, without blocking the event loop.

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.

python
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.

python
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.

python
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.