Skip to content

Latest commit

 

History

History
872 lines (658 loc) · 27.3 KB

File metadata and controls

872 lines (658 loc) · 27.3 KB

CLI Reference

The PyFly CLI provides command-line tools for project scaffolding, application management, database migrations, environment diagnostics, and framework information.

Install: uv sync --extra cli (requires Click, Rich, Jinja2, questionary)

Entry point: pyfly (registered as a console script via pyproject.toml)


Table of Contents


Overview

The CLI is built with Click for command parsing and Rich for beautiful terminal output. When you run pyfly --help, you'll see an ASCII banner followed by the command listing.

All commands are organized as a Click group under the pyfly entry point:

pyfly
├── new       — Create a new project
├── run       — Start the application server
├── info      — Display framework information
├── doctor    — Diagnose environment
├── db        — Database migration commands
│   ├── init      — Initialize Alembic
│   ├── migrate   — Generate migration
│   ├── upgrade   — Apply migrations
│   └── downgrade — Revert migrations
├── license   — Display the Apache 2.0 license
└── sbom      — Software Bill of Materials

Architecture

The CLI module follows the same patterns as the rest of PyFly:

src/pyfly/cli/
├── __init__.py
├── main.py          # PyFlyCLI group, command registration
├── console.py       # Shared Rich console, theme, print_banner()
├── new.py           # pyfly new — project scaffolding (questionary TUI + Click CLI)
├── run.py           # pyfly run — application server
├── info.py          # pyfly info — environment information
├── doctor.py        # pyfly doctor — environment diagnostics
├── db.py            # pyfly db — Alembic migration management
├── license.py       # pyfly license — display Apache 2.0 license
├── sbom.py          # pyfly sbom — Software Bill of Materials
├── templates.py     # Jinja2-based template renderer
└── templates/       # Jinja2 template files (.j2)
    ├── pyproject.toml.j2
    ├── app.py.j2
    ├── pyfly.yaml.j2
    ├── dockerfile.j2
    ├── readme.md.j2
    ├── ...
    ├── hex/         # Hexagonal archetype templates
    ├── web/         # Web (SSR) archetype templates
    └── cli/         # CLI archetype templates

Rich Console Theme

The CLI uses a custom Rich theme for consistent, colored output across all commands:

Style Color Usage
info Cyan Informational messages, labels
success Bold green Success indicators (checkmarks)
warning Bold yellow Warnings (non-fatal issues)
error Bold red Errors (fatal issues)
pyfly Bold magenta PyFly branding elements
dim Dim Secondary text, descriptions

All commands share a single console instance from pyfly.cli.console, ensuring consistent styling. The print_banner() function renders the PyFly ASCII art banner.


Global Options

pyfly --version    # Show PyFly version (from pyproject.toml)
pyfly --help       # Show help with ASCII banner and all commands

The --version flag reads the version from the pyfly package metadata.


pyfly new

Create a new PyFly project with a complete directory structure, configuration files, and starter code. Supports six archetypes, selective feature inclusion, and an interactive mode.

Usage

pyfly new <name> [OPTIONS]    # Direct mode
pyfly new [OPTIONS]           # Interactive mode (prompts for all options)

Arguments

Argument Required Description
name No Project name. Omit to enter interactive mode.

The project name is converted to a valid Python package name: my-service becomes my_service for the package directory.

Options

Option Default Description
--archetype core Project archetype (see below)
--features Per archetype Comma-separated PyFly extras (e.g. web,data-relational,cache)
--directory . Parent directory where the project folder will be created

Archetypes

Archetype Description Default Features
core Minimal microservice (none)
web-api Full REST API with layered architecture web
fastapi-api Full REST API with FastAPI and native OpenAPI fastapi
web Server-rendered web application with HTML templates web
hexagonal Hexagonal architecture (ports & adapters) web
library Reusable library package (none)
cli Command-line application with interactive shell shell

Available Features

Features control which PyFly extras are included as dependencies and which config sections are generated:

Feature What it adds
web HTTP server (Starlette), REST controllers, OpenAPI docs
fastapi HTTP server (FastAPI), REST controllers, native OpenAPI
granian Granian ASGI server (Rust/tokio, highest throughput)
hypercorn Hypercorn ASGI server (HTTP/2 and HTTP/3 support)
data-relational Data Relational -- SQL databases (SQLAlchemy ORM)
data-document Data Document -- Document databases (Beanie ODM)
eda Event-driven architecture, in-memory event bus
cache Caching with in-memory adapter
client Resilient HTTP client with retry and circuit breaker
security JWT authentication, password hashing
scheduling Cron-based task scheduling
observability Prometheus metrics, OpenTelemetry tracing
cqrs Command/Query Responsibility Segregation
shell Spring Shell-inspired CLI commands with DI

