mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-06 07:48:06 +00:00
fix(aiohttp): hand out bytes buffered before a dropped connection so a cut stream bills its partial usage
test(integration): stop the CyberArk override test failing when a short config value lands inside ciphertext Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
3f93350411
commit
d54b3dc4be
3 changed files with 40 additions and 1 deletions
|
|
@ -109,6 +109,15 @@ def map_aiohttp_exceptions() -> Generator[None, None, None]:
|
|||
raise mapped_exc(message) from exc
|
||||
|
||||
|
||||
def _unread_payload(content: object) -> bytes:
|
||||
"""Bytes the upstream sent before the connection dropped that aiohttp buffered but never handed out, since its
|
||||
reads raise the stored payload error ahead of the buffer."""
|
||||
if not isinstance(content, aiohttp.StreamReader):
|
||||
return b""
|
||||
buffered: Final = b"".join(content._buffer) # pyright: ignore[reportPrivateUsage] # no public read drains it
|
||||
return buffered[content._buffer_offset :] # pyright: ignore[reportPrivateUsage] # see above
|
||||
|
||||
|
||||
class AiohttpResponseStream(httpx.AsyncByteStream):
|
||||
CHUNK_SIZE = 1024 * 16
|
||||
|
||||
|
|
@ -125,6 +134,12 @@ class AiohttpResponseStream(httpx.AsyncByteStream):
|
|||
raise httpx.ReadError(str(e)) from e
|
||||
except aiohttp.http_exceptions.TransferEncodingError as e:
|
||||
raise httpx.ReadError(str(e)) from e
|
||||
except aiohttp.ClientPayloadError:
|
||||
unread: Final = _unread_payload(self._aiohttp_response.content)
|
||||
if unread:
|
||||
yield unread
|
||||
with map_aiohttp_exceptions():
|
||||
raise
|
||||
except Exception:
|
||||
# For other exceptions, use the normal mapping
|
||||
with map_aiohttp_exceptions():
|
||||
|
|
|
|||
|
|
@ -715,8 +715,11 @@ def test_cyberark_incremental_update_encrypts_reloads_on_restart_and_delete_clea
|
|||
}, saved.text
|
||||
stored_full: Final = _stored_override(database_url, "cyberark")
|
||||
assert set(stored_full) == set(full_config), stored_full
|
||||
assert all(stored_full[key] != value for key, value in full_config.items()), stored_full
|
||||
serialized_full: Final = json.dumps(stored_full)
|
||||
assert all(value not in serialized_full for value in full_config.values()), serialized_full
|
||||
assert all(value not in serialized_full for value in (cyberark_wire.url, account, username, api_key)), (
|
||||
serialized_full
|
||||
)
|
||||
|
||||
model_one: Final = "integration-cyberark-" + uuid.uuid4().hex
|
||||
_new_model(first, model_one, provider_wire.url, secret_names[0])
|
||||
|
|
|
|||
|
|
@ -141,6 +141,27 @@ async def test_client_payload_error_mid_stream_raises_read_error():
|
|||
assert mock_response.closed is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bytes_buffered_before_a_dropped_connection_are_yielded_before_the_read_error():
|
||||
loop: Final = asyncio.get_running_loop()
|
||||
content: Final = aiohttp.StreamReader(aiohttp.base_protocol.BaseProtocol(loop), 2**16, loop=loop)
|
||||
content.feed_data(b"event: message_start\n\n")
|
||||
content.set_exception(aiohttp.ClientPayloadError("Response payload is not completed"))
|
||||
mock_response: Final = MockAiohttpResponse()
|
||||
mock_response.content = content
|
||||
received: Final[list[bytes]] = []
|
||||
|
||||
async def _drain() -> None:
|
||||
async for chunk in AiohttpResponseStream(mock_response): # pyright: ignore[reportArgumentType] # mock response
|
||||
received.append(chunk)
|
||||
|
||||
with pytest.raises(httpx.ReadError):
|
||||
await _drain()
|
||||
|
||||
assert received == [b"event: message_start\n\n"]
|
||||
assert mock_response.closed is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_payload_error_before_first_chunk_raises_read_error():
|
||||
"""A connection reset before any body byte must surface, not yield an empty 200 body"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue