| name | cli-module |
| description | myfy CliModule for custom CLI commands with DI injection. Use when working with CliModule, @cli.command decorator, command groups, CLI arguments, CLI options, or myfy app commands. |
CliModule - Custom CLI Commands
CliModule enables custom CLI commands with full dependency injection and Typer integration.
Quick Start
from myfy.core import Application
from myfy.commands import CliModule, cli
@cli.command()
async def seed_users(user_service: UserService, count: int = 10):
'''Seed the database with test users.'''
for i in range(count):
await user_service.create(f"user{i}@example.com")
print(f"Created {count} users")
app = Application()
app.add_module(CliModule())
Configuration
Environment variables use the MYFY_CLI_ prefix:
| Variable | Default | Description |
|---|
MYFY_CLI_VERBOSE | False | Enable verbose output |
MYFY_CLI_NO_COLOR | False | Disable colored output |
MYFY_CLI_TIMEOUT | 300 | Command timeout (seconds) |
Defining Commands
Basic Command
from myfy.commands import cli
@cli.command()
async def hello():
'''Say hello.'''
print("Hello, World!")
With DI Injection
Services are automatically injected:
from myfy.commands import cli
from myfy.data import AsyncSession
@cli.command()
async def count_users(session: AsyncSession):
'''Count users in the database.'''
result = await session.execute(select(func.count(User.id)))
count = result.scalar()
print(f"Total users: {count}")
With Arguments
@cli.command()
async def greet(name: str):
'''Greet someone by name.'''
print(f"Hello, {name}!")
With Options
@cli.command()
async def seed(count: int = 10, verbose: bool = False):
'''Seed the database.'''
for i in range(count):
if verbose:
print(f"Creating user {i + 1}/{count}")
await create_user(i)
print(f"Created {count} users")
Command Groups
Organize related commands:
from myfy.commands import cli
db = cli.group("db")
@db.command()
async def seed(session: AsyncSession):
'''Seed the database.'''
await seed_data(session)
@db.command()
async def reset(session: AsyncSession, force: bool = False):
'''Reset the database.'''
if force or confirm("Are you sure?"):
await reset_data(session)
Typer Integration
Use Typer for advanced CLI features:
import typer
from myfy.commands import cli
@cli.command()
async def import_data(
db: Database,
file: str = typer.Argument(..., help="Path to import file"),
dry_run: bool = typer.Option(False, "--dry-run", "-n", help="Simulate import"),
format: str = typer.Option("json", "--format", "-f", help="File format"),
):
'''Import data from a file.'''
if dry_run:
print(f"Would import from {file} ({format} format)")
else:
await db.import_from_file(file, format)
Typer Features
import typer
@cli.command()
async def deploy(
env: str = typer.Argument(..., help="Target environment"),
version: str = typer.Option(None, "--version", "-v"),
skip_tests: bool = typer.Option(False, "--skip-tests"),
region: Region = typer.Option(Region.US, "--region", "-r"),
):
'''Deploy the application.'''
...
Parameter Classification
Parameters are automatically classified:
| Pattern | Classification |
|---|
typer.Argument(...) | CLI positional argument |
typer.Option(...) | CLI option/flag |
| Primitive with default | CLI option |
| Primitive without default | CLI argument |
| Complex type | DI dependency |
@cli.command()
async def process(
file: str,
count: int = 10,
verbose: bool = False,
session: AsyncSession,
settings: AppSettings,
):
...
Custom Command Names
@cli.command(name="import-users")
async def import_users_handler(file: str):
'''Import users from file.'''
...
Function name import_users_handler becomes command name import-users by default (underscores to hyphens).
Async vs Sync
Both async and sync handlers are supported:
@cli.command()
async def async_task(session: AsyncSession):
await session.execute(...)
@cli.command()
def sync_task(settings: AppSettings):
print(settings.app_name)
Error Handling
@cli.command()
async def risky_operation(session: AsyncSession):
'''Perform a risky operation.'''
try:
await do_risky_thing(session)
except RiskyError as e:
print(f"Error: {e}", file=sys.stderr)
raise typer.Exit(1)
Progress and Output
import typer
@cli.command()
async def process_files(files: list[str]):
'''Process multiple files.'''
with typer.progressbar(files) as progress:
for file in progress:
await process_file(file)
typer.echo(typer.style("Done!", fg=typer.colors.GREEN))
Running Commands
myfy app --help
myfy app seed-users
myfy app seed-users --count 50
myfy app db:reset --force
myfy app import users.csv
Best Practices
- Use docstrings - Become command help text
- Group related commands - Easier to discover
- Use Typer for complex args - Better help messages
- Handle errors gracefully - Use
typer.Exit(1) for failures
- Show progress - Use
typer.progressbar for long operations
- Inject services - Don't create DB connections manually
- Keep commands focused - One task per command