Runpy automatically converts your Python functions into command-line interfaces (CLIs) with minimal code changes. Simply decorate your functions, and Runpy generates a fully-featured CLI with argument parsing, help text, and type validation.
- π Zero Configuration: Convert functions to CLI commands with a single decorator
- π Automatic Help Generation: Docstrings become help text automatically
- π Type Validation: Automatic type checking based on type hints
- π Rich Output: Support for structured output (JSON, tables, etc.)
- ποΈ Command Groups: Organize commands in hierarchical groups
- π§ Flexible Configuration: YAML/JSON config file support
- π¦ Pydantic Integration: Full support for Pydantic models as parameters
- π Multiple Input Formats: Accept JSON, Python dict, and TypeScript object notation
- π¨ Customizable: Extensive customization options for advanced use cases
pip install runpycli
Create a file mycli.py
:
from runpycli import Runpy
# Create a Runpy instance
cli = Runpy()
# Register functions as commands
@cli.register
def hello(name: str = "World") -> str:
"""Say hello to someone"""
return f"Hello, {name}!"
@cli.register
def add(x: int, y: int) -> int:
"""Add two numbers"""
return x + y
if __name__ == "__main__":
cli.app()
Run your CLI:
python mycli.py hello --name Alice
# Output: Hello, Alice!
python mycli.py add --x 5 --y 3
# Output: 8
from pydantic import BaseModel, Field
from typing import List
class UserInput(BaseModel):
"""User information"""
name: str = Field(..., description="User's full name")
age: int = Field(..., ge=0, le=150, description="User's age")
emails: List[str] = Field(default_factory=list, description="Email addresses")
@cli.register
def create_user(user: UserInput) -> dict:
"""Create a new user from the provided data"""
return {"status": "created", "user": user.model_dump()}
Usage with multiple input formats:
# JSON format (standard)
python mycli.py create-user --user '{"name": "John Doe", "age": 30, "emails": ["john@example.com"]}'
# Python dict format
python mycli.py create-user --user "{'name': 'John Doe', 'age': 30, 'emails': ['john@example.com']}"
# TypeScript/JavaScript object format
python mycli.py create-user --user '{name: "John Doe", age: 30, emails: ["john@example.com"]}'
# Create command groups
cli = Runpy(name="myapp/db")
@cli.register
def migrate():
"""Run database migrations"""
pass
@cli.register
def seed():
"""Seed the database"""
pass
Create a config.json
:
{
"defaults": {
"environment": "development",
"debug": true
},
"shortcuts": {
"env": "e",
"debug": "d"
}
}
Use in your CLI:
cli = Runpy(config_file="config.json")
@cli.register
def deploy(environment: str, debug: bool = False):
"""Deploy the application"""
print(f"Deploying to {environment} (debug: {debug})")
import math
# Register all public functions from a module
cli.register_module(math)
# Or selectively register functions
cli.register(math.sin)
cli.register(math.cos)
Runpy automatically adds two special commands:
View detailed documentation for any command:
python mycli.py docs create-user
Generate OpenAPI-style schema documentation:
python mycli.py schema
python mycli.py schema --json # JSON output
python mycli.py schema --save api-docs.json # Save to file
Runpy supports a wide range of Python types:
- Basic types:
str
,int
,float
,bool
- Container types:
List[T]
,Dict[K, V]
,Set[T]
,Tuple[T, ...]
- Optional types:
Optional[T]
,Union[T1, T2]
- Pydantic models: Any class inheriting from
pydantic.BaseModel
- Enums:
Enum
subclasses - File types:
Path
,FilePath
,DirectoryPath
Boolean parameters are handled as regular options, not flags:
# Correct usage
python mycli.py command --bool-param true
python mycli.py command --bool-param false
# NOT as flags (this is not supported)
python mycli.py command --bool-param # β
Parameters with default values (including None
) are automatically optional:
def process(
required_param: str, # Required: must provide --required-param
optional_str: Optional[str] = None, # Optional: can omit
optional_int: Optional[int] = None, # Optional: can omit
optional_with_default: str = "default" # Optional: uses default if omitted
):
pass
- Use Type Hints: Always add type hints to get automatic type validation
- Write Docstrings: Function and parameter docstrings become help text
- Set Defaults: Default values make parameters optional
- Return Values: Return values are automatically displayed
- Use Pydantic: For complex inputs, Pydantic models provide validation
from runpycli import Runpy
import math
cli = Runpy(name="calc", version="1.0.0")
cli.register(math.sin)
cli.register(math.cos)
cli.register(math.sqrt)
@cli.register
def divide(x: float, y: float) -> float:
"""Divide x by y"""
if y == 0:
raise ValueError("Cannot divide by zero")
return x / y
if __name__ == "__main__":
cli.app()
from runpycli import Runpy
from pydantic import BaseModel
from typing import List, Optional
from datetime import datetime
cli = Runpy(name="tasks")
class Task(BaseModel):
title: str
description: Optional[str] = None
due_date: Optional[datetime] = None
tags: List[str] = []
tasks_db = []
@cli.register
def add(task: Task) -> dict:
"""Add a new task"""
task_dict = task.model_dump()
task_dict["id"] = len(tasks_db) + 1
tasks_db.append(task_dict)
return task_dict
@cli.register
def list_tasks(tag: Optional[str] = None) -> List[dict]:
"""List all tasks, optionally filtered by tag"""
if tag:
return [t for t in tasks_db if tag in t.get("tags", [])]
return tasks_db
if __name__ == "__main__":
cli.app()
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.