Intuitive, easy CLIs based on type hints.
—
File-based configuration loading system supporting JSON, TOML, YAML formats and environment variable integration.
Abstract base class for all configuration loaders.
class ConfigFromFile:
def __init__(self, file: Path):
"""
Base configuration loader from file.
Parameters
----------
file
Path to configuration file
"""
def load(self) -> dict[str, Any]:
"""
Load configuration from file.
Returns
-------
dict[str, Any]
Configuration data as dictionary
"""Load configuration from JSON files.
class Json(ConfigFromFile):
def __init__(self, file: Path):
"""
JSON configuration loader.
Parameters
----------
file
Path to JSON configuration file
"""
def load(self) -> dict[str, Any]:
"""
Load configuration from JSON file.
Returns
-------
dict[str, Any]
Parsed JSON configuration
Raises
------
FileNotFoundError
If configuration file doesn't exist
json.JSONDecodeError
If JSON is malformed
"""Load configuration from TOML files.
class Toml(ConfigFromFile):
def __init__(self, file: Path):
"""
TOML configuration loader.
Parameters
----------
file
Path to TOML configuration file
"""
def load(self) -> dict[str, Any]:
"""
Load configuration from TOML file.
Returns
-------
dict[str, Any]
Parsed TOML configuration
Raises
------
FileNotFoundError
If configuration file doesn't exist
tomllib.TOMLDecodeError
If TOML is malformed
"""Load configuration from YAML files.
class Yaml(ConfigFromFile):
def __init__(self, file: Path):
"""
YAML configuration loader.
Parameters
----------
file
Path to YAML configuration file
"""
def load(self) -> dict[str, Any]:
"""
Load configuration from YAML file.
Returns
-------
dict[str, Any]
Parsed YAML configuration
Raises
------
FileNotFoundError
If configuration file doesn't exist
yaml.YAMLError
If YAML is malformed
"""Load configuration from environment variables.
class Env:
def __init__(
self,
prefix: str = "",
case_sensitive: bool = False,
nested_separator: str = "__"
):
"""
Environment variable configuration loader.
Parameters
----------
prefix
Prefix for environment variable names
case_sensitive
Whether environment variable names are case sensitive
nested_separator
Separator for nested configuration keys
"""
def load(self) -> dict[str, Any]:
"""
Load configuration from environment variables.
Returns
-------
dict[str, Any]
Configuration from environment variables
"""Protocol that configuration loaders must implement.
class ConfigProtocol:
def load(self) -> dict[str, Any]:
"""
Load configuration data.
Returns
-------
dict[str, Any]
Configuration as dictionary
"""from cyclopts import App
from cyclopts.config import Json
from pathlib import Path
# config.json:
# {
# "database": {
# "host": "localhost",
# "port": 5432
# },
# "verbose": true
# }
app = App(config=[Json(Path("config.json"))])
@app.command
def connect(
host: str = "127.0.0.1",
port: int = 3306,
verbose: bool = False
):
"""Connect to database with config file support."""
if verbose:
print(f"Connecting to {host}:{port}")from cyclopts import App
from cyclopts.config import Toml
from pathlib import Path
# config.toml:
# [server]
# host = "0.0.0.0"
# port = 8080
# debug = true
app = App(config=[Toml(Path("config.toml"))])
@app.command
def start_server(
host: str = "localhost",
port: int = 8000,
debug: bool = False
):
"""Start server with TOML configuration."""
print(f"Starting server on {host}:{port} (debug={debug})")from cyclopts import App
from cyclopts.config import Env
# Environment variables:
# MYAPP_DATABASE__HOST=prod-db.example.com
# MYAPP_DATABASE__PORT=5432
# MYAPP_VERBOSE=true
app = App(config=[Env(prefix="MYAPP_", nested_separator="__")])
@app.command
def deploy(
database_host: str = "localhost",
database_port: int = 5432,
verbose: bool = False
):
"""Deploy with environment configuration."""
if verbose:
print(f"Deploying to database at {database_host}:{database_port}")from cyclopts import App
from cyclopts.config import Json, Env
from pathlib import Path
# Configuration precedence: CLI args > environment > config file
app = App(config=[
Json(Path("app.json")), # Loaded first (lowest priority)
Env(prefix="APP_") # Loaded second (higher priority)
])
@app.command
def run_job(
worker_count: int = 1,
timeout: float = 30.0,
output_dir: str = "./output"
):
"""Run job with multiple configuration sources."""
print(f"Running job with {worker_count} workers")
print(f"Timeout: {timeout}s, Output: {output_dir}")from cyclopts import App
from cyclopts.config import Yaml
from pathlib import Path
# config.yaml:
# logging:
# level: INFO
# file: app.log
# processing:
# batch_size: 100
# parallel: true
app = App(config=[Yaml(Path("config.yaml"))])
@app.command
def process_data(
logging_level: str = "WARNING",
logging_file: str = "default.log",
processing_batch_size: int = 50,
processing_parallel: bool = False
):
"""Process data with YAML configuration."""
print(f"Logging: {logging_level} -> {logging_file}")
print(f"Processing: batch_size={processing_batch_size}, parallel={processing_parallel}")from cyclopts import App
from cyclopts.config import ConfigFromFile
from pathlib import Path
import configparser
class IniConfig(ConfigFromFile):
"""Custom INI file configuration loader."""
def load(self) -> dict[str, Any]:
config = configparser.ConfigParser()
config.read(self.file)
result = {}
for section_name in config.sections():
section = {}
for key, value in config[section_name].items():
# Try to convert to appropriate types
if value.lower() in ('true', 'false'):
section[key] = value.lower() == 'true'
elif value.isdigit():
section[key] = int(value)
else:
section[key] = value
result[section_name] = section
return result
app = App(config=[IniConfig(Path("config.ini"))])
@app.command
def example(setting: str = "default"):
"""Example command with custom INI config."""
print(f"Setting: {setting}")Install with Tessl CLI
npx tessl i tessl/pypi-cyclopts