Spaces:
Sleeping
Sleeping
Download backend/app/core/reqctx.py from tntgroup/website_content: direct link, hf CLI and curl.
- Browser
- Download file 2.39 kB
-
https://huggingface.co/spaces/tntgroup/website_content/resolve/main/backend/app/core/reqctx.py
- Command line
-
hf download hf://spaces/tntgroup/website_content/backend/app/core/reqctx.py
-
curl -L -o reqctx.py https://huggingface.co/spaces/tntgroup/website_content/resolve/main/backend/app/core/reqctx.py
2.39 kB
| """Ngữ cảnh theo dõi đi kèm mỗi request (user / session / task / feature). | |
| VÌ SAO LÀ HEADER, KHÔNG PHẢI BODY: | |
| Job được tạo ở ~15 endpoint với ~15 model Pydantic khác nhau, lại có cả | |
| endpoint nhận multipart (upload). Thêm 6 trường vào từng model là sửa rải | |
| rác và rất dễ sót. Frontend gắn header `X-TNT-*` một chỗ duy nhất (bản vá | |
| `fetch` trong `lib/track.js`), backend đọc một chỗ duy nhất ở đây, và | |
| `jobs.create()` tự nhặt — endpoint không phải biết gì về tracking. | |
| Dùng ContextVar nên an toàn với cả endpoint `async def` lẫn `def` (Starlette | |
| chạy endpoint đồng bộ trong threadpool có sao chép context). | |
| """ | |
| from __future__ import annotations | |
| from contextvars import ContextVar | |
| _ctx: ContextVar[dict] = ContextVar("tnt_track_ctx", default={}) | |
| HEADERS = { | |
| "user_id": "x-tnt-user", | |
| "session_id": "x-tnt-session", | |
| "task_id": "x-tnt-task", | |
| "feature": "x-tnt-feature", | |
| "phase": "x-tnt-phase", | |
| "attempt": "x-tnt-attempt", | |
| } | |
| def get_ctx() -> dict: | |
| return _ctx.get() or {} | |
| class TrackContextMiddleware: | |
| """ASGI middleware thuần — CỐ Ý không dùng BaseHTTPMiddleware. | |
| BaseHTTPMiddleware bọc response vào một luồng trung gian; app này phục vụ | |
| file video lớn bằng FileResponse nên không đáng đánh đổi. Ở đây chỉ đọc vài | |
| header rồi đặt ContextVar, không đụng gì tới request/response. | |
| """ | |
| def __init__(self, app): | |
| self.app = app | |
| async def __call__(self, scope, receive, send): | |
| if scope.get("type") != "http": | |
| return await self.app(scope, receive, send) | |
| raw = {} | |
| for k, v in scope.get("headers") or (): | |
| key = k.decode("latin-1").lower() | |
| if key.startswith("x-tnt-"): | |
| raw[key] = v.decode("latin-1", "ignore")[:120] | |
| ctx = {k: raw.get(v, "") for k, v in HEADERS.items()} | |
| try: | |
| ctx["attempt"] = int(ctx["attempt"]) if ctx["attempt"] else None | |
| except (TypeError, ValueError): | |
| ctx["attempt"] = None | |
| ctx["user_id"] = ctx["user_id"].strip().lower() | |
| token = _ctx.set(ctx if ctx["user_id"] else {}) | |
| try: | |
| await self.app(scope, receive, send) | |
| finally: | |
| _ctx.reset(token) | |