A simple typed argument parser using dataclasses and type hints. This project is largely generated by LLMs.
Project description
SimpleArgParser
A simple typed argument parser for Python built on dataclasses. Define your config as a class, get CLI parsing, validation, and serialization.
This project is largely generated by LLMs.
Installation
pip install -U simpleArgParser
uv add simpleArgParser
Quick Start
from dataclasses import dataclass
from simpleArgParser import parse_args
@dataclass
class Config:
name: str # required (no default)
epochs: int = 10 # optional with default
lr: float = 0.001
config = parse_args(Config)
python main.py --name experiment1 --epochs 20
Features
Required and Optional Arguments
Fields without defaults are required. Fields with defaults are optional.
@dataclass
class Config:
required_field: str # must be provided
optional_field: int = 42 # has a default
optional_none: float | None = None # optional, defaults to None
python main.py --required_field hello
python main.py --required_field hello --optional_none 3.14
python main.py --required_field hello --optional_none none # explicitly set to None
Bool Arguments
Accepts true/false, yes/no, t/f, y/n, 1/0 (case-insensitive).
@dataclass
class Config:
verbose: bool = False
python main.py --verbose true
python main.py --verbose yes
Enum Arguments
Pass in the enum member name. Choices are displayed in --help.
import enum
class Mode(enum.Enum):
train = "train"
eval = "eval"
@dataclass
class Config:
mode: Mode = Mode.train
python main.py --mode eval
List Arguments
Comma-separated values. Supports none to pass None.
@dataclass
class Config:
devices: list[int] | None = None
python main.py --devices 0,1,2
python main.py --devices none
Nested Dataclasses
Nest dataclasses as fields. Arguments use dot-separated names. Unique field names get short aliases automatically.
@dataclass
class OptimizerConfig:
lr: float = 0.001
weight_decay: float = 0.01
@dataclass
class Config:
name: str = "exp"
optimizer: OptimizerConfig = field(default_factory=OptimizerConfig)
# full path always works
python main.py --optimizer.lr 0.01
# short alias works when the name is unique across all fields
python main.py --lr 0.01 --weight_decay 0.05
Inheritance
Child dataclasses inherit parent fields. You can override defaults.
@dataclass
class BaseConfig:
seed: int = 42
verbose: bool = False
@dataclass
class TrainConfig(BaseConfig):
lr: float = 0.001
verbose: bool = True # override parent default
python main.py --seed 123 --lr 0.01
Comments as Help Text
Comments above or inline with fields are extracted and shown in --help.
@dataclass
class Config:
# Learning rate for the optimizer
lr: float = 0.001
epochs: int = 10 # number of training epochs
python main.py --help
# shows:
# --lr (type: float) (default: 0.001) Learning rate for the optimizer
# --epochs (type: int) (default: 10) number of training epochs
JSON Config Loading
Use SpecialLoadMarker to load defaults from a JSON file. Priority: command line > pass_in > JSON config > default values.
from simpleArgParser import SpecialLoadMarker
@dataclass
class Config:
lr: float = 0.001
epochs: int = 10
load_from: str | None = SpecialLoadMarker()
{"lr": 0.01, "epochs": 50}
python main.py --load_from config.json # uses JSON values
python main.py --load_from config.json --lr 0.1 # CLI overrides JSON
Partial Defaults for Nested Dataclasses
When a nested dataclass has required fields, you can't use it directly as a default (e.g. field(default_factory=SamplingConfig) fails because SamplingConfig has required fields). Use partial_defaults() to provide defaults for some fields while keeping others required from CLI.
from simpleArgParser import parse_args, partial_defaults
@dataclass
class InnerConfig:
lr: float # required
weight_decay: float # required
warmup: int = 100
@dataclass
class Config:
# lr gets default 0.001, weight_decay stays required from CLI
optimizer: InnerConfig = partial_defaults(lr=0.001)
python main.py --optimizer.weight_decay 0.01
# optimizer.lr = 0.001 (from partial_defaults)
# optimizer.weight_decay = 0.01 (from CLI)
# optimizer.warmup = 100 (field default)
python main.py --optimizer.weight_decay 0.01 --optimizer.lr 0.1
# CLI overrides partial_defaults
Nested partial_defaults
partial_defaults can be nested to set defaults at multiple levels:
@dataclass
class LeafConfig:
gen_n: int # required
cache: bool = True
@dataclass
class MiddleConfig:
max_tokens: int # required
temperature: float = 0.6
leaf: LeafConfig = partial_defaults(cache=False)
@dataclass
class OuterConfig:
mid: MiddleConfig = partial_defaults(
max_tokens=123,
leaf=partial_defaults(cache=False),
)
# Only gen_n is required — everything else has defaults
python main.py --mid.leaf.gen_n 10
# mid.max_tokens = 123
# mid.temperature = 0.6
# mid.leaf.gen_n = 10
# mid.leaf.cache = False
python main.py --mid.leaf.gen_n 10 --mid.leaf.cache true
# CLI overrides nested partial_defaults
In --help, fields with partial defaults show their default value, and fields without defaults show (required):
--mid.leaf.gen_n (type: int) (required)
--mid.max_tokens (type: int) (default: 123)
--mid.leaf.cache (type: bool) (default: False)
Pre/Post Processing
Define pre_process() and post_process() methods for validation or side effects. They are called recursively on all nested dataclasses (pre_process top-down, post_process bottom-up).
@dataclass
class Config:
tp: int = 1
def pre_process(self):
print("validating...")
def post_process(self):
if self.tp < 1:
raise ValueError("tp must be >= 1")
Serialization
Convert configs to JSON or dict. Enum values are serialized by name.
from simpleArgParser import to_json, to_dict
config = parse_args(Config)
print(to_json(config)) # JSON string
print(to_dict(config)) # Python dict
Programmatic Usage
Use pass_in to provide arguments from code. Use disable_cmd=True to ignore sys.argv. Very useful for debugging and testing.
config = parse_args(Config, pass_in=["--lr", "0.01", "--epochs", "5"])
# ignore command line entirely
config = parse_args(Config, pass_in=["--lr", "0.01"], disable_cmd=True)
Subcommands (CLI Tools)
Build multi-command CLI tools with parse_args_with_commands. Define commands using enums for type-safe dispatching. Supports arbitrary nesting.
import enum
from simpleArgParser import parse_args_with_commands
class Command(enum.Enum):
train = "train" # start training
eval = "eval" # run evaluation
@dataclass
class TrainConfig:
lr: float = 0.001
epochs: int = 10
@dataclass
class EvalConfig:
checkpoint: str # required
command, config = parse_args_with_commands(
commands={
Command.train: TrainConfig,
Command.eval: EvalConfig,
},
)
if command == (Command.train,):
print(f"Training with lr={config.lr}")
elif command == (Command.eval,):
print(f"Evaluating {config.checkpoint}")
mycli train --lr 0.01 --epochs 20
mycli eval --checkpoint best.pt
mycli --help
Nested Commands
Group commands into modules with nested dicts and separate enums per level. The returned command is a tuple of enum members.
class Top(enum.Enum):
model = "model" # model operations
data = "data" # data operations
class ModelCmd(enum.Enum):
train = "train"
eval = "eval"
class DataCmd(enum.Enum):
process = "process"
command, config = parse_args_with_commands(
commands={
Top.model: {
ModelCmd.train: TrainConfig,
ModelCmd.eval: EvalConfig,
},
Top.data: {
DataCmd.process: ProcessConfig,
},
},
description="My ML CLI",
)
# command == (Top.model, ModelCmd.train)
if command[0] == Top.model:
if command[1] == ModelCmd.train:
...
mycli model train --lr 0.01
mycli data process --workers 8
mycli --help # shows command tree
mycli model --help # shows model sub-commands
Shared Config Across Commands
Embed a common config as a nested field. Short aliases are created automatically for unique field names.
@dataclass
class CommonConfig:
verbose: bool = False
@dataclass
class TrainConfig:
lr: float = 0.001
common: CommonConfig = field(default_factory=CommonConfig)
mycli train --verbose true # alias for --common.verbose
mycli train --common.verbose true # full path always works
Help Output Ordering
Arguments in --help are sorted by: required first, then by nesting depth (shallow first), then alphabetically.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file simpleargparser-0.2.4.tar.gz.
File metadata
- Download URL: simpleargparser-0.2.4.tar.gz
- Upload date:
- Size: 32.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
719a3c0152fecaa7eff7008a131fe30b542ac0363fa8544cbf00e90fe3eac24d
|
|
| MD5 |
3d5c8b73d620b195d0d45cd3872e43d3
|
|
| BLAKE2b-256 |
99c6e310bb85ffc306f71969d303d9c787af5b54aefcc4ca54c811ab0e9bea15
|
File details
Details for the file simpleargparser-0.2.4-py3-none-any.whl.
File metadata
- Download URL: simpleargparser-0.2.4-py3-none-any.whl
- Upload date:
- Size: 14.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae317667f3580d033dc10ecdef1d78a6c54614f5c8c7814c8be8219305695db6
|
|
| MD5 |
7d92efceccbacd69d224ddf28da86398
|
|
| BLAKE2b-256 |
a9d2b75ca3f3b69ed5a46f69675081eea4cd01a40dce1712fe2522f6bad907f4
|