simikkk's picture
Update app/main.py
46895bc verified
Raw
History Blame Contribute Delete
3.15 kB
import logging
import gradio as gr
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from app.config import DEMO_MODE
from app.middleware.rate_limit import auth_limiter, account_limiter
from app.routes import auth_routes, generate, brand_voice, history, account, payments, bulk
from app.legal import pages as legal_pages
from app.ui import build_ui
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("etsy_optimizer")
app = FastAPI(title="Etsy Listing Optimizer API")
# --- CORS: explicit allowlist, never wide open --------------------------------
# For a same-origin Gradio+FastAPI deployment on a single HF Space this can
# stay empty/self-only; add your own domain here if you build a separate
# frontend that calls this API cross-origin.
app.add_middleware(
CORSMiddleware,
allow_origins=[],
allow_credentials=True,
allow_methods=["GET", "POST", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
# --- Rate limiting -------------------------------------------------------------
app.state.limiter = auth_limiter # slowapi needs exactly one limiter on app.state;
# account_limiter shares the same in-memory backend semantics and is applied
# via its own decorator on /generate - both raise the same RateLimitExceeded.
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(SlowAPIMiddleware)
@app.exception_handler(Exception)
async def friendly_error_handler(request: Request, exc: Exception):
# Last-resort catch-all: never leak a raw stack trace to the UI.
logger.exception("Unhandled error on %s", request.url.path)
return JSONResponse(status_code=500, content={"detail": "Something went wrong. Please try again."})
app.include_router(auth_routes.router, tags=["auth"])
app.include_router(generate.router, tags=["generate"])
app.include_router(brand_voice.router, tags=["brand-voice"])
app.include_router(history.router, tags=["history"])
app.include_router(account.router, tags=["account"])
app.include_router(payments.router, tags=["billing"])
app.include_router(bulk.router, tags=["bulk"])
app.include_router(legal_pages.router, tags=["legal"])
@app.get("/health")
async def health():
return {"status": "ok", "demo_mode": DEMO_MODE}
# Mount the Gradio UI at the root path, on top of the FastAPI routes above.
#
# NOTE: deliberately NOT named `demo`. Hugging Face's Gradio SDK runtime
# scans app_file for a top-level `gr.Blocks`/`gr.Interface` variable named
# exactly `demo` and auto-launches it as its own standalone server, even
# though we never call .launch() ourselves. That collided with our explicit
# uvicorn process (server.py) on the next free port -> "address already in
# use: 7861". Renaming it opts this app out of that auto-launch behavior,
# since we're intentionally mounting it onto FastAPI instead.
gradio_ui = build_ui(app)
app = gr.mount_gradio_app(app, gradio_ui, path="/")