Spaces:
Sleeping
Sleeping
Download api/main.py from Moni38/smartvision-ai: direct link, hf CLI and curl.
- Browser
- Download file 4.22 kB
-
https://huggingface.co/spaces/Moni38/smartvision-ai/resolve/main/api/main.py
- Command line
-
hf download hf://spaces/Moni38/smartvision-ai/api/main.py
-
curl -L -o main.py https://huggingface.co/spaces/Moni38/smartvision-ai/resolve/main/api/main.py
4.22 kB
| # filename: api/main.py | |
| # purpose: FastAPI application β lifespan startup/shutdown + route registration. | |
| # | |
| # Rules applied: | |
| # Rule 32: lifespan context manager (not deprecated @app.on_event) | |
| # Rule 31: asyncio.get_running_loop() β event loop is already running inside async function | |
| # Rule 23: Redis failure handled gracefully in RedisCache.__init__ (1s timeout) | |
| # Rule 4: src/ never imports from api/ β dependency flows api/ β src/ only | |
| from __future__ import annotations | |
| import asyncio | |
| import logging | |
| from contextlib import asynccontextmanager | |
| from typing import AsyncGenerator | |
| from fastapi import FastAPI, Request, Response | |
| import config as cfg | |
| from api.prometheus_metrics import http_errors, models_loaded | |
| from api.routes import classify, detect, drift, ensemble, health, metrics | |
| from src.data.augmentor import get_eval_transforms | |
| from src.inference.model_loader import load_all_models | |
| from src.inference.redis_cache import RedisCache | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s %(levelname)-8s %(name)s %(message)s", | |
| ) | |
| logger = logging.getLogger(__name__) | |
| async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: | |
| # Initialise state flags before model loading so /health can respond immediately | |
| app.state.models_ready = False | |
| app.state.models = {} | |
| app.state.model_hashes = {} | |
| # Rule 31: get_running_loop() β loop is already running in an async context. | |
| # load_all_models is blocking (torch.load, HF Hub HTTP); run in thread pool so | |
| # the event loop stays responsive during startup (/health returns 503 while loading). | |
| loop = asyncio.get_running_loop() | |
| try: | |
| models, hashes = await loop.run_in_executor( | |
| None, lambda: load_all_models(device="cpu") | |
| ) | |
| app.state.models = models | |
| app.state.model_hashes = hashes | |
| app.state.models_ready = True | |
| models_loaded.set(1) | |
| logger.info(f"Models ready: {list(models.keys())}") | |
| except Exception as e: | |
| logger.error(f"Model loading failed: {e}") | |
| # models_ready stays False β /health returns 503 on every request | |
| # Redis β 1s socket_connect_timeout; disables cache gracefully if Redis is down | |
| app.state.redis = RedisCache( | |
| host=cfg.REDIS_HOST, port=cfg.REDIS_PORT, password=cfg.REDIS_PASSWORD | |
| ) | |
| # Build eval transform once at startup β not per-request (avoids per-call allocation) | |
| app.state.eval_transform = get_eval_transforms(image_size=cfg.IMAGE_SIZE) | |
| # Drift detector β initialized after Redis so redis_client is available for buffer restore | |
| from src.monitoring.drift_detector import DriftDetector | |
| try: | |
| app.state.drift_detector = DriftDetector( | |
| baseline_path=cfg.DRIFT_BASELINE_PATH, | |
| redis_client=app.state.redis, | |
| min_samples=cfg.KS_MIN_LIVE_SAMPLES, | |
| alert_threshold=cfg.KS_DRIFT_ALERT_THRESHOLD, | |
| ) | |
| logger.info("DriftDetector initialized: 22-class KS monitoring active") | |
| except FileNotFoundError as e: | |
| logger.error(f"DriftDetector init failed (baseline missing): {e}") | |
| app.state.drift_detector = None | |
| yield | |
| # Shutdown cleanup | |
| app.state.models = {} | |
| app.state.model_hashes = {} | |
| app.state.models_ready = False | |
| logger.info("SmartVision API shutdown complete.") | |
| app = FastAPI( | |
| title="SmartVision AI", | |
| description=( | |
| "Multi-class object recognition (ResNet50, MobileNetV2) " | |
| "and detection (YOLOv8n) API." | |
| ), | |
| version="1.0.0", | |
| lifespan=lifespan, | |
| ) | |
| async def count_http_errors(request: Request, call_next) -> Response: | |
| """Increment http_errors counter for any 4xx/5xx response.""" | |
| response = await call_next(request) | |
| if response.status_code >= 400: | |
| endpoint = request.url.path | |
| http_errors.labels( | |
| status_code=str(response.status_code), | |
| endpoint=endpoint, | |
| ).inc() | |
| return response | |
| app.include_router(health.router) | |
| app.include_router(classify.router) | |
| app.include_router(ensemble.router) | |
| app.include_router(detect.router) | |
| app.include_router(drift.router) | |
| app.include_router(metrics.router) | |