File size: 5,159 Bytes
b0c69db
 
 
 
 
 
 
 
 
 
 
4ea2ff7
b0c69db
 
 
 
 
 
 
 
 
 
 
 
 
 
2ce53e9
4ea2ff7
2ce53e9
 
4ea2ff7
b0c69db
4ea2ff7
 
 
b0c69db
 
4ea2ff7
 
 
 
 
 
 
2ce53e9
4ea2ff7
 
 
 
 
 
 
 
 
 
 
 
2ce53e9
4ea2ff7
 
 
2ce53e9
 
b0c69db
 
 
 
2ce53e9
b0c69db
 
 
 
 
 
 
 
 
2ce53e9
b0c69db
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# app.py
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware # If you need CORS
from contextlib import asynccontextmanager
import uvicorn
import logging
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
import sys # Import sys module
import config
from services import generation
from routers import ideas, images, videos

# --- Logging Setup ---
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# --- Rate Limiting Setup ---
limiter = Limiter(key_func=get_remote_address, default_limits=[config.RATE_LIMIT])

# --- Lifespan Management (Model Loading/Unloading) ---
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: Assign essential state FIRST
    app.state.limiter = limiter
    logger.info("Rate limiter assigned to app state.")

    models_loaded_successfully = False
    try:
        logger.info("Application startup: Loading models...")
        generation.load_models() # This might raise an exception
        models_loaded_successfully = True # Set flag ONLY if load_models completes
        logger.info("Models loaded successfully.")
    except Exception as e:
        logger.error(f"FATAL: Model loading failed during startup: {e}", exc_info=True)
        # Option 1: Exit the application (cleaner for Docker environments)
        # logger.critical("Exiting application due to model loading failure.")
        # sys.exit(1)
        # Option 2: Raise the exception again to make uvicorn aware of failure
        # This might depend on how uvicorn handles lifespan exceptions
        # raise # Re-raise the exception

    # >>> Only yield if models loaded <<<
    if models_loaded_successfully:
        yield # Application is now ready to serve requests
    else:
        # If models didn't load, we don't yield, preventing Uvicorn
        # from reporting "Application startup complete."
        # You might need to manually stop the process if Option 1 above isn't used.
        logger.error("Application startup failed due to model loading errors. Server will not serve requests effectively.")
        # Keep the process running but indicate failure. Or use sys.exit(1) above.
        # If you don't yield or exit, Uvicorn might hang or exit depending on version.
        # Testing needed here - sys.exit(1) is often simplest in Docker.
        # For now, just logging the failure and not yielding.

    # --- Shutdown Logic ---
    # This part might not be reached if sys.exit was called
    logger.info("Application shutdown sequence starting.")
    if "generation" in globals() and hasattr(generation, 'model_cache'):
         generation.model_cache.clear()
    # Add any other cleanup here
    logger.info("Resources cleaned up.")



# --- FastAPI App Initialization ---
app = FastAPI(
    title="AI Content Generation API",
    description="API for generating content ideas, images, and videos using Hugging Face models.",
    version="1.0.0",
    lifespan=lifespan # Use the lifespan context manager
)

# --- Middleware ---
# Rate Limiting Middleware - Now it can safely access app.state.limiter
app.add_middleware(SlowAPIMiddleware)

# CORS Middleware (Uncomment and configure if needed for browser-based clients)
# origins = [
#     "http://localhost",
#     "http://localhost:8080",
#     "https://your-frontend-domain.com", # Add your frontend domain
# ]
# app.add_middleware(
#     CORSMiddleware,
#     allow_origins=origins,
#     allow_credentials=True,
#     allow_methods=["*"],
#     allow_headers=["*"],
# )

# --- Exception Handlers ---
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
    logger.error(f"Unhandled exception: {exc}", exc_info=True)
    return JSONResponse(
        status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
        content={"detail": "An internal server error occurred."},
    )

# --- API Routers ---
app.include_router(ideas.router)
app.include_router(images.router)
app.include_router(videos.router)

# --- Root Endpoint ---
@app.get("/", tags=["Status"])
async def read_root():
    """Root endpoint providing basic API information."""
    return {
        "message": "Welcome to the AI Content Generation API!",
        "docs": "/docs",
        "models": {
            "text": config.TEXT_MODEL_NAME,
            "image": config.IMAGE_MODEL_NAME,
            "video": config.VIDEO_MODEL_NAME
        },
         "status": "OK"
    }

# --- Main Execution (for local testing) ---
if __name__ == "__main__":
    # When running locally: uvicorn app:app --reload
    # The following is mostly for structuring; direct execution isn't typical for deployment
    print("To run locally, use: uvicorn app:app --reload --host 0.0.0.0 --port 7860")
    # uvicorn.run(app, host="0.0.0.0", port=7860) # Port 7860 is common for HF Spaces