API Reference
This section documents the public API of Clevis.
Main Functions
- clevis.get_config(clz, name='project', user=True, project=True, cli=True, args=None, security=None, cascade=None)[source]
Load configuration from TOML files and CLI arguments.
Merges configuration from (in order of precedence): 1. CLI arguments (highest priority) - only when cli=True or args is provided 2. Middle cascade (default: project TOML, then user TOML) - deep-merged 3. Dataclass defaults (lowest priority, filled by dacite)
The middle cascade is a list of
ConfigProviderinstances. WhencascadeisNone(the default), the default cascade is built fromDEFAULT_CASCADEand filtered by theuser/projectflags. Whencascadeis a list, those providers replace the default middle layers and theuser/projectflags are ignored.The middle cascade uses deep (recursive) merge: nested tables are merged key-by-key rather than replaced wholesale. This is a breaking change from the previous shallow
dict.updatebehavior — see the changelog.WARNING: When a custom cascade is provided, clevis’s default security checks do NOT apply automatically. Each provider owns its own security. Use
UserConfigProvider/ProjectConfigProvideras secure building blocks, or apply the same checks manually withcheck_file_permissions()/check_directory_permissions()/load_toml_from_fd(). See alsoload_toml_file()for a convenient all-in-one secure loader.- TOML Parser Selection:
Automatically selects parser based on installed extras: - envtoml: Supports ${VAR} interpolation - pip install clevis[envtoml] - tomlev: Alternative parser - pip install clevis[tomlev] - tomli: Pure Python - pip install clevis[tomli] - tomllib: Python 3.11+ stdlib (no extras needed)
- Parameters:
clz (type[T]) – The dataclass type to populate
name (str) – Configuration file name (without .toml extension)
user (bool) – Whether to load user-level config(~/.{name}.toml). Ignored when
cascadeis provided.project (bool) – Whether to load project-level config (./{name}.toml). Ignored when
cascadeis provided.cli (bool) – Whether to parse CLI arguments from sys.argv (default: True)
args (list[str] | None) – Optional list of CLI arguments (overrides sys.argv when provided)
security (SecurityConfig | None) – Security check configuration for the default providers. If None, defaults to maximally strict (reject on all security issues). Ignored when
cascadeis provided (providers own their security).cascade (list[ConfigProvider] | None) – Optional list of
ConfigProviderinstances replacing the default middle layers. When provided,user/projectandsecurityare ignored.
- Returns:
An instance of the dataclass with merged configuration
- Raises:
ConfigError – If required fields are missing or values have wrong type
SecurityError – If security checks fail (when action=”reject”)
ImportError – If no TOML parser is available
- Return type:
T
- clevis.get_cmd(parser=None, args=None)[source]
Get the active subcommand name from parsed arguments.
- Parameters:
parser (Any) – Optional parser to use (defaults to creating a new default parser)
args (list[str] | None) – Optional list of CLI arguments (for testing)
- Returns:
The subcommand name or None if no subcommand was used
- Return type:
str | None
Factory Pattern
The Factory pattern enables multi-module orchestration with shared parsers and argument prefixes.
- clevis.configclass(cls=None, cmd=None, help=None, aliases=None, config=None, default_cmd=False)[source]
Decorator that registers a dataclass with Clevis’s factory system.
Applies @dataclass to the class and registers it with get_factory().
Usage:
@configclass class MyConfig: name: str = "default"
This is equivalent to:
@dataclass class MyConfig: name: str = "default" get_factory(MyConfig) # register
For subcommands:
@configclass(cmd="check", help="Run diagnostics", aliases=["c", "chk"]) class CheckConfig: verbose: bool = False
For TOML section override:
@configclass(cmd="cli", config="client") class CliConfig: server_url: str = "http://localhost:8000"
For a default subcommand (runs when no subcommand is given on the CLI):
@configclass(cmd="chat", default_cmd=True) class ChatConfig: model: str = "gpt-4"
- Parameters:
cls (type[T] | None) – The class to decorate.
cmd (str | None) – Optional subcommand name for CLI applications with multiple commands.
help (str | None) – Optional help text for the subcommand (used with cmd parameter).
aliases (list[str] | None) – Optional list of aliases for the subcommand (used with cmd parameter).
config (str | None) – Optional TOML extraction key (requires cmd parameter).
default_cmd (bool) – If True, this subcommand runs when no subcommand is given on the CLI. Requires cmd. Only one subcommand per parser may set this (raises ValueError at configuration time otherwise).
- Returns:
The decorated class (now a dataclass).
- Raises:
ValueError – If config or default_cmd is provided without cmd parameter.
- Return type:
type[T] | Callable[[type[T]], type[T]]
- clevis.get_factory(clz)[source]
Get or create the Factory for a configuration class.
This function implements the singleton pattern for Factory instances: - If a Factory already exists for the class, returns the existing instance - If no Factory exists, creates a new one, registers it, and returns it
The singleton behavior ensures that: - All parts of the code get the same Factory instance for a config class - Configuration state (prefix, parser, cmd, etc.) is shared consistently - Test isolation can be achieved via _reset_factories()
- Parameters:
clz (type) – The dataclass type to get a factory for.
- Returns:
Factory instance for the given class (existing or newly created).
- Return type:
Example
@configclass class AppConfig:
name: str = “default”
# Get the factory (creates if needed) factory = get_factory(AppConfig) factory.prefix = “app1”
# Later, same instance is returned factory2 = get_factory(AppConfig) assert factory2.prefix == “app1” # Same instance
- class clevis.Factory(config_class, prefix=None, parser=<factory>, cmd=None, help=None, aliases=None, config=None, default_cmd=False, _configured=False)[source]
Configuration factory for a dataclass.
Collects parser configuration for deferred setup, allowing orchestration code to customize prefixes and parsers before configuration loading.
- Parameters:
config_class (type)
prefix (str | None)
parser (Parser)
cmd (str | None)
help (str | None)
aliases (list[str] | None)
config (str | None)
default_cmd (bool)
_configured (bool)
- config_class
The dataclass type this factory configures.
- Type:
type
- prefix
Optional CLI argument prefix (e.g., “app1” -> “–app1-name”).
- Type:
str | None
- parser
The argparse-compatible parser to use.
- Type:
- cmd
Optional subcommand name for CLI applications with multiple commands.
- Type:
str | None
- help
Optional help text for the subcommand (used with cmd parameter).
- Type:
str | None
- aliases
Optional list of aliases for the subcommand (used with cmd parameter).
- Type:
list[str] | None
- config
Optional TOML extraction key (defaults to cmd if not set).
- Type:
str | None
- default_cmd
If True, this subcommand runs when no subcommand is given.
- Type:
bool
- _nested_prefix
Tracks the nesting level in config hierarchy (internal).
- Type:
str | None
- configure_parser()[source]
Configure the parser with arguments for this config class.
Called automatically on first get_config() - usually not called directly.
- Raises:
ValueError – If both cmd and prefix are set (mutually exclusive).
- Return type:
None
- get_args(args=None)[source]
Parse CLI arguments and return as dictionary.
- Parameters:
args (list[str] | None) – CLI arguments (defaults to sys.argv[1:])
- Returns:
“localhost”}). If _nested_prefix is set, keys are stripped of the prefix.
- Return type:
Dictionary with dotted keys (e.g., {“database.host”
- list_fields(clz=None, path=None)[source]
List all CLI-visible leaf fields in nested dataclasses.
Delegates to the single
_iter_cli_fieldswalker, filtering for “leaf” entries. Fields and subtrees markedmetadata["cli"] is Falseare excluded by the walker and never appear here.- Parameters:
clz (type | None) – The dataclass to inspect (defaults to self.config_class)
path (list[str] | None) – Current path in the hierarchy (used for recursion)
- Returns:
List of (field, path) tuples for each visible leaf field.
- Return type:
list[tuple[Field[Any], list[str]]]
- list_fields_with_owners(clz=None, path=None)[source]
List all CLI-visible leaf fields in nested dataclasses with owner class.
Delegates to the single
_iter_cli_fieldswalker, filtering for “leaf” entries. Fields and subtrees markedmetadata["cli"] is Falseare excluded by the walker and never appear here.- Parameters:
clz (type | None) – The dataclass to inspect (defaults to self.config_class)
path (list[str] | None) – Current path in the hierarchy (used for recursion)
- Returns:
List of (field, path, owner_class) tuples for each visible leaf field. owner_class is the dataclass that directly owns the field.
- Return type:
list[tuple[Field[Any], list[str], type]]
- class clevis.Parser(*args, **kwargs)[source]
Protocol for argparse-compatible parsers.
Any class implementing these two methods can be used as a parser for Clevis configuration.
- add_argument(*name_or_flags, action=Ellipsis, default=Ellipsis, type=Ellipsis, help=Ellipsis, dest=Ellipsis, **kwargs)[source]
Add an argument to the parser.
- Parameters:
name_or_flags (str)
action (str | type[Action])
default (Any)
type (Any)
help (str | None)
dest (str | None)
kwargs (Any)
- Return type:
Action
Exceptions
- class clevis.ConfigError(message, field_path, config_name, suggest_cli=True)[source]
Raised when configuration is missing or invalid.
- Parameters:
message (str)
field_path (str)
config_name (str)
suggest_cli (bool)
Helper Functions
These functions are used internally but may be useful for advanced use cases.
- clevis.unpack_type(type_def)[source]
Given a type, if a union type, return the not-None type (dataclass).
For Optional[T] or T | None, returns T. For container types (list, dict, set, tuple), returns as-is. For Literal types, returns as-is. For non-union types, returns the type as-is.
- Parameters:
type_def (type) – The type to unpack
- Returns:
The non-None type from a union, or the type itself
- Return type:
type
- clevis.apply_to_dict(args, dct)[source]
Apply dotted command line arguments to a nested dictionary.
Modifies the dictionary in-place, creating nested structure as needed.
- Parameters:
args (dict[str, Any]) – Dictionary with dotted keys (e.g., “database.host”)
dct (dict[str, Any]) – Target dictionary to modify
- Return type:
None
Config Override Cascade (P1-006)
The config override cascade enables pluggable config sources (providers) that are deep-merged between dataclass defaults and CLI arguments.
Warning
v0.7.0 breaking change: The merge of user-level and project-level TOML files
changed from shallow (dict.update) to deep (recursive dict merge). Nested
tables now merge key-by-key instead of being replaced wholesale.
- class clevis.ConfigProvider(*args, **kwargs)[source]
Callable protocol that provides a configuration dict.
A ConfigProvider is a zero-argument callable returning a dict of configuration data. Providers own their own security/validation (file permission checks, path resolution, TOCTOU-safe access) and raise on failure. Callers do not pass
nameorsecurityarguments to the provider; the provider captures everything it needs at construction time.- Security Contract:
Each provider is responsible for its own security validation. Clevis does not authenticate or validate provider security postures. Custom providers bypass clevis’s default security checks — use
UserConfigProvider/ProjectConfigProvideras secure building blocks for file-based providers.
- Returns:
A dict of configuration values. Nested dicts are deep-merged across providers in cascade order.
:raises Any exception on failure.
SecurityErrorshould be raised for: :raises security check failures to stay consistent with the default providers.:
- class clevis.FileConfigProvider(name, security=None)[source]
Shared base for file-based TOML config providers.
Encapsulates the TOCTOU-safe FD access pattern: open FD → fstat → read from the same FD. Also runs directory permission checks before opening the file. Subclass this to get security checks for free when loading TOML from non-standard paths.
Subclasses customize resolution by setting
_path_template(a{name}-style format string) and overriding_root_dirto return the directory the formatted filename is joined against.- Parameters:
name (str)
security (SecurityConfig | None)
- class clevis.UserConfigProvider(name, security=None)[source]
ConfigProvider that loads user-level TOML (
~/.{name}.toml).Owns its own security checks (file and directory permissions, TOCTOU-safe FD access). If the file does not exist, returns an empty dict. This is a public, reusable building block for custom cascades.
- Parameters:
name (str)
security (SecurityConfig | None)
- class clevis.ProjectConfigProvider(name, security=None)[source]
ConfigProvider that loads project-level TOML (
./{name}.toml).Owns its own security checks (file and directory permissions, TOCTOU-safe FD access). If the file does not exist, returns an empty dict. This is a public, reusable building block for custom cascades.
- Parameters:
name (str)
security (SecurityConfig | None)
- clevis.DEFAULT_CASCADE: tuple[type[ConfigProvider], ...] = (<class 'clevis.UserConfigProvider'>, <class 'clevis.ProjectConfigProvider'>)
Default cascade of provider classes (user-TOML, then project-TOML).
Each entry is a class accepting
(name, security)at construction and producing aConfigProviderinstance.get_configinstantiates these with thenameandsecurityarguments whencascadeis not provided.The most common customization is to append a custom provider to the default cascade. Because
ConfigProvideris a@runtime_checkable__call__protocol, any zero-argument callable returning a dict satisfies it — a plain function is the simplest form. Usebuild_default_cascade()to get the default providers, then add your own:from clevis import build_default_cascade, get_config def env_provider() -> dict: return {"api_key": os.environ.get("API_KEY", "")} cascade = build_default_cascade("myapp") + [env_provider] config = get_config(Config, name="myapp", cascade=cascade)
A class with
__call__is useful when the provider needs constructor parameters (state):from clevis import build_default_cascade, get_config class EnvProvider: def __init__(self, prefix: str) -> None: self._prefix = prefix def __call__(self) -> dict: return {f"{self._prefix}_key": os.environ.get(f"{self._prefix}_KEY")} cascade = build_default_cascade("myapp") + [EnvProvider("MYAPP")] config = get_config(Config, name="myapp", cascade=cascade)
For a fully custom cascade, construct provider instances directly:
from clevis import UserConfigProvider, ProjectConfigProvider cascade = [ UserConfigProvider("myapp", security), MyCustomProvider(), ProjectConfigProvider("myapp", security), ] config = get_config(Config, name="myapp", cascade=cascade)
- clevis.build_default_cascade(name, security=None, user=True, project=True)[source]
Build a list of default
ConfigProviderinstances.Instantiates
DEFAULT_CASCADEclasses with the givennameandsecurity, filtered by theuser/projectflags. Use this to append custom providers while keeping the secure defaults (any zero-arg callable returning a dict works — a plain function is the simplest):def my_provider() -> dict: return {"feature_flag": True} cascade = build_default_cascade("myapp") + [my_provider] config = get_config(Config, name="myapp", cascade=cascade)
- Parameters:
name (str) – Configuration file name (without
.tomlextension).security (SecurityConfig | None) – Security config for the providers. Defaults to maximally strict (REJECT) when
None.user (bool) – Whether to include
UserConfigProvider.project (bool) – Whether to include
ProjectConfigProvider.
- Returns:
A list of instantiated
ConfigProviderobjects.- Return type:
list[ConfigProvider]
- clevis.deep_merge(base, overlay)[source]
Recursively merge
overlayontobase, returning a new dict.When both
base[key]andoverlay[key]are dicts: recurse.Otherwise:
overlay[key]replacesbase[key](including lists and scalars; lists are replaced, not appended).Input dicts are not modified.
This is the merge strategy used by the config cascade middle layer. Override providers replace entire lists; only nested dicts are merged recursively. CLI args (a fixed last bookend) still append to list fields via
_merge_list_args.- Parameters:
base (dict[str, Any]) – The base dict (lower precedence).
overlay (dict[str, Any]) – The overlay dict (higher precedence).
- Returns:
A new dict with the merged result.
- Return type:
dict[str, Any]
Security Helpers (P1-006)
Exported security functions for custom ConfigProvider authors. These
implement the TOCTOU-safe file access pattern used by the built-in providers.
- clevis.check_file_permissions(path, action)[source]
Check if file has secure permissions (owner-only readable).
Uses file descriptor to prevent TOCTOU race condition between permission check and file read.
- Parameters:
path (Path) – Path to configuration file
action (SecurityAction) – Security action to take if check fails
- Returns:
check_passed: True if check passes or is skipped
file_descriptor: Opened file descriptor if file exists and check passes, None if file doesn’t exist or check is skipped
- Return type:
Tuple of (check_passed, file_descriptor)
- Raises:
SecurityError – If action is REJECT and check fails
Note
If file_descriptor is returned (not None), caller MUST close it after use to avoid resource leaks.
- clevis.check_directory_permissions(path, action)[source]
Check if parent directory is world-writable.
Returns True if check passes or is skipped. Raises SecurityError if action is REJECT and check fails. Logs warning if action is LOG and check fails.
- Parameters:
path (Path)
action (SecurityAction)
- Return type:
bool
- clevis.load_toml_from_fd(fd)[source]
Load TOML from a file descriptor.
Wraps the file descriptor in a file object for TOML parser. The file object takes ownership of the fd and closes it.
- Parameters:
fd (int) – File descriptor opened in read mode
- Returns:
Dictionary of parsed TOML data
- Return type:
dict[str, Any]
- clevis.load_toml_file(path, security=None)[source]
Securely load a TOML file with Clevis’s default security checks.
Combines directory check, TOCTOU-safe file check, and TOML parsing. Returns an empty dict if the file does not exist. This is the ready-to-use function for custom
ConfigProviderauthors who load TOML from non-standard paths.- Parameters:
path (Path) – Path to the TOML file.
security (SecurityConfig | None) – Security config. Defaults to maximally strict (REJECT) when
None.
- Returns:
Parsed TOML as a dictionary, or an empty dict if the file is absent.
- Raises:
SecurityError – If a security check fails with
SecurityAction.REJECT.- Return type:
dict[str, Any]
Public TOML API (P1-006)
Raw TOML parsers (no security checks) with stdlib-compatible signatures.
- clevis.load(fp)[source]
Load TOML from a binary file object.
Drop-in compatible with
tomllib.load. Uses Clevis’s automatic parser selection (envtoml > tomlev > tomli > tomllib).This is a RAW parser: no security checks (file permissions, directory permissions, TOCTOU-safe access). To load TOML config files with security validation, use
get_config()or construct aUserConfigProvider/ProjectConfigProvider.- Parameters:
fp (Any) – A binary file object (opened with mode
'rb').- Returns:
Parsed TOML as a dictionary.
- Raises:
ImportError – If no TOML parser is available.
Exception – Parser-specific errors for invalid TOML.
- Return type:
dict[str, Any]
- clevis.loads(s)[source]
Load TOML from a string.
Drop-in compatible with
tomllib.loads. Uses Clevis’s automatic parser selection.This is a RAW parser with no security checks (there is no file to check for a string input).
- Parameters:
s (str) – A string containing TOML data.
- Returns:
Parsed TOML as a dictionary.
- Raises:
ImportError – If no TOML parser is available.
Exception – Parser-specific errors for invalid TOML.
- Return type:
dict[str, Any]
- clevis.load_toml = <function load>
Load TOML from a binary file object.
Drop-in compatible with
tomllib.load. Uses Clevis’s automatic parser selection (envtoml > tomlev > tomli > tomllib).This is a RAW parser: no security checks (file permissions, directory permissions, TOCTOU-safe access). To load TOML config files with security validation, use
get_config()or construct aUserConfigProvider/ProjectConfigProvider.- Parameters:
fp (Any) – A binary file object (opened with mode
'rb').- Returns:
Parsed TOML as a dictionary.
- Raises:
ImportError – If no TOML parser is available.
Exception – Parser-specific errors for invalid TOML.
- Return type:
dict[str, Any]
- clevis.loads_toml = <function loads>
Load TOML from a string.
Drop-in compatible with
tomllib.loads. Uses Clevis’s automatic parser selection.This is a RAW parser with no security checks (there is no file to check for a string input).
- Parameters:
s (str) – A string containing TOML data.
- Returns:
Parsed TOML as a dictionary.
- Raises:
ImportError – If no TOML parser is available.
Exception – Parser-specific errors for invalid TOML.
- Return type:
dict[str, Any]
Testing Helpers
Internal Functions
- clevis._get_toml_parser()[source]
Get the appropriate TOML parser based on installed packages.
Priority: envtoml > tomlev > tomli > tomllib (stdlib)
- Returns:
A function that loads TOML from a file object
- Raises:
ImportError – If no TOML parser is available
- Return type:
Callable[[Any], dict[str, Any]]
Type Hints
All public functions are fully type-hinted. Here are the key type signatures:
from typing import Any, BinaryIO, Callable, Protocol, TypeVar, runtime_checkable
from dataclasses import Field, dataclass
from argparse import Action, Namespace
from enum import Enum
from pathlib import Path
T = TypeVar("T")
# Main functions
def get_config(
clz: type[T],
name: str = "project",
user: bool = True,
project: bool = True,
cli: bool = True,
args: list[str] | None = None,
security: SecurityConfig | None = None,
cascade: list[ConfigProvider] | None = None,
) -> T: ...
def get_cmd(parser=None, args: list[str] | None = None) -> str | None: ...
# Factory pattern
def configclass(
cls: type[T] | None = None,
cmd: str | None = None,
help: str | None = None,
aliases: list[str] | None = None,
config: str | None = None,
default_cmd: bool = False,
) -> type[T] | Callable[[type[T]], type[T]]: ...
def get_factory(clz: type) -> Factory: ...
@dataclass
class Factory:
config_class: type
prefix: str | None = None
parser: Parser = ...
cmd: str | None = None
help: str | None = None
aliases: list[str] | None = None
config: str | None = None
default_cmd: bool = False
sub_parser: Parser | None = ...
_configured: bool = False
def configure_parser(self) -> None: ...
def get_args(self, args: list[str] | None = None) -> dict[str, Any]: ...
def list_fields(
self,
clz: type | None = None,
path: list[str] | None = None
) -> list[tuple[Field[Any], list[str]]]: ...
class Parser(Protocol):
def add_argument(
self,
*name_or_flags: str,
action: str | type[Action] = ...,
default: Any = ...,
type: Any = ...,
help: str | None = ...,
dest: str | None = ...,
**kwargs: Any
) -> Action: ...
def add_subparsers(self, **kwargs: Any) -> SubParser: ...
def parse_args(self, args: list[str] | None = None) -> Namespace: ...
def parse_known_args(self, args: list[str] | None = None) -> tuple[Namespace, list[str]]: ...
class SubParser(Protocol):
required: bool
def add_parser(self, name: str, help: str | None = ..., aliases: list[str] | None = ..., **kwargs: Any) -> Parser: ...
# Config Override Cascade
@runtime_checkable
class ConfigProvider(Protocol):
def __call__(self) -> dict[str, Any]: ...
class FileConfigProvider:
_path_template: str
def __init__(self, name: str, security: SecurityConfig | None = None) -> None: ...
def _root_dir(self) -> Path: ...
def __call__(self) -> dict[str, Any]: ...
class UserConfigProvider(FileConfigProvider): ...
class ProjectConfigProvider(FileConfigProvider): ...
DEFAULT_CASCADE: tuple[type[ConfigProvider], ...]
def build_default_cascade(
name: str,
security: SecurityConfig | None = None,
user: bool = True,
project: bool = True,
) -> list[ConfigProvider]: ...
def deep_merge(base: dict[str, Any], overlay: dict[str, Any]) -> dict[str, Any]: ...
# Public TOML API
def load(fp: BinaryIO) -> dict[str, Any]: ...
def loads(s: str) -> dict[str, Any]: ...
load_toml: Callable[[BinaryIO], dict[str, Any]]
loads_toml: Callable[[str], dict[str, Any]]
# Security helpers
def check_file_permissions(path: Path, action: SecurityAction) -> tuple[bool, int | None]: ...
def check_directory_permissions(path: Path, action: SecurityAction) -> bool: ...
def load_toml_from_fd(fd: int) -> dict[str, Any]: ...
def load_toml_file(path: Path, security: SecurityConfig | None = None) -> dict[str, Any]: ...
# Utilities
def unpack_type(type_def: type) -> type: ...
def apply_to_dict(args: dict[str, Any], dct: dict[str, Any]) -> None: ...