Implement minimal boilerplate CLIs derived from type hints and parse from command line, config files and environment variables.
—
Enhanced namespace objects and configuration management utilities that provide nested structure support, dictionary-like access patterns, serialization capabilities, and configuration manipulation tools. The Namespace class extends argparse.Namespace with advanced features for handling complex, hierarchical configurations.
Extended namespace class that supports nested configuration structures with dictionary-like access patterns and comprehensive manipulation methods.
class Namespace:
def __init__(self, *args, **kwargs):
"""
Create a new namespace object.
Args:
*args: Positional arguments (dicts or namespaces to merge)
**kwargs: Keyword arguments to set as attributes
"""
def __getitem__(self, key: str) -> Any:
"""
Get item using dictionary-style access with dot notation support.
Args:
key: Key to retrieve (supports dot notation like 'a.b.c')
Returns:
Any: Value at the specified key
Raises:
KeyError: If key is not found
"""
def __setitem__(self, key: str, value: Any) -> None:
"""
Set item using dictionary-style access with dot notation support.
Args:
key: Key to set (supports dot notation like 'a.b.c')
value: Value to set
"""
def __delitem__(self, key: str) -> None:
"""
Delete item using dictionary-style access.
Args:
key: Key to delete (supports dot notation)
Raises:
KeyError: If key is not found
"""
def __contains__(self, key: str) -> bool:
"""
Check if key exists in namespace.
Args:
key: Key to check (supports dot notation)
Returns:
bool: True if key exists
"""
def as_dict(self) -> Dict[str, Any]:
"""
Convert namespace to nested dictionary.
Returns:
Dict[str, Any]: Dictionary representation of namespace
"""
def as_flat(self) -> argparse.Namespace:
"""
Convert to flat argparse.Namespace with dot-separated keys.
Returns:
argparse.Namespace: Flattened namespace
"""
def items(self, branches: bool = False) -> Iterator[Tuple[str, Any]]:
"""
Iterate over namespace items.
Args:
branches: Whether to include intermediate branch nodes
Yields:
Tuple[str, Any]: Key-value pairs
"""
def keys(self, branches: bool = False) -> Iterator[str]:
"""
Iterate over namespace keys.
Args:
branches: Whether to include intermediate branch keys
Yields:
str: Namespace keys
"""
def values(self, branches: bool = False) -> Iterator[Any]:
"""
Iterate over namespace values.
Args:
branches: Whether to include intermediate branch values
Yields:
Any: Namespace values
"""
def get_sorted_keys(self, branches: bool = True) -> List[str]:
"""
Get sorted list of all keys in namespace.
Args:
branches: Whether to include intermediate branch keys
Returns:
List[str]: Sorted list of keys
"""
def clone(self) -> "Namespace":
"""
Create a deep copy of the namespace.
Returns:
Namespace: Deep copy of this namespace
"""
def update(self, value: Union["Namespace", Any], key: Optional[str] = None) -> "Namespace":
"""
Update namespace with values from another namespace or dict.
Args:
value: Values to merge (Namespace, dict, or other object)
key: Optional key to update specific nested location
Returns:
Namespace: Updated namespace (self)
"""
def get(self, key: str, default: Any = None) -> Any:
"""
Get value with default fallback.
Args:
key: Key to retrieve (supports dot notation)
default: Default value if key not found
Returns:
Any: Value at key or default
"""
def pop(self, key: str, default: Any = None) -> Any:
"""
Remove and return value at key.
Args:
key: Key to remove (supports dot notation)
default: Default value if key not found
Returns:
Any: Removed value or default
"""Utility functions for converting between namespaces and dictionaries, and for cleaning configuration objects.
def dict_to_namespace(obj: Dict[str, Any]) -> Namespace:
"""
Convert nested dictionary to nested namespace.
Args:
obj: Dictionary to convert
Returns:
Namespace: Converted namespace object
"""
def strip_meta(cfg: Union[Namespace, Dict]) -> Union[Namespace, Dict]:
"""
Remove all metadata keys from configuration object.
Args:
cfg: Configuration object (Namespace or dict)
Returns:
Union[Namespace, Dict]: Configuration with metadata removed
"""from jsonargparse import Namespace
# Create namespace
config = Namespace()
# Set values using attribute access
config.model_name = "resnet50"
config.epochs = 100
# Set nested values using dot notation
config["training.learning_rate"] = 0.001
config["training.batch_size"] = 32
config["data.train_path"] = "/data/train"
config["data.val_path"] = "/data/val"
# Access values multiple ways
print(config.model_name) # Attribute access
print(config["epochs"]) # Dictionary access
print(config["training.learning_rate"]) # Dot notation
# Check if keys exist
if "training.learning_rate" in config:
print("Learning rate is set")
# Convert to dictionary
config_dict = config.as_dict()
print(config_dict)
# {
# "model_name": "resnet50",
# "epochs": 100,
# "training": {"learning_rate": 0.001, "batch_size": 32},
# "data": {"train_path": "/data/train", "val_path": "/data/val"}
# }from jsonargparse import Namespace
# Create nested configuration
config = Namespace({
"model": {
"type": "cnn",
"layers": 5,
"activation": "relu"
},
"training": {
"optimizer": "adam",
"learning_rate": 0.001,
"epochs": 100
},
"data": {
"batch_size": 32,
"shuffle": True
}
})
# Access nested values
print(f"Model type: {config.model.type}")
print(f"Learning rate: {config['training.learning_rate']}")
# Update nested values
config.model.layers = 10
config["training.epochs"] = 200
# Add new nested section
config["logging.level"] = "INFO"
config["logging.file"] = "training.log"
print(config.logging.level) # INFO
print(config.logging.file) # training.logfrom jsonargparse import Namespace
# Base configuration
base_config = Namespace({
"model": {"type": "resnet", "layers": 18},
"training": {"epochs": 100, "learning_rate": 0.001},
"data": {"batch_size": 32}
})
# User overrides
user_config = Namespace({
"model": {"layers": 50}, # Override layers
"training": {"epochs": 200}, # Override epochs
"evaluation": {"metrics": ["accuracy", "f1"]} # Add new section
})
# Merge configurations
base_config.update(user_config)
print(base_config.as_dict())
# {
# "model": {"type": "resnet", "layers": 50},
# "training": {"epochs": 200, "learning_rate": 0.001},
# "data": {"batch_size": 32},
# "evaluation": {"metrics": ["accuracy", "f1"]}
# }from jsonargparse import Namespace
config = Namespace({
"model": {"type": "cnn", "layers": 5},
"training": {"epochs": 100, "lr": 0.001},
"debug": True
})
# Iterate over all key-value pairs (leaf nodes only)
print("All configuration values:")
for key, value in config.items():
print(f" {key}: {value}")
# Output:
# model.type: cnn
# model.layers: 5
# training.epochs: 100
# training.lr: 0.001
# debug: True
# Iterate including branch nodes
print("\nAll keys including branches:")
for key in config.keys(branches=True):
print(f" {key}")
# Output:
# model
# model.type
# model.layers
# training
# training.epochs
# training.lr
# debug
# Get sorted keys
sorted_keys = config.get_sorted_keys(branches=False)
print(f"\nSorted leaf keys: {sorted_keys}")from jsonargparse import Namespace, dict_to_namespace
# Convert dict to namespace
config_dict = {
"database": {
"host": "localhost",
"port": 5432,
"credentials": {
"username": "user",
"password": "pass"
}
},
"cache": {
"enabled": True,
"ttl": 3600
}
}
config = dict_to_namespace(config_dict)
# Access with dot notation
print(config.database.host) # localhost
print(config["database.credentials.username"]) # user
print(config.cache.enabled) # True
# Convert back to dict
result_dict = config.as_dict()
print(result_dict == config_dict) # Truefrom jsonargparse import Namespace
# Original configuration
original = Namespace({
"model": {"type": "resnet", "layers": 18},
"training": {"epochs": 100}
})
# Create independent copy
copy = original.clone()
# Modify copy without affecting original
copy.model.layers = 50
copy.training.epochs = 200
copy["new_section.value"] = "added"
print(f"Original layers: {original.model.layers}") # 18
print(f"Copy layers: {copy.model.layers}") # 50
# Use get() with defaults
batch_size = original.get("training.batch_size", 32)
print(f"Batch size: {batch_size}") # 32 (default)
# Pop values
epochs = copy.pop("training.epochs", 100)
print(f"Popped epochs: {epochs}") # 200
print(f"Epochs after pop: {copy.get('training.epochs', 'missing')}") # missingfrom jsonargparse import Namespace, strip_meta
# Configuration with metadata
config = Namespace({
"model": {"type": "cnn", "layers": 5},
"training": {"epochs": 100},
"__metadata__": {"version": "1.0", "created": "2023-01-01"},
"_internal_config": {"debug": True}
})
# Remove metadata keys
clean_config = strip_meta(config)
print("Original keys:", list(config.keys(branches=True)))
print("Clean keys:", list(clean_config.keys(branches=True)))
# Metadata keys starting with _ or __ are removedfrom jsonargparse import Namespace
# Nested namespace
nested = Namespace({
"model": {"type": "cnn", "layers": 5},
"training": {"epochs": 100, "lr": 0.001}
})
# Convert to flat namespace with dot-separated keys
flat = nested.as_flat()
print(f"model.type: {flat.model.type}") # AttributeError - no nesting
print(f"model.type: {getattr(flat, 'model.type')}") # cnn
print(f"training.epochs: {getattr(flat, 'training.epochs')}") # 100
# Useful for integrating with systems expecting flat namespacesfrom jsonargparse import Namespace
# Complex nested configuration
config = Namespace()
# Build configuration programmatically
config["experiments.exp1.model.type"] = "resnet"
config["experiments.exp1.model.layers"] = 18
config["experiments.exp1.training.epochs"] = 100
config["experiments.exp2.model.type"] = "densenet"
config["experiments.exp2.model.layers"] = 121
config["experiments.exp2.training.epochs"] = 150
# Access experiments
for exp_name in ["exp1", "exp2"]:
exp_config = config[f"experiments.{exp_name}"]
print(f"{exp_name}: {exp_config.model.type}-{exp_config.model.layers}")
print(f" Epochs: {exp_config.training.epochs}")
# Delete entire experiment
del config["experiments.exp1"]
print("Remaining experiments:", list(config.experiments.keys()))Install with Tessl CLI
npx tessl i tessl/pypi-jsonargparse