A Django-like command system with Pydantic model integration for automatic argparse generation
Project description
Pydantic Commands
A Django-like command system with Pydantic model integration for automatic argparse generation.
Features
- 🎯 Django-inspired API: Familiar command structure similar to Django's management commands
- 🔒 Type-Safe: Leverage Pydantic models for automatic validation and type conversion
- 🚀 Auto-generated argparse: Convert Pydantic models to argparse arguments automatically
- 🎨 Flexible: Support both class-based and function-based (decorator) commands
- 📦 Registry System: Built on
any-registriesfor pluggable command architecture - ✅ Rich Type Support: Handles strings, integers, floats, booleans, enums, lists, Path objects, and more
Installation
pip install pydantic-commands
Quick Start
Class-Based Commands
from pydantic import BaseModel, Field
from pydantic_commands import BaseCommand
class CreateUserCommand(BaseCommand):
"""Create a new user in the system."""
name = "createuser"
help = "Create a new user"
class Arguments(BaseModel):
username: str = Field(..., description="Username for the new user")
email: str = Field(..., description="Email address")
age: int = Field(default=18, description="User age")
is_admin: bool = Field(default=False, description="Admin privileges")
def handle(self, args: Arguments) -> None:
print(f"Creating user: {args.username}")
print(f"Email: {args.email}")
print(f"Age: {args.age}")
print(f"Admin: {args.is_admin}")
Function-Based Commands (Decorator)
from pydantic import BaseModel
from pydantic_commands import command
class GreetArgs(BaseModel):
name: str
greeting: str = "Hello"
enthusiastic: bool = False
@command(name="greet", help="Greet someone", arguments=GreetArgs)
def greet_command(args: GreetArgs) -> None:
message = f"{args.greeting}, {args.name}!"
if args.enthusiastic:
message = message.upper() + "!!!"
print(message)
# The decorator automatically creates a command class
# and registers it in the command registry
CLI Application Entry Point (host_cli)
The simplest way to create a CLI application is using host_cli(), similar to Django's manage.py:
# cli.py
from pydantic import BaseModel, Field
from pydantic_commands import BaseCommand, command_registry, host_cli
class CreateUserCommand(BaseCommand):
name = "createuser"
help = "Create a new user"
class Arguments(BaseModel):
username: str = Field(..., description="Username")
email: str = Field(..., description="Email address")
def handle(self, args: Arguments) -> None:
print(f"Creating user: {args.username}")
# Register your commands
command_registry.register(CreateUserCommand())
# This is your entry point - that's it!
if __name__ == "__main__":
host_cli()
Run your CLI:
# Show help
python cli.py --help
# List commands
python cli.py
# Run a command
python cli.py createuser --username john --email john@example.com
# Get command help
python cli.py createuser --help
Alternative: Manual CommandExecutor
For more control, use CommandExecutor directly:
from pydantic_commands import CommandExecutor, command_registry
# Register your commands
cmd = CreateUserCommand()
command_registry.register(cmd)
# Execute from command line
if __name__ == "__main__":
executor = CommandExecutor(prog_name="myapp")
exit_code = executor.execute() # Uses sys.argv
sys.exit(exit_code)
Run your CLI:
# Show help
python myapp.py --help
# List commands
python myapp.py
# Run a command
python myapp.py createuser --username john --email john@example.com
# Get command help
python myapp.py createuser --help
Advanced Usage
Enum Fields
from enum import Enum
from pydantic import BaseModel
from pydantic_commands import BaseCommand
class Status(str, Enum):
ACTIVE = "active"
INACTIVE = "inactive"
PENDING = "pending"
class UpdateStatusCommand(BaseCommand):
name = "updatestatus"
class Arguments(BaseModel):
user_id: int
status: Status
def handle(self, args: Arguments) -> None:
print(f"Updating user {args.user_id} to {args.status.value}")
python myapp.py updatestatus --user-id 123 --status active
List Fields
class TagCommand(BaseCommand):
name = "tag"
class Arguments(BaseModel):
tags: list[str]
resource_ids: list[int]
def handle(self, args: Arguments) -> None:
print(f"Tagging resources {args.resource_ids} with {args.tags}")
python myapp.py tag --tags python cli tool --resource-ids 1 2 3
Path Fields
from pathlib import Path
class ConvertCommand(BaseCommand):
name = "convert"
class Arguments(BaseModel):
input_file: Path
output_file: Path
overwrite: bool = False
def handle(self, args: Arguments) -> None:
if args.output_file.exists() and not args.overwrite:
raise CommandError("Output file exists, use --overwrite")
# Process files...
Custom Argument Parsing
You can extend the add_arguments method to add custom argparse arguments:
class CustomCommand(BaseCommand):
name = "custom"
def add_arguments(self, parser):
parser.add_argument(
"--custom-flag",
action="store_true",
help="A custom flag"
)
def handle(self, args):
print(f"Custom flag: {args.custom_flag}")
Error Handling
from pydantic_commands import BaseCommand, CommandError
class RiskyCommand(BaseCommand):
name = "risky"
def handle(self, args):
if some_condition:
raise CommandError("Something went wrong!")
# Normal execution...
Auto-Loading Commands
The command registry automatically discovers and loads command files using a configurable pattern matching system:
from pydantic_commands import command_registry
# By default, all files matching "*/commands.py" are auto-loaded
# This means any file named "commands.py" in any directory will be discovered
Default Pattern: */commands.py
The registry will automatically find and import all Python files matching this pattern, making any command classes defined in those files available in your CLI application.
Custom Pattern with Environment Variable:
You can customize the discovery pattern using the COMMANDS_PATTERN environment variable:
# Load commands from specific patterns
export COMMANDS_PATTERN="myapp/management/commands/*.py"
python cli.py
# Load from multiple app directories
export COMMANDS_PATTERN="apps/*/commands.py"
python cli.py
# Load from nested command directories
export COMMANDS_PATTERN="**/commands/*.py"
python cli.py
Example Directory Structure:
myproject/
├── cli.py
├── user_management/
│ └── commands.py # Auto-loaded (contains UserCommands)
├── data_processing/
│ └── commands.py # Auto-loaded (contains DataCommands)
└── reporting/
└── commands.py # Auto-loaded (contains ReportCommands)
How It Works:
- When the command registry is initialized, it scans for files matching the pattern
- Each matching file is automatically imported
- Any
BaseCommandsubclasses or@commanddecorated functions in those files are registered - Commands become available immediately without manual registration
Manual Registration (if you prefer explicit control):
from pydantic_commands import command_registry
from myapp.commands import MyCommand
# Disable auto-loading and register manually
command_registry.register(MyCommand())
This auto-discovery system makes it easy to organize commands across multiple files and modules while keeping your CLI application simple and maintainable.
Comparison with Django Commands
| Feature | Django Commands | Pydantic Commands |
|---|---|---|
| Base Class | BaseCommand |
BaseCommand |
| Argument Definition | add_arguments() |
Pydantic model |
| Type Validation | Manual | Automatic (Pydantic) |
| Argument Types | argparse types | Python types + Pydantic |
| Error Handling | CommandError |
CommandError |
| Registry | Django app system | any-registries |
| Django Required | Yes | No |
API Reference
BaseCommand
The base class for all commands.
Attributes:
name(str): Command name (auto-inferred from class name if not set)help(str): Short help textdescription(str): Detailed descriptionArguments(Type[BaseModel]): Pydantic model for arguments
Methods:
handle(args): Main command logic (must be implemented)add_arguments(parser): Hook for adding custom argumentsexecute(argv): Execute the command with given argumentscreate_parser(): Create an ArgumentParser for this command
@command Decorator
Create commands from functions.
Parameters:
name(str): Command namehelp(str): Help textdescription(str): Detailed descriptionarguments(Type[BaseModel]): Pydantic model for argumentsregister(bool): Auto-register in registry (default: True)
host_cli()
The main entry point for CLI applications (recommended).
Function Signature:
def host_cli(
argv: Optional[list[str]] = None,
prog_name: Optional[str] = None
) -> None
Parameters:
argv(list[str], optional): Command-line arguments (defaults tosys.argv)prog_name(str, optional): Program name for help text (defaults tosys.argv[0])
Usage:
# cli.py
from pydantic_commands import host_cli
if __name__ == "__main__":
host_cli()
Features:
- Automatically discovers and executes registered commands
- Handles all argument parsing and validation
- Exits with appropriate exit code
- Similar to Django's
manage.pypattern
CommandExecutor
Execute commands from CLI arguments (for advanced usage).
Methods:
execute(argv=None): Execute command from argumentslist_commands(): Get list of registered commands
command_registry
Global registry for commands (instance of any_registries.Registry).
Methods:
register(command): Register a commandget(name): Get command by namelist(): List all command names
Development
Setup
# Clone the repository
git clone https://github.com/Starscribers/python-packages.git
cd python-packages/pydantic-commands
# Install dependencies
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
Running Tests
pytest tests/
Code Quality
# Format code
ruff format .
# Lint
ruff check .
# Type check
mypy src/
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Links
- GitHub: https://github.com/Starscribers/python-packages
- Issues: https://github.com/Starscribers/python-packages/issues
- PyPI: https://pypi.org/project/pydantic-commands/
Credits
Built with:
- Pydantic - Data validation using Python type hints
- any-registries - Flexible registry system
Inspired by Django's management command system.
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_commands-0.1.0.tar.gz.
File metadata
- Download URL: pydantic_commands-0.1.0.tar.gz
- Upload date:
- Size: 22.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5d29ae05f0b7729a1e854e19b71690cdb46b1109729a722d24ee2f90fccde3b6
|
|
| MD5 |
9a61d4c9a2672bc36785fc9dc4d9d84d
|
|
| BLAKE2b-256 |
3eac35b3a35bab96aaeaa15ba599de0b74f2077a3cf11ee8d0bc36c712b9c7b6
|
File details
Details for the file pydantic_commands-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pydantic_commands-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
41bd31d19b813e755927f3cf98f2bfbe5ad90af7892e41ee191f715809b9880d
|
|
| MD5 |
c85dd3308558f7f6bb1299946cd40cb3
|
|
| BLAKE2b-256 |
f78359cf39685d6c7b730928145122eaeecf172cd0ba413d59081d36e33fafdd
|