"""股票查询 API:/api/stocks。""" from __future__ import annotations from fastapi import APIRouter, HTTPException from app.api.deps import StockRepoDep from app.domain.entities.market import Stock router = APIRouter(prefix="/stocks", tags=["stocks"]) @router.get("", response_model=list[Stock], summary="股票列表") def list_stocks( repo: StockRepoDep, q: str | None = None, limit: int = 50, offset: int = 0, ) -> list[Stock]: if limit > 500: limit = 500 stocks = repo.list() if q: needle = q.upper() stocks = [s for s in stocks if needle in s.symbol or needle in s.name.upper()] return stocks[offset : offset + limit] @router.get("/names", response_model=dict[str, str], summary="全市场股票名称映射") def list_stock_names(repo: StockRepoDep) -> dict[str, str]: """`{symbol: name}` 全市场名称缓存(前端启动时一次性拉取,避免表格 N+1 查询)。 契约固定为 dict(不是数组):前端已有调用方按 dict 形状消费。 路由**必须**声明在 `/{symbol}` 之前,否则会被路径参数吞掉("names" 被当作代码)。 名称缺失(空字符串)的标的直接略过 —— 前端按「名称未知」渲染,不塞空串假装有名称。 """ return {s.symbol: s.name for s in repo.list() if s.name} @router.get("/{symbol}", response_model=Stock, summary="按代码查询") def get_stock(symbol: str, repo: StockRepoDep) -> Stock: stock = repo.get_by_symbol(symbol) if stock is None: raise HTTPException(status_code=404, detail=f"未找到股票 {symbol}") return stock