Configuration management for Python projects with dataclass-based schemas
Project description
Clevis
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
.tomlfiles 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 Overview
| Feature | Description | Example |
|---|---|---|
| Dataclass schemas | Type-safe configuration with Python dataclasses | @dataclass class Config: ... |
| TOML loading | Auto-discover config files in user/project directories | get_config(Config, name="myapp") |
| Environment vars | ${VAR} interpolation in TOML files |
pip install clevis[envtoml] |
| CLI arguments | Auto-generate argparse from dataclass fields | python app.py --database-host localhost |
| Layered config | Defaults < User < Project < CLI | Priority-based merging |
| Nested configs | Hierarchical configuration with nested dataclasses | [database] host = "localhost" |
| Subcommands | Build multi-command CLI apps | @configclass(cmd="build") |
| Factory pattern | Multi-module orchestration with shared parsers | get_factory(Config).prefix = "app1" |
| Dynamic registration | Plugin architecture with runtime field injection | register_field(Parent, "plugin", PluginConfig) |
| Security | Validate file permissions to protect credentials | SecurityAction.REJECT (default) |
| Custom validation | Post-initialization validation with __post_init__ |
def __post_init__(self): ... |
| Library mode | Use Clevis without CLI argument parsing | get_config(Config, cli=False) |
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 |
Env vars with defaults |
# Python 3.11+ - no extras needed
pip install clevis
# Python 3.10
pip install clevis[tomli]
# Environment variable support
pip install clevis[envtoml]
# Env vars with defaults
pip install clevis[tomlev]
Development installation:
git clone https://github.com/christophevg/clevis.git
cd clevis
make env-dev # Creates development environment
Core Concepts
1. Configuration Schemas
Define your configuration structure using Python dataclasses:
from dataclasses import dataclass, field
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
user: str | None = None
password: str | None = None
@dataclass
class AppConfig:
name: str = "MyApp"
debug: bool = False
database: DatabaseConfig = field(default_factory=DatabaseConfig)
Key points:
- All fields should have defaults (for fallback)
- Use
field(default_factory=...)for nested dataclasses - Optional fields use
str | None = None
2. TOML Files
Create TOML files matching your dataclass structure:
name = "Production App"
debug = false
[database]
host = "db.example.com"
port = 5432
user = "appuser"
password = "${DB_PASSWORD}" # Environment variable
Auto-discovered locations:
- User-level:
~/.{name}.toml(personal defaults) - Project-level:
./{name}.toml(checked into VCS)
3. CLI Arguments
Clevis auto-generates CLI arguments from your dataclass:
# Flat fields
python app.py --name "Custom App" --debug
# Nested fields (dots become dashes)
python app.py --database-host localhost --database-port 5433
# Boolean flags
python app.py --debug # Sets debug=True
4. Configuration Priority
Values are merged in order (highest priority wins):
- CLI arguments —
--database-host localhost - Environment variables — Only when using envtoml/tomlev
- Project TOML —
./myapp.toml - User TOML —
~/.myapp.toml - Dataclass defaults — Default values in class definition
5. 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,
"directory_permissions": SecurityAction.DONT_CHECK
}
)
# Log warnings instead of rejecting
config = get_config(
Config,
name="myapp",
security={
"file_permissions": SecurityAction.LOG,
"directory_permissions": SecurityAction.LOG
}
)
Fix security issues:
# Secure: owner read/write only
chmod 600 ~/.myapp.toml
Examples Showcase
The examples/ directory contains 9 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 |
| 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
Basic Configuration
from dataclasses import dataclass
from clevis import get_config
@dataclass
class Config:
name: str = "MyApp"
debug: bool = False
# Load from user/project TOML + CLI args
config = get_config(Config, name="myapp")
Environment Variables
pip install clevis[envtoml]
# myapp.toml
[database]
password = "${DB_PASSWORD}"
host = "${DB_HOST}"
export DB_PASSWORD=secret
export DB_HOST=prod.db.com
python app.py
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}")
python app.py check --verbose
python app.py c --fix # Alias
python app.py build --output dist
Factory Pattern (Multi-Module)
from clevis import configclass, get_config, get_factory
import argparse
@configclass
class AppConfig:
verbose: bool = False
@configclass
class Module1Config:
name: str = "module1"
@configclass
class Module2Config:
name: str = "module2"
# Configure prefixes for CLI args
get_factory(Module1Config).prefix = "m1" # --m1-name
get_factory(Module2Config).prefix = "m2" # --m2-name
# Share parser across modules
parser = argparse.ArgumentParser(description="Multi-Module App")
get_factory(AppConfig).parser = parser
get_factory(Module1Config).parser = parser
get_factory(Module2Config).parser = parser
# Each module gets its own prefixed config
m1 = Module1() # Uses --m1-name
m2 = Module2() # Uses --m2-name
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
TOML support:
[tools.list]
format = "json"
[tools.pkgq] # Registered field works with TOML
enabled = true
timeout = 60
CLI support:
python app.py --tools-pkgq-enabled --tools-pkgq-timeout 90
See examples/dynamic.py and examples/plugin.py for complete examples.
Custom Validation
from dataclasses import dataclass
from urllib.parse import urlparse
@dataclass
class Config:
server_url: str | None = None
def __post_init__(self):
if self.server_url:
parsed = urlparse(self.server_url)
if parsed.scheme not in ("http", "https"):
raise ValueError(f"Invalid URL: scheme must be http or https")
if not parsed.netloc:
raise ValueError(f"Invalid URL: missing host")
# Raises ValueError for invalid URLs
config = get_config(Config, name="myapp")
Library Mode
Use Clevis in web frameworks, tests, or embedded contexts:
from clevis import get_config
# Library mode - skip CLI parsing
config = get_config(Config, name="myapp", cli=False)
# Programmatic control
config = get_config(Config, name="myapp", cli=False, args=["--debug"])
# Testing
def test_my_config():
config = get_config(
TestConfig,
user=False,
project=False,
args=[]
)
assert config.name == "default"
API Reference
get_config(data_class, name="project", user=True, project=True, cli=True, args=None, security=None)
Load configuration from TOML files and CLI arguments.
Parameters:
data_class— The dataclass type to populatename— Config file name (without.tomlextension)user— Load user-level config (~/.{name}.toml)project— Load project-level config (./{name}.toml)cli— Parse CLI arguments fromsys.argv(default:True)args— CLI arguments (defaults tosys.argv[1:]whencli=True)security— Security check configuration (default: maximally strict)
Returns: Instance of the dataclass with merged configuration
Raises:
ConfigError— Missing required fields or wrong typesSecurityError— Security check failed (whenaction="reject")ImportError— No TOML parser available
configclass(cls=None, cmd=None, help=None, aliases=None)
Decorator that applies @dataclass and registers the class for CLI subcommands.
Parameters:
cls— The class to decoratecmd— Subcommand name (e.g.,"build")help— Help text for the subcommandaliases— List of aliases for the subcommand (e.g.,["b"])
register_field(parent_class, field_name, field_type)
Register a field to a dataclass at runtime (for plugin architectures).
Parameters:
parent_class— The parent dataclass to extend (must NOT be frozen)field_name— Name of the field to addfield_type— The dataclass type for the field
Raises:
TypeError— Parent class is frozenValueError— Field name already existsRuntimeError— Called afterget_config()with CLI enabled
get_factory(config_class)
Get the Factory instance for a configuration class (for advanced use).
get_cmd(parser=None, args=None)
Get the active subcommand name from parsed arguments.
SecurityAction
Enum for security check actions:
SecurityAction.DONT_CHECK— Skip validationSecurityAction.LOG— Log warning, continueSecurityAction.REJECT— RaiseSecurityError(default)
ConfigError
Raised when configuration is missing or invalid. Provides helpful error messages with actionable suggestions.
SecurityError
Raised when security validation fails. Contains path and check attributes.
For complete API documentation, see docs/api.rst or visit clevis.readthedocs.io.
Error Messages
Clevis provides helpful, actionable errors:
When using CLI (default):
======================================================================
Configuration Error
======================================================================
Field: database.host
Issue: Required field has no value
Provide this value in one of these ways:
1. Project config: ./myapp.toml
[database]
host = "your_value"
2. User config: ~/.myapp.toml
(same format as above)
3. CLI argument: --database-host <value>
======================================================================
When using library mode (cli=False):
======================================================================
Configuration Error
======================================================================
Field: database.host
Issue: Required field has no value
Provide this value in one of these ways:
1. Project config: ./myapp.toml
[database]
host = "your_value"
2. User config: ~/.myapp.toml
(same format as above)
======================================================================
Testing
# Run tests
make test
# Run tests with coverage
make test-cov
# Run tests on all Python versions
make test-all
Documentation
- Quick Start — This README
- Examples — examples/README.md with feature matrix
- Usage Guide — docs/usage.rst comprehensive guide
- API Reference — docs/api.rst or clevis.readthedocs.io
Contributing
We welcome contributions! Please see our development setup:
# 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clevis-0.4.0.tar.gz.
File metadata
- Download URL: clevis-0.4.0.tar.gz
- Upload date:
- Size: 215.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43f2b3f34ea76a0562d2c7651f25d617e69ee8aabf9f8bfae8f61a13ebd50006
|
|
| MD5 |
a43e41c29b75a489edc833b6e76ddaa4
|
|
| BLAKE2b-256 |
2d1f03e054fc30316454119bafb93f102e539431122dcef9e341180c2afe10b0
|
File details
Details for the file clevis-0.4.0-py3-none-any.whl.
File metadata
- Download URL: clevis-0.4.0-py3-none-any.whl
- Upload date:
- Size: 23.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4b67f541c9be2237efc29b1c3f4da890d494ac9b2369e978a9222de494778a0
|
|
| MD5 |
a8e35f39d15b6e44de31835d82f0e3dd
|
|
| BLAKE2b-256 |
6f002d726f24b2a652714c446eb4277002ccd82b59059c95ea75eeda80d3d215
|