You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

97 lines
3.1 KiB
Python

"""
Company metadata endpoints — sector/industry/exchange/market_cap for any ticker.
"""
import asyncio
import logging
from typing import List
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import AsyncSessionLocal, get_db
from app.schemas.company import (
BulkCompanyItem,
BulkCompanyRequest,
BulkCompanyResponse,
CompanyMetadataResponse,
)
from app.services import company_metadata_service as cms
logger = logging.getLogger(__name__)
router = APIRouter()
_BULK_MAX = 100
@router.get(
"/{ticker}",
response_model=CompanyMetadataResponse,
summary="Get company metadata",
description=(
"Returns sector, industry, exchange, market_cap, country, and other metadata "
"for a ticker. Valid tickers without financial statements still return 200. "
"Unknown tickers return 404."
),
)
async def get_company(
ticker: str,
db: AsyncSession = Depends(get_db),
):
ticker = ticker.upper()
try:
data = await cms.get_metadata(db, ticker)
except ValueError as e:
if "invalid ticker" in str(e).lower():
raise HTTPException(status_code=404, detail=f"Unknown ticker: {ticker}")
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
logger.error("company metadata error for %s: %s", ticker, e)
raise HTTPException(status_code=500, detail="Failed to retrieve company metadata")
return CompanyMetadataResponse(**data)
@router.post(
"/bulk",
response_model=BulkCompanyResponse,
summary="Bulk company metadata",
description=(
f"Fetch metadata for up to {_BULK_MAX} tickers in one request. "
"Partial failures are allowed — each item has either `data` or `error`."
),
)
async def bulk_company(
request: BulkCompanyRequest,
):
tickers = [t.upper() for t in request.tickers]
if not tickers:
raise HTTPException(status_code=400, detail="tickers list is empty")
if len(tickers) > _BULK_MAX:
raise HTTPException(
status_code=400,
detail=f"Too many tickers: max {_BULK_MAX}, got {len(tickers)}",
)
async def _fetch_one(ticker: str) -> BulkCompanyItem:
async with AsyncSessionLocal() as session:
try:
data = await cms.get_metadata(session, ticker)
return BulkCompanyItem(ticker=ticker, data=CompanyMetadataResponse(**data))
except ValueError as e:
return BulkCompanyItem(ticker=ticker, error=str(e))
except Exception as e:
logger.warning("bulk company error for %s: %s", ticker, e)
return BulkCompanyItem(ticker=ticker, error="lookup failed")
results: List[BulkCompanyItem] = await asyncio.gather(*[_fetch_one(t) for t in tickers])
success_count = sum(1 for r in results if r.data is not None)
return BulkCompanyResponse(
results=results,
total=len(results),
success_count=success_count,
error_count=len(results) - success_count,
)