- Fix 71 invalid-syntax files (class-body newline-broken assignments) - Add from/None chain to 307 B904 raise-without-from sites - Add B008 ignore to ruff.toml (already in pyproject.toml) - Noqa F401 on __init__.py re-exports (137 sites) - Noqa E402 on deferred imports (63 sites) - Bulk-add stdlib/FastAPI/project imports for F821 (127 sites) - Replace ×→x, –→-, …→... in docstrings (4093 chars) - Manual refactor of 5 SIM103/SIM116 patterns Tests: 791 passed (66 deselected due to pre-existing Redis issues in test_rag.py) Co-authored-by: opencode <opencode@rugmunch.io>
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
"""
|
|
Lightweight wallet label lookup endpoint for CF Worker enrichment.
|
|
Called by Workers to annotate tool responses with label data.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
|
|
logger = logging.getLogger("labels.lookup")
|
|
|
|
router = APIRouter(prefix="/api/v1/labels", tags=["wallet-labels"])
|
|
|
|
|
|
class LabelLookupRequest(BaseModel):
|
|
addresses: list[str] = Field(..., min_items=1, max_items=10)
|
|
chain: str = Field(default="ethereum")
|
|
|
|
|
|
@router.post("/lookup")
|
|
async def lookup_labels(req: LabelLookupRequest):
|
|
"""Batch lookup wallet labels for enrichment.
|
|
|
|
Called by CF Workers to annotate tool responses with wallet intelligence.
|
|
Returns labels from Redis cache + ClickHouse, with 5min Redis caching.
|
|
"""
|
|
try:
|
|
addresses = [a.strip() for a in req.addresses if a.strip()]
|
|
labels: dict[str, list] = {a: [] for a in addresses}
|
|
|
|
# Try ClickHouse first
|
|
try:
|
|
from clickhouse_driver import Client
|
|
|
|
ch = Client(
|
|
host=os.getenv("CH_HOST", "rmi-clickhouse"),
|
|
port=int(os.getenv("CH_PORT", "9000")),
|
|
user=os.getenv("CH_USER", "default"),
|
|
password=os.getenv("CH_PASSWORD", "") or None,
|
|
settings={"max_execution_time": 3},
|
|
)
|
|
placeholders = ", ".join(["%s"] * len(addresses))
|
|
rows = ch.execute(
|
|
f"SELECT address, label_name, label_category, source, is_sanctioned "
|
|
f"FROM wallet_memory.wallet_labels "
|
|
f"WHERE address IN ({placeholders}) "
|
|
f"ORDER BY loaded_at DESC LIMIT 50",
|
|
addresses,
|
|
)
|
|
for row in rows:
|
|
addr = row[0]
|
|
labels[addr].append(
|
|
{
|
|
"name": row[1] or "",
|
|
"category": row[2] or "",
|
|
"source": row[3] or "",
|
|
"sanctioned": bool(row[4]),
|
|
}
|
|
)
|
|
except Exception:
|
|
# Fallback to Redis
|
|
try:
|
|
import redis as _redis
|
|
|
|
r = _redis.Redis(
|
|
host=os.getenv("REDIS_HOST", "rmi-redis"),
|
|
port=int(os.getenv("REDIS_PORT", "6379")),
|
|
password=os.getenv("REDIS_PASSWORD", ""),
|
|
decode_responses=True,
|
|
socket_connect_timeout=2,
|
|
)
|
|
for addr in addresses:
|
|
cached = r.get(f"x402:enrich:{addr}")
|
|
if cached:
|
|
data = json.loads(cached)
|
|
labels[addr] = data.get("labels", [])
|
|
except Exception:
|
|
pass
|
|
|
|
# Count labeled addresses
|
|
labeled = sum(1 for v in labels.values() if v)
|
|
|
|
return {
|
|
"labels": labels,
|
|
"addresses_checked": len(addresses),
|
|
"addresses_labeled": labeled,
|
|
"total_labels": sum(len(v) for v in labels.values()),
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Label lookup failed: {e}")
|
|
raise HTTPException(status_code=500, detail=str(e)) from e
|