ProvenPin Install

pydantic-settings nested dotenv config with SecretStr masking: working versions (tested 2026-10-15)

Compose nested pydantic-settings models from .env files, mask credentials with SecretStr, and pin down env precedence and JSON parsing offline.

passed6 passed · 0 failed2026-10-15 18:10 UTC
Status
passed
Last passed
2026-10-15 18:10 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions3
pydantic-settings@2.16.0python-dotenv@1.2.4pytest@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 38783276983

The code

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

requirements.txt
pydantic-settings==2.16.0
python-dotenv==1.2.4
pytest==9.1.1
settings.py
from pydantic import BaseModel, SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict


class Database(BaseModel):
    host: str = "localhost"
    port: int = 5432
    user: str
    password: SecretStr


class Cache(BaseModel):
    url: str = "redis://localhost:6379/0"
    ttl: int = 60


class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_prefix="APP_",
        env_nested_delimiter="__",
        env_file=".env",
        extra="ignore",
    )

    debug: bool = False
    allowed_hosts: list[str] = []
    database: Database
    cache: Cache = Cache()
env.example
# Copy to .env. The __ delimiter maps keys onto nested settings.
APP_DEBUG=true
APP_ALLOWED_HOSTS=["api.example.com"]
APP_DATABASE__HOST=db.internal
APP_DATABASE__PORT=6543
APP_DATABASE__USER=svc
APP_DATABASE__PASSWORD=change-me
APP_CACHE__TTL=120
tests/test_settings.py
import os

import pytest
from pydantic import SecretStr, ValidationError

from settings import Settings

ENV = """\
# app config loaded from a dotenv file
APP_DEBUG=true
APP_DATABASE__HOST=db.internal
APP_DATABASE__PORT=6543
APP_DATABASE__USER=svc
APP_DATABASE__PASSWORD="p@ss word"
APP_CACHE__TTL=120
"""


@pytest.fixture(autouse=True)
def clean_env(monkeypatch):
    for key in list(os.environ):
        if key.startswith("APP_"):
            monkeypatch.delenv(key, raising=False)


@pytest.fixture
def env_file(tmp_path):
    path = tmp_path / ".env"
    path.write_text(ENV)
    return path


def load(env_file):
    return Settings(_env_file=env_file)


def test_loads_nested_values(env_file):
    s = load(env_file)
    assert s.debug is True
    assert s.database.host == "db.internal"
    assert s.database.port == 6543
    assert s.cache.ttl == 120


def test_secret_str_masks_but_keeps_value(env_file):
    s = load(env_file)
    assert isinstance(s.database.password, SecretStr)
    assert "p@ss word" not in repr(s)
    assert str(s.database.password) == "**********"
    assert s.database.password.get_secret_value() == "p@ss word"


def test_dumps_do_not_leak_secret(env_file):
    s = load(env_file)
    assert isinstance(s.model_dump()["database"]["password"], SecretStr)
    assert s.model_dump(mode="json")["database"]["password"] == "**********"


def test_env_var_overrides_dotenv(env_file, monkeypatch):
    monkeypatch.setenv("APP_DATABASE__HOST", "from-env")
    assert load(env_file).database.host == "from-env"


def test_missing_required_secret_raises(monkeypatch):
    monkeypatch.setenv("APP_DATABASE__USER", "svc")
    with pytest.raises(ValidationError) as exc:
        Settings(_env_file=None)
    assert "database" in str(exc.value)


def test_complex_values_need_json(monkeypatch):
    monkeypatch.setenv("APP_DATABASE__USER", "svc")
    monkeypatch.setenv("APP_DATABASE__PASSWORD", "y")
    monkeypatch.setenv("APP_ALLOWED_HOSTS", '["a.com","b.com"]')
    assert Settings(_env_file=None).allowed_hosts == ["a.com", "b.com"]
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.