# Advanced Fire CLI Patterns This guide covers advanced patterns and techniques for building sophisticated Fire CLIs. ## Pattern 1: Configuration Management with Persistence Implement persistent configuration storage: ```python import fire from rich.console import Console from pathlib import Path import json console = Console() class ConfigManager: """Handle configuration file I/O""" def __init__(self, config_path: Path): self.config_path = config_path self._ensure_exists() def _ensure_exists(self): """Create config file if missing""" self.config_path.parent.mkdir(parents=True, exist_ok=True) if not self.config_path.exists(): self.save({}) def load(self) -> dict: """Load configuration""" try: return json.loads(self.config_path.read_text()) except Exception as e: console.print(f"[red]Error loading config: {e}[/red]") return {} def save(self, config: dict): """Save configuration""" self.config_path.write_text(json.dumps(config, indent=2)) class MyCLI: def __init__(self): self.config_manager = ConfigManager( Path.home() / ".mycli" / "config.json" ) def configure(self, key, value): """Set configuration value""" config = self.config_manager.load() config[key] = value self.config_manager.save(config) console.print(f"[green]✓[/green] Set {key} = {value}") ``` ## Pattern 2: Environment-Based Configuration Handle multiple environments: ```python from enum import Enum class Environment(str, Enum): DEV = "dev" STAGING = "staging" PRODUCTION = "production" class MyCLI: def __init__(self): self.current_env = Environment.DEV def set_env(self, env: Environment): """Switch environment Args: env: Target environment (dev, staging, production) """ self.current_env = env console.print(f"[cyan]Environment: {env.value}[/cyan]") def deploy(self): """Deploy to current environment""" console.print(f"Deploying to {self.current_env.value}") # Usage: # python cli.py set-env staging # python cli.py deploy ``` ## Pattern 3: Property Access for Read-Only Values Use properties for values that should be readable but not settable: ```python class MyCLI: def __init__(self): self._version = "1.0.0" self._config_path = Path.home() / ".mycli" @property def version(self): """Get CLI version""" return self._version @property def config_path(self): """Get configuration path""" return str(self._config_path) # Usage: # python cli.py version # Returns "1.0.0" # python cli.py config-path # Returns path ``` ## Pattern 4: Validation and Error Handling Implement robust validation: ```python from pathlib import Path class MyCLI: def deploy(self, path: str, confirm=False): """Deploy from path Args: path: Path to deployment files confirm: Skip confirmation prompt """ deploy_path = Path(path) # Validate path exists if not deploy_path.exists(): console.print(f"[red]Error: Path not found: {path}[/red]") return {"status": "error", "message": "Path not found"} # Validate path is directory if not deploy_path.is_dir(): console.print(f"[red]Error: Not a directory: {path}[/red]") return {"status": "error", "message": "Not a directory"} # Require confirmation for sensitive operations if not confirm: console.print("[yellow]⚠ Use --confirm to proceed[/yellow]") return {"status": "cancelled"} # Perform deployment console.print(f"[green]Deploying from {path}...[/green]") return {"status": "success"} ``` ## Pattern 5: Chaining Commands Create chainable command patterns: ```python class Builder: """Build pipeline with chaining""" def __init__(self): self.steps = [] def clean(self): """Add clean step""" self.steps.append("clean") return self # Return self for chaining def build(self): """Add build step""" self.steps.append("build") return self def test(self): """Add test step""" self.steps.append("test") return self def execute(self): """Execute pipeline""" for step in self.steps: console.print(f"[cyan]Running: {step}[/cyan]") return {"steps": self.steps} class MyCLI: def __init__(self): self.builder = Builder() # Usage: # python cli.py builder clean # python cli.py builder build # python cli.py builder clean build test execute ``` ## Pattern 6: Context Managers for Resources Use context managers for resource handling: ```python from contextlib import contextmanager class MyCLI: @contextmanager def _deployment_context(self, env): """Context manager for deployments""" console.print(f"[cyan]Starting deployment to {env}...[/cyan]") try: yield console.print("[green]✓[/green] Deployment successful") except Exception as e: console.print(f"[red]✗ Deployment failed: {e}[/red]") raise finally: console.print("[dim]Cleanup complete[/dim]") def deploy(self, env='staging'): """Deploy with context management""" with self._deployment_context(env): # Deployment logic here console.print(" [dim]Building...[/dim]") console.print(" [dim]Uploading...[/dim]") ``` ## Pattern 7: Plugin Architecture Create extensible CLI with plugins: ```python from abc import ABC, abstractmethod from typing import List class Plugin(ABC): """Base plugin class""" @abstractmethod def execute(self, *args, **kwargs): """Execute plugin""" pass class DatabasePlugin(Plugin): """Database operations plugin""" def execute(self, operation): console.print(f"[cyan]Database: {operation}[/cyan]") class CachePlugin(Plugin): """Cache operations plugin""" def execute(self, operation): console.print(f"[yellow]Cache: {operation}[/yellow]") class MyCLI: def __init__(self): self.plugins: List[Plugin] = [ DatabasePlugin(), CachePlugin() ] def run_plugins(self, operation): """Execute operation on all plugins Args: operation: Operation to run """ for plugin in self.plugins: plugin.execute(operation) ``` ## Pattern 8: Async Operations Handle async operations in Fire CLI: ```python import asyncio from typing import List class MyCLI: def fetch(self, urls: List[str]): """Fetch multiple URLs concurrently Args: urls: List of URLs to fetch """ async def fetch_all(): tasks = [self._fetch_one(url) for url in urls] return await asyncio.gather(*tasks) results = asyncio.run(fetch_all()) return {"fetched": len(results)} async def _fetch_one(self, url): """Fetch single URL""" # Simulated async fetch await asyncio.sleep(0.1) return url # Usage: # python cli.py fetch https://example.com https://google.com ``` ## Pattern 9: Dry Run Mode Implement dry-run capability: ```python class MyCLI: def __init__(self): self.dry_run = False def deploy(self, env, dry_run=False): """Deploy to environment Args: env: Target environment dry_run: Show what would happen without executing """ self.dry_run = dry_run if self.dry_run: console.print("[yellow]DRY RUN MODE[/yellow]") self._execute("Build project", lambda: self._build()) self._execute("Run tests", lambda: self._test()) self._execute("Upload files", lambda: self._upload(env)) def _execute(self, description, action): """Execute or simulate action""" if self.dry_run: console.print(f"[dim]Would: {description}[/dim]") else: console.print(f"[cyan]{description}...[/cyan]") action() def _build(self): pass # Build logic def _test(self): pass # Test logic def _upload(self, env): pass # Upload logic ``` ## Pattern 10: Logging Integration Integrate structured logging: ```python import logging from pathlib import Path class MyCLI: def __init__(self): self._setup_logging() def _setup_logging(self): """Configure logging""" log_file = Path.home() / ".mycli" / "cli.log" log_file.parent.mkdir(parents=True, exist_ok=True) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_file), logging.StreamHandler() ] ) self.logger = logging.getLogger(__name__) def deploy(self, env): """Deploy with logging""" self.logger.info(f"Starting deployment to {env}") try: console.print(f"[cyan]Deploying to {env}...[/cyan]") # Deployment logic self.logger.info("Deployment successful") console.print("[green]✓[/green] Deployed!") except Exception as e: self.logger.error(f"Deployment failed: {e}") console.print(f"[red]✗ Error: {e}[/red]") raise ``` ## Pattern 11: Interactive Confirmation Add interactive prompts: ```python class MyCLI: def delete(self, resource, force=False): """Delete resource with confirmation Args: resource: Resource to delete force: Skip confirmation """ if not force: console.print(f"[yellow]⚠ Delete {resource}?[/yellow]") console.print("[dim]Use --force to skip this prompt[/dim]") return console.print(f"[red]Deleting {resource}...[/red]") # Delete logic here console.print("[green]✓[/green] Deleted") ``` ## Best Practices Summary 1. **Error Handling**: Always validate inputs and handle errors gracefully 2. **Confirmation**: Require confirmation for destructive operations 3. **Dry Run**: Implement dry-run mode for risky operations 4. **Logging**: Log important actions to file for audit trail 5. **Type Hints**: Use type hints for better IDE support 6. **Properties**: Use properties for read-only values 7. **Context Managers**: Use context managers for resource cleanup 8. **Enums**: Use Enums for constrained choices 9. **Async**: Use asyncio for concurrent operations 10. **Plugins**: Design for extensibility with plugin architecture