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
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,
|
|
)
|