feat(cli): agnes admin metrics {import,export,validate}
This commit is contained in:
parent
42b8d0309b
commit
ff5da0af90
3 changed files with 105 additions and 0 deletions
|
|
@ -5,9 +5,11 @@ import json
|
||||||
import typer
|
import typer
|
||||||
|
|
||||||
from cli.client import api_get, api_post, api_delete, api_patch
|
from cli.client import api_get, api_post, api_delete, api_patch
|
||||||
|
from cli.commands.admin_metrics import admin_metrics_app
|
||||||
from cli.commands.memory_admin import memory_admin_app
|
from cli.commands.memory_admin import memory_admin_app
|
||||||
|
|
||||||
admin_app = typer.Typer(help="Admin operations (requires admin role)")
|
admin_app = typer.Typer(help="Admin operations (requires admin role)")
|
||||||
|
admin_app.add_typer(admin_metrics_app, name="metrics")
|
||||||
admin_app.add_typer(memory_admin_app, name="memory")
|
admin_app.add_typer(memory_admin_app, name="memory")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
89
cli/commands/admin_metrics.py
Normal file
89
cli/commands/admin_metrics.py
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
"""`agnes admin metrics {import,export,validate}` — lifted from cli/commands/metrics.py.
|
||||||
|
|
||||||
|
Write paths to metric definitions live under `admin` because they mutate the
|
||||||
|
server-side metric registry (DuckDB direct, no API). Read paths (list/show)
|
||||||
|
live in `agnes catalog --metrics`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
admin_metrics_app = typer.Typer(help="Admin: metric definition management")
|
||||||
|
|
||||||
|
|
||||||
|
@admin_metrics_app.command("import")
|
||||||
|
def import_metrics(
|
||||||
|
path: str = typer.Argument(..., help="Path to a YAML file or directory of YAML files"),
|
||||||
|
):
|
||||||
|
"""Import metric definitions from YAML into DuckDB (direct, no API)."""
|
||||||
|
from src.db import get_system_db
|
||||||
|
from src.repositories.metrics import MetricRepository
|
||||||
|
|
||||||
|
import_path = Path(path)
|
||||||
|
if not import_path.exists():
|
||||||
|
typer.echo(f"Path not found: {path}", err=True)
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
conn = get_system_db()
|
||||||
|
try:
|
||||||
|
repo = MetricRepository(conn)
|
||||||
|
count = repo.import_from_yaml(import_path)
|
||||||
|
typer.echo(f"Imported {count} metric(s) from {path}")
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
@admin_metrics_app.command("export")
|
||||||
|
def export_metrics(
|
||||||
|
output_dir: str = typer.Option("./export/", "--dir", help="Output directory for YAML files"),
|
||||||
|
):
|
||||||
|
"""Export metric definitions from DuckDB to YAML files (direct, no API)."""
|
||||||
|
from src.db import get_system_db
|
||||||
|
from src.repositories.metrics import MetricRepository
|
||||||
|
|
||||||
|
conn = get_system_db()
|
||||||
|
try:
|
||||||
|
repo = MetricRepository(conn)
|
||||||
|
count = repo.export_to_yaml(output_dir)
|
||||||
|
typer.echo(f"Exported {count} metric(s) to {output_dir}")
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
@admin_metrics_app.command("validate")
|
||||||
|
def validate_metrics():
|
||||||
|
"""Check each metric's table reference against registered tables (direct, no API)."""
|
||||||
|
from src.db import get_system_db
|
||||||
|
from src.repositories.metrics import MetricRepository
|
||||||
|
from src.repositories.table_registry import TableRegistryRepository
|
||||||
|
|
||||||
|
conn = get_system_db()
|
||||||
|
try:
|
||||||
|
metric_repo = MetricRepository(conn)
|
||||||
|
registry_repo = TableRegistryRepository(conn)
|
||||||
|
|
||||||
|
metrics = metric_repo.list()
|
||||||
|
registered_tables = {t["name"] for t in registry_repo.list_all()}
|
||||||
|
|
||||||
|
ok_count = 0
|
||||||
|
warn_count = 0
|
||||||
|
|
||||||
|
for m in metrics:
|
||||||
|
name = m.get("name", m.get("id", "?"))
|
||||||
|
table = m.get("table_name")
|
||||||
|
if not table:
|
||||||
|
typer.echo(f" OK {name:30s} (no table reference)")
|
||||||
|
ok_count += 1
|
||||||
|
elif table in registered_tables:
|
||||||
|
typer.echo(f" OK {name:30s} table={table}")
|
||||||
|
ok_count += 1
|
||||||
|
else:
|
||||||
|
typer.echo(f" WARN {name:30s} table={table} (not registered)")
|
||||||
|
warn_count += 1
|
||||||
|
|
||||||
|
typer.echo(f"\nTotal: {len(metrics)} metric(s) — {ok_count} OK, {warn_count} WARN")
|
||||||
|
if warn_count > 0:
|
||||||
|
raise typer.Exit(1)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
14
tests/test_cli_admin_metrics.py
Normal file
14
tests/test_cli_admin_metrics.py
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
"""Tests for `agnes admin metrics {import,export,validate}` (lifted from `da metrics`)."""
|
||||||
|
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
from cli.commands.admin import admin_app
|
||||||
|
|
||||||
|
|
||||||
|
def test_admin_metrics_subcommands_present():
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(admin_app, ["metrics", "--help"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "import" in result.output
|
||||||
|
assert "export" in result.output
|
||||||
|
assert "validate" in result.output
|
||||||
Loading…
Reference in a new issue