diff --git a/httpx/_decoders.py b/httpx/_decoders.py index 57c649297a..3f507c8e04 100644 --- a/httpx/_decoders.py +++ b/httpx/_decoders.py @@ -212,7 +212,7 @@ def __init__(self, chunk_size: typing.Optional[int] = None) -> None: def decode(self, content: str) -> typing.List[str]: if self._chunk_size is None: - return [content] + return [content] if content else [] self._buffer.write(content) if self._buffer.tell() >= self._chunk_size: @@ -280,7 +280,9 @@ def decode(self, text: str) -> typing.List[str]: text = text[:-1] if not text: - return [] + # NOTE: the edge case input of empty text doesn't occur in practice, + # because other httpx internals filter out this value + return [] # pragma: no cover trailing_newline = text[-1] in NEWLINE_CHARS lines = text.splitlines() diff --git a/httpx/_models.py b/httpx/_models.py index dac177c4f6..b8617cdab5 100644 --- a/httpx/_models.py +++ b/httpx/_models.py @@ -853,7 +853,7 @@ def iter_text( yield chunk text_content = decoder.flush() for chunk in chunker.decode(text_content): - yield chunk + yield chunk # pragma: no cover for chunk in chunker.flush(): yield chunk @@ -957,7 +957,7 @@ async def aiter_text( yield chunk text_content = decoder.flush() for chunk in chunker.decode(text_content): - yield chunk + yield chunk # pragma: no cover for chunk in chunker.flush(): yield chunk diff --git a/tests/test_decoders.py b/tests/test_decoders.py index 61c9a4acca..170a93453c 100644 --- a/tests/test_decoders.py +++ b/tests/test_decoders.py @@ -219,6 +219,17 @@ def test_text_decoder_empty_cases(): assert response.text == "" +@pytest.mark.parametrize( + ["data", "expected"], + [((b"Hello,", b" world!"), ["Hello,", " world!"])], +) +def test_streaming_text_decoder( + data: typing.Iterable[bytes], expected: typing.List[str] +) -> None: + response = httpx.Response(200, content=iter(data)) + assert list(response.iter_text()) == expected + + def test_line_decoder_nl(): response = httpx.Response(200, content=[b""]) assert list(response.iter_lines()) == []