Python Fire turns any Python function, class, or module into a CLI with zero argument parsing code. pip install fire. Function: import fire; fire.Fire(fn) — positional args map to params, --name value for kwargs. Class: fire.Fire(MyClass) — methods become sub-commands; python app.py method --arg val. Multiple: fire.Fire({"deploy": deploy_fn, "rollback": rollback_fn}). fire.Fire(MyClass()) — instantiate then call methods. Named: fire.Fire(fn, name="mytool"). Trace: python app.py fn -- --trace — shows dispatch chain. Separator: python app.py fn other_fn -- --separator=,. Return: Fire prints return values — strings as-is, dicts/lists as JSON-like repr. Serializer: fire.core.Display for custom rendering. Nested class: class DB: def query(self): ...; class App: db = DB() — python app.py db query. Help: python app.py --help, python app.py method --help — auto from docstrings. Type: Fire coerces "true"/"false" → bool, "1" → int, "[1,2]" → list. --flag → True, --noflag → False. Default args: Fire shows them in help. Variadic: def fn(*args, **kwargs) — passes positional and keyword freely. Pipe: echo "arg" | python app.py fn -. Input: - means stdin. fire.Fire works as a main() body. Claude Code generates python-fire CLIs, admin scripts, and multi-command tools from existing class APIs.
CLAUDE.md for Python Fire
## Python Fire Stack
- Version: fire >= 0.6 | pip install fire
- Function: fire.Fire(fn) — positional → args; --key value → kwargs
- Class: fire.Fire(MyClass) — methods become subcommands; instantiates automatically
- Dict: fire.Fire({"cmd1": fn1, "cmd2": fn2}) — explicit sub-command registry
- Nested: fire.Fire(App()) where App has attribute objects — chains as subcommands
- Types: "true"/"false" → bool; "1" → int; "[1,2]" → list; --flag/--noflag for bools
- Help: auto-generated from docstrings; python app.py -- --help for each level
Python Fire CLI Pipeline
# app/cli.py — Python Fire CLIs: functions, classes, nested commands, and multi-tool
from __future__ import annotations
import json
import os
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
import fire
# ─────────────────────────────────────────────────────────────────────────────
# 1. Simple function CLI
# ─────────────────────────────────────────────────────────────────────────────
def greet(name: str, greeting: str = "Hello", *, count: int = 1, upper: bool = False) -> str:
"""
Print a greeting.
Args:
name: Person to greet.
greeting: Greeting word (default "Hello").
count: How many times to repeat.
upper: Convert to uppercase.
Usage:
python cli.py Alice
python cli.py Alice --greeting Hi --count 3
python cli.py Alice --upper
"""
msg = f"{greeting}, {name}!"
if upper:
msg = msg.upper()
for _ in range(count):
print(msg)
return msg
def add(*numbers: float) -> float:
"""Sum any number of arguments: python cli.py 1 2 3 4"""
total = sum(numbers)
print(f"Sum: {total}")
return total
# ─────────────────────────────────────────────────────────────────────────────
# 2. Class-based CLI (each method = subcommand)
# ─────────────────────────────────────────────────────────────────────────────
class DeployTool:
"""
Deployment CLI tool.
Usage:
python cli.py deploy --env staging
python cli.py deploy status
python cli.py deploy rollback --version v1.2.3
"""
def __init__(self, env: str = "development", dry_run: bool = False):
"""
Args:
env: Target environment (development|staging|production).
dry_run: Print actions without executing.
"""
self.env = env
self.dry_run = dry_run
def deploy(self, version: str = "latest", services: str = "all") -> dict[str, Any]:
"""
Deploy services to the environment.
Args:
version: Image tag to deploy.
services: Comma-separated service list, or "all".
"""
svc_list = services.split(",") if services != "all" else ["api", "worker", "scheduler"]
result = {
"env": self.env,
"version": version,
"services": svc_list,
"dry_run": self.dry_run,
}
if not self.dry_run:
for svc in svc_list:
print(f" Deploying {svc} @ {version} → {self.env}")
else:
print(f" [dry-run] Would deploy: {result}")
return result
def rollback(self, version: str) -> None:
"""Roll back all services to a previous version."""
if self.dry_run:
print(f" [dry-run] Would rollback to {version} in {self.env}")
else:
print(f" Rolling back {self.env} to {version}")
def status(self) -> dict[str, str]:
"""Show current deployment status."""
status = {
"env": self.env,
"api": "running",
"worker": "running",
"scheduler": "paused",
}
for k, v in status.items():
indicator = "✔" if v == "running" else "⚠"
print(f" {indicator} {k}: {v}")
return status
def logs(self, service: str = "api", lines: int = 50) -> None:
"""Tail logs for a service."""
print(f" [Showing last {lines} lines of {service} in {self.env}]")
# In real code: subprocess("kubectl logs ...", lines=lines)
# ─────────────────────────────────────────────────────────────────────────────
# 3. File operations CLI
# ─────────────────────────────────────────────────────────────────────────────
class FileTool:
"""
File manipulation commands.
python cli.py file count --path . --ext .py
python cli.py file find --pattern "*.log"
python cli.py file stats --path file.txt
"""
def count(self, path: str = ".", ext: str = "") -> dict[str, int]:
"""Count files by extension."""
root = Path(path)
counts: dict[str, int] = {}
for f in root.rglob("*"):
if f.is_file():
suffix = f.suffix or "(none)"
counts[suffix] = counts.get(suffix, 0) + 1
if ext:
counts = {k: v for k, v in counts.items() if k == ext}
for k, v in sorted(counts.items(), key=lambda x: -x[1])[:10]:
print(f" {k:<12} {v:>6}")
return counts
def find(self, pattern: str = "*.py", path: str = ".") -> list[str]:
"""Find files matching a glob pattern."""
matches = sorted(str(f) for f in Path(path).rglob(pattern))
for m in matches[:20]:
print(f" {m}")
if len(matches) > 20:
print(f" ... and {len(matches)-20} more")
return matches
def stats(self, path: str) -> dict[str, Any]:
"""Show file stats: size, lines, last modified."""
p = Path(path)
if not p.exists():
print(f" Error: {path} not found")
return {}
stat = p.stat()
lines = len(p.read_text(errors="replace").splitlines()) if p.is_file() else 0
info = {
"path": str(p),
"size": stat.st_size,
"lines": lines,
"modified": stat.st_mtime,
}
for k, v in info.items():
print(f" {k:<12} {v}")
return info
# ─────────────────────────────────────────────────────────────────────────────
# 4. Nested commands (attribute-based)
# ─────────────────────────────────────────────────────────────────────────────
class DBCommands:
"""Database operations."""
def migrate(self, target: str = "head") -> None:
"""Run database migrations."""
print(f" Running migrations → {target}")
def seed(self, env: str = "development") -> None:
"""Seed the database with test data."""
print(f" Seeding {env} database")
def reset(self, force: bool = False) -> None:
"""Drop and recreate the database (use --force to skip confirmation)."""
if not force:
confirm = input(" Reset database? [y/N] ").strip().lower()
if confirm != "y":
print(" Aborted")
return
print(" Resetting database...")
class ConfigCommands:
"""Configuration management."""
def show(self, env: str = "development") -> None:
"""Print current configuration for an environment."""
config = {"env": env, "debug": env == "development", "log_level": "INFO"}
for k, v in config.items():
print(f" {k}: {v!r}")
def validate(self, path: str = ".env") -> bool:
"""Validate a .env file for required keys."""
required = ["DATABASE_URL", "SECRET_KEY", "REDIS_URL"]
if not Path(path).exists():
print(f" File not found: {path}")
return False
content = Path(path).read_text()
missing = [k for k in required if k not in content]
if missing:
print(f" Missing keys: {missing}")
return False
print(" All required keys present ✔")
return True
class App:
"""
Multi-command application.
python cli.py db migrate
python cli.py db seed --env production
python cli.py config show --env staging
python cli.py config validate --path .env.prod
"""
def __init__(self):
self.db = DBCommands()
self.config = ConfigCommands()
self.file = FileTool()
self.deploy = DeployTool()
def version(self) -> str:
"""Print application version."""
v = "1.0.0"
print(f" v{v}")
return v
def health(self) -> dict[str, str]:
"""Run a health check."""
checks = {"database": "ok", "redis": "ok", "api": "ok"}
for service, status in checks.items():
indicator = "✔" if status == "ok" else "✗"
print(f" {indicator} {service}: {status}")
return checks
# ─────────────────────────────────────────────────────────────────────────────
# 5. Dict-based multi-tool
# ─────────────────────────────────────────────────────────────────────────────
def build(tag: str = "latest", push: bool = False, platform: str = "linux/amd64") -> None:
"""Build a Docker image."""
cmd = f"docker build -t myapp:{tag} --platform {platform} ."
if push:
cmd += f" && docker push myapp:{tag}"
print(f" {cmd}")
def test(path: str = ".", verbose: bool = False, coverage: bool = False) -> None:
"""Run the test suite."""
cmd = "pytest " + path
if verbose:
cmd += " -v"
if coverage:
cmd += " --cov=app --cov-report=term-missing"
print(f" {cmd}")
def lint(path: str = ".", fix: bool = False) -> None:
"""Run linters."""
for tool in ["ruff", "mypy"]:
suffix = " --fix" if fix and tool == "ruff" else ""
print(f" {tool}{suffix} {path}")
COMMANDS = {
"build": build,
"test": test,
"lint": lint,
"deploy": DeployTool,
"app": App,
}
# ─────────────────────────────────────────────────────────────────────────────
# Entry points
# ─────────────────────────────────────────────────────────────────────────────
def main_function():
"""fire.Fire on a single function."""
fire.Fire(greet)
def main_class():
"""fire.Fire on a class."""
fire.Fire(DeployTool)
def main_nested():
"""fire.Fire on a nested App instance."""
fire.Fire(App)
def main_dict():
"""fire.Fire on a dict of commands."""
fire.Fire(COMMANDS)
if __name__ == "__main__":
# Default: expose the full App with nested sub-commands
fire.Fire(App)
For the typer alternative — Typer provides type-annotated argument parsing with decorators (@app.command()), built-in help generation, and rich terminal output integration; Python Fire requires zero boilerplate to expose a function as a CLI (no decorators, no argument definitions), making it ideal for turning existing Python code into CLI scripts quickly, while Typer is better for production CLIs where you want fine-grained control over help text and argument validation. For the argparse alternative — argparse requires explicit add_argument() calls for each parameter; Python Fire reads function signatures, type annotations, default values, and docstrings automatically, eliminating 90% of the boilerplate for simple tools while still supporting complex nested sub-command hierarchies through class attributes. The Claude Skills 360 bundle includes Python Fire skill sets covering fire.Fire() on functions/classes/dicts, class method sub-commands, nested attribute commands, type coercion (bool/int/list), docstring help generation, DeployTool/FileTool/App class patterns, dict-based multi-command registry, variadic *args functions, —dry_run/—verbose boolean flags, and fire.Fire(App) nested application pattern. Start with the free tier to try auto-CLI code generation.