# Dataclass Args
Generate command-line interfaces from Python dataclasses.
[](https://github.com/bassmanitram/dataclass-args/actions/workflows/test.yml)
[](https://github.com/bassmanitram/dataclass-args/actions/workflows/lint.yml)
[](https://github.com/bassmanitram/dataclass-args/actions/workflows/examples.yml)
[](https://codecov.io/gh/bassmanitram/dataclass-args)
[](https://www.python.org/downloads/)
[](https://badge.fury.io/py/dataclass-args)
[](https://opensource.org/licenses/MIT)
## Features
- **Automatic CLI Generation** - Generate CLI from dataclass definitions
- **Type-Safe Parsing** - Type-aware argument parsing for standard Python types
- **Positional Arguments** - Support for positional args with `cli_positional()`
- **Short Options** - Concise `-n` flags in addition to `--name`
- **Boolean Flags** - Proper `--flag` and `--no-flag` boolean handling
- **Value Validation** - Restrict values with `cli_choices()`
- **File Loading** - Load parameters from files using `@filename` syntax
- **Config Merging** - Combine configuration files with CLI overrides
- **Flexible Types** - Support for `List`, `Dict`, `Optional`, and custom types
- **Rich Annotations** - Custom help text, exclusions, and combinations
- **Minimal Dependencies** - Lightweight with optional format support
## Quick Start
### Installation
```bash
pip install dataclass-args
# With optional format support
pip install "dataclass-args[yaml,toml]" # YAML and TOML config files
pip install "dataclass-args[all]" # All optional dependencies
```
### Basic Usage
```python
from dataclasses import dataclass
from dataclass_args import build_config
@dataclass
class Config:
name: str
count: int = 10
debug: bool = False
# Generate CLI from dataclass
config = build_config(Config)
# Use your config
print(f"Running {config.name} with count={config.count}, debug={config.debug}")
```
```bash
$ python app.py --name "MyApp" --count 5 --debug
Running MyApp with count=5, debug=True
$ python app.py --help
usage: app.py [-h] [--config CONFIG] [--name NAME] [--count COUNT] [--debug] [--no-debug]
Build Config from CLI
options:
-h, --help show this help message and exit
--config CONFIG Base configuration file (JSON, YAML, or TOML)
--name NAME name
--count COUNT count
--debug debug (default: False)
--no-debug Disable debug
```
## Core Features
### Short Options
Add concise short flags to your CLI:
```python
from dataclass_args import cli_short
@dataclass
class ServerConfig:
host: str = cli_short('h', default="localhost")
port: int = cli_short('p', default=8000)
debug: bool = cli_short('d', default=False)
```
```bash
# Use short forms
$ python server.py -h 0.0.0.0 -p 9000 -d
# Or long forms
$ python server.py --host 0.0.0.0 --port 9000 --debug
# Mix and match
$ python server.py -h 0.0.0.0 --port 9000 -d
```
### Boolean Flags
Booleans work as proper CLI flags with negative forms:
```python
@dataclass
class BuildConfig:
test: bool = True # Default enabled
deploy: bool = False # Default disabled
```
```bash
# Enable a flag
$ python build.py --deploy
# Disable a flag
$ python build.py --no-test
# Use defaults (omit flags)
$ python build.py # test=True, deploy=False
```
With short options:
```python
@dataclass
class Config:
verbose: bool = cli_short('v', default=False)
debug: bool = cli_short('d', default=False)
```
```bash
$ python app.py -v -d # Short flags
$ python app.py --verbose --debug # Long flags
$ python app.py --no-verbose # Negative form
```
### Value Choices
Restrict field values to a valid set:
```python
from dataclass_args import cli_choices
@dataclass
class DeployConfig:
environment: str = cli_choices(['dev', 'staging', 'prod'])
region: str = cli_choices(['us-east-1', 'us-west-2', 'eu-west-1'], default='us-east-1')
size: str = cli_choices(['small', 'medium', 'large'], default='medium')
```
```bash
# Valid choices
$ python deploy.py --environment prod --region us-west-2
# Invalid choice shows error
$ python deploy.py --environment invalid
error: argument --environment: invalid choice: 'invalid' (choose from 'dev', 'staging', 'prod')
```
### Positional Arguments
Add positional arguments that don't require `--` prefixes:
```python
from dataclass_args import cli_positional
@dataclass
class CopyCommand:
source: str = cli_positional(help="Source file")
dest: str = cli_positional(help="Destination file")
recursive: bool = cli_short('r', default=False)
```
```bash
# Positional arguments are matched by position
$ python cp.py source.txt destination.txt -r
# Optional flags can appear anywhere
$ python cp.py -r source.txt destination.txt
```
#### Variable Number of Arguments
Use `nargs` to accept multiple values:
```python
from typing import List
@dataclass
class GitCommit:
command: str = cli_positional(help="Git command")
files: List[str] = cli_positional(nargs='+', help="Files to commit")
message: str = cli_short('m', default="")
# CLI: python git.py commit file1.py file2.py file3.py -m "Add feature"
```
**nargs Options:**
- `None` (default) - Exactly one value (required)
- `'?'` - Zero or one value (optional)
- `'*'` - Zero or more values (optional list)
- `'+'` - One or more values (required list)
- `int` (e.g., `2`) - Exact count (required list)
#### Optional Positional Arguments
```python
@dataclass
class Convert:
input_file: str = cli_positional(help="Input file")
output_file: str = cli_positional(
nargs='?',
default='stdout',
help="Output file (default: stdout)"
)
format: str = cli_short('f', default='json')
```
```bash
# With output file
$ python convert.py input.json output.yaml -f yaml
# Without output file (uses default)
$ python convert.py input.json -f xml
```
#### ⚠️ Positional List Constraints
Positional arguments with variable length have important constraints:
**Rules:**
1. At most ONE positional field can use `nargs='*'` or `'+'`
2. If present, the positional list must be the LAST positional argument
3. For multiple lists, use optional arguments with flags
**Valid:**
```python
@dataclass
class Valid:
command: str = cli_positional() # First
files: List[str] = cli_positional(nargs='+') # Last (OK!)
exclude: List[str] = cli_short('e', default_factory=list) # Optional list with flag (OK!)
```
**Invalid:**
```python
@dataclass
class Invalid:
files: List[str] = cli_positional(nargs='+') # Positional list
output: str = cli_positional() # ERROR: positional after list!
# ConfigBuilderError: Positional list argument must be last.
# Fix: Make output an optional argument with a flag
```
**Why?** Positional lists are greedy and consume all remaining values. The parser can't determine where one positional list ends and another begins without `--` flags.
### Combining Annotations
Use `combine_annotations()` to merge multiple features:
```python
from dataclass_args import combine_annotations, cli_short, cli_choices, cli_help
@dataclass
class AppConfig:
# Combine short option + help text
name: str = combine_annotations(
cli_short('n'),
cli_help("Application name")
)
# Combine short + choices + help
environment: str = combine_annotations(
cli_short('e'),
cli_choices(['dev', 'staging', 'prod']),
cli_help("Deployment environment"),
default='dev'
)
# Boolean with short + help
debug: bool = combine_annotations(
cli_short('d'),
cli_help("Enable debug mode"),
default=False
)
```
```bash
# Concise CLI usage
$ python app.py -n myapp -e prod -d
# Clear help output
$ python app.py --help
options:
-n NAME, --name NAME Application name
-e {dev,staging,prod}, --environment {dev,staging,prod}
Deployment environment (default: dev)
-d, --debug Enable debug mode (default: False)
--no-debug Disable Enable debug mode
```
### Real-World Example
```python
from dataclasses import dataclass
from dataclass_args import build_config, cli_short, cli_choices, cli_help, combine_annotations
@dataclass
class DeploymentConfig:
"""Configuration for application deployment."""
# Basic settings with short options
name: str = combine_annotations(
cli_short('n'),
cli_help("Application name")
)
version: str = combine_annotations(
cli_short('v'),
cli_help("Version to deploy"),
default='latest'
)
# Validated choices
environment: str = combine_annotations(
cli_short('e'),
cli_choices(['dev', 'staging', 'prod']),
cli_help("Target environment"),
default='dev'
)
region: str = combine_annotations(
cli_short('r'),
cli_choices(['us-east-1', 'us-west-2', 'eu-west-1']),
cli_help("AWS region"),
default='us-east-1'
)
size: str = combine_annotations(
cli_short('s'),
cli_choices(['small', 'medium', 'large', 'xlarge']),
cli_help("Instance size"),
default='medium'
)
# Boolean flags
dry_run: bool = combine_annotations(
cli_short('d'),
cli_help("Perform dry run without deploying"),
default=False
)
notify: bool = combine_annotations(
cli_short('N'),
cli_help("Send deployment notifications"),
default=True
)
if __name__ == "__main__":
config = build_config(DeploymentConfig)
print(f"Deploying {config.name} v{config.version}")
print(f"Environment: {config.environment}")
print(f"Region: {config.region}")
print(f"Size: {config.size}")
print(f"Dry run: {config.dry_run}")
print(f"Notify: {config.notify}")
```
```bash
# Production deployment
$ python deploy.py -n myapp -v 2.1.0 -e prod -r us-west-2 -s large
# Dry run in staging
$ python deploy.py -n myapp -e staging -d --no-notify
# Help shows everything clearly
$ python deploy.py --help
```
## Advanced Features
### File-Loadable Parameters
Load string parameters from files using the `@filename` syntax. Supports home directory expansion with `~`:
```python
from dataclass_args import cli_file_loadable
@dataclass
class AppConfig:
name: str = cli_help("Application name")
system_prompt: str = cli_file_loadable(default="You are a helpful assistant")
welcome_message: str = cli_file_loadable()
config = build_config(AppConfig)
```
```bash
# Use literal values
$ python app.py --system-prompt "You are a coding assistant"
# Load from files (absolute paths)
$ python app.py --system-prompt "@/etc/prompts/assistant.txt"
# Load from home directory
$ python app.py --system-prompt "@~/prompts/assistant.txt"
# Load from another user's home
$ python app.py --system-prompt "@~alice/shared/prompt.txt"
# Load from relative paths
$ python app.py --welcome-message "@messages/welcome.txt"
# Mix literal and file-loaded values
$ python app.py --name "MyApp" --system-prompt "@~/prompts/assistant.txt"
```
**Path Expansion:**
- `@~/file.txt` → Expands to user's home directory (e.g., `/home/user/file.txt`)
- `@~username/file.txt` → Expands to specified user's home directory
- `@/absolute/path` → Used as-is
- `@relative/path` → Relative to current working directory
# config.yaml
name: "DefaultApp"
count: 100
database:
host: "localhost"
port: 5432
timeout: 30
```
```python
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
timeout: float = 30.0
@dataclass
class AppConfig:
name: str
count: int = 10
database: Dict[str, Any] = None
config = build_config_from_cli(AppConfig, [
'--config', 'config.yaml', # Load base configuration
'--name', 'OverriddenApp', # Override name
'--database', 'db.json', # Load additional database config
'--d', 'timeout:60' # Override database.timeout property
])
```
### Custom Help and Annotations
```python
from dataclass_args import cli_help, cli_exclude, cli_file_loadable
@dataclass
class ServerConfig:
# Custom help text
host: str = cli_help("Server bind address", default="127.0.0.1")
port: int = cli_help("Server port number", default=8000)
# File-loadable with help
ssl_cert: str = cli_file_loadable(cli_help("SSL certificate content"))
# Hidden from CLI
secret_key: str = cli_exclude(default="auto-generated")
# Multiple values
allowed_hosts: List[str] = cli_help("Allowed host headers", default_factory=list)
```
### Complex Types and Validation
```python
from typing import List, Dict, Optional
from pathlib import Path
@dataclass
class MLConfig:
# Basic types
model_name: str = cli_help("Model identifier")
learning_rate: float = cli_help("Learning rate", default=0.001)
epochs: int = cli_help("Training epochs", default=100)
# Complex types
layer_sizes: List[int] = cli_help("Neural network layer sizes", default_factory=lambda: [128, 64])
hyperparameters: Dict[str, Any] = cli_help("Model hyperparameters")
# Optional types
checkpoint_path: Optional[Path] = cli_help("Path to model checkpoint")
# File-loadable configurations
training_config: str = cli_file_loadable(cli_help("Training configuration"))
def __post_init__(self):
# Custom validation
if self.learning_rate <= 0:
raise ValueError("Learning rate must be positive")
if self.epochs <= 0:
raise ValueError("Epochs must be positive")
```
## API Reference
> **📖 Full API Documentation:** See [docs/API.md](docs/API.md) for complete API reference with detailed examples.
### Quick API Reference
### Main Functions
#### `build_config(config_class, args=None)`
Generate CLI from dataclass and parse arguments.
```python
config = build_config(MyDataclass) # Uses sys.argv automatically
```
#### `build_config_from_cli(config_class, args=None, **options)`
Generate CLI with additional options.
```python
config = build_config_from_cli(
MyDataclass,
args=['--name', 'test'],
```
### Annotations
#### `cli_short(letter, **kwargs)`
Add a short option flag to a field.
```python
field: str = cli_short('f', default="value")
# Or combine with other annotations
field: str = combine_annotations(
cli_short('f'),
cli_help("Help text"),
default="value"
)
```
#### `cli_choices(choices_list, **kwargs)`
Restrict field to a set of valid choices.
```python
env: str = cli_choices(['dev', 'prod'], default='dev')
# Or combine
env: str = combine_annotations(
cli_short('e'),
cli_choices(['dev', 'prod']),
cli_help("Environment"),
default='dev'
)
```
#### `cli_help(help_text, **kwargs)`
Add custom help text to CLI arguments.
```python
field: str = cli_help("Custom help text", default="default_value")
```
#### `cli_positional(nargs=None, metavar=None, **kwargs)`
Mark a field as a positional CLI argument (no `--` prefix required).
```python
# Required positional
source: str = cli_positional(help="Source file")
# Optional positional
output: str = cli_positional(nargs='?', default='stdout')
# Variable number (list)
files: List[str] = cli_positional(nargs='+', help="Files")
# Exact count
coords: List[float] = cli_positional(nargs=2, metavar='X Y')
# Combined with other annotations
input: str = combine_annotations(
cli_positional(),
cli_help("Input file path")
)
```
**Important:** At most one positional can use `nargs='*'` or `'+'`, and it must be the last positional.
#### `cli_exclude(**kwargs)`
Exclude fields from CLI argument generation.
```python
internal_field: str = cli_exclude(default="hidden")
```
#### `cli_file_loadable(**kwargs)`
Mark string fields as file-loadable via '@filename' syntax.
```python
content: str = cli_file_loadable(default="default content")
```
#### `combine_annotations(*annotations, **kwargs)`
Combine multiple annotations on a single field.
```python
field: str = combine_annotations(
cli_short('f'),
cli_choices(['a', 'b', 'c']),
cli_help("Description"),
default='a'
)
```
## Type Support
Dataclass CLI supports standard Python types:
| Type | CLI Behavior | Example |
|------|--------------|---------|
| `str` | Direct string value | `--name "hello"` |
| `int` | Parsed as integer | `--count 42` |
| `float` | Parsed as float | `--rate 0.1` |
| `bool` | Flag with negative | `--debug` or `--no-debug` |
| `List[T]` | Multiple values | `--items a b c` |
| `Dict[str, Any]` | Config file + overrides | `--config file.json --c key:value` |
| `Optional[T]` | Optional parameter | `--timeout 30` (or omit) |
| `Path` | Path object | `--output /path/to/file` |
| Custom types | String representation | `--custom "value"` |
## Configuration File Formats
Supports multiple configuration file formats:
### JSON
```json
{
"name": "MyApp",
"count": 42,
"database": {
"host": "localhost",
"port": 5432
}
}
```
### YAML (requires `pip install "dataclass-args[yaml]"`)
```yaml
name: MyApp
count: 42
database:
host: localhost
port: 5432
```
### TOML (requires `pip install "dataclass-args[toml]"`)
```toml
name = "MyApp"
count = 42
[database]
host = "localhost"
port = 5432
```
## Examples
Check the [`examples/`](examples/) directory for complete working examples:
- **`positional_example.py`** - Positional arguments and variable length args
- **`boolean_flags_example.py`** - Boolean flags with `--flag` and `--no-flag`
- **`cli_choices_example.py`** - Value validation with choices
- **`cli_short_example.py`** - Short option flags
- **`all_features_example.py`** - All features together
- And more...
### Web Server Configuration
```python
from dataclasses import dataclass
from typing import List
from dataclass_args import build_config, cli_short, cli_help, cli_exclude, cli_file_loadable, combine_annotations
@dataclass
class ServerConfig:
# Basic server settings
host: str = combine_annotations(
cli_short('h'),
cli_help("Server bind address"),
default="127.0.0.1"
)
port: int = combine_annotations(
cli_short('p'),
cli_help("Server port number"),
default=8000
)
workers: int = combine_annotations(
cli_short('w'),
cli_help("Number of worker processes"),
default=1
)
# Security settings
ssl_cert: str = cli_file_loadable(cli_help("SSL certificate content"))
ssl_key: str = cli_file_loadable(cli_help("SSL private key content"))
# Application settings
debug: bool = combine_annotations(
cli_short('d'),
cli_help("Enable debug mode"),
default=False
)
allowed_hosts: List[str] = cli_help("Allowed host headers", default_factory=list)
# Internal fields (hidden from CLI)
_server_id: str = cli_exclude(default_factory=lambda: f"server-{os.getpid()}")
if __name__ == "__main__":
config = build_config(ServerConfig)
print(f"Starting server on {config.host}:{config.port}")
```
```bash
# Start server with short options
$ python server.py -h 0.0.0.0 -p 9000 -w 4 -d
# Load SSL certificates from files
$ python server.py --ssl-cert "@certs/server.crt" --ssl-key "@certs/server.key"
```
## Contributing
Contributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
```bash
git clone https://github.com/bassmanitram/dataclass-args.git
cd dataclass-args
pip install -e ".[dev,all]"
```
### Development Setup
```bash
git clone https://github.com/bassmanitram/dataclass-args.git
cd dataclass-args
pip install -e ".[dev,all]"
make setup # Install dev dependencies and pre-commit hooks
```
### Running Tests
```bash
# Run all tests (coverage is automatic)
pytest
make test
# Run tests with detailed coverage report
make coverage
# Run tests with coverage and open HTML report
make coverage-html
# Run specific test file
pytest tests/test_cli_short.py
# Verbose output
pytest -v
```
### Code Coverage
This project maintains **94%+ code coverage**. Coverage reports are generated automatically when running tests.
- **Quick check**: `make coverage`
- **Detailed report**: See `htmlcov/index.html`
- **Coverage docs**: [COVERAGE.md](COVERAGE.md)
All code changes should maintain or improve coverage. The minimum required coverage is 90%.
### Code Formatting
```bash
# Format code
make format
black dataclass_args/ tests/ examples/
isort dataclass_args/ tests/ examples/
# Check formatting
make lint
black --check dataclass_args/ tests/
flake8 dataclass_args/ tests/
mypy dataclass_args/
```
### Full Check (like CI)
```bash
# Run all checks: linting, tests, and examples
make check
```
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for version history and changes.
## Support
- **Issues**: [GitHub Issues](https://github.com/bassmanitram/dataclass-args/issues)
- **Documentation**: This README and comprehensive docstrings
- **Examples**: See the [examples/](examples/) directory
## Quick Reference
```python
from dataclasses import dataclass
from dataclass_args import (
build_config, # Main function
cli_short, # Short options: -n
cli_positional, # Positional args
cli_choices, # Value validation
cli_help, # Custom help text
cli_exclude, # Hide from CLI
cli_file_loadable, # @file loading
combine_annotations, # Combine features
)
@dataclass
class Config:
# Simple field
name: str
# Positional argument
input_file: str = cli_positional()
# With short option
port: int = cli_short('p', default=8000)
# With choices
env: str = cli_choices(['dev', 'prod'], default='dev')
# Boolean flag
debug: bool = False # Creates --debug and --no-debug
# Combine everything
region: str = combine_annotations(
cli_short('r'),
cli_choices(['us-east-1', 'us-west-2']),
cli_help("AWS region"),
default='us-east-1'
)
# Hidden from CLI
secret: str = cli_exclude(default="hidden")
# File-loadable
config_text: str = cli_file_loadable(default="")
# Build and use
config = build_config(Config)
```
Define your dataclass, add annotations as needed, and call `build_config()` to parse command-line arguments.
Raw data
{
"_id": null,
"home_page": null,
"name": "dataclass-args",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "cli, dataclass, argparse, configuration, command-line, type-safe, validation, file-loading, config-merging",
"author": null,
"author_email": "Martin Bartlett <martin.j.bartlett@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/7a/80/d29499a1952b5e393b9fe66cf9f07605c48547ce517af617c1d6a92ce9e6/dataclass_args-1.1.0.tar.gz",
"platform": null,
"description": "# Dataclass Args\n\nGenerate command-line interfaces from Python dataclasses.\n\n[](https://github.com/bassmanitram/dataclass-args/actions/workflows/test.yml)\n[](https://github.com/bassmanitram/dataclass-args/actions/workflows/lint.yml)\n[](https://github.com/bassmanitram/dataclass-args/actions/workflows/examples.yml)\n[](https://codecov.io/gh/bassmanitram/dataclass-args)\n[](https://www.python.org/downloads/)\n[](https://badge.fury.io/py/dataclass-args)\n[](https://opensource.org/licenses/MIT)\n\n## Features\n\n- **Automatic CLI Generation** - Generate CLI from dataclass definitions\n- **Type-Safe Parsing** - Type-aware argument parsing for standard Python types\n- **Positional Arguments** - Support for positional args with `cli_positional()`\n- **Short Options** - Concise `-n` flags in addition to `--name`\n- **Boolean Flags** - Proper `--flag` and `--no-flag` boolean handling\n- **Value Validation** - Restrict values with `cli_choices()`\n- **File Loading** - Load parameters from files using `@filename` syntax\n- **Config Merging** - Combine configuration files with CLI overrides\n- **Flexible Types** - Support for `List`, `Dict`, `Optional`, and custom types\n- **Rich Annotations** - Custom help text, exclusions, and combinations\n- **Minimal Dependencies** - Lightweight with optional format support\n\n## Quick Start\n\n### Installation\n\n```bash\npip install dataclass-args\n\n# With optional format support\npip install \"dataclass-args[yaml,toml]\" # YAML and TOML config files\npip install \"dataclass-args[all]\" # All optional dependencies\n```\n\n### Basic Usage\n\n```python\nfrom dataclasses import dataclass\nfrom dataclass_args import build_config\n\n@dataclass\nclass Config:\n name: str\n count: int = 10\n debug: bool = False\n\n# Generate CLI from dataclass\nconfig = build_config(Config)\n\n# Use your config\nprint(f\"Running {config.name} with count={config.count}, debug={config.debug}\")\n```\n\n```bash\n$ python app.py --name \"MyApp\" --count 5 --debug\nRunning MyApp with count=5, debug=True\n\n$ python app.py --help\nusage: app.py [-h] [--config CONFIG] [--name NAME] [--count COUNT] [--debug] [--no-debug]\n\nBuild Config from CLI\n\noptions:\n -h, --help show this help message and exit\n --config CONFIG Base configuration file (JSON, YAML, or TOML)\n --name NAME name\n --count COUNT count\n --debug debug (default: False)\n --no-debug Disable debug\n```\n\n## Core Features\n\n### Short Options\n\nAdd concise short flags to your CLI:\n\n```python\nfrom dataclass_args import cli_short\n\n@dataclass\nclass ServerConfig:\n host: str = cli_short('h', default=\"localhost\")\n port: int = cli_short('p', default=8000)\n debug: bool = cli_short('d', default=False)\n```\n\n```bash\n# Use short forms\n$ python server.py -h 0.0.0.0 -p 9000 -d\n\n# Or long forms\n$ python server.py --host 0.0.0.0 --port 9000 --debug\n\n# Mix and match\n$ python server.py -h 0.0.0.0 --port 9000 -d\n```\n\n### Boolean Flags\n\nBooleans work as proper CLI flags with negative forms:\n\n```python\n@dataclass\nclass BuildConfig:\n test: bool = True # Default enabled\n deploy: bool = False # Default disabled\n```\n\n```bash\n# Enable a flag\n$ python build.py --deploy\n\n# Disable a flag\n$ python build.py --no-test\n\n# Use defaults (omit flags)\n$ python build.py # test=True, deploy=False\n```\n\nWith short options:\n\n```python\n@dataclass\nclass Config:\n verbose: bool = cli_short('v', default=False)\n debug: bool = cli_short('d', default=False)\n```\n\n```bash\n$ python app.py -v -d # Short flags\n$ python app.py --verbose --debug # Long flags\n$ python app.py --no-verbose # Negative form\n```\n\n### Value Choices\n\nRestrict field values to a valid set:\n\n```python\nfrom dataclass_args import cli_choices\n\n@dataclass\nclass DeployConfig:\n environment: str = cli_choices(['dev', 'staging', 'prod'])\n region: str = cli_choices(['us-east-1', 'us-west-2', 'eu-west-1'], default='us-east-1')\n size: str = cli_choices(['small', 'medium', 'large'], default='medium')\n```\n\n```bash\n# Valid choices\n$ python deploy.py --environment prod --region us-west-2\n\n# Invalid choice shows error\n$ python deploy.py --environment invalid\nerror: argument --environment: invalid choice: 'invalid' (choose from 'dev', 'staging', 'prod')\n```\n\n\n\n### Positional Arguments\n\nAdd positional arguments that don't require `--` prefixes:\n\n```python\nfrom dataclass_args import cli_positional\n\n@dataclass\nclass CopyCommand:\n source: str = cli_positional(help=\"Source file\")\n dest: str = cli_positional(help=\"Destination file\")\n recursive: bool = cli_short('r', default=False)\n```\n\n```bash\n# Positional arguments are matched by position\n$ python cp.py source.txt destination.txt -r\n\n# Optional flags can appear anywhere\n$ python cp.py -r source.txt destination.txt\n```\n\n#### Variable Number of Arguments\n\nUse `nargs` to accept multiple values:\n\n```python\nfrom typing import List\n\n@dataclass\nclass GitCommit:\n command: str = cli_positional(help=\"Git command\")\n files: List[str] = cli_positional(nargs='+', help=\"Files to commit\")\n message: str = cli_short('m', default=\"\")\n\n# CLI: python git.py commit file1.py file2.py file3.py -m \"Add feature\"\n```\n\n**nargs Options:**\n- `None` (default) - Exactly one value (required)\n- `'?'` - Zero or one value (optional)\n- `'*'` - Zero or more values (optional list)\n- `'+'` - One or more values (required list)\n- `int` (e.g., `2`) - Exact count (required list)\n\n#### Optional Positional Arguments\n\n```python\n@dataclass\nclass Convert:\n input_file: str = cli_positional(help=\"Input file\")\n output_file: str = cli_positional(\n nargs='?',\n default='stdout',\n help=\"Output file (default: stdout)\"\n )\n format: str = cli_short('f', default='json')\n```\n\n```bash\n# With output file\n$ python convert.py input.json output.yaml -f yaml\n\n# Without output file (uses default)\n$ python convert.py input.json -f xml\n```\n\n#### \u26a0\ufe0f Positional List Constraints\n\nPositional arguments with variable length have important constraints:\n\n**Rules:**\n1. At most ONE positional field can use `nargs='*'` or `'+'`\n2. If present, the positional list must be the LAST positional argument\n3. For multiple lists, use optional arguments with flags\n\n**Valid:**\n```python\n@dataclass\nclass Valid:\n command: str = cli_positional() # First\n files: List[str] = cli_positional(nargs='+') # Last (OK!)\n exclude: List[str] = cli_short('e', default_factory=list) # Optional list with flag (OK!)\n```\n\n**Invalid:**\n```python\n@dataclass\nclass Invalid:\n files: List[str] = cli_positional(nargs='+') # Positional list\n output: str = cli_positional() # ERROR: positional after list!\n\n# ConfigBuilderError: Positional list argument must be last.\n# Fix: Make output an optional argument with a flag\n```\n\n**Why?** Positional lists are greedy and consume all remaining values. The parser can't determine where one positional list ends and another begins without `--` flags.\n\n### Combining Annotations\n\nUse `combine_annotations()` to merge multiple features:\n\n```python\nfrom dataclass_args import combine_annotations, cli_short, cli_choices, cli_help\n\n@dataclass\nclass AppConfig:\n # Combine short option + help text\n name: str = combine_annotations(\n cli_short('n'),\n cli_help(\"Application name\")\n )\n\n # Combine short + choices + help\n environment: str = combine_annotations(\n cli_short('e'),\n cli_choices(['dev', 'staging', 'prod']),\n cli_help(\"Deployment environment\"),\n default='dev'\n )\n\n # Boolean with short + help\n debug: bool = combine_annotations(\n cli_short('d'),\n cli_help(\"Enable debug mode\"),\n default=False\n )\n```\n\n```bash\n# Concise CLI usage\n$ python app.py -n myapp -e prod -d\n\n# Clear help output\n$ python app.py --help\noptions:\n -n NAME, --name NAME Application name\n -e {dev,staging,prod}, --environment {dev,staging,prod}\n Deployment environment (default: dev)\n -d, --debug Enable debug mode (default: False)\n --no-debug Disable Enable debug mode\n```\n\n### Real-World Example\n\n```python\nfrom dataclasses import dataclass\nfrom dataclass_args import build_config, cli_short, cli_choices, cli_help, combine_annotations\n\n@dataclass\nclass DeploymentConfig:\n \"\"\"Configuration for application deployment.\"\"\"\n\n # Basic settings with short options\n name: str = combine_annotations(\n cli_short('n'),\n cli_help(\"Application name\")\n )\n\n version: str = combine_annotations(\n cli_short('v'),\n cli_help(\"Version to deploy\"),\n default='latest'\n )\n\n # Validated choices\n environment: str = combine_annotations(\n cli_short('e'),\n cli_choices(['dev', 'staging', 'prod']),\n cli_help(\"Target environment\"),\n default='dev'\n )\n\n region: str = combine_annotations(\n cli_short('r'),\n cli_choices(['us-east-1', 'us-west-2', 'eu-west-1']),\n cli_help(\"AWS region\"),\n default='us-east-1'\n )\n\n size: str = combine_annotations(\n cli_short('s'),\n cli_choices(['small', 'medium', 'large', 'xlarge']),\n cli_help(\"Instance size\"),\n default='medium'\n )\n\n # Boolean flags\n dry_run: bool = combine_annotations(\n cli_short('d'),\n cli_help(\"Perform dry run without deploying\"),\n default=False\n )\n\n notify: bool = combine_annotations(\n cli_short('N'),\n cli_help(\"Send deployment notifications\"),\n default=True\n )\n\nif __name__ == \"__main__\":\n config = build_config(DeploymentConfig)\n\n print(f\"Deploying {config.name} v{config.version}\")\n print(f\"Environment: {config.environment}\")\n print(f\"Region: {config.region}\")\n print(f\"Size: {config.size}\")\n print(f\"Dry run: {config.dry_run}\")\n print(f\"Notify: {config.notify}\")\n```\n\n```bash\n# Production deployment\n$ python deploy.py -n myapp -v 2.1.0 -e prod -r us-west-2 -s large\n\n# Dry run in staging\n$ python deploy.py -n myapp -e staging -d --no-notify\n\n# Help shows everything clearly\n$ python deploy.py --help\n```\n\n## Advanced Features\n\n### File-Loadable Parameters\n\nLoad string parameters from files using the `@filename` syntax. Supports home directory expansion with `~`:\n\n```python\nfrom dataclass_args import cli_file_loadable\n\n@dataclass\nclass AppConfig:\n name: str = cli_help(\"Application name\")\n system_prompt: str = cli_file_loadable(default=\"You are a helpful assistant\")\n welcome_message: str = cli_file_loadable()\n\nconfig = build_config(AppConfig)\n```\n\n```bash\n# Use literal values\n$ python app.py --system-prompt \"You are a coding assistant\"\n\n# Load from files (absolute paths)\n$ python app.py --system-prompt \"@/etc/prompts/assistant.txt\"\n\n# Load from home directory\n$ python app.py --system-prompt \"@~/prompts/assistant.txt\"\n\n# Load from another user's home\n$ python app.py --system-prompt \"@~alice/shared/prompt.txt\"\n\n# Load from relative paths\n$ python app.py --welcome-message \"@messages/welcome.txt\"\n\n# Mix literal and file-loaded values\n$ python app.py --name \"MyApp\" --system-prompt \"@~/prompts/assistant.txt\"\n```\n\n**Path Expansion:**\n- `@~/file.txt` \u2192 Expands to user's home directory (e.g., `/home/user/file.txt`)\n- `@~username/file.txt` \u2192 Expands to specified user's home directory\n- `@/absolute/path` \u2192 Used as-is\n- `@relative/path` \u2192 Relative to current working directory\n\n# config.yaml\nname: \"DefaultApp\"\ncount: 100\ndatabase:\n host: \"localhost\"\n port: 5432\n timeout: 30\n```\n\n```python\n@dataclass\nclass DatabaseConfig:\n host: str = \"localhost\"\n port: int = 5432\n timeout: float = 30.0\n\n@dataclass\nclass AppConfig:\n name: str\n count: int = 10\n database: Dict[str, Any] = None\n\nconfig = build_config_from_cli(AppConfig, [\n '--config', 'config.yaml', # Load base configuration\n '--name', 'OverriddenApp', # Override name\n '--database', 'db.json', # Load additional database config\n '--d', 'timeout:60' # Override database.timeout property\n])\n```\n\n### Custom Help and Annotations\n\n```python\nfrom dataclass_args import cli_help, cli_exclude, cli_file_loadable\n\n@dataclass\nclass ServerConfig:\n # Custom help text\n host: str = cli_help(\"Server bind address\", default=\"127.0.0.1\")\n port: int = cli_help(\"Server port number\", default=8000)\n\n # File-loadable with help\n ssl_cert: str = cli_file_loadable(cli_help(\"SSL certificate content\"))\n\n # Hidden from CLI\n secret_key: str = cli_exclude(default=\"auto-generated\")\n\n # Multiple values\n allowed_hosts: List[str] = cli_help(\"Allowed host headers\", default_factory=list)\n```\n\n### Complex Types and Validation\n\n```python\nfrom typing import List, Dict, Optional\nfrom pathlib import Path\n\n@dataclass\nclass MLConfig:\n # Basic types\n model_name: str = cli_help(\"Model identifier\")\n learning_rate: float = cli_help(\"Learning rate\", default=0.001)\n epochs: int = cli_help(\"Training epochs\", default=100)\n\n # Complex types\n layer_sizes: List[int] = cli_help(\"Neural network layer sizes\", default_factory=lambda: [128, 64])\n hyperparameters: Dict[str, Any] = cli_help(\"Model hyperparameters\")\n\n # Optional types\n checkpoint_path: Optional[Path] = cli_help(\"Path to model checkpoint\")\n\n # File-loadable configurations\n training_config: str = cli_file_loadable(cli_help(\"Training configuration\"))\n\n def __post_init__(self):\n # Custom validation\n if self.learning_rate <= 0:\n raise ValueError(\"Learning rate must be positive\")\n if self.epochs <= 0:\n raise ValueError(\"Epochs must be positive\")\n```\n\n## API Reference\n\n> **\ud83d\udcd6 Full API Documentation:** See [docs/API.md](docs/API.md) for complete API reference with detailed examples.\n\n### Quick API Reference\n\n### Main Functions\n\n#### `build_config(config_class, args=None)`\n\nGenerate CLI from dataclass and parse arguments.\n\n```python\nconfig = build_config(MyDataclass) # Uses sys.argv automatically\n```\n\n#### `build_config_from_cli(config_class, args=None, **options)`\n\nGenerate CLI with additional options.\n\n```python\nconfig = build_config_from_cli(\n MyDataclass,\n args=['--name', 'test'],\n```\n\n### Annotations\n\n#### `cli_short(letter, **kwargs)`\n\nAdd a short option flag to a field.\n\n```python\nfield: str = cli_short('f', default=\"value\")\n\n# Or combine with other annotations\nfield: str = combine_annotations(\n cli_short('f'),\n cli_help(\"Help text\"),\n default=\"value\"\n)\n```\n\n#### `cli_choices(choices_list, **kwargs)`\n\nRestrict field to a set of valid choices.\n\n```python\nenv: str = cli_choices(['dev', 'prod'], default='dev')\n\n# Or combine\nenv: str = combine_annotations(\n cli_short('e'),\n cli_choices(['dev', 'prod']),\n cli_help(\"Environment\"),\n default='dev'\n)\n```\n\n#### `cli_help(help_text, **kwargs)`\n\nAdd custom help text to CLI arguments.\n\n```python\nfield: str = cli_help(\"Custom help text\", default=\"default_value\")\n```\n\n\n\n#### `cli_positional(nargs=None, metavar=None, **kwargs)`\n\nMark a field as a positional CLI argument (no `--` prefix required).\n\n```python\n# Required positional\nsource: str = cli_positional(help=\"Source file\")\n\n# Optional positional\noutput: str = cli_positional(nargs='?', default='stdout')\n\n# Variable number (list)\nfiles: List[str] = cli_positional(nargs='+', help=\"Files\")\n\n# Exact count\ncoords: List[float] = cli_positional(nargs=2, metavar='X Y')\n\n# Combined with other annotations\ninput: str = combine_annotations(\n cli_positional(),\n cli_help(\"Input file path\")\n)\n```\n\n**Important:** At most one positional can use `nargs='*'` or `'+'`, and it must be the last positional.\n\n#### `cli_exclude(**kwargs)`\n\nExclude fields from CLI argument generation.\n\n```python\ninternal_field: str = cli_exclude(default=\"hidden\")\n```\n\n#### `cli_file_loadable(**kwargs)`\n\nMark string fields as file-loadable via '@filename' syntax.\n\n```python\ncontent: str = cli_file_loadable(default=\"default content\")\n```\n\n#### `combine_annotations(*annotations, **kwargs)`\n\nCombine multiple annotations on a single field.\n\n```python\nfield: str = combine_annotations(\n cli_short('f'),\n cli_choices(['a', 'b', 'c']),\n cli_help(\"Description\"),\n default='a'\n)\n```\n\n## Type Support\n\nDataclass CLI supports standard Python types:\n\n| Type | CLI Behavior | Example |\n|------|--------------|---------|\n| `str` | Direct string value | `--name \"hello\"` |\n| `int` | Parsed as integer | `--count 42` |\n| `float` | Parsed as float | `--rate 0.1` |\n| `bool` | Flag with negative | `--debug` or `--no-debug` |\n| `List[T]` | Multiple values | `--items a b c` |\n| `Dict[str, Any]` | Config file + overrides | `--config file.json --c key:value` |\n| `Optional[T]` | Optional parameter | `--timeout 30` (or omit) |\n| `Path` | Path object | `--output /path/to/file` |\n| Custom types | String representation | `--custom \"value\"` |\n\n## Configuration File Formats\n\nSupports multiple configuration file formats:\n\n### JSON\n```json\n{\n \"name\": \"MyApp\",\n \"count\": 42,\n \"database\": {\n \"host\": \"localhost\",\n \"port\": 5432\n }\n}\n```\n\n### YAML (requires `pip install \"dataclass-args[yaml]\"`)\n```yaml\nname: MyApp\ncount: 42\ndatabase:\n host: localhost\n port: 5432\n```\n\n### TOML (requires `pip install \"dataclass-args[toml]\"`)\n```toml\nname = \"MyApp\"\ncount = 42\n\n[database]\nhost = \"localhost\"\nport = 5432\n```\n\n## Examples\n\nCheck the [`examples/`](examples/) directory for complete working examples:\n\n- **`positional_example.py`** - Positional arguments and variable length args\n- **`boolean_flags_example.py`** - Boolean flags with `--flag` and `--no-flag`\n- **`cli_choices_example.py`** - Value validation with choices\n- **`cli_short_example.py`** - Short option flags\n- **`all_features_example.py`** - All features together\n- And more...\n\n### Web Server Configuration\n\n```python\nfrom dataclasses import dataclass\nfrom typing import List\nfrom dataclass_args import build_config, cli_short, cli_help, cli_exclude, cli_file_loadable, combine_annotations\n\n@dataclass\nclass ServerConfig:\n # Basic server settings\n host: str = combine_annotations(\n cli_short('h'),\n cli_help(\"Server bind address\"),\n default=\"127.0.0.1\"\n )\n\n port: int = combine_annotations(\n cli_short('p'),\n cli_help(\"Server port number\"),\n default=8000\n )\n\n workers: int = combine_annotations(\n cli_short('w'),\n cli_help(\"Number of worker processes\"),\n default=1\n )\n\n # Security settings\n ssl_cert: str = cli_file_loadable(cli_help(\"SSL certificate content\"))\n ssl_key: str = cli_file_loadable(cli_help(\"SSL private key content\"))\n\n # Application settings\n debug: bool = combine_annotations(\n cli_short('d'),\n cli_help(\"Enable debug mode\"),\n default=False\n )\n\n allowed_hosts: List[str] = cli_help(\"Allowed host headers\", default_factory=list)\n\n # Internal fields (hidden from CLI)\n _server_id: str = cli_exclude(default_factory=lambda: f\"server-{os.getpid()}\")\n\nif __name__ == \"__main__\":\n config = build_config(ServerConfig)\n print(f\"Starting server on {config.host}:{config.port}\")\n```\n\n```bash\n# Start server with short options\n$ python server.py -h 0.0.0.0 -p 9000 -w 4 -d\n\n# Load SSL certificates from files\n$ python server.py --ssl-cert \"@certs/server.crt\" --ssl-key \"@certs/server.key\"\n```\n\n## Contributing\n\nContributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n### Development Setup\n\n```bash\ngit clone https://github.com/bassmanitram/dataclass-args.git\ncd dataclass-args\npip install -e \".[dev,all]\"\n```\n\n### Development Setup\n\n```bash\ngit clone https://github.com/bassmanitram/dataclass-args.git\ncd dataclass-args\npip install -e \".[dev,all]\"\nmake setup # Install dev dependencies and pre-commit hooks\n```\n\n### Running Tests\n\n```bash\n# Run all tests (coverage is automatic)\npytest\nmake test\n\n# Run tests with detailed coverage report\nmake coverage\n\n# Run tests with coverage and open HTML report\nmake coverage-html\n\n# Run specific test file\npytest tests/test_cli_short.py\n\n# Verbose output\npytest -v\n```\n\n### Code Coverage\n\nThis project maintains **94%+ code coverage**. Coverage reports are generated automatically when running tests.\n\n- **Quick check**: `make coverage`\n- **Detailed report**: See `htmlcov/index.html`\n- **Coverage docs**: [COVERAGE.md](COVERAGE.md)\n\nAll code changes should maintain or improve coverage. The minimum required coverage is 90%.\n\n### Code Formatting\n\n```bash\n# Format code\nmake format\nblack dataclass_args/ tests/ examples/\nisort dataclass_args/ tests/ examples/\n\n# Check formatting\nmake lint\nblack --check dataclass_args/ tests/\nflake8 dataclass_args/ tests/\nmypy dataclass_args/\n```\n\n### Full Check (like CI)\n\n```bash\n# Run all checks: linting, tests, and examples\nmake check\n```\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history and changes.\n\n## Support\n\n- **Issues**: [GitHub Issues](https://github.com/bassmanitram/dataclass-args/issues)\n- **Documentation**: This README and comprehensive docstrings\n- **Examples**: See the [examples/](examples/) directory\n\n## Quick Reference\n\n```python\nfrom dataclasses import dataclass\nfrom dataclass_args import (\n build_config, # Main function\n cli_short, # Short options: -n\n cli_positional, # Positional args\n cli_choices, # Value validation\n cli_help, # Custom help text\n cli_exclude, # Hide from CLI\n cli_file_loadable, # @file loading\n combine_annotations, # Combine features\n)\n\n@dataclass\nclass Config:\n # Simple field\n name: str\n\n # Positional argument\n input_file: str = cli_positional()\n\n # With short option\n port: int = cli_short('p', default=8000)\n\n # With choices\n env: str = cli_choices(['dev', 'prod'], default='dev')\n\n # Boolean flag\n debug: bool = False # Creates --debug and --no-debug\n\n # Combine everything\n region: str = combine_annotations(\n cli_short('r'),\n cli_choices(['us-east-1', 'us-west-2']),\n cli_help(\"AWS region\"),\n default='us-east-1'\n )\n\n # Hidden from CLI\n secret: str = cli_exclude(default=\"hidden\")\n\n # File-loadable\n config_text: str = cli_file_loadable(default=\"\")\n\n# Build and use\nconfig = build_config(Config)\n```\n\nDefine your dataclass, add annotations as needed, and call `build_config()` to parse command-line arguments.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Zero-boilerplate CLI generation from Python dataclasses with advanced type support and file loading",
"version": "1.1.0",
"project_urls": {
"Documentation": "https://github.com/bassmanitram/dataclass-args#readme",
"Homepage": "https://github.com/bassmanitram/dataclass-args",
"Issues": "https://github.com/bassmanitram/dataclass-args/issues",
"Repository": "https://github.com/bassmanitram/dataclass-args"
},
"split_keywords": [
"cli",
" dataclass",
" argparse",
" configuration",
" command-line",
" type-safe",
" validation",
" file-loading",
" config-merging"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "5f03ac2ff57ebca8fbebfc407eae10fb180988f6c266c33dea6cf25a9d48d7b3",
"md5": "e31851cbd89410a06f95b4847d6087d4",
"sha256": "3e5ed7888e14f429c827a416ce68f1fb3b635262a3a05fc714d35d7da531b6e1"
},
"downloads": -1,
"filename": "dataclass_args-1.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "e31851cbd89410a06f95b4847d6087d4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 22921,
"upload_time": "2025-11-03T18:25:57",
"upload_time_iso_8601": "2025-11-03T18:25:57.001932Z",
"url": "https://files.pythonhosted.org/packages/5f/03/ac2ff57ebca8fbebfc407eae10fb180988f6c266c33dea6cf25a9d48d7b3/dataclass_args-1.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "7a80d29499a1952b5e393b9fe66cf9f07605c48547ce517af617c1d6a92ce9e6",
"md5": "5ce9f1215cf339bd2a52224304f7e72e",
"sha256": "9aa15a485c5b36efe15cda64b09d9a0144a79808f8163305a00ab517f7b66052"
},
"downloads": -1,
"filename": "dataclass_args-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "5ce9f1215cf339bd2a52224304f7e72e",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 47666,
"upload_time": "2025-11-03T18:25:58",
"upload_time_iso_8601": "2025-11-03T18:25:58.146345Z",
"url": "https://files.pythonhosted.org/packages/7a/80/d29499a1952b5e393b9fe66cf9f07605c48547ce517af617c1d6a92ce9e6/dataclass_args-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-11-03 18:25:58",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "bassmanitram",
"github_project": "dataclass-args#readme",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "dataclass-args"
}