Core Archetype

The core archetype creates a minimal microservice with configuration and Docker support:

my-service/
├── pyproject.toml
├── pyfly.yaml
├── Dockerfile
├── README.md
├── .gitignore
├── .env.example
├── src/
│   └── my_service/
│       ├── __init__.py
│       ├── app.py
│       └── main.py
└── tests/
    ├── __init__.py
    └── conftest.py

Web API Archetype

The web-api archetype creates a full REST API with layered controllers, services, models, and repositories — all using PyFly stereotypes. The example uses a Todo CRUD API with title, completed, and description fields:

my-api/
├── pyproject.toml
├── pyfly.yaml
├── Dockerfile
├── README.md
├── .gitignore
├── .env.example
├── src/
│   └── my_api/
│       ├── __init__.py
│       ├── app.py
│       ├── main.py
│       ├── controllers/
│       │   ├── __init__.py
│       │   ├── health_controller.py    # @rest_controller — /health
│       │   └── todo_controller.py      # @rest_controller — CRUD /todos
│       ├── services/
│       │   ├── __init__.py
│       │   └── todo_service.py         # @service — business logic
│       ├── models/
│       │   ├── __init__.py
│       │   └── todo.py                 # Pydantic request/response DTOs
│       └── repositories/
│           ├── __init__.py
│           └── todo_repository.py      # @repository — in-memory store
└── tests/
    ├── __init__.py
    ├── conftest.py
    └── test_todo_service.py

Web Archetype

The web archetype creates a server-rendered HTML application with Jinja2 templates, static assets, and the @controller stereotype:

my-site/
├── pyproject.toml
├── pyfly.yaml
├── Dockerfile
├── README.md
├── .gitignore
├── .env.example
├── src/
│   └── my_site/
│       ├── __init__.py
│       ├── app.py
│       ├── main.py                     # ASGI entry with StaticFiles mount
│       ├── controllers/
│       │   ├── __init__.py
│       │   ├── health_controller.py    # @rest_controller — /health
│       │   └── home_controller.py      # @controller — / and /about
│       ├── services/
│       │   ├── __init__.py
│       │   └── page_service.py         # @service — page context data
│       ├── templates/
│       │   ├── base.html               # Base layout with nav and footer
│       │   ├── home.html               # Home page
│       │   └── about.html              # About page
│       └── static/
│           └── css/
│               └── style.css           # Minimal stylesheet
└── tests/
    ├── __init__.py
    ├── conftest.py
    └── test_home_controller.py

The web archetype uses @controller (instead of @rest_controller) for endpoints that return TemplateResponse objects. The main.py mounts a /static route for serving CSS, JavaScript, and images via Starlette's StaticFiles.

Hexagonal Archetype

The hexagonal archetype creates a ports-and-adapters project with explicit domain, application, infrastructure, and API layers:

my-hex/
├── pyproject.toml
├── pyfly.yaml
├── Dockerfile
├── README.md
├── .gitignore
├── .env.example
├── src/
│   └── my_hex/
│       ├── __init__.py
│       ├── app.py
│       ├── main.py
│       ├── domain/
│       │   ├── __init__.py
│       │   ├── models.py              # Domain entities (dataclasses)
│       │   ├── events.py              # Domain events
│       │   └── ports/
│       │       ├── __init__.py
│       │       ├── inbound.py         # Use-case Protocols
│       │       └── outbound.py        # Repository Protocols
│       ├── application/
│       │   ├── __init__.py
│       │   └── services.py            # @service — implements inbound ports
│       ├── infrastructure/
│       │   ├── __init__.py
│       │   ├── config.py              # @configuration beans
│       │   └── adapters/
│       │       ├── __init__.py
│       │       └── persistence.py     # @repository — implements outbound ports
│       └── api/
│           ├── __init__.py
│           ├── controllers.py         # @rest_controller
│           └── dto.py                 # Pydantic request/response DTOs
└── tests/
    ├── __init__.py
    ├── conftest.py
    ├── domain/
    │   ├── __init__.py
    │   └── test_models.py
    └── application/
        ├── __init__.py
        └── test_services.py

Library Archetype

The library archetype creates a minimal reusable library with PEP 561 py.typed marker:

my-library/
├── pyproject.toml
├── README.md
├── .gitignore
├── src/
│   └── my_library/
│       ├── __init__.py
│       └── py.typed
└── tests/
    ├── __init__.py
    └── conftest.py

CLI Archetype

The cli archetype creates a command-line application with interactive shell, DI-powered commands, and service layer:

my-tool/
├── pyproject.toml
├── pyfly.yaml
├── Dockerfile
├── README.md
├── .gitignore
├── .env.example
├── src/
│   └── my_tool/
│       ├── __init__.py
│       ├── app.py
│       ├── main.py                    # CLI entry point (asyncio.run)
│       ├── commands/
│       │   ├── __init__.py
│       │   └── hello_command.py       # @shell_component — example commands
│       └── services/
│           ├── __init__.py
│           └── greeting_service.py    # @service — business logic
└── tests/
    ├── __init__.py
    ├── conftest.py
    └── test_hello_command.py

The CLI archetype differs from other archetypes in several ways:

  • No ASGI entry point — uses asyncio.run(pyfly.run()) instead of uvicorn/Starlette
  • No web section in pyfly.yaml — no port, no adapter config
  • No module: field in config — no ASGI app to reference
  • Shell enabledpyfly.shell.enabled: true is set automatically
  • Dockerfile uses CMD ["python", "-m", "my_tool.main"] instead of uvicorn, no EXPOSE

Interactive Mode

When pyfly new is run without a NAME argument, it enters interactive mode with a full-featured TUI experience powered by questionary:

$ pyfly new

  ╭─────────────────────────────────╮
  │   PyFly Project Generator       │
  ╰─────────────────────────────────╯

  ? Project name: my-service
  ? Package name: my_service
  ? Select archetype: (use arrow keys)
    ❯ core          Minimal microservice with DI container and config
      web-api       Full REST API with controller/service/repository layers
      web           Server-rendered HTML with Jinja2 templates and static assets
      hexagonal     Clean architecture with domain isolation
      library       Reusable library with py.typed and packaging best practices
      cli           Command-line application with interactive shell and DI

  ? Select features: (space to toggle, enter to confirm)
    ❯ [x] web          HTTP server, REST controllers, OpenAPI docs
      [ ] data-relational  Data Relational — SQL databases (SQLAlchemy ORM)
      [ ] data-document    Data Document — Document databases (Beanie ODM)
      [ ] cache        Caching with in-memory adapter
      [ ] security     JWT authentication, password hashing
      [ ] eda          Event-driven architecture, in-memory event bus
      [ ] client       Resilient HTTP client with retry and circuit breaker
      [ ] scheduling   Cron-based task scheduling
      [ ] observability  Prometheus metrics, OpenTelemetry tracing
      [ ] cqrs         Command/Query Responsibility Segregation
      [ ] shell        Spring Shell-inspired CLI commands with DI

  ╭─ Project Summary ───────────────╮
  │   Name:      my-service         │
  │   Package:   my_service         │
  │   Archetype: web-api            │
  │   Features:  web, data-relational │
  ╰─────────────────────────────────╯

  ? Create this project? Yes

Features:

  • Arrow-key navigation for archetype selection (single-select)
  • Space-bar toggling for feature selection (multi-select with checkbox)
  • Confirmation summary with Rich-styled panel before creation
  • Graceful handling of Ctrl+C (exits cleanly without traceback)

The library archetype skips the feature selection step since libraries don't include PyFly extras.

Error Handling

If the target directory already exists, the command exits with an error. If an unknown feature is specified, the command lists valid features and exits.

Examples

# Create a microservice (core archetype, no features)
pyfly new order-service

# Create a REST API (includes health controller, Todo CRUD example)
pyfly new order-api --archetype web-api

# Create a server-rendered web application with HTML templates
pyfly new my-site --archetype web

# Create a hexagonal project with data and cache
pyfly new order-svc --archetype hexagonal --features web,data-relational,cache

# Create a CLI tool with interactive shell
pyfly new admin-tool --archetype cli

# Create a shared library
pyfly new common-utils --archetype library

# Create in a specific directory
pyfly new payment-service --directory /projects

# Interactive mode
pyfly new

After creation, the CLI displays a Rich tree panel showing all created files and a hint to navigate into the project.


pyfly run

