Skip to main content

Configuration management for Python projects with dataclass-based schemas

Project description

Clevis

PyPI Python CI Coverage License Agentic PACKAGE.md

Configuration management for Python projects with dataclass-based schemas

Clevis provides type-safe configuration management for Python applications:

  • Dataclass schemas — Define config structure with Python dataclasses
  • TOML support — Load from .toml files with automatic discovery
  • Env vars${VAR} interpolation (with envtoml/tomlev extras)
  • CLI generation — Auto-generate argparse from dataclass
  • Layered config — User config < project config < CLI args
  • Subcommands — Build CLI apps with multiple commands
  • Dynamic registration — Plugin architecture with runtime field injection
  • Security — File permission validation to protect credentials

Quick Start

Get running in 30 seconds:

# Install (Python 3.11+)
pip install clevis

# Or with environment variable support
pip install clevis[envtoml]
from dataclasses import dataclass
from clevis import get_config

@dataclass
class Config:
    name: str = "MyApp"
    debug: bool = False
    port: int = 8080

# Load from ~/.myapp.toml, ./myapp.toml, and CLI args
config = get_config(Config, name="myapp")

print(config.name)   # Access as attribute
print(config.debug)  # Type-safe access
print(config.port)   # Automatic type conversion

That's it! Create a myapp.toml file:

name = "Production App"
debug = true

[database]
host = "db.example.com"
port = 5432

Override with CLI:

python app.py --name "Custom App" --port 9000

Features

Feature Description Example
Dataclass schemas Type-safe configuration @dataclass class Config: ...
TOML loading Auto-discover config files get_config(Config, name="myapp")
Environment vars ${VAR} interpolation pip install clevis[envtoml]
CLI arguments Auto-generate argparse python app.py --database-host localhost
Boolean flags --flag for True, --no-flag for False python app.py --debug / --no-debug
List append Repeat --field val python app.py --packages pkg --packages c3
Layered config Defaults < User < Project < CLI Priority-based merging
Nested configs Hierarchical configuration [database] host = "localhost"
Subcommands Multi-command CLI apps @configclass(cmd="build")
Factory pattern Multi-module orchestration get_factory(Config).prefix = "app1"
Dynamic registration Plugin architecture register_field(Parent, "plugin", PluginConfig)
Security File permission validation SecurityAction.REJECT (default)

Installation

Choose your TOML parser based on needs:

Extra Features Use When
(none) Stdlib tomllib Python 3.11+, minimal deps
tomli Pure Python TOML Python 3.10 compatibility
envtoml ${VAR} interpolation Environment-based config
tomlev `${VAR default}` syntax
# Python 3.11+ - no extras needed
pip install clevis

# Python 3.10
pip install clevis[tomli]

# Environment variable support
pip install clevis[envtoml]

Core Concepts

Configuration Priority

Values are merged in order (highest priority wins):

  1. CLI arguments--database-host localhost
  2. Environment variables — Only when using envtoml/tomlev
  3. Project TOML./myapp.toml
  4. User TOML~/.myapp.toml
  5. Dataclass defaults — Default values in class definition

Security

Clevis validates file permissions by default:

from clevis import get_config, SecurityAction

# Default: reject insecure configurations
config = get_config(Config, name="myapp")

# Disable checks (for containers, testing)
config = get_config(
    Config,
    name="myapp",
    security={"file_permissions": SecurityAction.DONT_CHECK}
)

# Log warnings instead of rejecting
config = get_config(
    Config,
    name="myapp",
    security={"file_permissions": SecurityAction.LOG}
)

Custom Config Sources

Inject config from any source (env vars, databases, remote services) by appending a custom provider to the default cascade. Any zero-argument callable returning a dict works — a plain function is the simplest form:

from clevis import build_default_cascade, get_config

def env_provider() -> dict:
    import os
    return {"api_key": os.environ.get("API_KEY", "")}

cascade = build_default_cascade("myapp") + [env_provider]
config = get_config(Config, name="myapp", cascade=cascade)

Use a class with __call__ when the provider needs constructor parameters.

See docs/usage.rst for the full guide including security helpers for custom providers.

Examples

The examples/ directory contains 10 comprehensive examples:

Example Features Demonstrated Run Command
main.py Basic config, CLI args, TOML, security uv run python main.py --help
nested.py Nested dataclasses, TOML sections uv run python nested.py --tool-settings-x 10
validation.py Custom validation, __post_init__ uv run python validation.py --server-url "http://localhost"
environment.py ${VAR} interpolation, credentials export DB_HOST=localhost && uv run python environment.py
factory.py Multi-module orchestration, prefixes uv run python factory.py --app1-name "first"
commands.py CLI subcommands, aliases uv run python commands.py check --verbose
subcommands.py TOML override with config parameter uv run python subcommands.py cli --server-url "https://api.example.com"
library_mode.py Web framework integration, testing uv run python library_mode.py
dynamic.py Plugin architecture, register_field() uv run python dynamic.py --help
plugin.py Practical plugin implementation uv run python plugin.py --pkgq-timeout 60

