Download api/errors.py from zlcy/acm: direct link, hf CLI and curl.
- Browser
- Download file 4.36 kB
-
https://huggingface.co/spaces/zlcy/acm/resolve/main/api/errors.py
- Command line
-
hf download hf://spaces/zlcy/acm/api/errors.py
-
curl -L -o errors.py https://huggingface.co/spaces/zlcy/acm/resolve/main/api/errors.py
4.36 kB
| from __future__ import annotations | |
| import time | |
| from fastapi import FastAPI, Request | |
| from fastapi.encoders import jsonable_encoder | |
| from fastapi.exceptions import RequestValidationError | |
| from fastapi.responses import JSONResponse | |
| from starlette.exceptions import HTTPException as StarletteHTTPException | |
| from starlette.requests import ClientDisconnect | |
| from services.protocol.error_response import anthropic_error_response, openai_error_response | |
| from utils.sensitive_data import sanitize_sensitive_data | |
| from utils.log import logger | |
| def _is_openai_compatible_path(path: str) -> bool: | |
| return path == "/v1" or path.startswith("/v1/") | |
| def _is_anthropic_messages_path(path: str) -> bool: | |
| return path == "/v1/messages" | |
| def _compatible_error_response( | |
| request: Request, | |
| detail: object, | |
| status_code: int, | |
| headers: dict[str, str] | None = None, | |
| ) -> JSONResponse: | |
| if _is_anthropic_messages_path(request.url.path): | |
| return anthropic_error_response(detail, status_code, headers=headers) | |
| return openai_error_response(detail, status_code, headers=headers) | |
| def install_exception_handlers(app: FastAPI) -> None: | |
| async def client_disconnect_handler(request: Request, exc: ClientDisconnect) -> JSONResponse: | |
| # A disconnected upload is not a server crash. Do not translate/record | |
| # this expected transport outcome in the unconfigured-error ledger. | |
| metrics = getattr(request.state, "image_upload_metrics", {}) | |
| length = request.headers.get("content-length", "") | |
| expected_bytes = int(length) if length.isascii() and length.isdigit() and len(length) <= 20 else None | |
| started = metrics.get("started_at") | |
| logger.info({ | |
| "event": "request_body_disconnected", | |
| "route": getattr(request.scope.get("route"), "path", "unknown"), | |
| "body_format": metrics.get("body_format", "unknown"), | |
| "expected_bytes": expected_bytes, | |
| "received_bytes": metrics.get("received_bytes"), | |
| "duration_ms": round((time.monotonic() - started) * 1000) if started is not None else None, | |
| }) | |
| message = ( | |
| "图片上传连接已中断,请检查网络后重新上传;请先确认任务列表中没有已提交的任务,避免重复提交。" | |
| if metrics else "请求数据传输已中断,请检查网络后重试。" | |
| ) | |
| if _is_anthropic_messages_path(request.url.path): | |
| content = {"type": "error", "error": {"type": "invalid_request_error", "message": message}} | |
| elif _is_openai_compatible_path(request.url.path): | |
| content = {"error": {"message": message, "type": "invalid_request_error", | |
| "code": "client_disconnected", "param": None}} | |
| else: | |
| content = {"detail": {"error": message, "code": "client_disconnected"}} | |
| # The peer is usually already gone: 499 describes our server outcome, | |
| # not a promise that the disconnected browser can receive this body. | |
| return JSONResponse(status_code=499, content=content, headers={"Cache-Control": "no-store"}) | |
| async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse: | |
| if _is_openai_compatible_path(request.url.path): | |
| return _compatible_error_response(request, exc.detail, exc.status_code, exc.headers) | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content={ | |
| "detail": sanitize_sensitive_data( | |
| jsonable_encoder(exc.detail), | |
| drop_personal_fields=True, | |
| ) | |
| }, | |
| headers=exc.headers, | |
| ) | |
| async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse: | |
| if _is_openai_compatible_path(request.url.path): | |
| return _compatible_error_response(request, exc.errors(), 422) | |
| return JSONResponse( | |
| status_code=422, | |
| content={ | |
| "detail": sanitize_sensitive_data( | |
| jsonable_encoder(exc.errors()), | |
| drop_personal_fields=True, | |
| ) | |
| }, | |
| ) | |