-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
154 lines (122 loc) · 4.71 KB
/
main.py
File metadata and controls
154 lines (122 loc) · 4.71 KB
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"""
FastAPI backend for pyplots platform.
AI-powered Python plotting examples that work with YOUR data.
"""
# Load .env file FIRST, before any other imports that might read env vars
from dotenv import load_dotenv # noqa: E402, I001
load_dotenv()
import logging # noqa: E402
from contextlib import asynccontextmanager # noqa: E402
from fastapi import FastAPI, HTTPException, Request, Response # noqa: E402
from fastapi.middleware.cors import CORSMiddleware # noqa: E402
from starlette.middleware.gzip import GZipMiddleware # noqa: E402
from api.exceptions import ( # noqa: E402
PyplotsException,
generic_exception_handler,
http_exception_handler,
pyplots_exception_handler,
)
from api.mcp.server import mcp_server # noqa: E402
from api.routers import ( # noqa: E402
debug_router,
download_router,
health_router,
libraries_router,
og_images_router,
plots_router,
proxy_router,
seo_router,
specs_router,
stats_router,
)
from core.database import close_db, init_db, is_db_configured # noqa: E402
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Create MCP HTTP app (needed for lifespan integration)
mcp_http_app = mcp_server.http_app(path="/")
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage application lifecycle."""
logger.info("Starting pyplots API...")
# Initialize database connection
if is_db_configured():
try:
await init_db()
logger.info("Database connection initialized")
except Exception as e:
logger.error(f"Failed to initialize database: {e}")
# Initialize MCP server lifespan
async with mcp_http_app.lifespan(app):
logger.info("MCP server initialized")
yield
# Cleanup database connection
logger.info("Shutting down pyplots API...")
await close_db()
# Create FastAPI application
app = FastAPI(
title="pyplots API",
description="Backend API for pyplots.ai - Python plotting gallery across 9 libraries",
version="1.0.0",
lifespan=lifespan,
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json",
)
# Register exception handlers
app.add_exception_handler(PyplotsException, pyplots_exception_handler)
app.add_exception_handler(HTTPException, http_exception_handler)
app.add_exception_handler(Exception, generic_exception_handler)
# Enable GZip compression for responses > 500 bytes
# This significantly reduces payload size for JSON API responses
# (e.g., /plots/filter: 301KB -> ~40KB with gzip)
# Note: GZip must be added before CORS so compression happens before CORS headers are added
app.add_middleware(GZipMiddleware, minimum_size=500)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["https://pyplots.ai"],
allow_origin_regex=r"http://localhost:\d+",
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["Mcp-Session-Id"], # MCP session tracking
)
# Add cache headers middleware
@app.middleware("http")
async def add_cache_headers(request: Request, call_next):
"""Add Cache-Control headers to API responses for better browser caching."""
response: Response = await call_next(request)
# Skip for non-GET requests or error responses
if request.method != "GET" or response.status_code >= 400:
return response
path = request.url.path
# Static data that rarely changes (5 min cache, stale-while-revalidate for 1 hour)
if path in ("/libraries", "/stats"):
response.headers["Cache-Control"] = "public, max-age=300, stale-while-revalidate=3600"
# Specs list - moderate caching (2 min cache)
elif path == "/specs":
response.headers["Cache-Control"] = "public, max-age=120, stale-while-revalidate=600"
# Filter endpoint - short cache (30 sec) with stale-while-revalidate
elif path == "/plots/filter":
response.headers["Cache-Control"] = "public, max-age=30, stale-while-revalidate=300"
# Individual spec details
elif path.startswith("/specs/"):
response.headers["Cache-Control"] = "public, max-age=120, stale-while-revalidate=600"
return response
# Mount MCP server for AI assistant integration
app.mount("/mcp", mcp_http_app)
# Register routers
app.include_router(health_router)
app.include_router(stats_router)
app.include_router(specs_router)
app.include_router(libraries_router)
app.include_router(plots_router)
app.include_router(download_router)
app.include_router(seo_router)
app.include_router(og_images_router)
app.include_router(proxy_router)
app.include_router(debug_router)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)