"""Asset catalog CLI commands.""" from __future__ import annotations import typer from app.cli.core.utils import execute_command asset_app = typer.Typer(help="Asset catalog management commands.") def _with_repo(ctx: typer.Context, callback): app_context = ctx.obj repository = app_context.repository_factory(app_context.settings) try: return execute_command(app_context.console, callback=lambda: callback(repository, app_context)) finally: if callable(getattr(type(repository), "close", None)): repository.close() def _confirm(force: bool, message: str) -> None: if force: return if not typer.confirm(message): raise typer.Exit(code=0) def _asset_to_dict(asset) -> dict: return { "asset_id": asset.asset_id, "symbol": asset.symbol, "markets": asset.markets, "display_name": asset.display_name, "status": asset.status, "created_at": str(asset.created_at) if asset.created_at else None, "updated_at": str(asset.updated_at) if asset.updated_at else None, } @asset_app.command("create") def asset_create( ctx: typer.Context, symbol: str = typer.Argument(..., help="Asset symbol (e.g. 005930, BTC)."), display_name: str | None = typer.Option(None, "--display-name", help="Human-readable name."), market: list[str] | None = typer.Option(None, "--market", help="Market code. Repeatable."), status: str = typer.Option("active", help="Asset status."), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt."), ) -> None: _confirm(force, f"Create asset {symbol!r}?") markets = list(market or []) def _callback(repository, app_context): return repository.insert_asset(symbol=symbol, markets=markets, display_name=display_name, status=status) result = _with_repo(ctx, _callback) d = _asset_to_dict(result) if ctx.obj.output_format == "json": ctx.obj.console.print_json(data=d) else: ctx.obj.console.print(f"Created asset: {d['symbol']} {d['asset_id']}") @asset_app.command("list") def asset_list( ctx: typer.Context, active_only: bool = typer.Option(False, "--active-only", help="Show active assets only."), ) -> None: def _callback(repository, app_context): assets = repository.list_assets() if active_only: assets = [a for a in assets if a.status == "active"] return assets results = _with_repo(ctx, _callback) items = [_asset_to_dict(a) for a in results] if results else [] if not items: ctx.obj.console.print("No assets found.") return if ctx.obj.output_format == "json": ctx.obj.console.print_json(data=items) else: for a in items: display_name = a["display_name"] or "" markets = ",".join(a["markets"]) if a["markets"] else "-" ctx.obj.console.print( f"{a['symbol']} {a['status']} [{markets}] {display_name} {a['asset_id']}" ) @asset_app.command("update") def asset_update( ctx: typer.Context, asset_id: str = typer.Argument(..., help="Asset UUID."), display_name: str | None = typer.Option(None, "--display-name", help="Updated display name."), market: list[str] | None = typer.Option(None, "--market", help="Updated market codes. Repeatable."), status: str | None = typer.Option(None, help="Updated status."), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt."), ) -> None: _confirm(force, f"Update asset {asset_id!r}?") markets = list(market) if market is not None else None def _callback(repository, app_context): return repository.update_asset(asset_id, markets=markets, display_name=display_name, status=status) result = _with_repo(ctx, _callback) d = _asset_to_dict(result) if ctx.obj.output_format == "json": ctx.obj.console.print_json(data=d) else: ctx.obj.console.print(f"Updated asset: {d['symbol']} {d['status']} {d['asset_id']}") @asset_app.command("deactivate") def asset_deactivate( ctx: typer.Context, asset_id: str = typer.Argument(..., help="Asset UUID."), force: bool = typer.Option(False, "--force", help="Skip confirmation prompt."), ) -> None: _confirm(force, f"Deactivate asset {asset_id!r}?") def _callback(repository, app_context): return repository.update_asset(asset_id, status="inactive") result = _with_repo(ctx, _callback) d = _asset_to_dict(result) if ctx.obj.output_format == "json": ctx.obj.console.print_json(data=d) else: ctx.obj.console.print(f"Deactivated asset: {d['symbol']} {d['status']} {d['asset_id']}")