From 640ef86ee3a975a5a213fc4b2044e5559fb14d16 Mon Sep 17 00:00:00 2001 From: Harsh Kashyap <55448981+Harsh23Kashyap@users.noreply.github.com> Date: Sun, 13 Sep 2026 13:59:57 +0530 Subject: [PATCH] fix: raise APIConnectionError when async response body read fails When aiohttp fails to read a non-streaming response body (e.g. the connection drops mid-read), the async interpretation path logged the error and fell through, raising UnboundLocalError on the unassigned content variable instead of a meaningful SDK error. Raise error.APIConnectionError, matching the synchronous path and arequest_raw. --- src/together/abstract/api_requestor.py | 4 +- tests/unit/test_async_response_errors.py | 50 ++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_async_response_errors.py diff --git a/src/together/abstract/api_requestor.py b/src/together/abstract/api_requestor.py index e956bb3a..1d775254 100644 --- a/src/together/abstract/api_requestor.py +++ b/src/together/abstract/api_requestor.py @@ -694,7 +694,9 @@ async def binary_stream_generator() -> ( except (aiohttp.ServerTimeoutError, asyncio.TimeoutError) as e: raise error.Timeout("Request timed out") from e except aiohttp.ClientError as e: - utils.log_warn(e, body=result.content) + raise error.APIConnectionError( + "Error communicating with Together" + ) from e if content_type in ["application/octet-stream", "audio/wav", "audio/mpeg"]: # Binary content - keep as bytes diff --git a/tests/unit/test_async_response_errors.py b/tests/unit/test_async_response_errors.py new file mode 100644 index 00000000..5955541c --- /dev/null +++ b/tests/unit/test_async_response_errors.py @@ -0,0 +1,50 @@ +from unittest.mock import patch + +import aiohttp +import pytest + +from together.abstract.api_requestor import APIRequestor +from together.error import APIConnectionError, Timeout +from together.types import TogetherClient + + +class _FailingReadResponse: + """Minimal stand-in for aiohttp.ClientResponse whose read() fails.""" + + status = 200 + headers = {"Content-Type": "application/json"} + content = None + + def __init__(self, exc): + self._exc = exc + + async def read(self): + raise self._exc + + def release(self): + pass + + +class TestAsyncResponseReadErrors: + @pytest.fixture + def requestor(self): + with patch.dict("os.environ", {"TOGETHER_API_KEY": "fake_api_key"}): + return APIRequestor(client=TogetherClient(api_key="fake_api_key")) + + @pytest.mark.asyncio + async def test_client_error_reading_body_raises_connection_error(self, requestor): + """ + A connection failure while reading a non-streaming response body must + surface as APIConnectionError, not UnboundLocalError. + """ + resp = _FailingReadResponse(aiohttp.ClientError("connection reset")) + + with pytest.raises(APIConnectionError): + await requestor._interpret_async_response(resp, stream=False) + + @pytest.mark.asyncio + async def test_timeout_reading_body_raises_timeout(self, requestor): + resp = _FailingReadResponse(aiohttp.ServerTimeoutError("read timeout")) + + with pytest.raises(Timeout): + await requestor._interpret_async_response(resp, stream=False)