Skip to content

Commit def28d2

Browse files
yeldarbyclaude
andcommitted
rfapi: extract error message from JSON body on non-2xx
Backend trash endpoints (DELETE /:ws/:project, DELETE /:ws/:project/:version, DELETE /:ws/workflows/:url, GET /:ws/trash, POST /:ws/trash/restore) now return proper 4xx/5xx HTTP status codes with `{"error": "..."}` JSON bodies instead of 200 with an error tucked into the body. Update the Python SDK to pull the `error` string out before raising RoboflowError so users (and the CLI's error formatter) see a clean message instead of the raw response body. Falls back to response.text if the body isn't JSON or doesn't have an `error` field, so non-trash endpoints (which still return raw text on some failure paths) keep their existing behavior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2a97b6c commit def28d2

1 file changed

Lines changed: 22 additions & 5 deletions

File tree

‎roboflow/adapters/rfapi.py‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,23 @@ def search_universe(query, *, api_key=None, project_type=None, limit=12, page=1)
855855
# ---------------------------------------------------------------------------
856856

857857

858+
def _raise_for_trash_response(response):
859+
"""Raise RoboflowError with the cleanest message available.
860+
861+
Backend trash endpoints return `{"error": "..."}` JSON on non-2xx.
862+
Surface that string to the caller instead of the raw response body so
863+
error messages are agent-friendly. Falls back to the raw text if the
864+
body isn't JSON or doesn't contain an `error` field.
865+
"""
866+
try:
867+
body = response.json()
868+
if isinstance(body, dict) and body.get("error"):
869+
raise RoboflowError(body["error"])
870+
except (ValueError, AttributeError):
871+
pass
872+
raise RoboflowError(response.text)
873+
874+
858875
def delete_project(api_key, workspace_url, project_url):
859876
"""DELETE /{workspace}/{project} — move a project to Trash (30-day retention).
860877
@@ -865,7 +882,7 @@ def delete_project(api_key, workspace_url, project_url):
865882
url = f"{API_URL}/{workspace_url}/{project_url}?api_key={api_key}"
866883
response = requests.delete(url)
867884
if response.status_code != 200:
868-
raise RoboflowError(response.text)
885+
_raise_for_trash_response(response)
869886
return response.json()
870887

871888

@@ -877,7 +894,7 @@ def delete_version(api_key, workspace_url, project_url, version):
877894
url = f"{API_URL}/{workspace_url}/{project_url}/{version}?api_key={api_key}"
878895
response = requests.delete(url)
879896
if response.status_code != 200:
880-
raise RoboflowError(response.text)
897+
_raise_for_trash_response(response)
881898
return response.json()
882899

883900

@@ -888,7 +905,7 @@ def delete_workflow(api_key, workspace_url, workflow_url):
888905
url = f"{API_URL}/{workspace_url}/workflows/{workflow_url}?api_key={api_key}"
889906
response = requests.delete(url)
890907
if response.status_code != 200:
891-
raise RoboflowError(response.text)
908+
_raise_for_trash_response(response)
892909
return response.json()
893910

894911

@@ -902,7 +919,7 @@ def list_trash(api_key, workspace_url):
902919
url = f"{API_URL}/{workspace_url}/trash?api_key={api_key}"
903920
response = requests.get(url)
904921
if response.status_code != 200:
905-
raise RoboflowError(response.text)
922+
_raise_for_trash_response(response)
906923
return response.json()
907924

908925

@@ -918,7 +935,7 @@ def restore_trash_item(api_key, workspace_url, item_type, item_id, parent_id=Non
918935
payload["parentId"] = parent_id
919936
response = requests.post(url, json=payload)
920937
if response.status_code != 200:
921-
raise RoboflowError(response.text)
938+
_raise_for_trash_response(response)
922939
return response.json()
923940

924941

0 commit comments

Comments
 (0)