Download app.py from KompongS/Rc: direct link, hf CLI and curl.
- Browser
- Download file 7.99 kB
-
https://huggingface.co/spaces/KompongS/Rc/resolve/main/app.py
- Command line
-
hf download hf://spaces/KompongS/Rc/app.py
-
curl -L -o app.py https://huggingface.co/spaces/KompongS/Rc/resolve/main/app.py
7.99 kB
| """ | |
| ONNX Grid Vision API | |
| ===================== | |
| FastAPI-based image grid classification API with web automation. | |
| Uses ONNX Runtime for inference, patchright for browser automation. | |
| Deploy to cloud platforms with Docker support. | |
| """ | |
| import asyncio | |
| import logging | |
| import uuid | |
| import time | |
| from contextlib import asynccontextmanager | |
| from typing import Optional | |
| from fastapi import FastAPI, HTTPException, BackgroundTasks | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel, Field | |
| from browser_engine import VisionEngine | |
| # ββ Logging ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", | |
| ) | |
| logger = logging.getLogger("gridvision") | |
| # ββ Globals ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| engine: Optional[VisionEngine] = None | |
| active_sessions: dict = {} # task_id -> status info | |
| # ββ Request / Response Models ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class ProcessRequest(BaseModel): | |
| sitekey: str = Field(..., description="Widget site key") | |
| url: str = Field(..., description="Page URL for domain context") | |
| is_enterprise: bool = Field(False, description="Enterprise widget mode") | |
| is_invisible: bool = Field(False, description="Invisible widget mode") | |
| proxy: Optional[str] = Field(None, description="Proxy URL (socks5://user:pass@host:port)") | |
| headless: bool = Field(True, description="Run browser headless") | |
| timeout: int = Field(120, ge=10, le=300, description="Processing timeout in seconds") | |
| class ProcessResponse(BaseModel): | |
| success: bool | |
| token: Optional[str] = None | |
| error: Optional[str] = None | |
| elapsed: Optional[float] = None | |
| class StatusResponse(BaseModel): | |
| task_id: str | |
| status: str # pending, processing, completed, failed | |
| token: Optional[str] = None | |
| error: Optional[str] = None | |
| elapsed: float | |
| # ββ Lifespan βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def lifespan(app: FastAPI): | |
| global engine | |
| logger.info("Loading ONNX models...") | |
| engine = VisionEngine(models_dir="models") | |
| engine.load_models() | |
| logger.info("ONNX Grid Vision API ready") | |
| yield | |
| logger.info("Shutting down") | |
| # ββ FastAPI App ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app = FastAPI( | |
| title="ONNX Grid Vision API", | |
| description=( | |
| "Image grid classification service using YOLOv5 segmentation " | |
| "and per-class binary classifiers with web automation." | |
| ), | |
| version="1.0.0", | |
| lifespan=lifespan, | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # ββ Background Processor ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def _process_background(task_id: str, req: ProcessRequest): | |
| active_sessions[task_id]["status"] = "processing" | |
| start = time.time() | |
| try: | |
| token = await engine.process( | |
| sitekey=req.sitekey, | |
| url=req.url, | |
| proxy=req.proxy, | |
| headless=req.headless, | |
| timeout=req.timeout, | |
| is_enterprise=req.is_enterprise, | |
| is_invisible=req.is_invisible, | |
| ) | |
| active_sessions[task_id]["status"] = "completed" | |
| active_sessions[task_id]["token"] = token | |
| active_sessions[task_id]["elapsed"] = round(time.time() - start, 2) | |
| logger.info(f"Task {task_id} completed in {active_sessions[task_id]['elapsed']}s") | |
| except Exception as e: | |
| active_sessions[task_id]["status"] = "failed" | |
| active_sessions[task_id]["error"] = str(e) | |
| active_sessions[task_id]["elapsed"] = round(time.time() - start, 2) | |
| logger.error(f"Task {task_id} failed: {e}") | |
| # ββ Endpoints ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async def root(): | |
| return { | |
| "name": "ONNX Grid Vision API", | |
| "version": "1.0.0", | |
| "endpoints": { | |
| "POST /process": "Process verification synchronously (blocking)", | |
| "POST /process/async": "Process verification asynchronously", | |
| "GET /status/{task_id}": "Check async task status", | |
| "GET /health": "Health check", | |
| }, | |
| } | |
| async def health(): | |
| return {"status": "ok", "models_loaded": engine is not None and engine.loaded} | |
| async def process(req: ProcessRequest): | |
| """ | |
| Process verification synchronously. | |
| Returns the token directly (blocks until done or timeout). | |
| """ | |
| if not engine or not engine.loaded: | |
| raise HTTPException(503, "Engine not ready (models still loading)") | |
| start = time.time() | |
| try: | |
| token = await engine.process( | |
| sitekey=req.sitekey, | |
| url=req.url, | |
| proxy=req.proxy, | |
| headless=req.headless, | |
| timeout=req.timeout, | |
| is_enterprise=req.is_enterprise, | |
| is_invisible=req.is_invisible, | |
| ) | |
| elapsed = round(time.time() - start, 2) | |
| if token: | |
| return ProcessResponse(success=True, token=token, elapsed=elapsed) | |
| else: | |
| return ProcessResponse(success=False, error="Timeout: could not complete", elapsed=elapsed) | |
| except Exception as e: | |
| elapsed = round(time.time() - start, 2) | |
| logger.error(f"Sync process failed: {e}") | |
| return ProcessResponse(success=False, error=str(e), elapsed=elapsed) | |
| async def process_async(req: ProcessRequest, background_tasks: BackgroundTasks): | |
| """ | |
| Process verification asynchronously. | |
| Returns a task_id immediately. Poll /status/{task_id} for results. | |
| """ | |
| if not engine or not engine.loaded: | |
| raise HTTPException(503, "Engine not ready (models still loading)") | |
| task_id = str(uuid.uuid4()) | |
| active_sessions[task_id] = { | |
| "status": "pending", | |
| "token": None, | |
| "error": None, | |
| "elapsed": 0.0, | |
| } | |
| background_tasks.add_task(_process_background, task_id, req) | |
| return {"task_id": task_id, "status": "pending"} | |
| async def status(task_id: str): | |
| """Check the status of an async processing task.""" | |
| if task_id not in active_sessions: | |
| raise HTTPException(404, "Task not found") | |
| session = active_sessions[task_id] | |
| return StatusResponse( | |
| task_id=task_id, | |
| status=session["status"], | |
| token=session.get("token"), | |
| error=session.get("error"), | |
| elapsed=session["elapsed"], | |
| ) | |
| # ββ Entry Point ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |