Skip to main content

Automatically generate CLI from Pydantic models

Project description

pydantic-autocli

CI codecov

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 --help option

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:

  1. Type annotation on the method parameter
  2. Naming convention (CommandArgs class for run_command method)
  3. 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

If you're using Claude Code with your pydantic-autocli application, add this section to your project's CLAUDE.md:

## AutoCLI Usage

Key patterns:
- `def run_foo_bar(self, args):``python script.py foo-bar`
- `def prepare(self, args):` → shared initialization  
- `class FooBarArgs(AutoCLI.CommonArgs):` → command arguments
- Return `True`/`None` (success), `False` (fail), `int` (exit code)

For details: `python your_script.py --help`

License

See LICENSE file.

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

pydantic_autocli-0.3.0.tar.gz (68.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pydantic_autocli-0.3.0-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

File details

Details for the file pydantic_autocli-0.3.0.tar.gz.

File metadata

  • Download URL: pydantic_autocli-0.3.0.tar.gz
  • Upload date:
  • Size: 68.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for pydantic_autocli-0.3.0.tar.gz
Algorithm Hash digest
SHA256 9373239fb56d5750ff5078bd4108babe0f22d0f51a89065b88539871ad2f6a2d
MD5 9a88d8a720e5a0d0ef82e1cefb941982
BLAKE2b-256 4919045491b9ff6cf950efed334dd3b1fb642ce5eaa9317375293a36acb80407

See more details on using hashes here.

File details

Details for the file pydantic_autocli-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_autocli-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4a6e104dedaba583526b853386773a22d4d0b7da772d8ba0850ab299b0547cc2
MD5 3c8f1ddf81e7757bf79af92b6dddde22
BLAKE2b-256 46feb4b1e645111dd1c3dbd24dba1490c57e2fd0e389720bf332d0029074b900

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