Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

simpleargparser-0.2.4.tar.gz (32.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

simpleargparser-0.2.4-py3-none-any.whl (14.9 kB view details)

Uploaded Python 3

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

Hashes for simpleargparser-0.2.4.tar.gz
Algorithm Hash digest
SHA256 719a3c0152fecaa7eff7008a131fe30b542ac0363fa8544cbf00e90fe3eac24d
MD5 3d5c8b73d620b195d0d45cd3872e43d3
BLAKE2b-256 99c6e310bb85ffc306f71969d303d9c787af5b54aefcc4ca54c811ab0e9bea15

See more details on using hashes here.

File details

Details for the file simpleargparser-0.2.4-py3-none-any.whl.

File metadata

File hashes

Hashes for simpleargparser-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 ae317667f3580d033dc10ecdef1d78a6c54614f5c8c7814c8be8219305695db6
MD5 7d92efceccbacd69d224ddf28da86398
BLAKE2b-256 a9d2b75ca3f3b69ed5a46f69675081eea4cd01a40dce1712fe2522f6bad907f4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page