Complete Examples¶
Comprehensive examples of using pyclif decorators to build various types of CLI applications.
Basic CLI Application¶
from pyclif import app_group, command, option
@app_group(
name="myapp",
auto_envvar_prefix="MYAPP"
)
def cli():
"""My Application — a sample CLI built with pyclif."""
pass
@cli.command()
@option("--message", "-m", default="Hello World", help="Message to display")
def hello(message):
"""Display a greeting message."""
print(message)
if __name__ == "__main__":
cli()
python myapp.py hello --message "Welcome!"
python myapp.py hello -m "Hi there!"
MYAPP_MESSAGE="Hello from env!" python myapp.py hello
python myapp.py --log-file /tmp/myapp.log hello
Database Management CLI¶
from pyclif import app_group, group, option
import click
@app_group(name="dbmanager", auto_envvar_prefix="DBMGR")
def cli():
"""Database Management Tool."""
pass
@cli.group()
@group(name="database")
def database():
"""Database management commands."""
pass
@database.command()
@option("--url", "-u", required=True, help="Database URL")
@option("--timeout", "-t", type=int, default=30, help="Connection timeout")
@option("--ssl/--no-ssl", default=True, help="Use SSL connection")
def connect(url, timeout, ssl):
"""Connect to the database."""
ssl_status = "with SSL" if ssl else "without SSL"
print(f"Connecting to {url} with timeout {timeout}s {ssl_status}")
@database.command()
@option(
"--backup-dir", "-d", required=True,
type=click.Path(exists=True, file_okay=False, dir_okay=True),
help="Backup directory"
)
@option("--compress/--no-compress", default=True, help="Compress backup")
@option("--tables", multiple=True, help="Specific tables to backup")
def backup(backup_dir, compress, tables):
"""Backup the database."""
compression = "with compression" if compress else "without compression"
if tables:
table_list = ", ".join(tables)
print(f"Backing up tables [{table_list}] to {backup_dir} {compression}")
else:
print(f"Backing up entire database to {backup_dir} {compression}")
if __name__ == "__main__":
cli()
dbmanager database connect --url postgres://localhost/mydb --timeout 60
dbmanager database backup --backup-dir /backups --compress
dbmanager database backup -d /backups --tables users orders --no-compress
Web Service CLI¶
from pyclif import app_group, group, option
import click
@app_group(name="webctl", auto_envvar_prefix="WEBCTL")
def cli():
"""Web Service Control Tool."""
pass
@cli.group()
@group(name="service")
def service():
"""Service management commands."""
pass
@service.command()
@option("--port", "-p", type=int, default=8080, help="Service port")
@option("--host", "-h", default="localhost", help="Service host")
@option("--workers", "-w", type=int, default=4, help="Number of workers")
@option("--debug/--no-debug", default=False, help="Enable debug mode")
def start(port, host, workers, debug):
"""Start the web service."""
mode = "debug" if debug else "production"
print(f"Starting service on {host}:{port} with {workers} workers in {mode} mode")
@service.command()
@option("--force", "-f", is_flag=True, help="Force stop without graceful shutdown")
def stop(force):
"""Stop the web service."""
method = "force" if force else "graceful"
print(f"Stopping service with {method} shutdown")
@service.command()
def status():
"""Show service status."""
print("Service status: Running")
Configuration file (~/.config/webctl/config.toml):
webctl service start --port 9000 --workers 8 --debug
webctl service stop --force
webctl service status
webctl --config ~/.config/webctl/production.toml service start
WEBCTL_PORT=9090 webctl service start
Structured Output with Response¶
Attach a BaseRenderer subclass to control the output for every format — table columns,
JSON fields, and Rich display — from a single class.
from pyclif import app_group, BaseRenderer, OperationResult, Response
import click
class UserRenderer(BaseRenderer):
fields = ["id", "name", "active"] # included in JSON / YAML / raw
columns = ["id", "name", "active"] # shown in the table
rich_title = "Users"
success_message = "Users retrieved."
@app_group(handle_response=True)
@click.pass_context
def cli(ctx):
"""API management CLI."""
pass
@cli.command()
@click.pass_context
def list_users(ctx):
"""List all users."""
users = [
{"id": 1, "name": "Alice", "active": True},
{"id": 2, "name": "Bob", "active": False},
]
results = [OperationResult.ok(str(u["id"]), data=u) for u in users]
return Response.from_results(results, renderer=UserRenderer())
myapp list-users # table (default format)
myapp --output-format json list-users # JSON output
myapp -o yaml list-users # YAML output
myapp -o text list-users # plain message only
Advanced Patterns¶
Custom Validation¶
from pyclif import app_group, option
import click
def validate_email(ctx, param, value):
"""Custom email validation."""
if value and "@" not in value:
raise click.BadParameter("Must be a valid email address")
return value
@app_group(name="userctl", auto_envvar_prefix="USERCTL")
def cli():
"""User Management CLI."""
pass
@cli.command()
@option("--email", required=True, callback=validate_email, help="User email address")
@option(
"--role",
type=click.Choice(["admin", "user", "guest"]),
default="user",
help="User role"
)
def create_user(email, role):
"""Create a new user."""
print(f"Creating user {email} with role {role}")
Global Options¶
from pyclif import app_group, option
import click
@app_group(name="myapp", auto_envvar_prefix="MYAPP")
@option("--api-key", is_global=True, help="API key for authentication")
@click.pass_context
def cli(ctx, api_key):
"""My Application with a global API key."""
pass
@cli.command()
@click.pass_context
def fetch(ctx, api_key):
"""Fetch data using the global API key."""
print(f"Fetching with key: {api_key}")
Testing Your CLI¶
import pytest
from click.testing import CliRunner
from myapp import cli
def test_hello_command():
runner = CliRunner()
result = runner.invoke(cli, ["hello", "--message", "Test"])
assert result.exit_code == 0
assert "Test" in result.output
def test_environment_variable():
runner = CliRunner(env={"MYAPP_MESSAGE": "From Env"})
result = runner.invoke(cli, ["hello"])
assert result.exit_code == 0
assert "From Env" in result.output
def test_config_file():
runner = CliRunner()
with runner.isolated_filesystem():
with open("config.toml", "w") as f:
f.write('[hello]\nmessage = "From Config"\n')
result = runner.invoke(cli, ["--config", "config.toml", "hello"])
assert result.exit_code == 0
assert "From Config" in result.output