InvestingTest / main.py
Mbonea's picture
Cursor
Fix HF Docker deps and expose remote MCP at /mcp.
178a66c
Raw History Blame Contribute Delete
4.79 kB
import asyncio
import logging
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError, HTTPException
from starlette.status import HTTP_400_BAD_REQUEST
from App.routers.stocks.routes import router as stocks_router
from App.routers.funds.routes import router as funds_router
from App.routers.bonds.routes import router as bonds_router
from App.routers.tasks.routes import router as tasks_router
from App.routers.users.routes import router as users_router
from App.routers.portfolio.routes import router as portfolio_router
from App.routers.admin.routes import router as admin_router
from App.routers.economy.config import load_local_env
from App.routers.economy.routes import api_router as economy_api_router, router as economy_router
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
from App.schemas import ResponseModel, AppException
from App.scheduler import (
scheduled_economy_loop,
scheduled_market_loop,
scheduled_news_loop,
scheduled_stock_price_loop,
startup_refresh,
)
from db import init_db, close_db, clear_db
from App.mcp.server import create_mcp_http_app
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)
load_local_env()
app = FastAPI(
title="Uwekezaji API", description="Stock Market Data API", redirect_slashes=False
)
def _json_safe(value):
if isinstance(value, bytes):
return value.decode("utf-8", errors="replace")
if isinstance(value, dict):
return {key: _json_safe(item) for key, item in value.items()}
if isinstance(value, list):
return [_json_safe(item) for item in value]
if isinstance(value, tuple):
return [_json_safe(item) for item in value]
return value
@app.exception_handler(AppException)
async def custom_http_exception_handler(request: Request, exc: AppException):
return JSONResponse(
status_code=exc.status_code,
content=ResponseModel(
success=False,
message=getattr(exc, "message", str(exc.detail)),
data=getattr(exc, "data", None),
).model_dump(),
)
@app.exception_handler(HTTPException)
async def generic_http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content=ResponseModel(
success=False,
message=str(exc.detail),
data=None,
).model_dump(),
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=HTTP_400_BAD_REQUEST,
content=ResponseModel(
success=False,
message="Validation error",
data={"errors": _json_safe(exc.errors())},
).model_dump(),
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(stocks_router)
app.include_router(funds_router)
app.include_router(bonds_router)
app.include_router(tasks_router)
app.include_router(users_router)
app.include_router(portfolio_router)
app.include_router(admin_router)
app.include_router(economy_router)
app.include_router(economy_api_router)
app.mount("/mcp", create_mcp_http_app())
app.add_middleware(ProxyHeadersMiddleware, trusted_hosts="*")
@app.on_event("startup")
async def startup_event():
logger.info("[startup] database initialization started")
await init_db()
logger.info("[startup] database initialization complete")
asyncio.create_task(scheduled_market_loop())
logger.info("[startup] scheduled market background loop started")
asyncio.create_task(scheduled_news_loop())
logger.info("[startup] scheduled news background loop started")
asyncio.create_task(scheduled_economy_loop())
logger.info("[startup] economy pipeline loop registered")
asyncio.create_task(scheduled_stock_price_loop())
logger.info("[startup] intraday stock-price loop started (hourly during DSE trading hours)")
asyncio.create_task(startup_refresh())
logger.info("[startup] startup refresh registered (syncs all assets if last market sync > 3 days)")
@app.on_event("shutdown")
async def shutdown_event():
await close_db()
@app.get("/")
async def root():
return {
"message": "Welcome to Uwekezaji API",
"mcp": {
"name": "uwekezaji-equities",
"url": "/mcp",
"health": "/mcp/health",
"docs": "See mcp-customer/README.md for agent connection instructions",
},
}