Extension module for argparse
Project description
argparse-boost
argparse-boost is a library for building CLI applications with automatic argument parsing from dataclasses and environment variable support.
Installation
pip install argparse-boost
How to use
Quick Start: Multi-Command CLI
Build multi-command CLIs (like git, docker, kubectl) with automatic command discovery:
Project structure:
myapp/
└── cli/
├── __main__.py
└── greet.py
myapp/cli/main.py:
from argparse_boost import Config, setup_cli
from myapp import cli
def main() -> None:
config = Config(
app_name="myapp",
env_prefix="MYAPP_",
)
setup_cli(
config=config,
description="My CLI application",
commands_package=cli,
)
if __name__ == "__main__":
main()
myapp/cli/greet.py:
from dataclasses import dataclass
@dataclass(kw_only=True)
class GreetConfig:
name: str
greeting: str = "Hello"
def main(args: GreetConfig) -> None:
"""Greet someone with a custom message."""
print(f"{args.greeting}, {args.name}!")
Usage:
python -m myapp.cli greet --name World --greeting Hi
# Output: Hi, World!
python -m myapp.cli greet --name Alice
# Output: Hello, Alice!
python -m myapp.cli --help
# Shows available commands
python -m myapp.cli greet --help
# Shows arguments for greet command
The library automatically:
- Discovers commands in the
cli/directory - Parses arguments from the dataclass type hint in
main() - Generates help text from dataclass fields
- Supports environment variables with the specified prefix
Writing Commands with Dataclass Auto-Parsing
The easiest way to write a command is to use a dataclass type hint in the main() function.
Arguments are automatically parsed from the dataclass fields:
# myapp/cli/deploy.py
from dataclasses import dataclass, field
from typing import Annotated
from argparse_boost import Help
@dataclass(kw_only=True)
class DeployConfig:
environment: Annotated[str, Help("Target environment (dev/staging/prod)")]
version: Annotated[str, Help("Version to deploy")]
dry_run: Annotated[bool, Help("Simulate deployment without making changes")] = False
services: Annotated[list[str], Help("Services to deploy")] = field(
default_factory=list
)
def main(args: DeployConfig) -> None:
"""Deploy application to the specified environment."""
if args.dry_run:
print(f"[DRY RUN] Would deploy {args.version} to {args.environment}")
else:
print(f"Deploying {args.version} to {args.environment}...")
if args.services:
print(f"Services: {', '.join(args.services)}")
Usage:
python -m myapp.cli deploy --environment prod --version 1.2.3
python -m myapp.cli deploy --environment staging --version 1.2.4 --dry-run true
python -m myapp.cli deploy --environment dev --version 1.2.5 --services "api,worker"
This approach gives you:
- Type-safe argument parsing
- Automatic validation (required fields, type checking)
- Auto-generated help text with descriptions
- Support for complex types (lists, dicts, nested dataclasses)
Writing Commands with Manual Parser Setup
For more control over argument parsing, use setup_parser() and work with argparse.Namespace:
import argparse
def setup_parser(parser: argparse.ArgumentParser) -> None:
"""Configure arguments for this command."""
parser.add_argument(
"--environment",
required=True,
choices=["dev", "staging", "prod"],
help="Target environment",
)
parser.add_argument(
"--version",
required=True,
help="Version to deploy",
)
parser.add_argument(
"--force",
action="store_true",
help="Force deployment even if validation fails",
)
def main(args: argparse.Namespace) -> None:
"""Deploy application to the specified environment."""
print(f"Deploying {args.version} to {args.environment}")
if args.force:
print("Force mode enabled - skipping validation")
This approach is useful when you need:
- Custom argument behavior (choices, actions, mutual exclusivity)
- Integration with existing argparse code
- Arguments that don't map cleanly to dataclass fields
Async command support:
Commands can also be async - the library automatically handles them with asyncio.run():
import asyncio
async def main() -> None:
"""Async command example."""
await asyncio.sleep(1)
Configuration Management (Without CLI)
You can use argparse-boost for configuration management in your project, completely separate from CLI applications. This is useful for web apps, services, or any Python project that needs type-safe configuration from environment variables:
from dataclasses import dataclass, field
from argparse_boost import construct_dataclass, Config
@dataclass(kw_only=True)
class DatabaseConfig:
host: str
port: int = 5432
user: str = "postgres"
password: str
pool_size: int = 10
@dataclass(kw_only=True)
class AppConfig:
debug: bool = False
db: DatabaseConfig = field(default_factory=DatabaseConfig)
allowed_hosts: list[str] = field(default_factory=list)
# Load configuration from environment variables
app_config = construct_dataclass(
AppConfig,
config=Config(env_prefix="APP_"),
)
# Now use your type-safe config
print(f"Connecting to {app_config.db.host}:{app_config.db.port}")
print(f"Debug mode: {app_config.debug}")
Environment variables:
APP_DEBUG=true
APP_DB_HOST=localhost
APP_DB_PORT=5433
APP_DB_PASSWORD=secret
APP_DB_POOL_SIZE=20
APP_ALLOWED_HOSTS=example.com,api.example.com
The configuration is loaded with full type conversion:
APP_DEBUG=true→config.debug = TrueAPP_DB_PORT=5433→config.db.port = 5433APP_ALLOWED_HOSTS=a,b→config.allowed_hosts = ["a", "b"]
This approach gives you:
- Type-safe configuration with dataclasses
- Automatic type conversion from environment variables
- Support for nested configuration (flattened to ENV vars)
- No CLI overhead - just configuration management
Core Features
- Multi-Command CLI Framework - Build git-like CLIs with automatic command discovery
- Dataclass Auto-Parsing - Type-safe argument parsing from dataclass type hints
- Configuration Management - Load type-safe config from environment variables
- Environment Variable Support - Auto-merge ENV vars with CLI args (CLI takes priority)
- Nested Dataclasses - Automatic flattening with intuitive naming
- Custom Parsers - Extend parsing with custom functions via
Annotated[T, Parser(func)] - Type-Safe Parsing - Support for int, float, str, bool, list, dict, Optional
- Custom Help Text - Enhanced help formatting with defaults and ENV var display
Environment Variable Support
Commands automatically read environment variables before parsing CLI arguments. CLI arguments always
take priority over environment variables. The env_prefix specified in setup_main() is used for
all commands.
Environment variable naming:
- CLI option
--host→ ENV varMYAPP_HOST(with prefix from setup_main) - CLI option
--port→ ENV varMYAPP_PORT - Multi-word options:
--log-level→MYAPP_LOG_LEVEL(dashes become underscores)
Example:
# myapp/cli/connect.py
from dataclasses import dataclass
@dataclass(kw_only=True)
class ConnectConfig:
host: str
port: int = 5432
log_level: str = "INFO"
def main(args: ConnectConfig) -> None:
"""Connect to a server with logging."""
print(f"Connecting to {args.host}:{args.port}")
print(f"Log level: {args.log_level}")
Usage:
# Using environment variables only
MYAPP_HOST=localhost MYAPP_LOG_LEVEL=DEBUG python -m myapp.cli connect
# CLI overrides ENV
MYAPP_HOST=localhost python -m myapp.cli connect --host production.example.com --port 443
# Result: host='production.example.com', port=443
# Mix both
MYAPP_LOG_LEVEL=DEBUG python -m myapp.cli connect --host db.example.com
# Result: host='db.example.com', log_level='DEBUG'
Environment variables are displayed in the help output when they are currently set:
MYAPP_HOST=localhost python -m myapp.cli connect --help
# Shows: --host (env: MYAPP_HOST=localhost)
Nested Dataclasses
Nested dataclasses are automatically flattened into CLI arguments with intuitive naming:
# myapp/cli/migrate.py
from dataclasses import dataclass, field
@dataclass(kw_only=True)
class DatabaseConfig:
host: str
port: int = 5432
use_ssl: bool = False
@dataclass(kw_only=True)
class MigrateConfig:
migration_name: str
db: DatabaseConfig = field(default_factory=lambda: DatabaseConfig(host="localhost"))
def main(args: MigrateConfig) -> None:
"""Run database migrations."""
ssl_status = "with SSL" if args.db.use_ssl else "without SSL"
print(f"Running migration '{args.migration_name}'")
print(f"Connecting to {args.db.host}:{args.db.port} {ssl_status}")
Generated CLI arguments:
--migration-name→args.migration_name--db-host→args.db.host--db-port→args.db.port--db-use-ssl→args.db.use_ssl
Environment variables (with MYAPP prefix from setup_main):
MYAPP_MIGRATION_NAMEMYAPP_DB_HOSTMYAPP_DB_PORTMYAPP_DB_USE_SSL
Usage:
# Using CLI arguments
python -m myapp.cli migrate --migration-name add_users --db-host postgres.local --db-port 5433
# Using environment variables
MYAPP_DB_HOST=postgres.local MYAPP_DB_USE_SSL=true python -m myapp.cli migrate --migration-name add_users
Custom Parsers
Use custom parsing functions for specialized types via Annotated:
# myapp/cli/analyze.py
from dataclasses import dataclass
from typing import Annotated
from argparse_boost import Parser
def parse_percentage(value: str) -> float:
"""Parse percentage string like '75%' to float 0.75"""
if isinstance(value, float):
return value
return float(value.rstrip("%")) / 100
@dataclass(kw_only=True)
class AnalyzeConfig:
dataset: str
threshold: Annotated[float, Parser(parse_percentage)] = 0.5
min_confidence: Annotated[float, Parser(parse_percentage)] = 0.8
def main(args: AnalyzeConfig) -> None:
"""Analyze dataset with custom threshold."""
print(f"Analyzing {args.dataset}")
print(f"Threshold: {args.threshold:.2%}")
print(f"Min confidence: {args.min_confidence:.2%}")
Usage:
python -m myapp.cli analyze --dataset users.csv --threshold 75% --min-confidence 90%
# Output:
# Analyzing users.csv
# Threshold: 75.00%
# Min confidence: 90.00%
Custom parsers are useful for:
- Parsing duration strings (e.g., "1h30m" → seconds)
- Parsing file sizes (e.g., "10MB" → bytes)
- Parsing percentages, ratios, or custom formats
- Validating and transforming complex inputs
Help Text
Add descriptive help text to fields using Annotated with Help:
# myapp/cli/backup.py
from dataclasses import dataclass
from typing import Annotated
from argparse_boost import Help
@dataclass(kw_only=True)
class BackupConfig:
source: Annotated[str, Help("Source directory to backup")]
destination: Annotated[str, Help("Destination directory for backup")]
timeout: Annotated[int, Help("Backup timeout in seconds")] = 3600
retries: Annotated[int, Help("Number of retry attempts on failure")] = 3
compress: Annotated[bool, Help("Compress backup files")] = True
def main(args: BackupConfig) -> None:
"""Create a backup of the source directory."""
compression = "compressed" if args.compress else "uncompressed"
print(f"Backing up {args.source} to {args.destination} ({compression})")
print(f"Timeout: {args.timeout}s, Retries: {args.retries}")
View help:
python -m myapp.cli backup --help
# Shows:
# --source Source directory to backup (Required)
# --destination Destination directory for backup (Required)
# --timeout Backup timeout in seconds (Default: 3600)
# --retries Number of retry attempts on failure (Default: 3)
# --compress Compress backup files (Default: True)
Help text is automatically enhanced with default values and "Required" indicators for fields without defaults.
Advanced Types
Lists
Lists are parsed from comma-separated values:
# myapp/cli/tag.py
from dataclasses import dataclass, field
@dataclass(kw_only=True)
class TagConfig:
resource: str
tags: list[str] = field(default_factory=list)
allowed_ports: list[int] = field(default_factory=list)
def main(args: TagConfig) -> None:
"""Tag a resource with metadata."""
print(f"Tagging resource: {args.resource}")
if args.tags:
print(f"Tags: {', '.join(args.tags)}")
if args.allowed_ports:
print(f"Allowed ports: {', '.join(map(str, args.allowed_ports))}")
Usage:
# Using CLI arguments
python -m myapp.cli tag --resource server-01 --tags "web,api,prod" --allowed-ports "80,443,8080"
# Output:
# Tagging resource: server-01
# Tags: web, api, prod
# Allowed ports: 80, 443, 8080
# Using environment variables
MYAPP_RESOURCE=server-02 MYAPP_TAGS="database,backup" python -m myapp.cli tag
Dictionaries
Dictionaries are parsed from comma-separated key-value pairs using : or =:
# myapp/cli/configure.py
from dataclasses import dataclass, field
@dataclass(kw_only=True)
class ConfigureConfig:
service: str
limits: dict[str, int] = field(default_factory=dict)
settings: dict[str, str] = field(default_factory=dict)
def main(args: ConfigureConfig) -> None:
"""Configure service with limits and settings."""
print(f"Configuring {args.service}")
if args.limits:
for key, value in args.limits.items():
print(f" Limit {key}: {value}")
if args.settings:
for key, value in args.settings.items():
print(f" Setting {key}: {value}")
Usage:
# Using colon separator
python -m myapp.cli configure --service api --limits "daily:100,monthly:3000"
# Using equals separator
python -m myapp.cli configure --service worker --settings "timeout=30,retries=3"
Optional Types
Optional fields are supported via T | None or Optional[T]:
# myapp/cli/process.py
from dataclasses import dataclass
@dataclass(kw_only=True)
class ProcessConfig:
input_file: str
output_file: str | None = None
max_retries: int | None = 3
format: str | None = None
def main(args: ProcessConfig) -> None:
"""Process a file with optional parameters."""
print(f"Processing {args.input_file}")
if args.output_file:
print(f"Output to: {args.output_file}")
else:
print("Output to: stdout")
print(f"Max retries: {args.max_retries}")
if args.format:
print(f"Format: {args.format}")
Usage:
# Only required field
python -m myapp.cli process --input-file data.csv
# Output: stdout, max_retries: 3, format: None
# With optional fields
python -m myapp.cli process --input-file data.csv --output-file result.json --format json
Programmatic Usage
For more control, use the low-level API:
from dataclasses import dataclass
from argparse_boost import (
BoostedArgumentParser,
construct_dataclass,
Config,
dict_from_args,
)
@dataclass(kw_only=True)
class Settings:
host: str
port: int = 5432
# Option 1: Simple approach - automatic ENV loading
settings = construct_dataclass(
Settings,
config=Config(env_prefix="APP_"),
)
# Option 2: Manual approach with BoostedArgumentParser
parser = BoostedArgumentParser(prog="myapp", env_prefix="APP_")
parser.parse_arguments_from_dataclass(Settings)
args = parser.parse_args(["--host", "example.com"])
# Extract CLI arguments and construct with overrides
cli_data = dict_from_args(args, Settings)
settings = construct_dataclass(
Settings,
override=cli_data,
config=Config(env_prefix="APP_"),
)
API Reference
Framework Functions:
setup_main()- Entry point for multi-command CLI applications with automatic command discovery
Configuration Functions:
construct_dataclass(dc_type, override, *, config)- Construct dataclass from environment and overridesenv_for_dataclass(fields, config)- Read ENV vars for specific fields (advanced use)Config- Configuration class with env_prefix, loaders, and other options
Parser Classes:
BoostedArgumentParser- Extended ArgumentParser with ENV variable supportDefaultsHelpFormatter- Help formatter showing defaults and required indicators
Dataclass Parsing:
dict_from_args(args, dataclass_type)- Extract CLI args to flat dict
Annotations:
Parser(func)- Custom parsing function forAnnotatedfieldsHelp(text)- Custom help text forAnnotatedfields
Exceptions:
CliSetupError- Base exception for CLI setup errorsFieldNameConflictError- Raised when flattened field names collideUnsupportedFieldTypeError- Raised for unsupported field types
Supported Types
| Type | Example | CLI Input | ENV Input |
|---|---|---|---|
int |
count: int |
--count 42 |
APP_COUNT=42 |
float |
ratio: float |
--ratio 3.14 |
APP_RATIO=3.14 |
str |
name: str |
--name hello |
APP_NAME=hello |
bool |
debug: bool |
--debug true |
APP_DEBUG=yes |
list[T] |
tags: list[str] |
--tags a,b,c |
APP_TAGS=a,b,c |
dict[K,V] |
limits: dict[str,int] |
--limits a:1,b:2 |
APP_LIMITS=a:1,b:2 |
T | None |
port: int | None |
--port 80 |
APP_PORT=80 |
| Nested dataclass | db: Database |
--db-host localhost |
APP_DB_HOST=localhost |
| Custom | Annotated[T, Parser(f)] |
Custom parsing | Custom parsing |
Type constraints:
list[T]anddict[K,V]: T, K, V must be simple types (int, float, str, bool) or Optional simple typesUnionis only supported asOptional[T](i.e.,T | None)- Generic unions like
str | intare not supported
Boolean parsing accepts (case-insensitive):
- True:
true,yes,on,1 - False:
false,no,off,0
Tips and Best Practices
-
Use
field(default_factory=...)for mutable defaults:from dataclasses import dataclass, field @dataclass(kw_only=True) class Settings: tags: list[str] = field(default_factory=list) # Good # tags: list[str] = [] # Bad - mutable default
-
Avoid field name conflicts with nested dataclasses:
# This will raise FieldNameConflictError: @dataclass(kw_only=True) class Database: password: str @dataclass(kw_only=True) class Settings: db_password: str # Conflicts with db.password when flattened db: Database
-
Use
env_prefixto scope environment variables:parser = BoostedArgumentParser(prog="myapp", env_prefix="MYAPP_") # Prevents conflicts with other apps' ENV vars
Development
Quick Start
- Clone the repository:
git clone <repository-url>
- (?) Copy the example settings file and configure your settings:
cp settings.example.yaml settings.yaml
- Build the Docker images:
docker-compose build - Install dependencies:
make uv args="sync"
- Start the service:
docker-compose up
Making Changes
-
List available
makecommands:make help
-
Check code style with:
make lint -
Run tests using:
make test
-
Manage dependencies via uv:
make uv args="<uv-args>"
- For example:
make uv args="add picodi"
- For example:
-
For local CI debugging:
make run-ci
Pre-commit Hooks
We use pre-commit for linting and formatting:
- It runs inside a Docker container by default.
- Optionally, set up hooks locally:
pre-commit install
Mypy
We use mypy for static type checking.
It is configured for strictly typed code, so you may need to add type hints to your code.
But don't be very strict, sometimes it's better to use Any type.
License
Credits
This project was generated with
yakimka/cookiecutter-pyproject.
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 argparse_boost-0.2.0.tar.gz.
File metadata
- Download URL: argparse_boost-0.2.0.tar.gz
- Upload date:
- Size: 38.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4a145b8dbf05d17fb1974154bef593c4c4faf07b6f817e3bec2e533bf5dfe14a
|
|
| MD5 |
2d10efe23d0803cceeafb5986a1096ca
|
|
| BLAKE2b-256 |
9115da6cef88a1a381875c32fe37eef39489eb2854b0378001f17e49df6ebbe3
|
File details
Details for the file argparse_boost-0.2.0-py3-none-any.whl.
File metadata
- Download URL: argparse_boost-0.2.0-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6daaa7138f435cfe581b22f76e58ff8c0ddd4eb1211874697883ee5a13062fe5
|
|
| MD5 |
dc0b6fd4f610aa8c3f302772bea72f21
|
|
| BLAKE2b-256 |
259ee83cc201ac695cd163121dd737a0c421ef801ca4e53445fd2b74d0b20f5d
|