Skip to content

Instantly share code, notes, and snippets.

@a-recknagel
Last active January 6, 2023 09:23
Show Gist options
  • Save a-recknagel/6faacb042b6638cb30249cc52fb320da to your computer and use it in GitHub Desktop.
Save a-recknagel/6faacb042b6638cb30249cc52fb320da to your computer and use it in GitHub Desktop.
Config example with custom toml loaders and the builtin env loader
MY_APP_APP__PASSWORD="secret_foo"
MY_APP_DB__PASSWORD="secret_bar"
import importlib.resources
from pathlib import Path
from pydantic import BaseModel, BaseSettings, validator
import toml
def default_settings(*_):
with importlib.resources.path("my_app", "default.toml") as path:
with path.open() as f:
return toml.load(f)
def user_settings(*_):
user_config = Path("user.toml") # expected to be in cwd
try:
with open(user_config) as f:
return toml.load(f)
except FileNotFoundError:
print(f"User config expected at '{user_config}', but not found.")
return {}
class App(BaseModel):
loglevel: str
user: str
password: str
version: tuple[int, int, int] # really shouldn't be in a config, but good example for a complex setting
@validator("version", pre=True)
def parse_version(cls, v):
if isinstance(v, str):
raw_version = v.split(".")
if len(raw_version) != 3:
raise ValueError(f"Expected simple semver format, got {v} instead.")
v = tuple(int(x) for x in raw_version)
return v
class Model(BaseModel):
C: float
gamma: float
class Database(BaseModel):
backend: str
host: str
port: int
user: str
password: str
class Settings(BaseSettings):
app: App
model: Model
db: Database
class Config:
@classmethod
def customise_sources(cls, env_settings, **kwargs):
# the order in which these functions are returned decides the
# override order, which (apparently) is from right to left
return env_settings, user_settings, default_settings
env_prefix = "MY_APP_"
env_nested_delimiter = "__"
cfg = Settings()
# part of the source code, provide default values for all settings
[app]
loglevel = "DEBUG"
user = ""
password = ""
version = "0.10.2"
[model]
C = 5
gamma = 0.01
[db]
backend = "sqlite"
host = ""
port = 0
user = ""
password = ""
# in cwd when code is run
[app]
user = "foo"
[db]
backend = "postgresql"
user = "bar"
host = "127.0.23.2"
port = 3099
[model]
C = 7
gamma = 0.3
@a-recknagel
Copy link
Author

Given an installed package my_app and after exporting the .env file, the config would look like this:

>>> from my_app.config import cfg
>>> cfg.json()  # indented for readability
{
  "app": {
    "loglevel": "DEBUG",
    "user": "foo",
    "password": "secret_foo",
    "version": [0, 10, 2]
  },
  "model": {
    "C": 7.0,
    "gamma": 0.3
  },
  "db": {
    "backend": "postgresql",
    "host": "127.0.23.2",
    "port": 3099,
    "user": "bar",
    "password": "secret_bar"
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment