dca4f2d618
- web/core.py 858→236行,拆出 schemas/middleware/auth/diagnostics - ops_api.py 2876→4个 domain 模块 (users/payments/health/config) - DBManager Supabase HTTP 调用提取到 SupabaseAdminClient - 新增 LockedSQLiteConnection 统一多进程读写锁 - 新增 WeatherCacheManager 替代 12 个独立缓存字典 - METAR 缓存迁移至统一缓存管理器 - analysis_service 提取 _build_intraday_meteorology 到独立模块 - DEB 改进:偏差惩罚、分歧回退、自适应 lookback (MAE ↓12.6%) - 新增 PyTorch 注意力模型 deb_attention.py (数据积累后启用) - 新增 torch 到 requirements.lock
79 lines
2.2 KiB
Python
79 lines
2.2 KiB
Python
"""HTTP middlewares for the PolyWeather FastAPI application."""
|
|
|
|
import hashlib
|
|
import time
|
|
|
|
from fastapi import Request
|
|
from fastapi.responses import Response
|
|
|
|
from src.utils.metrics import counter_inc, histogram_observe
|
|
|
|
|
|
async def metrics_middleware(request: Request, call_next):
|
|
started = time.perf_counter()
|
|
try:
|
|
response = await call_next(request)
|
|
except Exception:
|
|
duration_ms = (time.perf_counter() - started) * 1000.0
|
|
counter_inc(
|
|
"polyweather_http_requests_total",
|
|
method=request.method,
|
|
path=request.url.path,
|
|
status="500",
|
|
)
|
|
histogram_observe(
|
|
"polyweather_http_request_duration_ms",
|
|
duration_ms,
|
|
method=request.method,
|
|
path=request.url.path,
|
|
status="500",
|
|
)
|
|
raise
|
|
|
|
duration_ms = (time.perf_counter() - started) * 1000.0
|
|
status_code = str(response.status_code)
|
|
counter_inc(
|
|
"polyweather_http_requests_total",
|
|
method=request.method,
|
|
path=request.url.path,
|
|
status=status_code,
|
|
)
|
|
histogram_observe(
|
|
"polyweather_http_request_duration_ms",
|
|
duration_ms,
|
|
method=request.method,
|
|
path=request.url.path,
|
|
status=status_code,
|
|
)
|
|
return response
|
|
|
|
|
|
async def etag_middleware(request: Request, call_next):
|
|
"""Add ETag to GET /api/* responses; return 304 on If-None-Match hit."""
|
|
response = await call_next(request)
|
|
|
|
if request.method != "GET" or response.status_code != 200:
|
|
return response
|
|
|
|
path = request.url.path
|
|
if not path.startswith("/api/") or path.endswith("/stream"):
|
|
return response
|
|
|
|
body = getattr(response, "body", None) or b""
|
|
if not body:
|
|
return response
|
|
|
|
try:
|
|
etag = hashlib.md5(body).hexdigest()
|
|
except Exception:
|
|
return response
|
|
|
|
etag_value = f'"{etag}"'
|
|
if_none_match = request.headers.get("If-None-Match", "")
|
|
if if_none_match == etag_value:
|
|
return Response(status_code=304, headers={"ETag": etag_value})
|
|
|
|
response.headers["ETag"] = etag_value
|
|
response.headers["Cache-Control"] = "private, max-age=30"
|
|
return response
|