app.py 4.79 KB
"""
Main FastAPI application for the search service.

Usage:
    uvicorn api.app:app --host 0.0.0.0 --port 8000 --reload
"""

import os
import sys
from typing import Optional
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
import argparse

# Add parent directory to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from config import ConfigLoader, CustomerConfig
from utils import ESClient
from search import Searcher
from query import QueryParser

# Global instances
_config: Optional[CustomerConfig] = None
_es_client: Optional[ESClient] = None
_searcher: Optional[Searcher] = None
_query_parser: Optional[QueryParser] = None


def init_service(customer_id: str = "customer1", es_host: str = "http://localhost:9200"):
    """
    Initialize search service with configuration.

    Args:
        customer_id: Customer configuration ID
        es_host: Elasticsearch host URL
    """
    global _config, _es_client, _searcher, _query_parser

    print(f"Initializing search service for customer: {customer_id}")

    # Load configuration
    config_loader = ConfigLoader("config/schema")
    _config = config_loader.load_customer_config(customer_id)

    # Validate configuration
    errors = config_loader.validate_config(_config)
    if errors:
        raise ValueError(f"Configuration validation failed: {errors}")

    print(f"Configuration loaded: {_config.customer_name}")

    # Initialize ES client
    _es_client = ESClient(hosts=[es_host])
    if not _es_client.ping():
        raise ConnectionError(f"Failed to connect to Elasticsearch at {es_host}")

    print(f"Connected to Elasticsearch: {es_host}")

    # Initialize query parser
    _query_parser = QueryParser(_config)
    print("Query parser initialized")

    # Initialize searcher
    _searcher = Searcher(_config, _es_client, _query_parser)
    print("Searcher initialized")

    print("Search service ready!")


def get_config() -> CustomerConfig:
    """Get customer configuration."""
    if _config is None:
        raise RuntimeError("Service not initialized")
    return _config


def get_es_client() -> ESClient:
    """Get Elasticsearch client."""
    if _es_client is None:
        raise RuntimeError("Service not initialized")
    return _es_client


def get_searcher() -> Searcher:
    """Get searcher instance."""
    if _searcher is None:
        raise RuntimeError("Service not initialized")
    return _searcher


def get_query_parser() -> QueryParser:
    """Get query parser instance."""
    if _query_parser is None:
        raise RuntimeError("Service not initialized")
    return _query_parser


# Create FastAPI app
app = FastAPI(
    title="E-Commerce Search API",
    description="Configurable search engine for cross-border e-commerce",
    version="1.0.0"
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.on_event("startup")
async def startup_event():
    """Initialize service on startup."""
    customer_id = os.getenv("CUSTOMER_ID", "customer1")
    es_host = os.getenv("ES_HOST", "http://localhost:9200")

    try:
        init_service(customer_id=customer_id, es_host=es_host)
    except Exception as e:
        print(f"Failed to initialize service: {e}")
        print("Service will start but may not function correctly")


@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    """Global exception handler."""
    return JSONResponse(
        status_code=500,
        content={
            "error": "Internal server error",
            "detail": str(exc)
        }
    )


@app.get("/")
async def root():
    """Root endpoint."""
    return {
        "service": "E-Commerce Search API",
        "version": "1.0.0",
        "status": "running"
    }


# Include routers
from .routes import search, admin

app.include_router(search.router)
app.include_router(admin.router)


if __name__ == "__main__":
    import uvicorn

    parser = argparse.ArgumentParser(description='Start search API service')
    parser.add_argument('--host', default='0.0.0.0', help='Host to bind to')
    parser.add_argument('--port', type=int, default=8000, help='Port to bind to')
    parser.add_argument('--customer', default='customer1', help='Customer ID')
    parser.add_argument('--es-host', default='http://localhost:9200', help='Elasticsearch host')
    parser.add_argument('--reload', action='store_true', help='Enable auto-reload')
    args = parser.parse_args()

    # Set environment variables
    os.environ['CUSTOMER_ID'] = args.customer
    os.environ['ES_HOST'] = args.es_host

    # Run server
    uvicorn.run(
        "api.app:app",
        host=args.host,
        port=args.port,
        reload=args.reload
    )