419 lines
11 KiB
Markdown
419 lines
11 KiB
Markdown
# 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
|