Implement minimal boilerplate CLIs derived from type hints and parse from command line, config files and environment variables.
—
Tools for adding arguments to parsers based on function, method, and class signatures. These utilities automatically introspect Python callables and generate appropriate argument configurations using type hints and docstrings, enabling seamless integration between existing code and command-line interfaces.
Mixin class that provides methods for adding arguments based on callable signatures with automatic type detection and parameter introspection.
class SignatureArguments:
def add_class_arguments(self,
theclass: Type,
nested_key: Optional[str] = None,
as_group: bool = True,
as_positional: bool = False,
default: Optional[Union[dict, Namespace, Any, Type]] = None,
skip: Optional[Set[Union[str, int]]] = None,
instantiate: bool = True,
fail_untyped: bool = True,
sub_configs: bool = False,
**kwargs
) -> List[str]:
"""
Add arguments for a class constructor.
Args:
theclass: Class to introspect for arguments
nested_key: Key for nested configuration
as_group: Whether to group arguments
as_positional: Whether to add as positional argument
default: Default value or configuration
skip: Parameter names/positions to skip
instantiate: Whether to instantiate the class
fail_untyped: Whether to fail on untyped parameters
sub_configs: Whether to enable sub-configurations
Returns:
List[str]: List of added argument names
"""
def add_method_arguments(self,
theclass: Type,
themethod: str,
nested_key: Optional[str] = None,
as_group: bool = True,
as_positional: bool = False,
skip: Optional[Set[Union[str, int]]] = None,
fail_untyped: bool = True,
sub_configs: bool = False,
**kwargs
) -> List[str]:
"""
Add arguments for a class method.
Args:
theclass: Class containing the method
themethod: Method name to introspect
nested_key: Key for nested configuration
as_group: Whether to group arguments
as_positional: Whether to add as positional argument
skip: Parameter names/positions to skip
fail_untyped: Whether to fail on untyped parameters
sub_configs: Whether to enable sub-configurations
Returns:
List[str]: List of added argument names
"""
def add_function_arguments(self,
function: Callable,
nested_key: Optional[str] = None,
as_group: bool = True,
as_positional: bool = False,
skip: Optional[Set[Union[str, int]]] = None,
fail_untyped: bool = True,
sub_configs: bool = False,
**kwargs
) -> List[str]:
"""
Add arguments for a function.
Args:
function: Function to introspect for arguments
nested_key: Key for nested configuration
as_group: Whether to group arguments
as_positional: Whether to add as positional argument
skip: Parameter names/positions to skip
fail_untyped: Whether to fail on untyped parameters
sub_configs: Whether to enable sub-configurations
Returns:
List[str]: List of added argument names
"""
def add_subclass_arguments(self,
baseclass: Union[Type, Tuple[Type, ...]],
nested_key: str,
as_group: bool = True,
skip: Optional[Set[str]] = None,
instantiate: bool = True,
required: bool = False,
metavar: str = "CONFIG | CLASS_PATH_OR_NAME | .INIT_ARG_NAME VALUE",
help: str = "One or more arguments specifying \"class_path\" and \"init_args\"...",
**kwargs
) -> List[str]:
"""
Add arguments for subclass selection and instantiation.
Args:
baseclass: Base class or tuple of base classes
nested_key: Key for nested configuration
as_group: Whether to group arguments
skip: Parameter names to skip
instantiate: Whether to instantiate the selected class
required: Whether subclass selection is required
metavar: Metavar for help display
help: Help text for the subclass argument
Returns:
List[str]: List of added argument names
"""Utility for composing multiple dataclasses into a single dataclass for complex configuration scenarios.
def compose_dataclasses(*dataclasses: Type) -> Type:
"""
Create a dataclass that inherits from multiple dataclasses.
Args:
*dataclasses: Dataclass types to compose
Returns:
Type: New dataclass inheriting from all input dataclasses
"""from jsonargparse import ArgumentParser
from dataclasses import dataclass
@dataclass
class ModelConfig:
hidden_size: int = 128
dropout_rate: float = 0.1
num_layers: int = 3
activation: str = "relu"
# Create parser and add class arguments
parser = ArgumentParser()
parser.add_class_arguments(ModelConfig, "model")
# Parse arguments
config = parser.parse_args()
# Access nested configuration
print(f"Hidden size: {config.model.hidden_size}")
print(f"Dropout: {config.model.dropout_rate}")
# Instantiate the class if needed
model_instance = ModelConfig(**config.model.as_dict())Usage:
python script.py --model.hidden_size 256 --model.dropout_rate 0.2 --model.num_layers 5from jsonargparse import ArgumentParser
def train_model(
data_path: str,
model_name: str,
epochs: int = 100,
learning_rate: float = 0.001,
batch_size: int = 32,
save_checkpoints: bool = True
) -> None:
"""Train a machine learning model.
Args:
data_path: Path to training data
model_name: Name of the model architecture
epochs: Number of training epochs
learning_rate: Learning rate for training
batch_size: Batch size for training
save_checkpoints: Whether to save model checkpoints
"""
print(f"Training {model_name} for {epochs} epochs")
print(f"Data: {data_path}, LR: {learning_rate}, Batch: {batch_size}")
# Add function arguments to parser
parser = ArgumentParser()
parser.add_function_arguments(train_model)
config = parser.parse_args()
# Call function with parsed arguments
train_model(
data_path=config.data_path,
model_name=config.model_name,
epochs=config.epochs,
learning_rate=config.learning_rate,
batch_size=config.batch_size,
save_checkpoints=config.save_checkpoints
)from jsonargparse import ArgumentParser
class DataProcessor:
def __init__(self, base_path: str):
self.base_path = base_path
def process(self,
input_format: str,
output_format: str = "json",
validate: bool = True,
chunk_size: int = 1000
) -> None:
"""Process data with specified parameters.
Args:
input_format: Format of input data
output_format: Format for output data
validate: Whether to validate processed data
chunk_size: Size of processing chunks
"""
print(f"Processing {input_format} -> {output_format}")
print(f"Validation: {validate}, Chunk size: {chunk_size}")
# Add method arguments
parser = ArgumentParser()
parser.add_method_arguments(DataProcessor, "process")
config = parser.parse_args()
# Create instance and call method
processor = DataProcessor("/data")
processor.process(
input_format=config.input_format,
output_format=config.output_format,
validate=config.validate,
chunk_size=config.chunk_size
)from jsonargparse import ArgumentParser
from abc import ABC, abstractmethod
class Optimizer(ABC):
@abstractmethod
def step(self) -> None:
pass
class SGD(Optimizer):
def __init__(self, learning_rate: float = 0.01, momentum: float = 0.0):
self.learning_rate = learning_rate
self.momentum = momentum
def step(self) -> None:
print(f"SGD step: lr={self.learning_rate}, momentum={self.momentum}")
class Adam(Optimizer):
def __init__(self, learning_rate: float = 0.001, beta1: float = 0.9, beta2: float = 0.999):
self.learning_rate = learning_rate
self.beta1 = beta1
self.beta2 = beta2
def step(self) -> None:
print(f"Adam step: lr={self.learning_rate}, β1={self.beta1}, β2={self.beta2}")
# Add subclass arguments for optimizer selection
parser = ArgumentParser()
parser.add_subclass_arguments(Optimizer, "optimizer", required=True)
config = parser.parse_args()
# The optimizer is automatically instantiated
optimizer = config.optimizer
optimizer.step()Usage:
# Use SGD optimizer
python script.py --optimizer SGD --optimizer.learning_rate 0.02 --optimizer.momentum 0.9
# Use Adam optimizer
python script.py --optimizer Adam --optimizer.learning_rate 0.0005 --optimizer.beta1 0.95from dataclasses import dataclass
from jsonargparse import ArgumentParser, compose_dataclasses
@dataclass
class ModelConfig:
hidden_size: int = 128
num_layers: int = 3
@dataclass
class TrainingConfig:
epochs: int = 100
learning_rate: float = 0.001
batch_size: int = 32
@dataclass
class LoggingConfig:
log_level: str = "INFO"
log_file: str = "training.log"
# Compose all configs into one
AllConfig = compose_dataclasses(ModelConfig, TrainingConfig, LoggingConfig)
parser = ArgumentParser()
parser.add_class_arguments(AllConfig)
config = parser.parse_args()
# Access all composed fields
print(f"Model: {config.hidden_size} hidden, {config.num_layers} layers")
print(f"Training: {config.epochs} epochs, lr={config.learning_rate}")
print(f"Logging: level={config.log_level}, file={config.log_file}")from dataclasses import dataclass
from jsonargparse import ArgumentParser
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
username: str = "user"
password: str = "pass"
@dataclass
class CacheConfig:
enabled: bool = True
ttl: int = 3600
max_size: int = 1000
@dataclass
class AppConfig:
debug: bool = False
workers: int = 4
parser = ArgumentParser()
# Add nested configurations
parser.add_class_arguments(DatabaseConfig, "database", as_group=True)
parser.add_class_arguments(CacheConfig, "cache", as_group=True)
parser.add_class_arguments(AppConfig, "app", as_group=True)
config = parser.parse_args()
# Access nested configs
print(f"DB: {config.database.host}:{config.database.port}")
print(f"Cache: enabled={config.cache.enabled}, ttl={config.cache.ttl}")
print(f"App: debug={config.app.debug}, workers={config.app.workers}")Usage:
python script.py --database.host db.example.com --database.port 3306 \
--cache.enabled --cache.ttl 7200 \
--app.debug --app.workers 8from jsonargparse import ArgumentParser
def process_data(
input_file: str,
output_file: str,
temp_dir: str = "/tmp", # Skip this parameter
debug_mode: bool = False,
log_level: str = "INFO" # Skip this parameter too
) -> None:
"""Process data files."""
print(f"Processing {input_file} -> {output_file}")
parser = ArgumentParser()
# Skip temp_dir (index 2) and log_level (by name)
parser.add_function_arguments(
process_data,
skip={"temp_dir", 4} # Skip by name and position
)
config = parser.parse_args()
# Only input_file, output_file, and debug_mode will be available
process_data(
input_file=config.input_file,
output_file=config.output_file,
debug_mode=config.debug_mode,
# temp_dir and log_level use their defaults
)Arguments can be organized into logical groups for better help display:
parser.add_class_arguments(ModelConfig, "model", as_group=True)
parser.add_class_arguments(TrainingConfig, "training", as_group=True)Provide custom default values or configurations:
# Use custom defaults
default_config = {"hidden_size": 256, "dropout_rate": 0.2}
parser.add_class_arguments(ModelConfig, default=default_config)
# Use existing instance as default
existing_config = ModelConfig(hidden_size=512, num_layers=6)
parser.add_class_arguments(ModelConfig, default=existing_config)Control behavior for untyped parameters:
# Fail on untyped parameters (default)
parser.add_function_arguments(my_function, fail_untyped=True)
# Allow untyped parameters (treated as strings)
parser.add_function_arguments(my_function, fail_untyped=False)Install with Tessl CLI
npx tessl i tessl/pypi-jsonargparse