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=[])