phase 16: fix UUID serialization in symbol registry responses
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
"""Symbol Registry API - FastAPI application."""
|
||||
import re
|
||||
import uuid
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import List, Optional
|
||||
from typing import Any, List, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import asyncpg
|
||||
@@ -16,6 +17,15 @@ config = load_config()
|
||||
pool: Optional[asyncpg.Pool] = None
|
||||
|
||||
|
||||
def _row_dict(row: asyncpg.Record) -> dict[str, Any]:
|
||||
"""Convert asyncpg Record to dict with UUID→str coercion."""
|
||||
d = dict(row)
|
||||
for k, v in d.items():
|
||||
if isinstance(v, uuid.UUID):
|
||||
d[k] = str(v)
|
||||
return d
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
global pool
|
||||
@@ -132,7 +142,7 @@ async def create_company(body: CompanyCreate):
|
||||
)
|
||||
except asyncpg.UniqueViolationError:
|
||||
raise HTTPException(409, f"Company {body.ticker} on {body.exchange} already exists")
|
||||
return dict(row)
|
||||
return _row_dict(row)
|
||||
|
||||
|
||||
@app.get("/companies", response_model=List[CompanyResponse])
|
||||
@@ -141,7 +151,7 @@ async def list_companies(active: bool = True):
|
||||
"SELECT id, ticker, legal_name, exchange, sector, industry, market_cap_bucket, active FROM companies WHERE active = $1 ORDER BY ticker",
|
||||
active,
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
return [_row_dict(r) for r in rows]
|
||||
|
||||
|
||||
@app.get("/companies/{company_id}", response_model=CompanyResponse)
|
||||
@@ -152,7 +162,7 @@ async def get_company(company_id: str):
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(404, "Company not found")
|
||||
return dict(row)
|
||||
return _row_dict(row)
|
||||
|
||||
|
||||
@app.put("/companies/{company_id}", response_model=CompanyResponse)
|
||||
@@ -166,7 +176,7 @@ async def update_company(company_id: str, body: CompanyCreate):
|
||||
)
|
||||
if not row:
|
||||
raise HTTPException(404, "Company not found")
|
||||
return dict(row)
|
||||
return _row_dict(row)
|
||||
|
||||
|
||||
# --- Alias Endpoints ---
|
||||
@@ -177,7 +187,7 @@ async def add_alias(company_id: str, body: AliasCreate):
|
||||
"INSERT INTO company_aliases (company_id, alias, alias_type) VALUES ($1, $2, $3) RETURNING id, alias, alias_type",
|
||||
company_id, body.alias, body.alias_type,
|
||||
)
|
||||
return dict(row)
|
||||
return _row_dict(row)
|
||||
|
||||
|
||||
@app.get("/companies/{company_id}/aliases")
|
||||
@@ -186,7 +196,7 @@ async def list_aliases(company_id: str):
|
||||
"SELECT id, alias, alias_type FROM company_aliases WHERE company_id = $1",
|
||||
company_id,
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
return [_row_dict(r) for r in rows]
|
||||
|
||||
|
||||
# --- Watchlist Endpoints ---
|
||||
@@ -200,13 +210,13 @@ async def create_watchlist(body: WatchlistCreate):
|
||||
)
|
||||
except asyncpg.UniqueViolationError:
|
||||
raise HTTPException(409, f"Watchlist '{body.name}' already exists")
|
||||
return dict(row)
|
||||
return _row_dict(row)
|
||||
|
||||
|
||||
@app.get("/watchlists")
|
||||
async def list_watchlists():
|
||||
rows = await pool.fetch("SELECT id, name, description, active FROM watchlists ORDER BY name")
|
||||
return [dict(r) for r in rows]
|
||||
return [_row_dict(r) for r in rows]
|
||||
|
||||
|
||||
@app.post("/watchlists/{watchlist_id}/members/{company_id}", status_code=201)
|
||||
@@ -231,7 +241,7 @@ async def list_watchlist_members(watchlist_id: str):
|
||||
WHERE wm.watchlist_id = $1 ORDER BY c.ticker""",
|
||||
watchlist_id,
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
return [_row_dict(r) for r in rows]
|
||||
|
||||
|
||||
# --- Source Endpoints ---
|
||||
@@ -249,7 +259,7 @@ async def add_source(company_id: str, body: SourceCreate):
|
||||
company_id, body.source_type, body.source_name,
|
||||
body.config, body.credibility_score, body.retention_days, body.access_policy,
|
||||
)
|
||||
return dict(row)
|
||||
return _row_dict(row)
|
||||
|
||||
|
||||
@app.get("/companies/{company_id}/sources")
|
||||
@@ -258,4 +268,4 @@ async def list_sources(company_id: str):
|
||||
"SELECT id, source_type, source_name, config, credibility_score, retention_days, access_policy, active FROM sources WHERE company_id = $1",
|
||||
company_id,
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
return [_row_dict(r) for r in rows]
|
||||
|
||||
Reference in New Issue
Block a user