Automatically generate CLI from Pydantic models
Project description
pydantic-autocli
Automatically generate sub-command based CLI applications from Pydantic models.
Installation
pip install pydantic-autocli
Features
- Automatically generate CLI commands from class methods
- Map Pydantic model fields to CLI arguments
- Customize CLI arguments with short/long forms and other options
- Automatically handle help text generation
- Support for common arguments across all commands
- Support for async commands
- Support for array arguments (list[str], list[int], list[float], etc.)
- Default command support (
run_default) - Per-subcommand
--helpoption
Requires Pydantic v2.
Basic Usage
from pydantic import BaseModel
from pydantic_autocli import AutoCLI, param
class MyCLI(AutoCLI):
# Default command: runs when no subcommand is provided
class DefaultArgs(BaseModel):
message: str = param("Hello", l="--message", s="-m")
def run_default(self, args: DefaultArgs):
print(args.message)
# Subcommand: `python script.py greet`
class GreetArgs(BaseModel):
name: str = param("World", l="--name", s="-n")
count: int = param(1, l="--count", s="-c")
def run_greet(self, args: GreetArgs):
for _ in range(args.count):
print(f"Hello, {args.name}!")
if __name__ == "__main__":
MyCLI().run()
# Default command
python script.py # prints "Hello"
python script.py --message "Hi" # prints "Hi"
# Subcommand
python script.py greet --name Alice # prints "Hello, Alice!"
python script.py greet -n Bob -c 3 # prints "Hello, Bob!" 3 times
# Help
python script.py --help # shows default command help
python script.py greet --help # shows greet command help
Note: --help is reserved and cannot be used as a field name.
Advanced Usage
from pydantic import Field
from pydantic_autocli import AutoCLI, param
class MyCLI(AutoCLI):
# Common arguments for all commands and act as a fallback
class CommonArgs(AutoCLI.CommonArgs):
# `param` `param()` is syntax sugar for `Field()`
verbose: bool = param(False, l="--verbose", s="-v", description="Enable detailed output")
# Field can also be used
seed: int = Field(42, json_schema_extra={"l": "--seed"})
# Executed commonly for all subcommands
def prepare(self, args:CommonArgs):
print(f'Using seed: {args.seed}')
class VeryAdvancedArgs(CommonArgs):
# file_name becomes --file-name in command line
file_name: str = param(..., l="--name", pattern=r"^[a-zA-Z]+\.(txt|json|yaml)$")
# Restrict choices
mode: str = param("read", l="--mode", choices=["read", "write", "append"])
# You can use float, too
wait: float = Field(0.5, json_schema_extra={"l": "--wait", "s": "-w"})
# This will be triggered by `python xxx.py very-advanced` command
# Args class selection rule: run_very_advanced -> VeryAdvancedArgs (by naming convention)
# This is an async method that can be awaited
async def run_very_advanced(self, args):
print(f"File name: {args.file_name}")
print(f"Mode: {args.mode}")
print(f"Waiting for {args.wait}s..")
await asyncio.sleep(args.wait)
if args.verbose:
print("Verbose mode enabled")
if not os.path.exists(args.file_name):
return False # Indicates error (exit code 1)
return True # Indicates success (exit code 0)
# Also supports custom exit codes
# return 423
if __name__ == "__main__":
cli = MyCLI()
# Uses sys.argv by default
cli.run()
# Explicitly pass sys.argv
cli.run(sys.argv)
# Pass custom arguments
cli.run(["program_name", "command", "--value", "value1", "--flag"])
param passes all CLI-specific options (like s for short form, l for long form) to Field's json_schema_extra. All other options (like ge, le, gt, lt, min_length, max_length, pattern) are passed directly to Field for validation.
# Run very-advanced command
python your_script.py very-advanced --file-name data.txt --mode write --wait 1.5 --verbose
Argument Resolution
Using Type Annotations
You can directly specify the argument class using type annotations:
from pydantic import BaseModel
from pydantic_autocli import AutoCLI, param
class MyCLI(AutoCLI):
class CustomArgs(AutoCLI.CommonArgs):
value: int = param(42, l="--value", s="-v")
# Use type annotation to specify args class
def run_command(self, args: CustomArgs):
print(f"Value: {args.value}")
Using Naming Convention
You can specify argument classes for CLI commands using naming conventions:
class MyCLI(AutoCLI):
# Naming convention:
# run_command → CommandArgs
# run_foo_bar → FooBarArgs
# Single-word command example
class CommandArgs(AutoCLI.CommonArgs):
name: str = param("default", l="--name", s="-n")
def run_command(self, args):
print(f"Name: {args.name}")
# Two-word command example
class FooBarArgs(AutoCLI.CommonArgs):
option: str = param("default", l="--option")
def run_foo_bar(self, args):
print(f"Option: {args.option}")
Resolution Priority
pydantic-autocli uses the following priority order to determine which argument class to use:
- Type annotation on the method parameter
- Naming convention (CommandArgs class for run_command method)
- Fall back to CommonArgs
When both naming convention and type annotation could apply to a method, the type annotation takes precedence (as per the priority above). In such cases, a warning is displayed about the conflict:
class MyCLI(AutoCLI):
# Args class that follows naming convention
class CommandArgs(BaseModel):
name: str = param("default", l="--name")
# Different args class specified by type annotation
class CustomArgs(BaseModel):
value: int = param(42, l="--value")
# Type annotation takes precedence over naming convention
# A warning will be displayed about the conflict
def run_command(self, args: CustomArgs):
# Uses CustomArgs even though CommandArgs exists
print(f"Value: {args.value}")
return True
This command will use CustomArgs (from type annotation) instead of CommandArgs (from naming convention), with a warning about the detected conflict.
Development and Testing
# Install all dependencies
uv sync
# Run tests
uv run pytest
# Run tests with coverage
uv run task coverage
Examples
To run the example CLI:
python examples/example.py greet --verbose
# Or using taskipy
uv run task example file --file README.md
Claude Code Integration
Add to your project's CLAUDE.md:
## AutoCLI Usage
- `def run_foo_bar(self, a: FooBarArgs)` → `script.py foo-bar`
- `def run_default(self, a: DefaultArgs)` → `script.py` (no subcommand)
- `class CommonArgs` → shared arguments across all commands
- `def prepare(self, a: CommonArgs)` → runs before every command
- Return `True`/`None` (exit 0), `False` (exit 1), `int` (custom exit code)
For details: `script.py --help` or `script.py <command> --help`
License
See LICENSE file.
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 pydantic_autocli-0.3.1.tar.gz.
File metadata
- Download URL: pydantic_autocli-0.3.1.tar.gz
- Upload date:
- Size: 69.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47ca2f10d2af013b1f34071b52c74b073fef9218dec018027693acbd7325ef16
|
|
| MD5 |
380e2d87fc47f44f57bf2b98458122f1
|
|
| BLAKE2b-256 |
50da61cb3c71f56e4c86a60f40f81d679901ec4a87f1a595f7bb3e68a9bda258
|
File details
Details for the file pydantic_autocli-0.3.1-py3-none-any.whl.
File metadata
- Download URL: pydantic_autocli-0.3.1-py3-none-any.whl
- Upload date:
- Size: 12.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65bad5b4bec89ec505ed56103481c0da6817a39e82dfad1493652ee23a8a63e2
|
|
| MD5 |
0483c6157e9a0a86ee152fb94bc2defe
|
|
| BLAKE2b-256 |
a33a4b229d1b7101c42ea5f77d76fecc848a03fcea9b3a4c4302271c68b35343
|