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.3.tar.gz (32.1 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.3-py3-none-any.whl (14.6 kB view details)

Uploaded Python 3

File details

Details for the file simpleargparser-0.2.3.tar.gz.

File metadata

  • Download URL: simpleargparser-0.2.3.tar.gz
  • Upload date:
  • Size: 32.1 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.3.tar.gz
Algorithm Hash digest
SHA256 7291cadf4402e4821f3596a19435d5f31e3e608563ea9657e1d7c17fce668317
MD5 058f8f3f82468b8fcc5372590d4f3d41
BLAKE2b-256 2c7de4029cd80d58067af86f7618d99e118b4672505c5cfcb717ca4df1f4610f

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for simpleargparser-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 889679f7f673b02bfe17a85cdc0d35aff34c7a79c8c43ac04c43cd7c93f0a2d7
MD5 0a57b2af8eeb381af57b3b48100447ed
BLAKE2b-256 bd25fc255fe429587335c0a41ee4c6486367e49efb0989a5d8d03cb665f18f53

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