See examples/README.md for detailed feature matrix and learning path.

Usage Patterns

For detailed usage patterns and API reference, see PACKAGE.md.

Basic Configuration

from dataclasses import dataclass
from clevis import get_config

@dataclass
class Config:
    name: str = "MyApp"
    debug: bool = False

config = get_config(Config, name="myapp")

CLI Subcommands

from clevis import configclass, get_cmd, get_config

@configclass(cmd="check", help="Run diagnostics", aliases=["c", "chk"])
class CheckConfig:
    verbose: bool = False
    fix: bool = False

@configclass(cmd="build", help="Build the project")
class BuildConfig:
    output: str = "dist"

if __name__ == "__main__":
    cmd = get_cmd()
    if cmd == "check":
        config = get_config(CheckConfig, project=False, user=False)
        print(f"Checking with verbose={config.verbose}")
    elif cmd == "build":
        config = get_config(BuildConfig, project=False, user=False)
        print(f"Building to {config.output}")

Dynamic Registration (Plugin Architecture)

from dataclasses import dataclass
from clevis import register_field, get_config

# Parent config (must NOT be frozen)
@dataclass
class ToolsConfig:
    list: str = "default"

# Plugin config
@dataclass
class PkgqToolConfig:
    enabled: bool = True
    timeout: int = 30

# Register plugin field at runtime
register_field(ToolsConfig, "pkgq", PkgqToolConfig)

# Now ToolsConfig has a pkgq field
config = get_config(ToolsConfig, name="tools")
print(config.pkgq.enabled)  # True
print(config.pkgq.timeout)  # 30

Library Mode

from clevis import get_config

# Library mode - skip CLI parsing
config = get_config(Config, name="myapp", cli=False)

# Testing
def test_my_config():
    config = get_config(
        TestConfig,
        user=False,
        project=False,
        args=[]
    )
    assert config.name == "default"

API Reference

For complete API documentation, see PACKAGE.md or docs/api.rst.

Key functions:

  • get_config(data_class, name, ...) — Load configuration from TOML and CLI
  • configclass(cmd=None, help=None, ...) — Decorator for configuration classes
  • register_field(parent_class, field_name, field_type) — Register plugin fields
  • get_factory(config_class) — Get Factory for multi-module apps
  • get_cmd() — Get active subcommand name

Documentation

Testing

# Run tests
make test

# Run tests with coverage
make test-cov

# Run quality checks
make check

Contributing

# Clone the repository
git clone https://github.com/christophevg/clevis.git
cd clevis

# Create development environment
make env-dev

# Run tests
make test

# Run quality checks
make check

# Format code
make format

See Makefile for all available targets.

Acknowledgments

Clevis builds on excellent work from the Python community:

  • tomllib — Python 3.11+ stdlib
  • tomli — Pure Python TOML 1.0
  • envtoml — Env var interpolation
  • tomlev — Env vars with defaults
  • dacite — Dict-to-dataclass conversion

License

MIT

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

clevis-0.7.0.tar.gz (281.5 kB view details)

Uploaded Source

Built Distribution

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

clevis-0.7.0-py3-none-any.whl (35.8 kB view details)

Uploaded Python 3

File details

Details for the file clevis-0.7.0.tar.gz.

File metadata

  • Download URL: clevis-0.7.0.tar.gz
  • Upload date:
  • Size: 281.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for clevis-0.7.0.tar.gz
Algorithm Hash digest
SHA256 f8c75e7812d63ac816eecd0dc0252ae54aa5bf1e90eeaca03301ba04744e0220
MD5 9236f2ddae92f6a016dc0759a936fe9a
BLAKE2b-256 bfdcf249f731ac8ccf5bec66e1af19ca3ef6b18c90d7021cf2c9e897993314ca

See more details on using hashes here.

File details

Details for the file clevis-0.7.0-py3-none-any.whl.

File metadata

  • Download URL: clevis-0.7.0-py3-none-any.whl
  • Upload date:
  • Size: 35.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.15

File hashes

Hashes for clevis-0.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 99fc1364cc80e7d085a8a7b0319079c21696cb1ef1379d0b0b9115fc84f5f054
MD5 4eda7b1c8315af3b9c3501a682d984e6
BLAKE2b-256 a85fb56e6d221f14140b9310f61638f678e6694d7c5af3370851635f2da66dfd

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