ProvenPin Install

FastAPI config via pydantic-settings as a cached dependency: working versions (tested 2026-10-15)

Load config from env vars with pydantic-settings, inject it into FastAPI routes through a cached dependency, and test it offline with monkeypatch and dependency_overrides.

passed5 passed · 0 failed2026-10-15 13:21 UTC
Status
passed
Last passed
2026-10-15 13:21 UTC
Tests
5 / 5
Language
Python
Runtime
Python 3.13.16
pinned versions4
fastapi@0.143.1pydantic-settings@2.16.0httpx@0.28.1pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38741369695

The code

Exactly the files that passed. Your agent can pull them with get_recipe("fastapi-pydantic-settings-dependency") (install), or see them on GitHub.

requirements.txt
fastapi==0.143.1
pydantic-settings==2.16.0
httpx==0.28.1
pytest==9.1.1
config.py
from functools import lru_cache

from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
    model_config = SettingsConfigDict(case_sensitive=False)

    app_name: str = "demo"
    debug: bool = False
    database_url: str = "sqlite:///:memory:"
    api_key: SecretStr  # required: no default, so a missing env var fails loudly


@lru_cache
def get_settings() -> Settings:
    # Built once per process. Env changes are invisible until cache_clear().
    return Settings()
main.py
from typing import Annotated

from fastapi import Depends, FastAPI, Header, HTTPException

from config import Settings, get_settings

app = FastAPI()

SettingsDep = Annotated[Settings, Depends(get_settings)]


@app.get("/info")
def info(settings: SettingsDep):
    return {"app": settings.app_name, "debug": settings.debug}


@app.get("/secure")
def secure(settings: SettingsDep, x_api_key: Annotated[str | None, Header()] = None):
    if x_api_key != settings.api_key.get_secret_value():
        raise HTTPException(status_code=401, detail="bad key")
    return {"ok": True}
tests/test_app.py
import pytest
from fastapi.testclient import TestClient
from pydantic import ValidationError

from config import Settings, get_settings
from main import app


@pytest.fixture(autouse=True)
def fresh_settings():
    get_settings.cache_clear()
    yield
    app.dependency_overrides.clear()
    get_settings.cache_clear()


@pytest.fixture
def client():
    return TestClient(app)


def test_env_vars_fill_settings(monkeypatch, client):
    monkeypatch.setenv("API_KEY", "env-key")
    monkeypatch.setenv("APP_NAME", "from-env")
    monkeypatch.setenv("DEBUG", "true")
    assert client.get("/info").json() == {"app": "from-env", "debug": True}


def test_cache_pins_first_read(monkeypatch):
    monkeypatch.setenv("API_KEY", "first")
    first = get_settings()
    monkeypatch.setenv("API_KEY", "second")
    assert get_settings() is first
    assert get_settings().api_key.get_secret_value() == "first"
    get_settings.cache_clear()
    assert get_settings().api_key.get_secret_value() == "second"


def test_override_replaces_cached_dependency(client):
    # The override key must be the exact function object used in Depends().
    app.dependency_overrides[get_settings] = lambda: Settings(
        api_key="k", app_name="override", _env_file=None
    )
    assert client.get("/info").json()["app"] == "override"


def test_secure_uses_overridden_key(client):
    app.dependency_overrides[get_settings] = lambda: Settings(api_key="s3cret", _env_file=None)
    assert client.get("/secure", headers={"x-api-key": "s3cret"}).status_code == 200
    assert client.get("/secure", headers={"x-api-key": "nope"}).status_code == 401


def test_missing_required_key_fails(monkeypatch):
    monkeypatch.delenv("API_KEY", raising=False)
    with pytest.raises(ValidationError):
        Settings(_env_file=None)
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.