Skip to content

Instantly share code, notes, and snippets.

@braindevices
Last active April 16, 2026 18:32
Show Gist options
  • Select an option

  • Save braindevices/76eb519553b4ac314cd2555f0b31decc to your computer and use it in GitHub Desktop.

Select an option

Save braindevices/76eb519553b4ac314cd2555f0b31decc to your computer and use it in GitHub Desktop.
pydantic config with --config some.toml as toml config source

help works fine:

uv run test-pydantic.py -h
pydantic_settings.VERSION='2.13.1'
usage: test-pydantic.py [-h] [--config CONFIG] [--db_url HttpUrl] [--api_key str] [--port int] [--env {dev,stg,prod}] [--created_at AwareDatetime] [--debug bool] [--log_dir Path]

options:
  -h, --help            show this help message and exit
  --config CONFIG
  --db_url HttpUrl      The database connection string (required)
  --api_key str         API key for external services (default: default_key)
  --port int            (default: 8000)
  --env {dev,stg,prod}  (default: dev)
  --created_at AwareDatetime
                        (default: 2026-04-16 20:00:00.909125+02:00)
  --debug bool          (default: False)
  --log_dir Path        Directory for log files (default: logs)

config file also works fine

uv run test-pydantic.py --config test.toml
pydantic_settings.VERSION='2.13.1'
Verified Config: {'db_url': HttpUrl('http://localhost/'), 'api_key': 'default_key', 'port': 9999, 'env': <Environment.stg: 'staging'>, 'created_at': datetime.datetime(2026, 4, 16, 20, 30, 50, 348786, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')), 'debug': True, 'log_dir': PosixPath('logs')}
db_url=HttpUrl('http://localhost/') api_key='default_key' port=9999 env=<Environment.stg: 'staging'> created_at=datetime.datetime(2026, 4, 16, 20, 30, 50, 348786, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200), 'CEST')) debug=True log_dir=PosixPath('logs')
{
  "$defs": {
    "Environment": {
      "enum": [
        "development",
        "staging",
        "production"
      ],
      "title": "Environment",
      "type": "string"
    }
  },
  "properties": {
    "db_url": {
      "description": "The database connection string",
      "format": "uri",
      "maxLength": 2083,
      "minLength": 1,
      "title": "Db Url",
      "type": "string"
    },
    "api_key": {
      "default": "default_key",
      "description": "API key for external services",
      "title": "Api Key",
      "type": "string"
    },
    "port": {
      "default": 8000,
      "examples": [
        8080
      ],
      "maximum": 65535,
      "minimum": 1,
      "title": "Port",
      "type": "integer"
    },
    "env": {
      "$ref": "#/$defs/Environment",
      "default": "development"
    },
    "created_at": {
      "default": "2026-04-16T20:30:50.348786+02:00",
      "format": "date-time",
      "title": "Created At",
      "type": "string"
    },
    "debug": {
      "default": false,
      "title": "Debug",
      "type": "boolean"
    },
    "log_dir": {
      "default": "logs",
      "description": "Directory for log files",
      "format": "path",
      "title": "Log Dir",
      "type": "string"
    }
  },
  "required": [
    "db_url"
  ],
  "title": "SettingModel",
  "type": "object"
}
#:schema ./test.schema
db_url = "http://localhost/"
api_key = "default_key"
port = 9999
env = "staging"
created_at = "2026-04-16T20:30:50.348786+02:00"
debug = true
log_dir = "logs"

#:schema ./test.schema
db_url = "http://localhost/"
api_key = "default_key"
port = 9999
env = "staging"
created_at = "2026-04-16T20:30:50.348786+02:00"
debug = true
log_dir = "logs"
test = 2

{
  "$schema": "./test.schema",
  "db_url": "http://localhost/",
  "api_key": "default_key",
  "port": 9999,
  "env": "staging",
  "created_at": "2026-04-16T20:30:50.348786+02:00",
  "debug": true,
  "log_dir": "logs"
}
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.14"
# dependencies = ["tomlkit", "pydantic-settings"]
# ///
import argparse
from datetime import datetime
from enum import Enum
import json
from pathlib import Path
from typing import Annotated, Tuple, Type
from pydantic import BaseModel, Field, HttpUrl, AwareDatetime
from pydantic_settings import (
BaseSettings,
CliApp,
CliSettingsSource,
PydanticBaseSettingsSource,
SettingsConfigDict,
TomlConfigSettingsSource,
)
import pydantic_settings
import tomlkit
Port = Annotated[int, Field(ge=1, le=65535, json_schema_extra={"examples": [8080]})]
class Environment(Enum):
dev = "development"
stg = "staging"
prod = "production"
class SettingModel(BaseModel):
db_url: HttpUrl = Field(..., description="The database connection string")
api_key: str = Field("default_key", description="API key for external services")
port: Port = 8000
env: Environment = Environment.dev
created_at: AwareDatetime = Field(default=datetime.now().astimezone())
debug: bool = False
log_dir: Path = Field(default=Path("./logs"), description="Directory for log files")
def settings_factory(config_path: str) -> Type[BaseSettings]:
# Inherit from both the Schema and BaseSettings
class FinalSettings(SettingModel, BaseSettings):
model_config = SettingsConfigDict(
toml_file=config_path,
env_prefix="APP_",
# This allows the CLI to look for --db-url instead of --db_url
cli_kebab_case=True,
cli_parse_args=True
)
@classmethod
def settings_customise_sources(
cls,
settings_cls: Type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
**kwargs,
) -> Tuple[PydanticBaseSettingsSource, ...]:
# Priority: CLI > ENV > TOML
return (
env_settings,
TomlConfigSettingsSource(settings_cls, toml_file=config_path)
)
return FinalSettings
def main():
print(f"{pydantic_settings.VERSION=}")
# main can type-hint the BaseModel for better decoupling
# Bootstrap parser to get the config path
bootstrap = argparse.ArgumentParser(add_help=False) # required otherwise the help does not contain main options
bootstrap.add_argument("--config", type=str, default="settings.toml")
args, remain = bootstrap.parse_known_args()
parser = argparse.ArgumentParser(parents=[bootstrap]) # required otherwise there is no -h option
DynamicSettingsClass = settings_factory(args.config)
cli_settings = CliSettingsSource(DynamicSettingsClass, root_parser=parser)
conf = CliApp.run(DynamicSettingsClass, cli_settings_source=cli_settings, cli_args=remain)
print(f"Verified Config: {conf.model_dump()}")
pure_model = SettingModel.model_validate(conf.model_dump())
print(pure_model)
schema = SettingModel.model_json_schema()
print(json.dumps(schema, indent=2))
jdata = conf.model_dump(mode="json")
schema_path = "./test.schema"
toml_dump = tomlkit.dumps(jdata)
toml_dump = f"#:schema {schema_path}\n{toml_dump}"
print(toml_dump)
toml_doc = tomlkit.loads(toml_dump)
toml_doc["test"] = 2
print(toml_doc.as_string())
jdata = {
"$schema": schema_path,
**jdata
}
print(json.dumps(jdata, indent=2))
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment