AgentStack
SKILL unreviewed MIT Self-run

Python Cli Engineering

skill-aeyeops-aeo-skill-marketplace-python-cli-engineering · by AeyeOps

Engineer production-grade Python CLI tools with UV for package management, Ruff for linting, Pyright for strict typing, Typer for commands, and Rich for polished output. Addresses fail-fast patterns, pydantic-settings configuration, modular code organization, and professional UX conventions. Apply when creating admin utilities, data processors, or developer tooling.

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-aeyeops-aeo-skill-marketplace-python-cli-engineering

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Pipes remote content directly into a shell (remote code execution).

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Python Cli Engineering? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Python CLI Engineering

Modern patterns for building production-grade Python command-line applications.

When to Use This Skill

Use when building:

  • Command-line tools and utilities
  • Data processing applications
  • Admin/operations tooling
  • Developer utilities
  • ETL/sync scripts with user interaction
  • Analysis tools with formatted output
  • Database management CLIs

Technology Stack Overview

Core Tools

  • UV - Package manager (10-100x faster than pip/poetry)
  • Ruff - Linting + formatting (Rust-based, replaces 6+ tools)
  • Pyright - Type checking in strict mode (3-5x faster than mypy)
  • Typer - CLI framework with type hints + auto-help
  • Rich - Console output with colors, tables, progress bars

Installation:

curl -LsSf https://astral.sh/uv/install.sh | sh  # Install UV
uv init my-cli          # Initialize project
uv add typer rich       # Add dependencies

For complete tool guides, configuration examples, and advanced patterns: See @references/tech-stack.md


Fail-Fast Discipline

Core Principles

  1. No default values (except in explicit config files)
  2. No try/catch swallowing (let exceptions propagate)
  3. No fallback mechanisms (fail immediately on errors)
  4. Explicit configuration (missing config = app stops)
  5. Type everything (strict Pyright mode)

Exception Handling Pattern

# Custom exceptions (core/exceptions.py)
class AppError(Exception):
    """Base exception - let it bubble up."""
    pass

# CLI main entry (cli/main.py)
def main() -> None:
    """Main entry point - ONLY catch at top level."""
    try:
        app()
    except AppError as e:
        console.print(f"[red]ERROR: {e}[/red]")
        raise typer.Exit(code=1) from e

Key Rules:

  • Never: except Exception: pass or except: return None
  • Always: Let exceptions bubble to main entry point
  • Always: Use specific exception types for different failure modes
  • Always: Exit with non-zero code on failure

Modular Architecture

Module Size Constraints

CRITICAL RULE: 500 lines maximum per module

When module approaches 500 lines:

  1. Extract related functions to new module
  2. Split by domain/responsibility
  3. Create subpackage if >3 related modules

Project Structure

my_cli/
├── pyproject.toml          # UV project config
├── .env.example            # Template for .env
├── config.yaml            # Application defaults
├── Makefile               # Development tasks
└── src/
    └── my_cli/
        ├── __init__.py
        ├── __main__.py         # Entry point
        ├── cli/                # CLI commands
        │   ├── __init__.py
        │   └── commands.py     # Typer app
        ├── core/               # Business logic
        │   ├── __init__.py
        │   ├── config.py       # pydantic-settings
        │   └── exceptions.py
        ├── db/                 # Database layer
        │   ├── __init__.py
        │   ├── models.py       # SQLAlchemy models
        │   └── connection.py
        └── services/           # External integrations
            └── __init__.py

For complete architecture patterns, module organization, and size management: See @references/architecture.md


Configuration Pattern

Dual Configuration (YAML + .env)

Pattern: Merge YAML defaults with .env overrides using pydantic-settings

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        case_sensitive=False,
    )

    # Database
    db_host: str = "localhost"
    db_port: int = 5432
    db_name: str
    db_user: str
    db_password: str  # Must be in .env

    # API
    api_key: str
    api_url: str = "https://api.example.com"

settings = Settings()  # Fails if db_password or api_key missing

config.yaml (defaults):

db_host: localhost
db_port: 5432
api_url: https://api.example.com

.env (secrets):

DB_PASSWORD=secret
API_KEY=abc123

For complete configuration patterns, validation, and advanced examples: See @references/configuration.md


Quick Start

1. Initialize Project

uv init my-cli
cd my-cli
uv add typer rich pydantic-settings pyyaml
uv add --dev ruff pyright pytest

2. Create Entry Point

# src/my_cli/__main__.py
import typer
from rich.console import Console

app = typer.Typer()
console = Console()

@app.command()
def hello(name: str) -> None:
    """Say hello."""
    console.print(f"[green]Hello {name}![/green]")

