agnes-the-ai-analyst/cli/v2_client.py
ZdenekSrotyr 2e1dfb7553
feat(v2): claude-driven fetch primitives + 0.14.0 (#102)
Replaces the BigQuery wrap-view pattern with a discovery + scoped-fetch toolkit driven by the analyst's Claude session. Adds /api/v2/{catalog,schema,sample,scan,scan/estimate}, da catalog/schema/describe/fetch/snapshot/disk-info CLI commands, sqlglot-backed WHERE validator, process-local quota tracker, agent rails skill (cli/skills/agnes-data-querying.md). BREAKING: BQ wrap views off by default — set data_source.bigquery.legacy_wrap_views=true for one cycle. Backward-compat field_validator on primary_key. Catalog cache now matches documented 300s TTL with RBAC fresh per request. Cuts release v0.14.0.
2026-04-29 01:07:19 +02:00

55 lines
1.8 KiB
Python

"""HTTP client helpers for /api/v2/* endpoints (CLI side)."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import io
import httpx
import pyarrow as pa
from cli.config import get_server_url, get_token
@dataclass
class V2ClientError(Exception):
status_code: int
body: Any
message: str = ""
def __str__(self) -> str:
return f"HTTP {self.status_code}: {self.message or self.body}"
def _headers() -> dict:
token = get_token()
return {"Authorization": f"Bearer {token}"} if token else {}
def api_get_json(path: str, **params) -> dict:
url = f"{get_server_url().rstrip('/')}{path}"
r = httpx.get(url, headers=_headers(), params=params or None, timeout=30)
if r.status_code >= 400:
body = r.json() if "json" in r.headers.get("content-type", "") else r.text
raise V2ClientError(status_code=r.status_code, body=body, message=str(body)[:200])
return r.json()
def api_post_json(path: str, payload: dict) -> dict:
url = f"{get_server_url().rstrip('/')}{path}"
r = httpx.post(url, json=payload, headers=_headers(), timeout=120)
if r.status_code >= 400:
body = r.json() if "json" in r.headers.get("content-type", "") else r.text
raise V2ClientError(status_code=r.status_code, body=body, message=str(body)[:200])
return r.json()
def api_post_arrow(path: str, payload: dict) -> pa.Table:
"""Post JSON, expect Arrow IPC stream response."""
url = f"{get_server_url().rstrip('/')}{path}"
r = httpx.post(url, json=payload, headers=_headers(), timeout=600)
if r.status_code >= 400:
body = r.json() if "json" in r.headers.get("content-type", "") else r.text
raise V2ClientError(status_code=r.status_code, body=body, message=str(body)[:200])
reader = pa.ipc.open_stream(io.BytesIO(r.content))
return reader.read_all()