Spaces:
Running
Running
stouchinglhh
Claude
sync processor from GitLab c697b6e: 版本 1.19.4 / 前端 1.12.4 —— 服务端 i18n(首屏不再闪英文)、推理模块并入模型页、前端抽出 shared/、缓存键改用前端号
bb59211 Download processor/app/main.py from Stouching-Robotics/DataFlowing: direct link, hf CLI and curl.
- Browser
- Download file 6.87 kB
-
https://huggingface.co/spaces/Stouching-Robotics/DataFlowing/resolve/main/processor/app/main.py
- Command line
-
hf download hf://spaces/Stouching-Robotics/DataFlowing/processor/app/main.py
-
curl -L -o main.py https://huggingface.co/spaces/Stouching-Robotics/DataFlowing/resolve/main/processor/app/main.py
6.87 kB
| """FastAPI application entry point — 纯本地文件存储,不依赖 PostgreSQL。""" | |
| import asyncio | |
| import mimetypes | |
| import threading | |
| from contextlib import asynccontextmanager | |
| from pathlib import Path | |
| from fastapi import FastAPI | |
| from fastapi.staticfiles import StaticFiles | |
| from starlette.middleware.gzip import GZipMiddleware | |
| from app.config import settings | |
| from app.routes import ingestion, video, export, pages, session, annotations, dashboard, devices, auth | |
| from app.routes import training | |
| from app.api import workflows, worker, projects, exceptions, quality | |
| from app.api import users | |
| from app.i18n import LanguageMiddleware | |
| from app.middleware import AuthMiddleware | |
| from app.paths import STATIC_DIR | |
| from app.version import __version__ | |
| async def lifespan(app: FastAPI): | |
| # Metadata remains file-backed for compatibility, while EGOData stores | |
| # managed accounts and future relational metadata. | |
| try: | |
| from app.database import init_db | |
| await init_db() | |
| except Exception as exc: | |
| # Keep the legacy file-backed application available during a temporary | |
| # database/tunnel outage; auth.py will use its JSON fallback. | |
| print(f"[Startup] Database initialization skipped: {exc}") | |
| from app import localstore | |
| localstore.STATE_ROOT.mkdir(parents=True, exist_ok=True) | |
| try: | |
| if localstore.migrate_workflow_definitions(): | |
| print("[Startup] Migrated workflow node types to the canonical RGB/RGB-D schema") | |
| except Exception as exc: | |
| # The API/worker also accept legacy aliases, so a migration failure is | |
| # non-fatal and can be retried on the next startup. | |
| print(f"[Startup] Workflow type migration skipped: {exc}") | |
| # Do the one potentially slow storage scan before accepting requests. This | |
| # keeps a cold network-mounted data directory from becoming the first page | |
| # load seen by a user after a service restart. A transient storage issue | |
| # must not prevent the compatibility service from starting. | |
| try: | |
| await asyncio.to_thread(localstore.scan_sessions) | |
| except Exception as exc: | |
| print(f"[Startup] Session cache warm-up skipped: {exc}") | |
| threading.Thread( | |
| target=localstore.warm_metadata_caches, | |
| name="metadata-cache-warmup", | |
| daemon=True, | |
| ).start() | |
| from app import upload_queue | |
| upload_queue.start() | |
| print(f"[Startup] processor Service on port {settings.PORT}") | |
| print(f"[Startup] Storage: {settings.STORAGE_DIR} ({settings.STORAGE_BACKEND})") | |
| try: | |
| yield | |
| finally: | |
| upload_queue.stop() | |
| print("[Shutdown] Done") | |
| # 版本号取自 app/version.py —— 此前这里硬编码 "0.1.0",与 version.py 的 | |
| # 1.7.0 长期不一致,/docs 的 OpenAPI 版本与 /health 报的是两个数。 | |
| app = FastAPI( | |
| title="processor Service", | |
| version=__version__, | |
| lifespan=lifespan, | |
| ) | |
| # GZip compression — JSON responses shrink ~10× | |
| app.add_middleware(GZipMiddleware, minimum_size=500) | |
| # Auth middleware — protect routes (after gzip, before everything else) | |
| # 语言中间件要在 Auth 之外(先于它)跑:鉴权失败跳登录页时,那个页面本身也要 | |
| # 按 cookie 里的语言渲染。add_middleware 是**后加的先跑**,所以放在 Auth 之后加。 | |
| app.add_middleware(AuthMiddleware) | |
| app.add_middleware(LanguageMiddleware) | |
| # 媒体/压缩文件下载不走 gzip:zip/mp4/parquet/hdf5 本身已是压缩格式, | |
| # 再压一遍只是烧 CPU(zlib level 9 压缩 180MB ≈ 10s),而且流式压缩会 | |
| # 删掉 Content-Length —— 浏览器下载进度显示"未知大小",大文件看起来 | |
| # 像卡死。这些路径直接透传原始字节(去掉 accept-encoding 头即可)。 | |
| _NO_COMPRESS_PATH_HINTS = ( | |
| "download", "stream", "depth-codes", "depth-preview", "skeleton", "hdf5", | |
| "tactile-matrix-data", | |
| ) | |
| async def no_compress_media(request, call_next): | |
| if any(hint in request.url.path for hint in _NO_COMPRESS_PATH_HINTS): | |
| request.scope["headers"] = [ | |
| (key, value) for key, value in request.scope["headers"] | |
| if key.lower() != b"accept-encoding" | |
| ] | |
| return await call_next(request) | |
| # Static JS/CSS is versioned (workflow assets use content hashes; the legacy | |
| # page scripts use a ``?v=...`` query). Tell the browser to retain these | |
| # resources across full page navigation instead of revalidating every common | |
| # asset on each switch between Projects, Review and Workflow Studio. | |
| async def static_cache_headers(request, call_next): | |
| response = await call_next(request) | |
| path = request.url.path | |
| if path.startswith("/static/"): | |
| if "/assets/" in path and "/workflow-studio/" in path: | |
| response.headers["Cache-Control"] = "public, max-age=31536000, immutable" | |
| elif request.url.query: | |
| response.headers["Cache-Control"] = "public, max-age=86400" | |
| elif path.startswith("/static/js/"): | |
| # ES module 的相对 import 解析出来的 URL **不带查询串** —— 就算入口 | |
| # script 写了 ?v=,`import './api.js'` 也永远是同一个地址。给它任何 | |
| # 正数的 max-age,升级后都会出现「新入口 + 旧子模块」混用,直接抛 | |
| # TypeError。这类文件必须每次回源校验(未变则 304,开销可忽略)。 | |
| # | |
| # 范围刻意收窄到 /static/js/:带 ?v= 的老脚本走上面那支不受影响; | |
| # /static/ 顶层的第三方库(plyr.js、iconify-*)也不在范围里。 | |
| response.headers["Cache-Control"] = "no-cache" | |
| else: | |
| response.headers["Cache-Control"] = "public, max-age=3600" | |
| return response | |
| # Static files (CSS/JS) | |
| static_dir = STATIC_DIR | |
| static_dir.mkdir(parents=True, exist_ok=True) | |
| mimetypes.add_type("text/javascript", ".js") | |
| mimetypes.add_type("text/javascript", ".mjs") | |
| mimetypes.add_type("text/css", ".css") | |
| mimetypes.add_type("application/json", ".json") | |
| app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") | |
| # Routes | |
| app.include_router(ingestion.router) | |
| app.include_router(video.router) | |
| app.include_router(export.router) | |
| app.include_router(pages.router) | |
| app.include_router(session.router) | |
| app.include_router(annotations.router) | |
| from app import ai_annotation # AI 辅助标注(信号切段 + VLM 提案) | |
| app.include_router(ai_annotation.router) | |
| app.include_router(dashboard.router) | |
| # tasks.router 已并入 projects.router(项目概念,任务已移除),不再注册 | |
| app.include_router(devices.router) | |
| app.include_router(auth.router) | |
| app.include_router(workflows.router) | |
| app.include_router(worker.router) | |
| app.include_router(projects.router) | |
| app.include_router(exceptions.router) | |
| app.include_router(quality.router) | |
| app.include_router(users.router) | |
| app.include_router(training.router) | |