Skip to content

Commit 244b7e7

Browse files
authored
Merge pull request #22 from workflowai/yann/wor-2705-fix-errorresponse-validation-error
Fix ErrorResponse validation error
2 parents fbb490c + 36a153c commit 244b7e7

File tree

3 files changed

+94
-3
lines changed

3 files changed

+94
-3
lines changed

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "workflowai"
3-
version = "0.4.1"
3+
version = "0.4.2"
44
description = ""
55
authors = ["Guillaume Aquilina <guillaume@workflowai.com>"]
66
readme = "README.md"
@@ -51,6 +51,8 @@ ignore = [
5151
"TD",
5252
"PYI051",
5353
"FIX002",
54+
"SLF001", #reportPrivateUsage
55+
"PT017", # Do not force using pytest.raises
5456
]
5557

5658
# Allow fix for all enabled rules (when `--fix`) is provided.

workflowai/core/client/api.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from json import JSONDecodeError
21
from typing import Any, AsyncIterator, Literal, Optional, TypeVar, Union, overload
32

43
import httpx
@@ -99,7 +98,7 @@ def _extract_error(
9998
try:
10099
res = ErrorResponse.model_validate_json(data)
101100
return WorkflowAIError(error=res.error, task_run_id=res.task_run_id, response=response)
102-
except JSONDecodeError:
101+
except ValidationError:
103102
raise WorkflowAIError(
104103
error=BaseError(
105104
message="Unknown error" if exception is None else str(exception),
@@ -123,6 +122,11 @@ async def stream(
123122
content=data.model_dump_json(exclude_none=True),
124123
headers={"Content-Type": "application/json"},
125124
) as response:
125+
if not response.is_success:
126+
# We need to read the response to get the error message
127+
await response.aread()
128+
response.raise_for_status()
129+
126130
async for chunk in response.aiter_bytes():
127131
payload = ""
128132
try:

workflowai/core/client/api_test.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import httpx
2+
import pytest
3+
from pydantic import BaseModel
4+
from pytest_httpx import HTTPXMock
5+
6+
from workflowai.core.client.api import APIClient
7+
from workflowai.core.domain.errors import WorkflowAIError
8+
9+
10+
class TestAPIClientExtractError:
11+
def test_extract_error(self):
12+
client = APIClient(endpoint="test_endpoint", api_key="test_api_key")
13+
14+
# Test valid JSON error response
15+
response = httpx.Response(
16+
status_code=400,
17+
json={
18+
"error": {
19+
"message": "Test error message",
20+
"details": {"key": "value"},
21+
},
22+
"task_run_id": "test_task_123",
23+
},
24+
)
25+
26+
error = client._extract_error(response, response.content) # pyright:ignore[reportPrivateUsage]
27+
assert isinstance(error, WorkflowAIError)
28+
assert error.error.message == "Test error message"
29+
assert error.error.details == {"key": "value"}
30+
assert error.task_run_id == "test_task_123"
31+
assert error.response == response
32+
33+
def test_extract_error_invalid_json(self):
34+
client = APIClient(endpoint="test_endpoint", api_key="test_api_key")
35+
36+
# Test invalid JSON response
37+
invalid_data = b"Invalid JSON data"
38+
response = httpx.Response(status_code=400, content=invalid_data)
39+
40+
with pytest.raises(WorkflowAIError) as e:
41+
client._extract_error(response, invalid_data) # pyright:ignore[reportPrivateUsage]
42+
assert isinstance(e.value, WorkflowAIError)
43+
assert e.value.error.message == "Unknown error"
44+
assert e.value.error.details == {"raw": "b'Invalid JSON data'"}
45+
assert e.value.response == response
46+
47+
def test_extract_error_with_custom_error(self):
48+
client = APIClient(endpoint="test_endpoint", api_key="test_api_key")
49+
50+
# Test with provided exception
51+
invalid_data = "{'detail': 'Not Found'}"
52+
response = httpx.Response(status_code=404, content=invalid_data)
53+
exception = ValueError("Custom error")
54+
55+
with pytest.raises(WorkflowAIError) as e:
56+
client._extract_error(response, invalid_data, exception) # pyright:ignore[reportPrivateUsage]
57+
assert isinstance(e.value, WorkflowAIError)
58+
assert e.value.error.message == "Custom error"
59+
assert e.value.error.details == {"raw": "{'detail': 'Not Found'}"}
60+
assert e.value.response == response
61+
62+
63+
async def test_stream_404(httpx_mock: HTTPXMock):
64+
class TestInputModel(BaseModel):
65+
test_input: str
66+
67+
class TestOutputModel(BaseModel):
68+
test_output: str
69+
70+
httpx_mock.add_response(status_code=404)
71+
72+
client = APIClient(endpoint="https://blabla.com", api_key="test_api_key")
73+
74+
try:
75+
async for _ in client.stream(
76+
method="GET",
77+
path="test_path",
78+
data=TestInputModel(test_input="test"),
79+
returns=TestOutputModel,
80+
):
81+
pass
82+
except httpx.HTTPStatusError as e:
83+
assert isinstance(e, httpx.HTTPStatusError)
84+
assert e.response.status_code == 404
85+
assert e.response.reason_phrase == "Not Found"

0 commit comments

Comments
 (0)