agnes-the-ai-analyst/cli/commands/explore.py
ZdenekSrotyr 1287e63ed9 feat: complete system — web UI, all API endpoints, governance, admin, CLI commands
Major additions:
- Web UI: Jinja2 templates in FastAPI (login, dashboard, catalog, corporate memory, admin)
- API: catalog profiles/metrics, telegram verify/unlink/status, admin table registry CRUD
- Corporate memory governance: approve/reject/mandate/revoke/edit/batch + audit log
- Sync: real DataSyncManager trigger, sync-settings, table-subscriptions
- CLI: setup (init/test/deploy/verify), server (logs/restart/deploy/backup), explore
- Instance config integration (instance.yaml loaded at startup)
- 140 tests passing (25 new)
2026-03-27 16:52:22 +01:00

104 lines
3.5 KiB
Python

"""Explore commands — da explore {table}."""
import json
import os
from pathlib import Path
import typer
explore_app = typer.Typer(help="Explore data tables")
@explore_app.callback(invoke_without_command=True)
def explore(
table: str = typer.Argument(..., help="Table name to explore"),
remote: bool = typer.Option(False, "--remote", help="Fetch from server"),
as_json: bool = typer.Option(False, "--json", help="Output as JSON"),
):
"""Show profile and sample data for a table."""
if remote:
_explore_remote(table, as_json)
else:
_explore_local(table, as_json)
def _explore_local(table: str, as_json: bool):
import duckdb
local_dir = Path(os.environ.get("DA_LOCAL_DIR", "."))
db_path = local_dir / "user" / "duckdb" / "analytics.duckdb"
if not db_path.exists():
typer.echo("Local DuckDB not found. Run: da sync", err=True)
raise typer.Exit(1)
conn = duckdb.connect(str(db_path), read_only=True)
try:
# Check table exists
tables = [r[0] for r in conn.execute(
"SELECT table_name FROM information_schema.tables WHERE table_name = ?", [table]
).fetchall()]
if not tables:
# Also check views
tables = [r[0] for r in conn.execute(
"SELECT table_name FROM information_schema.tables WHERE table_name = ? AND table_type='VIEW'", [table]
).fetchall()]
if not tables:
typer.echo(f"Table '{table}' not found. Available:", err=True)
for r in conn.execute("SELECT table_name FROM information_schema.tables ORDER BY table_name").fetchall():
typer.echo(f" {r[0]}")
raise typer.Exit(1)
# Row count
count = conn.execute(f'SELECT count(*) FROM "{table}"').fetchone()[0]
# Column info
columns = conn.execute(f"DESCRIBE \"{table}\"").fetchall()
col_info = [{"name": c[0], "type": c[1], "nullable": c[2]} for c in columns]
# Sample rows
sample = conn.execute(f'SELECT * FROM "{table}" LIMIT 5').fetchall()
sample_cols = [desc[0] for desc in conn.description]
info = {
"table": table,
"row_count": count,
"columns": col_info,
"sample_rows": [dict(zip(sample_cols, row)) for row in sample],
}
if as_json:
typer.echo(json.dumps(info, indent=2, default=str))
else:
typer.echo(f"Table: {table}")
typer.echo(f"Rows: {count:,}")
typer.echo(f"Columns ({len(col_info)}):")
for c in col_info:
typer.echo(f" {c['name']:30s} {c['type']}")
typer.echo(f"\nSample ({min(5, count)} rows):")
from rich.console import Console
from rich.table import Table
console = Console()
t = Table()
for c in sample_cols:
t.add_column(c)
for row in sample:
t.add_row(*(str(v) if v is not None else "" for v in row))
console.print(t)
finally:
conn.close()
def _explore_remote(table: str, as_json: bool):
from cli.client import api_get
resp = api_get(f"/api/catalog/profile/{table}")
if resp.status_code != 200:
typer.echo(f"Profile not found: {resp.json().get('detail', resp.text)}", err=True)
raise typer.Exit(1)
if as_json:
typer.echo(json.dumps(resp.json(), indent=2))
else:
profile = resp.json()
typer.echo(f"Table: {table}")
typer.echo(json.dumps(profile, indent=2, default=str))