Skip to content

Commit 699c16b

Browse files
fix(streaming): respect size in S3Object.readline (#8558)
Co-authored-by: xili-ytl <293821412+xili-ytl@users.noreply.github.com> Co-authored-by: Leandro Damascena <lcdama@amazon.pt>
1 parent 492161c commit 699c16b

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

‎aws_lambda_powertools/utilities/streaming/s3_object.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ def read(self, size: int = -1) -> bytes:
231231
return self.transformed_stream.read(size)
232232

233233
def readline(self, size: int | None = -1) -> bytes:
234-
return self.transformed_stream.readline()
234+
return self.transformed_stream.readline(-1 if size is None else size)
235235

236236
def readlines(self, hint: int = -1) -> list[bytes]:
237237
return self.transformed_stream.readlines(hint)

‎tests/functional/streaming/_boto3/test_s3_object.py‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
from __future__ import annotations
22

3+
import gzip
4+
import io
35
from csv import DictReader
46
from gzip import GzipFile
57

8+
import boto3
9+
import pytest
10+
from botocore import stub
11+
612
from aws_lambda_powertools.utilities.streaming import S3Object
713
from aws_lambda_powertools.utilities.streaming._s3_seekable_io import _S3SeekableIO
14+
from aws_lambda_powertools.utilities.streaming.compat import PowertoolsStreamingBody
815
from aws_lambda_powertools.utilities.streaming.transformations import GzipTransform
916

1017

18+
@pytest.fixture
19+
def s3_client():
20+
return boto3.client("s3")
21+
22+
23+
@pytest.fixture
24+
def s3_client_stub(s3_client):
25+
with stub.Stubber(s3_client) as s3_stub:
26+
yield s3_stub
27+
28+
1129
def test_s3_basic_stream():
1230
obj = S3Object(bucket="bucket", key="key")
1331
assert type(obj.transformed_stream) is _S3SeekableIO
@@ -40,3 +58,36 @@ def test_s3_transform_in_place():
4058

4159
new_obj = obj.transform(GzipTransform(), in_place=True)
4260
assert new_obj is None
61+
62+
63+
@pytest.mark.parametrize("is_gzip", [False, True], ids=["raw", "gzip"])
64+
@pytest.mark.parametrize(
65+
"readline_kwargs, expected",
66+
[
67+
pytest.param({"size": 0}, b"", id="zero"),
68+
pytest.param({"size": 2}, b"he", id="short"),
69+
pytest.param({}, b"hello world\n", id="default"),
70+
pytest.param({"size": -1}, b"hello world\n", id="negative"),
71+
pytest.param({"size": None}, b"hello world\n", id="none"),
72+
pytest.param({"size": 100}, b"hello world\n", id="past-newline"),
73+
],
74+
)
75+
def test_s3_readline_respects_size(s3_client, s3_client_stub, is_gzip, readline_kwargs, expected):
76+
payload = b"hello world\nnext line\n"
77+
wire_payload = gzip.compress(payload) if is_gzip else payload
78+
streaming_body = PowertoolsStreamingBody(raw_stream=io.BytesIO(wire_payload), content_length=len(wire_payload))
79+
s3_client_stub.add_response(
80+
"get_object",
81+
{"Body": streaming_body},
82+
{"Bucket": "bucket", "Key": "key", "Range": "bytes=0-"},
83+
)
84+
85+
with S3Object(bucket="bucket", key="key", boto3_client=s3_client, is_gzip=is_gzip) as obj:
86+
assert obj.readline(**readline_kwargs) == expected
87+
assert obj.tell() == len(expected)
88+
assert obj.read() == payload[len(expected) :]
89+
assert obj.tell() == len(payload)
90+
assert obj.readline(**readline_kwargs) == b""
91+
assert obj.tell() == len(payload)
92+
93+
s3_client_stub.assert_no_pending_responses()

0 commit comments

Comments
 (0)