Initial commit
This commit is contained in:
50
skills/typer-patterns/examples/basic-cli/README.md
Normal file
50
skills/typer-patterns/examples/basic-cli/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Basic CLI Example
|
||||
|
||||
Simple type-safe CLI demonstrating fundamental Typer patterns.
|
||||
|
||||
## Features
|
||||
|
||||
- Type hints on all parameters
|
||||
- Path validation
|
||||
- Optional output file
|
||||
- Boolean flags
|
||||
- Verbose mode
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Process and display
|
||||
python cli.py input.txt
|
||||
|
||||
# Process and save
|
||||
python cli.py input.txt --output result.txt
|
||||
|
||||
# Convert to uppercase
|
||||
python cli.py input.txt --uppercase
|
||||
|
||||
# Verbose output
|
||||
python cli.py input.txt --verbose
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Create test file
|
||||
echo "hello world" > test.txt
|
||||
|
||||
# Run CLI
|
||||
python cli.py test.txt --uppercase
|
||||
# Output: HELLO WORLD
|
||||
|
||||
# Save to file
|
||||
python cli.py test.txt --output out.txt --uppercase --verbose
|
||||
# Outputs: Processing: test.txt
|
||||
# ✓ Written to: out.txt
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
1. **Path type**: Automatic validation of file existence
|
||||
2. **Optional parameters**: Using `Optional[Path]` for optional output
|
||||
3. **Boolean flags**: Simple `bool` type for flags
|
||||
4. **Colored output**: Using `typer.secho()` for success messages
|
||||
43
skills/typer-patterns/examples/basic-cli/cli.py
Normal file
43
skills/typer-patterns/examples/basic-cli/cli.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Basic CLI example using type hints.
|
||||
|
||||
This example demonstrates:
|
||||
- Simple type-safe command
|
||||
- Path validation
|
||||
- Optional parameters
|
||||
- Verbose output
|
||||
"""
|
||||
|
||||
import typer
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
app = typer.Typer(help="Basic file processing CLI")
|
||||
|
||||
|
||||
@app.command()
|
||||
def process(
|
||||
input_file: Path = typer.Argument(
|
||||
..., help="Input file to process", exists=True
|
||||
),
|
||||
output: Optional[Path] = typer.Option(None, "--output", "-o"),
|
||||
uppercase: bool = typer.Option(False, "--uppercase", "-u"),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
||||
) -> None:
|
||||
"""Process text file with optional transformations."""
|
||||
if verbose:
|
||||
typer.echo(f"Processing: {input_file}")
|
||||
|
||||
content = input_file.read_text()
|
||||
|
||||
if uppercase:
|
||||
content = content.upper()
|
||||
|
||||
if output:
|
||||
output.write_text(content)
|
||||
typer.secho(f"✓ Written to: {output}", fg=typer.colors.GREEN)
|
||||
else:
|
||||
typer.echo(content)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
66
skills/typer-patterns/examples/enum-cli/README.md
Normal file
66
skills/typer-patterns/examples/enum-cli/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Enum CLI Example
|
||||
|
||||
Type-safe CLI using Enums for constrained choices.
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple Enum types (LogLevel, OutputFormat)
|
||||
- Autocomplete support
|
||||
- Type-safe matching with match/case
|
||||
- Validated input values
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Export as JSON (default)
|
||||
python cli.py export
|
||||
|
||||
# Export as YAML
|
||||
python cli.py export yaml
|
||||
|
||||
# Export with custom log level
|
||||
python cli.py export json --log-level debug
|
||||
|
||||
# Save to file
|
||||
python cli.py export yaml --output config.yaml
|
||||
|
||||
# Validate log level
|
||||
python cli.py validate info
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Export JSON to console
|
||||
python cli.py export json
|
||||
# Output: {"app": "example", "version": "1.0.0", ...}
|
||||
|
||||
# Export YAML to file
|
||||
python cli.py export yaml --output test.yaml --log-level warning
|
||||
# Creates test.yaml with YAML format
|
||||
|
||||
# Validate enum value
|
||||
python cli.py validate error
|
||||
# Output: Log Level: error
|
||||
# Severity: High - error messages
|
||||
|
||||
# Invalid enum (will fail with helpful message)
|
||||
python cli.py export invalid
|
||||
# Error: Invalid value for 'FORMAT': 'invalid' is not one of 'json', 'yaml', 'text'.
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
1. **Enum as Argument**: `format: OutputFormat = typer.Argument(...)`
|
||||
2. **Enum as Option**: `log_level: LogLevel = typer.Option(...)`
|
||||
3. **String Enum**: Inherit from `str, Enum` for string values
|
||||
4. **Match/Case**: Use pattern matching with enum values
|
||||
5. **Autocomplete**: Automatic shell completion for enum values
|
||||
|
||||
## Benefits
|
||||
|
||||
- Type safety at runtime and compile time
|
||||
- IDE autocomplete for enum values
|
||||
- Automatic validation of inputs
|
||||
- Self-documenting constrained choices
|
||||
- Easy to extend with new values
|
||||
102
skills/typer-patterns/examples/enum-cli/cli.py
Normal file
102
skills/typer-patterns/examples/enum-cli/cli.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""Enum-based CLI example.
|
||||
|
||||
This example demonstrates:
|
||||
- Enum usage for constrained choices
|
||||
- Multiple enum types
|
||||
- Autocomplete with enums
|
||||
- Match/case with enum values
|
||||
"""
|
||||
|
||||
import typer
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
import json
|
||||
|
||||
|
||||
class LogLevel(str, Enum):
|
||||
"""Logging levels."""
|
||||
|
||||
debug = "debug"
|
||||
info = "info"
|
||||
warning = "warning"
|
||||
error = "error"
|
||||
|
||||
|
||||
class OutputFormat(str, Enum):
|
||||
"""Output formats."""
|
||||
|
||||
json = "json"
|
||||
yaml = "yaml"
|
||||
text = "text"
|
||||
|
||||
|
||||
app = typer.Typer(help="Configuration export CLI with Enums")
|
||||
|
||||
|
||||
@app.command()
|
||||
def export(
|
||||
format: OutputFormat = typer.Argument(
|
||||
OutputFormat.json, help="Export format"
|
||||
),
|
||||
log_level: LogLevel = typer.Option(
|
||||
LogLevel.info, "--log-level", "-l", help="Logging level"
|
||||
),
|
||||
output: Optional[str] = typer.Option(None, "--output", "-o"),
|
||||
) -> None:
|
||||
"""Export configuration in specified format.
|
||||
|
||||
The format parameter uses an Enum, providing:
|
||||
- Autocomplete in the shell
|
||||
- Validation of input values
|
||||
- Type safety in code
|
||||
"""
|
||||
# Sample data
|
||||
data = {
|
||||
"app": "example",
|
||||
"version": "1.0.0",
|
||||
"log_level": log_level.value,
|
||||
"features": ["auth", "api", "cache"],
|
||||
}
|
||||
|
||||
# Format output based on enum
|
||||
match format:
|
||||
case OutputFormat.json:
|
||||
output_text = json.dumps(data, indent=2)
|
||||
case OutputFormat.yaml:
|
||||
# Simplified YAML output
|
||||
output_text = "\n".join(f"{k}: {v}" for k, v in data.items())
|
||||
case OutputFormat.text:
|
||||
output_text = "\n".join(
|
||||
f"{k.upper()}: {v}" for k, v in data.items()
|
||||
)
|
||||
|
||||
# Output
|
||||
if output:
|
||||
with open(output, "w") as f:
|
||||
f.write(output_text)
|
||||
typer.secho(f"✓ Exported to {output}", fg=typer.colors.GREEN)
|
||||
else:
|
||||
typer.echo(output_text)
|
||||
|
||||
|
||||
@app.command()
|
||||
def validate(
|
||||
level: LogLevel = typer.Argument(..., help="Log level to validate")
|
||||
) -> None:
|
||||
"""Validate and display log level information."""
|
||||
typer.echo(f"Log Level: {level.value}")
|
||||
|
||||
# Access enum properties
|
||||
match level:
|
||||
case LogLevel.debug:
|
||||
typer.echo("Severity: Lowest - detailed debugging information")
|
||||
case LogLevel.info:
|
||||
typer.echo("Severity: Low - informational messages")
|
||||
case LogLevel.warning:
|
||||
typer.echo("Severity: Medium - warning messages")
|
||||
case LogLevel.error:
|
||||
typer.echo("Severity: High - error messages")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
128
skills/typer-patterns/examples/factory-cli/README.md
Normal file
128
skills/typer-patterns/examples/factory-cli/README.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Factory Pattern CLI Example
|
||||
|
||||
Testable CLI using factory pattern with dependency injection.
|
||||
|
||||
## Features
|
||||
|
||||
- Factory function for app creation
|
||||
- Dependency injection via Protocol
|
||||
- Multiple storage implementations
|
||||
- Configuration injection
|
||||
- Highly testable structure
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Save data
|
||||
python cli.py save name "John Doe"
|
||||
python cli.py save email "john@example.com"
|
||||
|
||||
# Load data
|
||||
python cli.py load name
|
||||
# Output: John Doe
|
||||
|
||||
python cli.py load email
|
||||
# Output: john@example.com
|
||||
|
||||
# Show configuration
|
||||
python cli.py config-show
|
||||
|
||||
# Use verbose mode
|
||||
python cli.py --verbose save status "active"
|
||||
|
||||
# Custom data directory
|
||||
python cli.py --data-dir /tmp/mydata save test "value"
|
||||
```
|
||||
|
||||
## Testing Example
|
||||
|
||||
```python
|
||||
# test_cli.py
|
||||
from cli import create_app, Config, MemoryStorage
|
||||
from typer.testing import CliRunner
|
||||
|
||||
def test_save_and_load():
|
||||
"""Test save and load commands."""
|
||||
# Create test configuration
|
||||
config = Config(verbose=True)
|
||||
storage = MemoryStorage()
|
||||
|
||||
# Create app with test dependencies
|
||||
app = create_app(config=config, storage=storage)
|
||||
|
||||
# Test runner
|
||||
runner = CliRunner()
|
||||
|
||||
# Test save
|
||||
result = runner.invoke(app, ["save", "test_key", "test_value"])
|
||||
assert result.exit_code == 0
|
||||
assert "Saved test_key" in result.output
|
||||
|
||||
# Test load
|
||||
result = runner.invoke(app, ["load", "test_key"])
|
||||
assert result.exit_code == 0
|
||||
assert "test_value" in result.output
|
||||
```
|
||||
|
||||
Run tests:
|
||||
```bash
|
||||
pytest test_cli.py
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Factory Function
|
||||
```python
|
||||
def create_app(config: Config, storage: Storage) -> typer.Typer:
|
||||
"""Create app with injected dependencies."""
|
||||
```
|
||||
|
||||
### Storage Protocol
|
||||
```python
|
||||
class Storage(Protocol):
|
||||
def save(self, key: str, value: str) -> None: ...
|
||||
def load(self, key: str) -> str: ...
|
||||
```
|
||||
|
||||
### Implementations
|
||||
- `MemoryStorage`: In-memory storage (for testing)
|
||||
- `FileStorage`: File-based storage (for production)
|
||||
|
||||
## Key Patterns
|
||||
|
||||
1. **Factory Function**: Returns configured Typer app
|
||||
2. **Protocol Types**: Interface for dependency injection
|
||||
3. **Dataclass Config**: Type-safe configuration
|
||||
4. **Dependency Injection**: Pass storage and config to factory
|
||||
5. **Testability**: Easy to mock dependencies in tests
|
||||
|
||||
## Benefits
|
||||
|
||||
- Unit testable without file I/O
|
||||
- Swap implementations easily
|
||||
- Configuration flexibility
|
||||
- Clean dependency management
|
||||
- Follows SOLID principles
|
||||
- Easy to extend with new storage types
|
||||
|
||||
## Extension Example
|
||||
|
||||
Add new storage type:
|
||||
|
||||
```python
|
||||
class DatabaseStorage:
|
||||
"""Database storage implementation."""
|
||||
|
||||
def __init__(self, connection_string: str) -> None:
|
||||
self.conn = connect(connection_string)
|
||||
|
||||
def save(self, key: str, value: str) -> None:
|
||||
self.conn.execute("INSERT INTO data VALUES (?, ?)", (key, value))
|
||||
|
||||
def load(self, key: str) -> str:
|
||||
return self.conn.execute("SELECT value FROM data WHERE key = ?", (key,)).fetchone()
|
||||
|
||||
# Use it
|
||||
storage = DatabaseStorage("postgresql://localhost/mydb")
|
||||
app = create_app(storage=storage)
|
||||
```
|
||||
162
skills/typer-patterns/examples/factory-cli/cli.py
Normal file
162
skills/typer-patterns/examples/factory-cli/cli.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""Factory pattern CLI example.
|
||||
|
||||
This example demonstrates:
|
||||
- Factory function for app creation
|
||||
- Dependency injection
|
||||
- Testable CLI structure
|
||||
- Configuration injection
|
||||
"""
|
||||
|
||||
import typer
|
||||
from typing import Protocol, Optional
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass
|
||||
class Config:
|
||||
"""Application configuration."""
|
||||
|
||||
verbose: bool = False
|
||||
data_dir: Path = Path("./data")
|
||||
max_items: int = 100
|
||||
|
||||
|
||||
class Storage(Protocol):
|
||||
"""Storage interface for dependency injection."""
|
||||
|
||||
def save(self, key: str, value: str) -> None:
|
||||
"""Save data."""
|
||||
...
|
||||
|
||||
def load(self, key: str) -> str:
|
||||
"""Load data."""
|
||||
...
|
||||
|
||||
|
||||
class MemoryStorage:
|
||||
"""In-memory storage implementation."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.data: dict[str, str] = {}
|
||||
|
||||
def save(self, key: str, value: str) -> None:
|
||||
"""Save to memory."""
|
||||
self.data[key] = value
|
||||
|
||||
def load(self, key: str) -> str:
|
||||
"""Load from memory."""
|
||||
return self.data.get(key, "")
|
||||
|
||||
|
||||
class FileStorage:
|
||||
"""File-based storage implementation."""
|
||||
|
||||
def __init__(self, base_dir: Path) -> None:
|
||||
self.base_dir = base_dir
|
||||
self.base_dir.mkdir(exist_ok=True)
|
||||
|
||||
def save(self, key: str, value: str) -> None:
|
||||
"""Save to file."""
|
||||
file_path = self.base_dir / f"{key}.txt"
|
||||
file_path.write_text(value)
|
||||
|
||||
def load(self, key: str) -> str:
|
||||
"""Load from file."""
|
||||
file_path = self.base_dir / f"{key}.txt"
|
||||
return file_path.read_text() if file_path.exists() else ""
|
||||
|
||||
|
||||
def create_app(config: Optional[Config] = None, storage: Optional[Storage] = None) -> typer.Typer:
|
||||
"""Factory function to create Typer app with dependencies.
|
||||
|
||||
This pattern enables:
|
||||
- Dependency injection for testing
|
||||
- Configuration flexibility
|
||||
- Multiple app instances
|
||||
- Easier unit testing
|
||||
|
||||
Args:
|
||||
config: Application configuration
|
||||
storage: Storage implementation
|
||||
|
||||
Returns:
|
||||
Configured Typer application
|
||||
"""
|
||||
config = config or Config()
|
||||
storage = storage or FileStorage(config.data_dir)
|
||||
|
||||
app = typer.Typer(
|
||||
help="Data management CLI with factory pattern",
|
||||
no_args_is_help=True,
|
||||
)
|
||||
|
||||
@app.command()
|
||||
def save(
|
||||
key: str = typer.Argument(..., help="Data key"),
|
||||
value: str = typer.Argument(..., help="Data value"),
|
||||
) -> None:
|
||||
"""Save data using injected storage."""
|
||||
if config.verbose:
|
||||
typer.echo(f"Saving {key}={value}")
|
||||
|
||||
try:
|
||||
storage.save(key, value)
|
||||
typer.secho(f"✓ Saved {key}", fg=typer.colors.GREEN)
|
||||
except Exception as e:
|
||||
typer.secho(f"✗ Error: {e}", fg=typer.colors.RED, err=True)
|
||||
raise typer.Exit(1)
|
||||
|
||||
@app.command()
|
||||
def load(key: str = typer.Argument(..., help="Data key")) -> None:
|
||||
"""Load data using injected storage."""
|
||||
if config.verbose:
|
||||
typer.echo(f"Loading {key}")
|
||||
|
||||
try:
|
||||
value = storage.load(key)
|
||||
if value:
|
||||
typer.echo(value)
|
||||
else:
|
||||
typer.secho(f"✗ Key not found: {key}", fg=typer.colors.YELLOW)
|
||||
except Exception as e:
|
||||
typer.secho(f"✗ Error: {e}", fg=typer.colors.RED, err=True)
|
||||
raise typer.Exit(1)
|
||||
|
||||
@app.command()
|
||||
def config_show() -> None:
|
||||
"""Show current configuration."""
|
||||
typer.echo("Configuration:")
|
||||
typer.echo(f" Verbose: {config.verbose}")
|
||||
typer.echo(f" Data dir: {config.data_dir}")
|
||||
typer.echo(f" Max items: {config.max_items}")
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main entry point with configuration."""
|
||||
import sys
|
||||
|
||||
# Parse global flags
|
||||
verbose = "--verbose" in sys.argv or "-v" in sys.argv
|
||||
data_dir = Path("./data")
|
||||
|
||||
# Check for custom data directory
|
||||
if "--data-dir" in sys.argv:
|
||||
idx = sys.argv.index("--data-dir")
|
||||
if idx + 1 < len(sys.argv):
|
||||
data_dir = Path(sys.argv[idx + 1])
|
||||
sys.argv.pop(idx) # Remove flag
|
||||
sys.argv.pop(idx) # Remove value
|
||||
|
||||
# Create configuration
|
||||
config = Config(verbose=verbose, data_dir=data_dir)
|
||||
|
||||
# Create and run app
|
||||
app = create_app(config=config)
|
||||
app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
77
skills/typer-patterns/examples/subapp-cli/README.md
Normal file
77
skills/typer-patterns/examples/subapp-cli/README.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Sub-Application CLI Example
|
||||
|
||||
Multi-level CLI with organized command groups.
|
||||
|
||||
## Features
|
||||
|
||||
- Three sub-apps: db, server, user
|
||||
- Shared context via callback
|
||||
- Global options (--config, --verbose)
|
||||
- Clean command hierarchy
|
||||
- Logical command grouping
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Show main help
|
||||
python cli.py --help
|
||||
|
||||
# Show sub-app help
|
||||
python cli.py db --help
|
||||
python cli.py server --help
|
||||
python cli.py user --help
|
||||
|
||||
# Database commands
|
||||
python cli.py db init
|
||||
python cli.py db migrate --steps 5
|
||||
python cli.py db seed
|
||||
|
||||
# Server commands
|
||||
python cli.py server start --port 8080
|
||||
python cli.py server stop
|
||||
python cli.py server restart
|
||||
|
||||
# User commands
|
||||
python cli.py user create alice --email alice@example.com
|
||||
python cli.py user create bob --email bob@example.com --admin
|
||||
python cli.py user list
|
||||
python cli.py user delete alice
|
||||
|
||||
# Global options
|
||||
python cli.py --verbose db migrate
|
||||
python cli.py --config prod.yaml server start
|
||||
```
|
||||
|
||||
## Command Structure
|
||||
|
||||
```
|
||||
cli.py
|
||||
├── db
|
||||
│ ├── init - Initialize database
|
||||
│ ├── migrate - Run migrations
|
||||
│ └── seed - Seed test data
|
||||
├── server
|
||||
│ ├── start - Start server
|
||||
│ ├── stop - Stop server
|
||||
│ └── restart - Restart server
|
||||
└── user
|
||||
├── create - Create user
|
||||
├── delete - Delete user
|
||||
└── list - List users
|
||||
```
|
||||
|
||||
## Key Patterns
|
||||
|
||||
1. **Sub-App Creation**: `db_app = typer.Typer()`
|
||||
2. **Adding Sub-Apps**: `app.add_typer(db_app, name="db")`
|
||||
3. **Global Callback**: `@app.callback()` for shared options
|
||||
4. **Context Sharing**: `ctx.obj` for passing data to sub-commands
|
||||
5. **Command Organization**: Group related commands in sub-apps
|
||||
|
||||
## Benefits
|
||||
|
||||
- Clear command hierarchy
|
||||
- Easier navigation with help text
|
||||
- Logical grouping of functionality
|
||||
- Shared configuration across commands
|
||||
- Scalable structure for large CLIs
|
||||
132
skills/typer-patterns/examples/subapp-cli/cli.py
Normal file
132
skills/typer-patterns/examples/subapp-cli/cli.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""Sub-application CLI example.
|
||||
|
||||
This example demonstrates:
|
||||
- Multi-level command structure
|
||||
- Sub-apps for logical grouping
|
||||
- Shared context across commands
|
||||
- Clean command organization
|
||||
"""
|
||||
|
||||
import typer
|
||||
from typing import Optional
|
||||
from pathlib import Path
|
||||
|
||||
# Main app
|
||||
app = typer.Typer(
|
||||
help="Project management CLI with sub-commands", add_completion=True
|
||||
)
|
||||
|
||||
# Sub-applications
|
||||
db_app = typer.Typer(help="Database commands")
|
||||
server_app = typer.Typer(help="Server commands")
|
||||
user_app = typer.Typer(help="User management commands")
|
||||
|
||||
# Add sub-apps to main app
|
||||
app.add_typer(db_app, name="db")
|
||||
app.add_typer(server_app, name="server")
|
||||
app.add_typer(user_app, name="user")
|
||||
|
||||
|
||||
# Global callback for shared options
|
||||
@app.callback()
|
||||
def main(
|
||||
ctx: typer.Context,
|
||||
config: Optional[Path] = typer.Option(None, "--config", "-c"),
|
||||
verbose: bool = typer.Option(False, "--verbose", "-v"),
|
||||
) -> None:
|
||||
"""Global options for all commands."""
|
||||
ctx.obj = {"config": config, "verbose": verbose}
|
||||
if verbose:
|
||||
typer.echo(f"Config: {config or 'default'}")
|
||||
|
||||
|
||||
# Database commands
|
||||
@db_app.command("init")
|
||||
def db_init(ctx: typer.Context) -> None:
|
||||
"""Initialize database."""
|
||||
if ctx.obj["verbose"]:
|
||||
typer.echo("Initializing database...")
|
||||
typer.secho("✓ Database initialized", fg=typer.colors.GREEN)
|
||||
|
||||
|
||||
@db_app.command("migrate")
|
||||
def db_migrate(ctx: typer.Context, steps: int = typer.Option(1)) -> None:
|
||||
"""Run database migrations."""
|
||||
if ctx.obj["verbose"]:
|
||||
typer.echo(f"Running {steps} migration(s)...")
|
||||
typer.secho("✓ Migrations complete", fg=typer.colors.GREEN)
|
||||
|
||||
|
||||
@db_app.command("seed")
|
||||
def db_seed(ctx: typer.Context) -> None:
|
||||
"""Seed database with test data."""
|
||||
if ctx.obj["verbose"]:
|
||||
typer.echo("Seeding database...")
|
||||
typer.secho("✓ Database seeded", fg=typer.colors.GREEN)
|
||||
|
||||
|
||||
# Server commands
|
||||
@server_app.command("start")
|
||||
def server_start(
|
||||
ctx: typer.Context,
|
||||
port: int = typer.Option(8000, "--port", "-p"),
|
||||
host: str = typer.Option("127.0.0.1", "--host"),
|
||||
) -> None:
|
||||
"""Start application server."""
|
||||
if ctx.obj["verbose"]:
|
||||
typer.echo(f"Starting server on {host}:{port}...")
|
||||
typer.secho(f"✓ Server running at http://{host}:{port}", fg=typer.colors.GREEN)
|
||||
|
||||
|
||||
@server_app.command("stop")
|
||||
def server_stop(ctx: typer.Context) -> None:
|
||||
"""Stop application server."""
|
||||
if ctx.obj["verbose"]:
|
||||
typer.echo("Stopping server...")
|
||||
typer.secho("✓ Server stopped", fg=typer.colors.RED)
|
||||
|
||||
|
||||
@server_app.command("restart")
|
||||
def server_restart(ctx: typer.Context) -> None:
|
||||
"""Restart application server."""
|
||||
if ctx.obj["verbose"]:
|
||||
typer.echo("Restarting server...")
|
||||
typer.secho("✓ Server restarted", fg=typer.colors.GREEN)
|
||||
|
||||
|
||||
# User commands
|
||||
@user_app.command("create")
|
||||
def user_create(
|
||||
ctx: typer.Context,
|
||||
username: str = typer.Argument(...),
|
||||
email: str = typer.Option(..., "--email", "-e"),
|
||||
admin: bool = typer.Option(False, "--admin"),
|
||||
) -> None:
|
||||
"""Create a new user."""
|
||||
if ctx.obj["verbose"]:
|
||||
typer.echo(f"Creating user: {username}")
|
||||
role = "admin" if admin else "user"
|
||||
typer.secho(f"✓ User {username} created as {role}", fg=typer.colors.GREEN)
|
||||
|
||||
|
||||
@user_app.command("delete")
|
||||
def user_delete(ctx: typer.Context, username: str = typer.Argument(...)) -> None:
|
||||
"""Delete a user."""
|
||||
confirm = typer.confirm(f"Delete user {username}?")
|
||||
if not confirm:
|
||||
typer.echo("Cancelled")
|
||||
raise typer.Abort()
|
||||
typer.secho(f"✓ User {username} deleted", fg=typer.colors.RED)
|
||||
|
||||
|
||||
@user_app.command("list")
|
||||
def user_list(ctx: typer.Context) -> None:
|
||||
"""List all users."""
|
||||
users = ["alice", "bob", "charlie"]
|
||||
typer.echo("Users:")
|
||||
for user in users:
|
||||
typer.echo(f" - {user}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
Reference in New Issue
Block a user