Start the PyFly application using the auto-configured ASGI server (Granian, Uvicorn, or Hypercorn).

Usage

pyfly run [OPTIONS]

Options

Option Default Description
--host 0.0.0.0 Bind address
--port From pyfly.yaml or 8080 Port number (resolved from: CLI flag → pyfly.web.port in config → 8080)
--server From config or auto ASGI server: granian, uvicorn, hypercorn (auto-selects highest-priority installed)
--workers From config or 0 Number of worker processes (0 = cpu_count)
--reload false Enable auto-reload on code changes (for development)
--app Auto-discovered Application import path (e.g., myapp.main:app)

Path Setup

Before discovery, pyfly run automatically adds the src/ directory to sys.path if it exists. This allows running from a src-layout project root without installing the project first.

Application Discovery

When --app is not provided, pyfly run attempts to discover the application automatically using a three-stage resolution:

  1. Canonical: reads pyfly.app.module from pyfly.yaml (nested under pyfly: key)
  2. Flat fallback: reads app.module from pyfly.yaml (flat layout)
  3. Auto-discovery: scans src/<package>/main.py and constructs the import path
# pyfly.yaml — canonical layout
pyfly:
  app:
    module: my_service.main:app

If neither --app nor a discoverable config file is found, the command exits with a clear error:

No application found.
Provide --app flag or create a pyfly.yaml in the current directory.

Server Selection

When --server is not specified, pyfly run auto-selects the highest-priority installed ASGI server:

Priority Server Condition
1 Granian granian is importable
2 Uvicorn uvicorn is importable
3 Hypercorn hypercorn is importable

Requirements

Requires at least one ASGI server to be installed (Granian, Uvicorn, or Hypercorn). If none is available, the command suggests installing the web extra:

✗ No ASGI server found.
  Install the web extra: pyfly[web]

Examples

# Development with auto-reload
pyfly run --reload

# Force Granian with 4 workers
pyfly run --server granian --workers 4

# Custom port
pyfly run --port 3000

# Explicit app path
pyfly run --app my_service.app:Application

# Production binding with Granian on all cores
pyfly run --host 0.0.0.0 --port 80 --server granian --workers 0

pyfly info

Display comprehensive information about the PyFly installation and environment.

Usage

pyfly info

Output

The command displays two Rich tables:

1. Environment Table

Field Example
Python 3.12.5
Platform macOS-14.5-arm64
Architecture arm64

2. Installed Extras Table

Shows the installation status of each optional module by attempting to import the underlying library. The table has two columns — Extra and Status:

Extra Detection Module
web starlette
data-relational sqlalchemy
data-document beanie
eda aiokafka
kafka aiokafka
rabbitmq aio_pika
redis redis
cache redis
client httpx
observability prometheus_client
security jwt
scheduling croniter
cli rich

Each extra shows as either installed (green) or not installed (dimmed).

This is useful for verifying which framework modules are available after installation, especially when using selective extras rather than full.


pyfly doctor

Run a comprehensive health check on your development environment, verifying Python version, tools, and PyFly installation.

Usage

pyfly doctor

Checks Performed

1. Python Version

Verifies Python >= 3.12. Displays the exact version with a pass/fail indicator.

✓ Python 3.12.5          # Pass
✗ Python 3.11.2 (requires >=3.12)  # Fail

2. Virtual Environment

Checks if a virtual environment is active by comparing sys.prefix to sys.base_prefix. Shows a warning if not in a venv — this is non-fatal but recommended.

✓ Virtual environment active    # In venv
! No virtual environment detected  # Warning

3. Required Tools

Checks that essential tools are available on PATH using shutil.which():

Tool Purpose Impact if Missing
git Version control Overall check fails
uv Package manager Overall check fails

4. Optional Tools

Checks for recommended development tools:

Tool Purpose Impact if Missing
uvicorn ASGI server (pyfly run) Shown as - (dimmed)
alembic Database migrations (pyfly db) Shown as - (dimmed)
ruff Linter & formatter Shown as - (dimmed)
mypy Type checker Shown as - (dimmed)

Missing optional tools are shown with a - dash indicator (dimmed), while missing required tools use (red) and cause the overall check to fail.

5. PyFly Installation

Verifies that PyFly itself is importable and displays the installed version:

✓ pyfly v26.05.01

Summary

The doctor command ends with a clear summary:

  • All checks passed! (green) — Environment is ready
  • Some issues found. See above for details. (yellow) — Action needed

pyfly db