if __name__ == "__main__":
    app()

3. Configure Tools

Copy configuration templates:

  • [templates/pyproject.toml](templates/pyproject.toml) - UV + Ruff + Pyright config
  • [templates/Makefile](templates/Makefile) - Development tasks

4. Run

uv run python -m my_cli hello World

Development Workflow

Standard Tasks (via Makefile)

make lint       # Run ruff check + format
make type       # Run pyright
make test       # Run pytest
make check      # lint + type + test
make clean      # Remove build artifacts

Pre-commit Checks

Always run before committing:

make check

Why: Catches type errors, style issues, and test failures early


Common Patterns

Advanced Pattern References

Build integration: See [patterns/make-integration.md](patterns/make-integration.md) - Integrate CLI with Makefiles Multi-method authentication: See [patterns/multi-method-auth.md](patterns/multi-method-auth.md) - Support OAuth, TBA, API keys PostgreSQL JSONB: See [patterns/postgresql-jsonb.md](patterns/postgresql-jsonb.md) - Flexible schema with JSONB columns Pydantic flexibility: See [patterns/pydantic-flexible.md](patterns/pydantic-flexible.md) - Handle dynamic/unknown fields Schema resilience: See [patterns/schema-resilience.md](patterns/schema-resilience.md) - Robust API schema handling Database migrations: See @reference/database-migrations.md - Alembic migration patterns

CLI Command with Config

@app.command()
def sync(
    config_file: Path = typer.Option("config.yaml"),
    verbose: bool = typer.Option(False, "--verbose", "-v"),
) -> None:
    """Sync data from source."""
    settings = Settings(_env_file=".env")
    if verbose:
        console.print(f"[yellow]Connecting to {settings.db_host}[/yellow]")
    # Implementation

Database Connection with SQLAlchemy

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

Base = declarative_base()
engine = create_engine(f"postgresql://{settings.db_user}:{settings.db_password}@{settings.db_host}/{settings.db_name}")
Session = sessionmaker(bind=engine)

Rich Progress Bar

from rich.progress import track

for item in track(items, description="Processing..."):
    process(item)

Error Handling

Custom Exception Hierarchy

class AppError(Exception):
    """Base for all app exceptions."""
    pass

class ConfigurationError(AppError):
    """Config missing/invalid."""
    pass

class DatabaseError(AppError):
    """DB operation failed."""
    pass

class ExternalServiceError(AppError):
    """External API failed."""
    pass

Usage in Commands

@app.command()
def process() -> None:
    """Process data."""
    if not settings.api_key:
        raise ConfigurationError("API_KEY not set in .env")

    try:
        response = api.fetch_data()
    except Exception as e:
        raise ExternalServiceError(f"API fetch failed: {e}") from e

    # Process response (let exceptions bubble)

Type Checking Best Practices

Strict Mode Configuration

[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.12"
reportMissingTypeStubs = true
reportUnknownMemberType = true

Common Type Patterns

from typing import Any
from collections.abc import Sequence

# Function signatures
def process_items(items: Sequence[dict[str, Any]]) -> list[str]:
    return [item["name"] for item in items]

# Optional values
from typing import Optional
def get_value(key: str) -> Optional[str]:
    return cache.get(key)  # Returns str | None

# Type guards
from typing import TypeGuard
def is_string_list(val: list[Any]) -> TypeGuard[list[str]]:
    return all(isinstance(x, str) for x in val)

Testing

Pytest Structure

tests/
├── conftest.py           # Fixtures
├── test_cli.py          # CLI command tests
├── test_config.py       # Config loading tests
└── test_services.py     # Service integration tests

Testing CLI Commands

from typer.testing import CliRunner

runner = CliRunner()

def test_hello_command():
    result = runner.invoke(app, ["hello", "World"])
    assert result.exit_code == 0
    assert "Hello World" in result.stdout

Deployment

Building Distribution

uv build                 # Creates wheel + sdist
ls dist/                 # my_cli-0.1.0-py3-none-any.whl

Installing

uv pip install dist/my_cli-0.1.0-py3-none-any.whl
my-cli hello World       # Now available as command

Key Principles Summary

  1. Fail Fast - No silent errors, no defaults for critical config
  2. Type Everything - Strict Pyright mode catches issues early
  3. Modular - 500-line max per module, clear separation of concerns
  4. Explicit Config - YAML defaults + .env secrets, no magic
  5. Rich Output - Professional CLI UX with colors and progress
  6. UV for Speed - 10-100x faster than traditional tools
  7. Test Early - pytest + type checking before commit

For detailed guides: See the references/ directory For project templates: See the templates/ directory

End of Skill

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.