IndraneelKumar's picture
Added RSS Feeds for Medium Articles and Individual Publications
804054e
import os
from contextlib import asynccontextmanager
import dotenv
from fastapi import FastAPI
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from qdrant_client.http.exceptions import UnexpectedResponse
from src.api.exceptions.exception_handlers import (
general_exception_handler,
qdrant_exception_handler,
validation_exception_handler,
)
from src.api.middleware.logging_middleware import LoggingMiddleware
from src.api.routes.health_routes import router as health_router
from src.api.routes.search_routes import router as search_router
from src.infrastructure.qdrant.qdrant_vectorstore import AsyncQdrantVectorStore
from src.utils.logger_util import setup_logging
# Load environment variables from .env file
dotenv.load_dotenv()
# -----------------------
# Logger setup
# -----------------------
logger = setup_logging()
# -----------------------
# Lifespan
# -----------------------
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Lifespan context manager to handle startup and shutdown events.
Initializes the Qdrant vector store on startup and ensures proper cleanup on shutdown.
Args:
app (FastAPI): The FastAPI application instance.
Yields:
None
Exceptions:
Raises exceptions if initialization or cleanup fails.
"""
## Ensure the cache directory exists and is writable (HF downloads the models here)
cache_dir = "/tmp/fastembed_cache"
os.makedirs(cache_dir, exist_ok=True) # Ensure directory exists
# Force /tmp/huggingface in Google Cloud so that it's writable.
# This is the default cache dir of Huggingface.
# Otherwise it tries ~/.cache/huggingface (read-only directory) in Google Cloud.
# That directory is not writable.
logger.info(f"HF_HOME: {os.environ.get('HF_HOME', 'Not set')}")
logger.info(f"Cache dir: {cache_dir}, Writable: {os.access(cache_dir, os.W_OK)}")
cache_contents = os.listdir(cache_dir) if os.path.exists(cache_dir) else "Empty"
logger.info(f"Cache contents before: {cache_contents}")
try:
# creates Qdrant client internally
app.state.vectorstore = AsyncQdrantVectorStore(cache_dir=cache_dir)
except Exception as e:
logger.exception("Failed to initialize QdrantVectorStore")
raise e
yield
try:
await app.state.vectorstore.client.close()
except Exception:
logger.exception("Failed to close Qdrant client")
# -----------------------
# FastAPI application
# -----------------------
app = FastAPI(
title="Search Engine RAG API",
version="1.0",
description="API for Articles Search Retrieval-Augmented Generation (RAG) system",
lifespan=lifespan,
# root_path=root_path,
)
# -----------------------
# Middleware
# -----------------------
# Log the allowed origins
allowed_origins = os.getenv("ALLOWED_ORIGINS", "").split(",")
logger.info(f"CORS allowed origins: {allowed_origins}")
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins, # ["*"], # allowed_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "OPTIONS"], # only the methods the app uses
allow_headers=["Authorization", "Content-Type"], # only headers needed
)
app.add_middleware(LoggingMiddleware)
# -----------------------
# Exception Handlers
# -----------------------
app.add_exception_handler(RequestValidationError, validation_exception_handler)
app.add_exception_handler(UnexpectedResponse, qdrant_exception_handler)
app.add_exception_handler(Exception, general_exception_handler)
# -----------------------
# Routers
# -----------------------
app.include_router(search_router, prefix="/search", tags=["search"])
app.include_router(health_router, tags=["health"])
# For Cloud Run, run the app directly
if __name__ == "__main__":
import uvicorn
port = int(os.environ.get("PORT", 8080)) # Cloud Run provides PORT env var
uvicorn.run(
"src.api.main:app",
host="0.0.0.0",
port=port,
log_level="info",
reload=True, # Enable auto-reload for development
)
# config = uvicorn.Config(
# app,
# port=port,
# log_level="info",
# # loop="uvloop",
# # workers=1,
# reload=True
# )
# server = uvicorn.Server(config)
# server.run()