Skip to content

Commit d44ff3e

Browse files
committed
support return type with raw type
1 parent f78bc2f commit d44ff3e

File tree

7 files changed

+182
-21
lines changed

7 files changed

+182
-21
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ class MyAPIClient(RequestsWebClient):
7272
def create_user(self, user: CreateUser) -> UserResponse:
7373
pass
7474

75+
@get("/users/string)
76+
def get_user_string(self, user_id: str) -> dict:
77+
# will get raw json data
78+
...
79+
80+
@get("/users/bytes")
81+
def get_user_bytes(self, user_id: str) -> bytes:
82+
# will get raw content, bytes type.
83+
...
84+
7585
@delete("/users", agno_tool=True, tool_description="this is the function to delete user")
7686
def delete_user(self, user_id: str, request_headers: Dict[str, Any]):
7787
...

pydantic_client/async_client.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@ async def _request(self, request_info: RequestInfo) -> Any:
3636
timeout=aiohttp.ClientTimeout(total=self.timeout)
3737
) as response:
3838
response.raise_for_status()
39-
data = await response.json()
4039

41-
if response_model is not None:
42-
return response_model.model_validate(data, from_attributes=True)
43-
return data
40+
if response_model is str:
41+
return await response.text()
42+
elif response_model is bytes:
43+
return await response.content.read()
44+
elif not response_model:
45+
return await response.json()
46+
return response_model.model_validate(await response.json(), from_attributes=True)
4447

4548

4649
class HttpxWebClient(BaseWebClient):
@@ -69,7 +72,11 @@ async def _request(self, request_info: RequestInfo) -> Any:
6972
async with httpx.AsyncClient(timeout=self.timeout) as client:
7073
response = await client.request(**request_params)
7174
response.raise_for_status()
72-
data = response.json()
73-
if response_model is not None:
74-
return response_model.model_validate(data, from_attributes=True)
75-
return data
75+
76+
if response_model is str:
77+
return response.text
78+
elif response_model is bytes:
79+
return response.content
80+
elif not response_model:
81+
return response.json()
82+
return response_model.model_validate(response.json(), from_attributes=True)

pydantic_client/decorators.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ def _process_request_params(
3636
# Get return type for response model
3737
return_type = sig.return_annotation
3838

39-
response_model = (
40-
return_type
41-
if isinstance(return_type, type) and issubclass(return_type, BaseModel)
42-
else None
43-
)
39+
if isinstance(return_type, type) and issubclass(return_type, BaseModel):
40+
response_model = return_type
41+
elif return_type in [str, bytes]:
42+
response_model = return_type
43+
else:
44+
response_model = None
4445

4546
# Format path with parameters
4647
formatted_path = path.format(**params)

pydantic_client/sync_client.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ def _request(self, request_info: RequestInfo) -> Any:
2929

3030
response = requests.request(**request_params, timeout=self.timeout)
3131
response.raise_for_status()
32-
33-
data = response.json()
34-
if response_model is not None:
35-
return response_model.model_validate(data, from_attributes=True)
36-
return data
32+
33+
if response_model is str:
34+
return response.text
35+
elif response_model is bytes:
36+
return response.content
37+
elif not response_model:
38+
return response.json()
39+
return response_model.model_validate(response.json(), from_attributes=True)

tests/conftest.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,13 @@ async def user_by_id(request):
2828
"email": f"user{user_id}@example.com"
2929
})
3030

31+
3132
async def list_users(request):
32-
return web.json_response({})
33+
return web.json_response({"users": []})
34+
35+
36+
async def get_user_string(request):
37+
return web.Response(text="abc")
3338

3439

