Skip to main content

pydantic-autocli

CI codecov

Automatically generate sub-command based CLI applications from Pydantic models.

Installation

pip install pydantic-autocli

Features

  • Automatic CLI generation from Pydantic models
  • Type-safe argument parsing with validation
  • Async command support
  • Default command (run_default) and subcommands
  • Nested command groups (AutoCLI.mount)
  • Positional and remainder arguments (--) via ExtraArgsMixin

Requires Python 3.10+ and 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

Common Arguments and Initialization

from pydantic_autocli import AutoCLI, param

class MyCLI(AutoCLI):
    # Shared arguments across all commands
    class CommonArgs(AutoCLI.CommonArgs):
        verbose: bool = param(False, l="--verbose", s="-v")

    # Runs before every command
    def prepare(self, args: CommonArgs):
        if args.verbose:
            print("Verbose mode enabled")

    def run_task(self, args: CommonArgs):
        print("Running task...")

Parameter Options

class TaskArgs(AutoCLI.CommonArgs):
    # Required argument (no default)
    name: str = param(..., l="--name", s="-n")
    # Choices
    mode: str = param("read", l="--mode", choices=["read", "write"])
    # Validation
    count: int = param(1, l="--count", ge=1, le=100)
    pattern: str = param(".*", l="--pattern", pattern=r"^[a-z]+$")

Supported Types

Annotation CLI option
str / int / float --opt VALUE
Path, datetime, ... (parsed from a string or number) --opt VALUE
bool flag --opt (defaults to False)
Literal["a", "b"] / Enum --opt VALUE with choices (an Enum takes its values)
list[X] --opt V1 V2 ...
Optional[X] / X | None same as X; None when omitted
  • A bool option is a flag that defaults to False. A default of True is an error, because the flag could never be turned off. Use a separate negative field instead (e.g. no_cache: bool = False).
  • Other unions (e.g. int | str) and unsupported types raise an error when the CLI is constructed.

Unknown Arguments

Arguments a command does not accept are errors (exit code 2), e.g. script.py greet --nmae foo → error: unrecognized arguments: --nmae foo. Positional arguments and arguments after -- are accepted only when the args class uses ExtraArgsMixin (see below); unknown options are errors even then.

Async Commands and Return Values

async def run_fetch(self, args):
    await asyncio.sleep(1)
    if error:
        return False  # exit code 1
    return True       # exit code 0
    # return 42       # custom exit code

Positional and Remainder Arguments (ExtraArgsMixin)

Use ExtraArgsMixin to capture positional arguments and arguments after --:

from pydantic import BaseModel
from pydantic_autocli import AutoCLI, param, ExtraArgsMixin

class MyCLI(AutoCLI):
    class RunArgs(ExtraArgsMixin, BaseModel):
        verbose: bool = param(False, l="--verbose", s="-v")

    def run_exec(self, args: RunArgs):
        # Positional arguments (before --)
        files = args.get_positional()  # ['file1.py', 'file2.py']

        # Remainder arguments (after --)
        cmd = args.get_remainder()       # 'python -m pytest --tb=short'
        cmd_list = args.get_remainder_list()  # ['python', '-m', 'pytest', '--tb=short']

        print(f"Files: {files}")
        print(f"Command: {cmd}")

if __name__ == "__main__":
    MyCLI().run()
# Positional args and remainder after --
python script.py exec file1.py file2.py -- python -m pytest --tb=short

# Only remainder
python script.py exec -- nested-command --option value

Note: For the default command (run_default), get_positional() always returns an empty list to avoid ambiguity with subcommand names. Use get_remainder() instead.

Command Groups (Nested Subcommands)

Assign another AutoCLI class to a run_* attribute to mount it as a command group.

class DbCLI(AutoCLI):
    """Database commands"""

    class MigrateArgs(BaseModel):
        dry: bool = param(False, l="--dry")

    def run_migrate(self, args: MigrateArgs):
        print(self.parent.verbose)  # the mounting CLI is available as self.parent

class MyCLI(AutoCLI):
    class CommonArgs(AutoCLI.CommonArgs):
        verbose: bool = param(False, s="-v")

    def prepare(self, args):
        self.verbose = args.verbose

    run_db = AutoCLI.mount(DbCLI, common=True)  # script.py db migrate
    run_cache = CacheCLI                        # same as AutoCLI.mount(CacheCLI)
python script.py db migrate --dry -v
python script.py db --help
  • common=False (default): the group is independent. The parent's prepare() is not called.
  • common=True: the parent's CommonArgs options are accepted by every command in the group, and the parent's prepare() runs before the group's prepare(). A field name that conflicts with the parent's CommonArgs raises an error (subclassing the parent's CommonArgs is fine).
  • Groups can be nested. Each mounted CLI still works on its own with DbCLI().run().

Argument Resolution

Args class is resolved in this order:

  1. Type annotation: def run_cmd(self, args: MyArgs) → uses MyArgs
  2. Naming convention: run_foo_bar → looks for FooBarArgs
  3. Fallback: uses CommonArgs
class MyCLI(AutoCLI):
    class FooBarArgs(BaseModel):
        option: str = param("x", l="--option")

    # Uses FooBarArgs by naming convention
    def run_foo_bar(self, args):
        print(args.option)

Development

uv sync                  # Install dependencies
uv run task test         # Run tests
uv run task coverage     # Run tests with coverage
uv run task lint         # Lint code
uv run task example      # Run example CLI

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)
- `run_db = AutoCLI.mount(DbCLI, common=True)` → `script.py db <command>` (nested group)
- `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.

Metadata

Release files for pydantic-autocli 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pydantic-autocli 0.4.0
File Size Uploaded
pydantic_autocli-0.4.0.tar.gz 65.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pydantic-autocli 0.4.0
File Interpreter ABI Platform
pydantic_autocli-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 80.6 kB

Release files / pydantic_autocli-0.4.0.tar.gz

Download URL pydantic_autocli-0.4.0.tar.gz
Size 65.3 kB
Tags Source
SHA-256 checksum
How to use checksums
21ad0518423b34526c377bcdf2cd2205a6c392cf5e688993e3ae2f9cc2149a79
BLAKE2b-256 checksum
How to use checksums
a17c9a1afc77c8cdef30402fe418be5c978c7dd6d2b86bef53d7fcafd0943782
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / pydantic_autocli-0.4.0-py3-none-any.whl

Download URL pydantic_autocli-0.4.0-py3-none-any.whl
Size 15.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
16ba4c22859a8874c4501e1877c7713fd50a528bcb9af369746e9b6dd2139b1b
BLAKE2b-256 checksum
How to use checksums
419ae6c020f148cdcd3a5a63889d245d96cbc97e0e9a8782ad96ca0beb8ccafe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page