fix(scripts): HTTP 错误带上响应体

raise_for_status 只给一行 URL,而 Label Studio 把真正原因写在响应体里。
批量导入返回 500 时看不到任何线索,排查只能靠猜。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-22 13:25:41 +08:00
co-authored by Claude Opus 5
parent 744e5fd18a
commit 3c964abaa2
2 changed files with 24 additions and 3 deletions
+16 -3
View File
@@ -87,7 +87,12 @@ class LabelStudio:
if response.status_code == 401: if response.status_code == 401:
self._authorize() self._authorize()
response = self.session.request(method, f"{self.url}{path}", **kwargs) 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 return response
def tasks(self, project: int, page_size: int = 500) -> list[dict]: def tasks(self, project: int, page_size: int = 500) -> list[dict]:
@@ -292,11 +297,19 @@ def main() -> int:
total_boxes += len(boxes) total_boxes += len(boxes)
with_boxes += bool(boxes) with_boxes += bool(boxes)
if len(pending) >= args.chunk: if len(pending) >= args.chunk:
flush() try:
flush()
except RuntimeError as exc:
logger.error("%s", exc)
return 1
processed = start + len(chunk) processed = start + len(chunk)
if processed % 1000 < args.batch: if processed % 1000 < args.batch:
logger.info("处理 [%d/%d],已上传 %d", processed, len(pairs), uploaded) logger.info("处理 [%d/%d],已上传 %d", processed, len(pairs), uploaded)
flush() try:
flush()
except RuntimeError as exc:
logger.error("%s", exc)
return 1
logger.info( logger.info(
"完成:%d 个任务,%d 个有框(%d 个框),上传 %d 条预测,版本 %s", "完成:%d 个任务,%d 个有框(%d 个框),上传 %d 条预测,版本 %s",
+8
View File
@@ -169,3 +169,11 @@ def test_predictions_endpoint_is_not_paginated():
methods = [method for method, _, _ in session.requests] methods = [method for method, _, _ in session.requests]
assert methods == ["GET", "DELETE", "DELETE"] # 只列一次,删两条 assert methods == ["GET", "DELETE", "DELETE"] # 只列一次,删两条
assert session.requests[1][1] == "http://ls/api/predictions/1/" 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=[])