3540
@pytest.fixture
@@ -48,6 +53,9 @@ async def mock_server(aiohttp_client) -> AsyncGenerator[
4853
app.router.add_post('/echo', echo_json)
4954
app.router.add_get('/users/{user_id}', user_by_id)
5055
app.router.add_get('/users', list_users)
56+
app.router.add_post('/users/string', get_user_string)
57+
app.router.add_post('/users/bytes', get_user_string)
58+
app.router.add_post('/users/dict', list_users)
5159

5260
client = await aiohttp_client(app)
5361
yield client

tests/test_async_client.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ async def get_user(self, user_id: str) -> User:
1717
@post("/users")
1818
async def create_user(self, name: str, email: str) -> User:
1919
...
20+
21+
@post("/users/string")
22+
async def get_user_string(self, name: str) -> str:
23+
...
24+
25+
@post("/users/bytes")
26+
async def get_user_bytes(self, name: str) -> bytes:
27+
...
28+
29+
@post("/users/dict")
30+
async def get_user_dict(self, name: str) -> dict:
31+
...
2032

2133

2234
class TestHttpxClient(HttpxWebClient):
@@ -27,6 +39,18 @@ async def get_user(self, user_id: str) -> User:
2739
@post("/users")
2840
async def create_user(self, name: str, email: str) -> User:
2941
...
42+
43+
@post("/users/string")
44+
async def get_user_string(self, name: str) -> str:
45+
...
46+
47+
@post("/users/bytes")
48+
async def get_user_bytes(self, name: str) -> bytes:
49+
...
50+
51+
@post("/users/dict")
52+
async def get_user_dict(self, name: str) -> dict:
53+
...
3054

3155

3256
@pytest.mark.asyncio
@@ -62,4 +86,58 @@ async def test_aiohttp_error_handling(mock_server, base_url):
6286
async def test_httpx_error_handling(mock_server, base_url):
6387
client = TestHttpxClient(base_url=f"{base_url}/nonexistent")
6488
with pytest.raises(BaseException):
65-
await client.get_user("123")
89+
await client.get_user("123")
90+
91+
92+
@pytest.mark.asyncio
93+
async def test_aiohttp_get_string_request(mock_server, base_url):
94+
client = TestAiohttpClient(base_url=base_url)
95+
response = await client.get_user_string("123")
96+
97+
assert isinstance(response, str)
98+
assert response == "abc"
99+
100+
101+
@pytest.mark.asyncio
102+
async def test_aiohttp_get_bytes_request(mock_server, base_url):
103+
client = TestAiohttpClient(base_url=base_url)
104+
response = await client.get_user_bytes("123")
105+
print(response)
106+
assert isinstance(response, bytes)
107+
assert response == "abc".encode()
108+
109+
110+
@pytest.mark.asyncio
111+
async def test_httpx_get_dict_request(mock_server, base_url):
112+
client = TestAiohttpClient(base_url=base_url)
113+
response = await client.get_user_dict("123")
114+
115+
assert isinstance(response, dict)
116+
assert response == {"users": []}
117+
118+
119+
@pytest.mark.asyncio
120+
async def test_httpx_get_string_request(mock_server, base_url):
121+
client = TestHttpxClient(base_url=base_url)
122+
response = await client.get_user_string("123")
123+
124+
assert isinstance(response, str)
125+
assert response == "abc"
126+
127+
128+
@pytest.mark.asyncio
129+
async def test_httpx_get_bytes_request(mock_server, base_url):
130+
client = TestHttpxClient(base_url=base_url)
131+
response = await client.get_user_bytes("123")
132+
print(response)
133+
assert isinstance(response, bytes)
134+
assert response == "abc".encode()
135+
136+
137+
@pytest.mark.asyncio
138+
async def test_aiohttp_get_dict_request(mock_server, base_url):
139+
client = TestHttpxClient(base_url=base_url)
140+
response = await client.get_user_dict("123")
141+
142+
assert isinstance(response, dict)
143+
assert response == {"users": []}

tests/test_sync_client.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@ def get_user(self, user_id: str) -> User:
2020
def create_user(self, name: str, email: str) -> User:
2121
...
2222

23+
@get("/users/string")
24+
def get_user_string(self, name: str) -> str:
25+
...
26+
27+
@get("/users/bytes")
28+
def get_user_bytes(self, name: str) -> bytes:
29+
...
30+
31+
@get("/users/dict")
32+
def get_user_dict(self, name: str) -> dict:
33+
...
34+
2335

2436
def test_sync_get_request():
2537
with requests_mock.Mocker() as m:
@@ -66,4 +78,46 @@ def test_sync_error_handling():
6678

6779
client = TestClient(base_url="http://example.com")
6880
with pytest.raises(requests.exceptions.HTTPError):
69-
client.get_user("123")
81+
client.get_user("123")
82+
83+
84+
def test_sync_get_string_request():
85+
with requests_mock.Mocker() as m:
86+
m.get(
87+
'http://example.com/users/string',
88+
text="ac"
89+
)
90+
91+
client = TestClient(base_url="http://example.com")
92+
response = client.get_user_string("123")
93+
94+
assert isinstance(response, str)
95+
assert response == "ac"
96+
97+
98+
def test_sync_get_bytes_request():
99+
with requests_mock.Mocker() as m:
100+
m.get(
101+
'http://example.com/users/bytes',
102+
content="123".encode()
103+
)
104+
105+
client = TestClient(base_url="http://example.com")
106+
response = client.get_user_bytes("123")
107+
108+
assert isinstance(response, bytes)
109+
assert response == b"123"
110+
111+
112+
def test_sync_get_dict_request():
113+
with requests_mock.Mocker() as m:
114+
m.get(
115+
'http://example.com/users/dict',
116+
json={"user": 123}
117+
)
118+
119+
client = TestClient(base_url="http://example.com")
120+
response = client.get_user_dict("123")
121+
122+
assert isinstance(response, dict)
123+
assert response == {"user": 123}

0 commit comments

Comments
 (0)