Database migration commands powered by Alembic. These commands wrap Alembic's functionality with PyFly-specific defaults and Rich output.

The migrate, upgrade, and downgrade subcommands expect alembic.ini to exist in the current directory (created by pyfly db init).

All pyfly db subcommands require Alembic to be installed. If not available:

✗ alembic is not installed.
  Install the data-relational extra: pyfly[data-relational]

pyfly db init

Initialize the Alembic migration environment in the current directory.

pyfly db init

What it does:

  1. Creates an alembic/ directory with Alembic's standard structure
  2. Creates alembic.ini configuration file
  3. Overwrites alembic/env.py with a PyFly-customized template that includes:
    • async_engine_from_config for async database support (asyncpg, aiosqlite)
    • Base.metadata from pyfly.data.relational.sqlalchemy as the target metadata for autogeneration
    • Support for both offline (SQL script) and online (async connection) migration modes

Error handling: If an alembic/ directory already exists, the command exits with an error rather than overwriting.

✗ Directory 'alembic' already exists. Remove it first if you want to re-initialize.

pyfly db migrate

Auto-generate a new migration revision by comparing your SQLAlchemy models to the current database state.

pyfly db migrate [-m "description"]
Option Description
-m, --message Revision message describing the changes

Examples:

pyfly db migrate -m "add user table"
pyfly db migrate -m "add order status column"

This runs Alembic's revision --autogenerate, which:

  1. Compares your current Base.metadata (all entity models) with the database
  2. Generates upgrade/downgrade functions in a new version file under alembic/versions/

Prerequisites: alembic.ini must exist (run pyfly db init first).

pyfly db upgrade

Apply pending migrations to bring the database up to a specific revision.

pyfly db upgrade [REVISION]
Argument Default Description
revision head Target revision (use head for latest)

Examples:

pyfly db upgrade           # Apply all pending migrations
pyfly db upgrade head      # Same as above (explicit)
pyfly db upgrade abc123    # Upgrade to specific revision

pyfly db downgrade

Revert the database to a previous revision.

pyfly db downgrade REVISION
Argument Required Description
revision Yes Target revision to downgrade to

Examples:

pyfly db downgrade -1       # Revert one migration step
pyfly db downgrade abc123   # Revert to specific revision
pyfly db downgrade base     # Revert all migrations

pyfly license

Display the Apache 2.0 license text for the PyFly Framework.

Usage

pyfly license

The command first tries to read the LICENSE file from the installed package resources, then falls back to the project root filesystem. If no license file is found, it displays a summary with a link to the Apache 2.0 license.


pyfly sbom

Display the Software Bill of Materials (SBOM) — a table of all PyFly dependencies with their required and installed versions.

Usage

pyfly sbom [OPTIONS]

Options

Option Description
--json Output as JSON instead of a Rich table

Output

The command displays a Rich table with three columns:

Column Description
Package Dependency name
Required Version specifier from pyproject.toml
Installed Installed version (green) or not installed (yellow)

The JSON output includes the PyFly version, license, and full dependency list — useful for compliance and auditing pipelines.

Examples

# Display as Rich table
pyfly sbom

# Export as JSON
pyfly sbom --json

Typical Development Workflow

Here's a typical workflow using the CLI tools throughout the lifecycle of a PyFly project:

# 1. Check your environment is ready
pyfly doctor

# 2. Create a new project
pyfly new order-service
cd order-service

# 3. Verify framework info and installed extras
pyfly info

# 4. Initialize database migrations
pyfly db init

# 5. Create initial migration from your entity models
pyfly db migrate -m "initial schema"

# 6. Apply the migration
pyfly db upgrade

# 7. Start developing with auto-reload
pyfly run --reload

# 8. As you add/modify entities, generate new migrations
pyfly db migrate -m "add order status"
pyfly db upgrade

# 9. If you need to roll back a migration
pyfly db downgrade -1

Extending the CLI

The CLI is built on Click, making it straightforward to add custom commands for your project. The entry point is defined in pyproject.toml:

[project.scripts]
pyfly = "pyfly.cli.main:cli"

To add custom commands, create a Click command and register it with the CLI group:

import click
from pyfly.cli.main import cli

@click.command()
@click.argument("entity_name")
def generate_command(entity_name: str) -> None:
    """Generate boilerplate for a new entity."""
    click.echo(f"Generating {entity_name} entity, repository, and service...")

cli.add_command(generate_command, name="generate")