Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/openai/_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,10 +419,11 @@ def decode(self, line: str) -> ServerSentEvent | None:
else:
self._last_event_id = value
elif fieldname == "retry":
try:
# Per the SSE spec, a retry field is valid only if it consists entirely
# of ASCII digits; anything else (a sign, whitespace, a decimal point)
# must be ignored rather than parsed leniently by int().
if value.isascii() and value.isdigit():
self._retry = int(value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore unrepresentable retry values

On the default supported CPython configuration, an otherwise valid ASCII-digit retry value of 4,301+ digits passes this predicate but int(value) raises ValueError because of Python’s integer-string conversion limit, aborting both sync and async SSE iteration. The prior try/except safely ignored this field, so preserve that handling after validation rather than letting a large streaming line terminate the response.

AGENTS.md reference: AGENTS.md:L114-L121

Useful? React with 👍 / 👎.

except (TypeError, ValueError):
pass
else:
pass # Field is ignored.

Expand Down
23 changes: 22 additions & 1 deletion tests/test_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest

from openai import OpenAI, AsyncOpenAI, APITimeoutError, APIConnectionError
from openai._streaming import Stream, AsyncStream, ServerSentEvent
from openai._streaming import SSEDecoder, Stream, AsyncStream, ServerSentEvent


@pytest.fixture(
Expand Down Expand Up @@ -408,6 +408,27 @@ def body() -> Iterator[bytes]:
assert response.is_closed


@pytest.mark.parametrize("value", ["-1", "+1000", "1.5", " 100", "100 ", "1e3", ""])
def test_sse_decoder_ignores_invalid_retry_value(value: str) -> None:
decoder = SSEDecoder()
decoder.decode(f"retry: {value}")
decoder.decode("data: ok")
sse = decoder.decode("")

assert sse is not None
assert sse.retry is None


def test_sse_decoder_accepts_valid_retry_value() -> None:
decoder = SSEDecoder()
decoder.decode("retry: 3000")
decoder.decode("data: ok")
sse = decoder.decode("")

assert sse is not None
assert sse.retry == 3000


async def to_aiter(iter: Iterator[bytes]) -> AsyncIterator[bytes]:
for chunk in iter:
yield chunk
Expand Down