Implement minimal boilerplate CLIs derived from type hints and parse from command line, config files and environment variables.
—
Utility functions and classes for path handling, lazy instantiation, parser introspection, and advanced type support. These utilities provide foundational functionality used throughout jsonargparse and offer helpful tools for building sophisticated CLI applications.
Enhanced path class for robust file system path validation, resolution, and access control.
class Path:
def __init__(self, path: Union[str, os.PathLike], mode: str = "fr"):
"""
Create a Path object with validation and access control.
Args:
path: File system path (relative or absolute)
mode: Access mode string (combinations of 'f'=file, 'd'=dir, 'r'=read, 'w'=write, 'x'=executable)
Raises:
TypeError: If path validation fails
"""
def __str__(self) -> str:
"""
Get string representation of the path.
Returns:
str: String path
"""
@property
def absolute(self) -> pathlib.Path:
"""
Get absolute path as pathlib.Path object.
Returns:
pathlib.Path: Absolute path
"""
def exists(self) -> bool:
"""
Check if path exists.
Returns:
bool: True if path exists
"""
def is_file(self) -> bool:
"""
Check if path is a file.
Returns:
bool: True if path is a file
"""
def is_dir(self) -> bool:
"""
Check if path is a directory.
Returns:
bool: True if path is a directory
"""Utility for creating lazy instances where class instantiation is delayed until first method call.
def lazy_instance(init_fn: Callable[[], Any]) -> Any:
"""
Create a lazy instance that delays instantiation until first use.
Args:
init_fn: Function that returns the instance when called
Returns:
Any: Lazy proxy object that behaves like the target instance
"""Utilities for capturing and introspecting ArgumentParser instances during execution.
def capture_parser() -> ArgumentParser:
"""
Capture the ArgumentParser being used in the current execution context.
Returns:
ArgumentParser: The parser instance currently being used
Raises:
RuntimeError: If no parser is active in current context
"""Utilities for creating classes dynamically from functions and managing class introspection.
def class_from_function(function: Callable, *args, **kwargs) -> Type:
"""
Create a dynamic class equivalent to calling a function.
Args:
function: Function to wrap as a class
*args: Positional arguments for function
**kwargs: Keyword arguments for function
Returns:
Type: Dynamic class that instantiates by calling the function
"""
def register_unresolvable_import_paths(*import_paths: str) -> None:
"""
Register import paths for objects whose import path cannot be automatically resolved.
Args:
*import_paths: Import path strings to register
"""from jsonargparse import ArgumentParser, Path
parser = ArgumentParser()
# Add path arguments with validation
parser.add_argument(
"--input-file",
type=Path(mode="fr"), # Must exist, be a file, and be readable
help="Input file path"
)
parser.add_argument(
"--output-dir",
type=Path(mode="dw"), # Must be a directory and writable
help="Output directory path"
)
parser.add_argument(
"--script",
type=Path(mode="fx"), # Must be a file and executable
help="Script to execute"
)
config = parser.parse_args()
# Paths are validated automatically
print(f"Input file: {config.input_file}")
print(f"Absolute path: {config.input_file.absolute}")
print(f"Exists: {config.input_file.exists()}")
print(f"Is file: {config.input_file.is_file()}")
# Use the paths
with open(config.input_file, 'r') as f:
data = f.read()from jsonargparse import lazy_instance
import expensive_module
# Create lazy instance that delays expensive initialization
def create_model():
print("Creating expensive model...")
return expensive_module.LargeModel()
# Model is not created yet
lazy_model = lazy_instance(create_model)
print("Lazy model created")
# Model is created only when first accessed
result = lazy_model.predict(data) # "Creating expensive model..." printed here
print(f"Prediction: {result}")
# Subsequent calls use the same instance
result2 = lazy_model.predict(more_data) # No "Creating..." messagefrom jsonargparse import ArgumentParser, capture_parser
def setup_logging_args(enable_capture: bool = True):
"""Function that adds logging arguments to current parser."""
if enable_capture:
# Capture the current parser being used
parser = capture_parser()
# Add logging arguments to captured parser
parser.add_argument("--log-level",
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
default="INFO")
parser.add_argument("--log-file", type=str, help="Log file path")
parser.add_argument("--quiet", action="store_true", help="Suppress output")
print("Added logging arguments to parser")
# Create parser
parser = ArgumentParser()
parser.add_argument("--name", type=str, required=True)
# Add function arguments - this will capture the current parser
parser.add_function_arguments(setup_logging_args)
config = parser.parse_args()
print(f"Name: {config.name}")
print(f"Log level: {config.log_level}")
if config.log_file:
print(f"Log file: {config.log_file}")from jsonargparse import class_from_function, ArgumentParser
def train_model(data_path: str, epochs: int = 100, learning_rate: float = 0.001):
"""Train a machine learning model."""
print(f"Training model with data from {data_path}")
print(f"Epochs: {epochs}, Learning rate: {learning_rate}")
return {"status": "trained", "epochs": epochs}
# Create a class from the function
TrainingClass = class_from_function(train_model)
# Use the dynamic class with jsonargparse
parser = ArgumentParser()
parser.add_class_arguments(TrainingClass, "training")
config = parser.parse_args()
# Instantiate the dynamic class (calls the original function)
trainer = TrainingClass(**config.training.as_dict())
print(f"Training result: {trainer}")from jsonargparse import register_unresolvable_import_paths
from some_complex_module import HardToResolveClass
# Register import paths for classes that can't be automatically resolved
register_unresolvable_import_paths(
"some_complex_module.HardToResolveClass",
"another_module.DynamicClass"
)
# Now these classes can be properly serialized and deserialized
# in configuration files and command line argumentsfrom jsonargparse import ArgumentParser, Path
parser = ArgumentParser()
# Different path modes for different use cases
parser.add_argument("--config", type=Path(mode="fr")) # File, readable
parser.add_argument("--output", type=Path(mode="fw")) # File, writable (may not exist)
parser.add_argument("--data-dir", type=Path(mode="dr")) # Directory, readable
parser.add_argument("--temp-dir", type=Path(mode="dw")) # Directory, writable
parser.add_argument("--script", type=Path(mode="frx")) # File, readable, executable
try:
config = parser.parse_args()
# All paths are validated according to their modes
print(f"Config file: {config.config}")
print(f"Output will be written to: {config.output}")
print(f"Data directory: {config.data_dir}")
print(f"Script to run: {config.script}")
except Exception as e:
print(f"Path validation failed: {e}")from jsonargparse import ArgumentParser, Path, lazy_instance, capture_parser
import logging
def create_logger():
"""Create logger instance only when needed."""
logger = logging.getLogger("myapp")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
def setup_app_args():
"""Setup application-specific arguments."""
parser = capture_parser()
parser.add_argument("--debug", action="store_true")
parser.add_argument("--workers", type=int, default=1)
class AppConfig:
def __init__(self,
input_file: Path(mode="fr"),
output_dir: Path(mode="dw"),
log_file: Path(mode="fw") = None):
self.input_file = input_file
self.output_dir = output_dir
self.log_file = log_file
# Create lazy logger
self.logger = lazy_instance(create_logger)
def process(self):
self.logger.info(f"Processing {self.input_file}")
self.logger.info(f"Output will go to {self.output_dir}")
# Process files here
print(f"Processing {self.input_file} -> {self.output_dir}")
parser = ArgumentParser()
parser.add_class_arguments(AppConfig, "config")
parser.add_function_arguments(setup_app_args)
args = parser.parse_args()
app = AppConfig(**args.config.as_dict())
app.process()
if args.debug:
print("Debug mode enabled")
print(f"Using {args.workers} workers")Path validation modes:
"f" - Must be a file"d" - Must be a directory"r" - Must be readable"w" - Must be writable"x" - Must be executable"c" - File can be created (parent directory must exist and be writable)"u" - Path can be a URLCommon combinations:
"fr" - Existing readable file"fw" - Writable file (may not exist yet)"dr" - Existing readable directory"dw" - Writable directory"frx" - Executable file"fc" - File that can be createdInstall with Tessl CLI
npx tessl i tessl/pypi-jsonargparse