From 3c964abaa2cb2170f79131d7cbc190470c0b43b3 Mon Sep 17 00:00:00 2001 From: Shaun Zhou Date: Tue, 22 Sep 2026 13:25:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(scripts):=20HTTP=20=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E5=B8=A6=E4=B8=8A=E5=93=8D=E5=BA=94=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit raise_for_status 只给一行 URL,而 Label Studio 把真正原因写在响应体里。 批量导入返回 500 时看不到任何线索,排查只能靠猜。 Co-Authored-By: Claude Opus 5 --- scripts/push_predictions.py | 19 ++++++++++++++++--- tests/test_push_predictions.py | 8 ++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/scripts/push_predictions.py b/scripts/push_predictions.py index e714493..b3d92b4 100644 --- a/scripts/push_predictions.py +++ b/scripts/push_predictions.py @@ -87,7 +87,12 @@ class LabelStudio: if response.status_code == 401: self._authorize() response = self.session.request(method, f"{self.url}{path}", **kwargs) - response.raise_for_status() + if response.status_code >= 400: + # 带上响应体:Label Studio 的 4xx/5xx 常把真正原因写在里面, + # raise_for_status 只给一行 URL,排查时毫无用处 + raise RuntimeError( + f"{method} {path} 失败 HTTP {response.status_code}:{response.text[:500]}" + ) return response def tasks(self, project: int, page_size: int = 500) -> list[dict]: @@ -292,11 +297,19 @@ def main() -> int: total_boxes += len(boxes) with_boxes += bool(boxes) if len(pending) >= args.chunk: - flush() + try: + flush() + except RuntimeError as exc: + logger.error("%s", exc) + return 1 processed = start + len(chunk) if processed % 1000 < args.batch: logger.info("处理 [%d/%d],已上传 %d", processed, len(pairs), uploaded) - flush() + try: + flush() + except RuntimeError as exc: + logger.error("%s", exc) + return 1 logger.info( "完成:%d 个任务,%d 个有框(%d 个框),上传 %d 条预测,版本 %s", diff --git a/tests/test_push_predictions.py b/tests/test_push_predictions.py index dc37bdf..f8c63f0 100644 --- a/tests/test_push_predictions.py +++ b/tests/test_push_predictions.py @@ -169,3 +169,11 @@ def test_predictions_endpoint_is_not_paginated(): methods = [method for method, _, _ in session.requests] assert methods == ["GET", "DELETE", "DELETE"] # 只列一次,删两条 assert session.requests[1][1] == "http://ls/api/predictions/1/" + + +def test_http_errors_include_the_response_body(): + """Label Studio 把真正原因写在响应体里,只抛 URL 没法排查。""" + session = _TokenSession(statuses=[500], payload={"detail": "boom"}) + client = _client(session) + with pytest.raises(RuntimeError, match="HTTP 500.*boom"): + client.request("POST", "/api/projects/39/import/predictions", json=[])