Skip to main content

A deterministic, typed configuration engine for serious automation systems

Project description

utilityhub_config

A deterministic, typed configuration loader for modern Python applications. Load settings from multiple sources with clear precedence, comprehensive metadata tracking, and detailed validation errors.

Features ✨

  • Multi-source configuration loading with explicit precedence order
  • Strongly typed with Pydantic v2+ (full type safety)
  • Metadata tracking — see which source provided each field
  • Multiple formats — TOML, YAML, .env, environment variables
  • Rich error reporting — Validation failures show sources, checked files, and precedence
  • Zero magic — Deterministic, transparent resolution order

Installation

pip install utilityhub_config

Quick Start

from pydantic import BaseModel
from utilityhub_config import load_settings

class Config(BaseModel):
    database_url: str = "sqlite:///default.db"
    debug: bool = False

# Load settings and metadata
settings, metadata = load_settings(Config)

# Type-safe access (no casting needed)
print(settings.database_url)

# Track which source provided a field
source = metadata.get_source("database_url")
print(f"database_url came from: {source.source}")

How It Works

Settings are resolved in strict precedence order (lowest to highest):

  1. Defaults — Field defaults from your Pydantic model
  2. Global config~/.config/{app_name}/{app_name}.{toml,yaml}
  3. Project config{cwd}/{app_name}.{toml,yaml} or {cwd}/config/*.{toml,yaml}
  4. Dotenv.env file in current directory
  5. Environment variables{APP_NAME}_{FIELD_NAME} or {FIELD_NAME}
  6. Runtime overrides — Passed via overrides parameter (highest priority)

Each level overrides the previous one. Only sources that exist are consulted.

Examples

Basic usage with model defaults

from pydantic import BaseModel
from utilityhub_config import load_settings

class PizzaShopConfig(BaseModel):
    shop_name: str = "lazy_pepperoni_palace"
    delivery_radius_km: int = 5
    accepts_orders: bool = False  # closed by default

settings, metadata = load_settings(PizzaShopConfig)
print(f"🍕 {settings.shop_name} is {'open' if settings.accepts_orders else 'closed'}")

Override with environment variables

import os

# Friday night rush: OPEN ALL THE STORES!
os.environ["ACCEPTS_ORDERS"] = "true"
os.environ["DELIVERY_RADIUS_KM"] = "15"

settings, metadata = load_settings(PizzaShopConfig)
print(f"🚗 Delivering pizza up to {settings.delivery_radius_km}km away!")

Runtime overrides (highest priority)

# Emergency: meteor incoming, expand radius and accept everything!
settings, metadata = load_settings(
    PizzaShopConfig,
    overrides={
        "accepts_orders": True,
        "delivery_radius_km": 100,
        "shop_name": "doomsday_pizza_bunker"
    }
)
print(f"🚀 {settings.shop_name} now delivers {settings.delivery_radius_km}km!")

Custom app name and config directory

settings, metadata = load_settings(
    PizzaShopConfig,
    app_name="pizza_empire",
    cwd="/etc/pizza_shops/"
)
# Looks for: /etc/pizza_shops/pizza_empire.toml or .yaml

Environment variable prefix

os.environ["PIZZASHOP_ACCEPTS_ORDERS"] = "true"
os.environ["PIZZASHOP_DELIVERY_RADIUS_KM"] = "42"

settings, metadata = load_settings(
    PizzaShopConfig,
    env_prefix="PIZZASHOP"
)
# Will check: PIZZASHOP_ACCEPTS_ORDERS, then ACCEPTS_ORDERS (in that order)
print(f"🍕 Accepting orders: {settings.accepts_orders}")

Inspect metadata (detective mode 🕵️)

settings, metadata = load_settings(PizzaShopConfig)

# Which source provided this field?
source = metadata.get_source("delivery_radius_km")
print(f"Delivery radius came from: {source.source}")
print(f"Location: {source.source_path or 'model defaults'}")
print(f"Raw value: {source.raw_value}")

# Track all field origins
for field, source_info in metadata.per_field.items():
    print(f"  {field}: from {source_info.source}")

Configuration Files

TOML example (pizza_empire.toml)

# 🍕 Pizza Empire Global Settings
shop_name = "the_great_carb_dispensary"
delivery_radius_km = 5
accepts_orders = false

# The business secret sauce 🔥
[quality]
cheese_ratio = 0.42  # more cheese = more problems (and happiness)
crust_crispiness = "perfect"
pineapple_tolerance = 0.0  # this is not a debate

[timings]
avg_prep_time_minutes = 15
delivery_timeout_minutes = 45

YAML example (pizza_empire.yaml)

# 🍕 Pizza Empire Configuration
shop_name: the_great_carb_dispensary
delivery_radius_km: 5
accepts_orders: false

# The art of pizza
quality:
  cheese_ratio: 0.42
  crust_crispiness: "perfect"
  pineapple_tolerance: 0.0

timings:
  avg_prep_time_minutes: 15
  delivery_timeout_minutes: 45

Dotenv example (.env)

# 🍕 Quick overrides for this deployment
SHOP_NAME=emergency_pizza_hut
DELIVERY_RADIUS_KM=100
ACCEPTS_ORDERS=true
CHEESE_RATIO=0.99
PINEAPPLE_TOLERANCE=0.0  # NEVER SURRENDER

API Reference

load_settings(model, *, app_name=None, cwd=None, env_prefix=None, overrides=None)

Load and validate settings from all sources.

Parameters:

  • model (type[T]): A Pydantic BaseModel subclass to validate and populate.
  • app_name (str | None): Application name for config file lookup. Defaults to lowercased model class name.
  • cwd (Path | None): Working directory for config file search. Defaults to current directory.
  • env_prefix (str | None): Optional prefix for environment variables (e.g., "MYAPP").
  • overrides (dict[str, Any] | None): Runtime overrides (highest precedence).

Returns:

A tuple (settings, metadata) where:

  • settings is an instance of your model type (fully type-safe, no casting needed).
  • metadata is a SettingsMetadata object tracking field sources.

Raises:

  • ConfigValidationError — If validation fails, includes detailed context:
    • Validation errors from Pydantic
    • Files that were checked
    • Precedence order
    • Which source provided each field

SettingsMetadata

Tracks where each field value came from.

  • per_field: dict[str, FieldSource] — Field name to source mapping.
  • get_source(field: str) -> FieldSource | None — Look up a single field's source.

FieldSource

  • source: str — Source name ("defaults", "env", "project", etc.).
  • source_path: str | None — File path or env var name.
  • raw_value: Any — The raw value before type coercion.

Known Limitations

  • Nested types: Complex nested Pydantic models in TOML/YAML are supported (Pydantic handles validation), but the loader doesn't do special merging. Flat dictionaries are recommended.
  • Case sensitivity: Dotenv keys are normalized to lowercase; model field names are case-sensitive.
  • Variable expansion: Dotenv values don't expand environment variables (e.g., $HOME won't expand). Use python-dotenv directly if needed.

Error Handling

When validation fails, you get a detailed error with full context (perfect for debugging at 3 AM):

from utilityhub_config import load_settings
from utilityhub_config.errors import ConfigValidationError

class PizzaShopConfig(BaseModel):
    delivery_radius_km: int  # REQUIRED (no default, no pizza!)

try:
    settings, metadata = load_settings(PizzaShopConfig)
except ConfigValidationError as e:
    # Shows:
    # - What validation failed
    # - Which files were checked
    # - The precedence order
    # - Which source provided each field
    print(e)  # Complete context for debugging!

Output example:

Validation failed

Validation errors:
input should be a valid integer [type=int_parsing, input_value=None, input_type=NoneType]

Files checked:
 - ~/.config/pizzashop/pizzashop.toml
 - ~/.config/pizzashop/pizzashop.yaml
 - /home/user/.env

Precedence (low -> high):
defaults -> global -> project -> dotenv -> env -> overrides

Field sources:
 - delivery_radius_km: defaults (None)

Contributing

For issues, improvements, or questions, please open an issue or pull request.

Documentation

Full documentation and examples will be available at: [docs link TBD]


License: See project LICENSE file.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

utilityhub_config-0.1.1.tar.gz (7.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

utilityhub_config-0.1.1-py3-none-any.whl (11.5 kB view details)

Uploaded Python 3

File details

Details for the file utilityhub_config-0.1.1.tar.gz.

File metadata

  • Download URL: utilityhub_config-0.1.1.tar.gz
  • Upload date:
  • Size: 7.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for utilityhub_config-0.1.1.tar.gz
Algorithm Hash digest
SHA256 19ef9ca80d8b5f5701060ccdfc6d77d86f4e34df9837c7f04654004e8b966dd1
MD5 7361e74827d0c120218fb7e883176725
BLAKE2b-256 0d83f7253bf93ec959e3721e2328cdcf6ae650fee3808efc3cf4873afd932312

See more details on using hashes here.

File details

Details for the file utilityhub_config-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: utilityhub_config-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 11.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for utilityhub_config-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 fa7516e9cbe398ceab53d7bdcf86d899de98ea62319f4cb2676436d198921e4a
MD5 fcdd577defc2a6f5049b7306941ebc02
BLAKE2b-256 1f72a5d2043b9912e7a26d0fb7312fe792e8905aecf694c3c0967a5a4e7f6cbe

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page