Skip to content

Commit 02ee3d4

Browse files
committed
patch AwsChunkedWrapper.read
1 parent 8eea9b9 commit 02ee3d4

File tree

5 files changed

+159
-1
lines changed

5 files changed

+159
-1
lines changed

CHANGES.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
Changes
22
-------
33

4+
2.20.0 (2025-02-13)
5+
^^^^^^^^^^^^^^^^^^^
6+
* patch `AwsChunkedWrapper.read`
7+
48
2.19.0 (2025-01-22)
59
^^^^^^^^^^^^^^^^^^^
610
* support custom `ttl_dns_cache` connector configuration

aiobotocore/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.19.0'
1+
__version__ = '2.20.0'

aiobotocore/httpchecksum.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,31 @@
1515

1616

1717
class AioAwsChunkedWrapper(AwsChunkedWrapper):
18+
async def read(self, size=None):
19+
# Normalize "read all" size values to None
20+
if size is not None and size <= 0:
21+
size = None
22+
23+
# If the underlying body is done and we have nothing left then
24+
# end the stream
25+
if self._complete and not self._remaining:
26+
return b""
27+
28+
# While we're not done and want more bytes
29+
want_more_bytes = size is None or size > len(self._remaining)
30+
while not self._complete and want_more_bytes:
31+
self._remaining += await self._make_chunk()
32+
want_more_bytes = size is None or size > len(self._remaining)
33+
34+
# If size was None, we want to return everything
35+
if size is None:
36+
size = len(self._remaining)
37+
38+
# Return a chunk up to the size asked for
39+
to_return = self._remaining[:size]
40+
self._remaining = self._remaining[size:]
41+
return to_return
42+
1843
async def _make_chunk(self):
1944
# NOTE: Chunk size is not deterministic as read could return less. This
2045
# means we cannot know the content length of the encoded aws-chunked
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
from io import BytesIO
2+
3+
import pytest
4+
from botocore.httpchecksum import AwsChunkedWrapperError, Crc32Checksum
5+
6+
from aiobotocore.httpchecksum import AioAwsChunkedWrapper
7+
8+
9+
class TestAwsChunkedWrapper:
10+
async def test_single_chunk_body(self):
11+
# Test a small body that fits in a single chunk
12+
bytes = BytesIO(b"abcdefghijklmnopqrstuvwxyz")
13+
wrapper = AioAwsChunkedWrapper(bytes)
14+
body = await wrapper.read()
15+
expected = b"1a\r\n" b"abcdefghijklmnopqrstuvwxyz\r\n" b"0\r\n\r\n"
16+
assert body == expected
17+
18+
async def test_multi_chunk_body(self):
19+
# Test a body that requires multiple chunks
20+
bytes = BytesIO(b"abcdefghijklmnopqrstuvwxyz")
21+
wrapper = AioAwsChunkedWrapper(bytes, chunk_size=10)
22+
body = await wrapper.read()
23+
expected = (
24+
b"a\r\n"
25+
b"abcdefghij\r\n"
26+
b"a\r\n"
27+
b"klmnopqrst\r\n"
28+
b"6\r\n"
29+
b"uvwxyz\r\n"
30+
b"0\r\n\r\n"
31+
)
32+
assert body == expected
33+
34+
async def test_read_returns_less_data(self):
35+
class OneLessBytesIO(BytesIO):
36+
def read(self, size=-1):
37+
# Return 1 less byte than was asked for
38+
return super().read(size - 1)
39+
40+
bytes = OneLessBytesIO(b"abcdefghijklmnopqrstuvwxyz")
41+
wrapper = AioAwsChunkedWrapper(bytes, chunk_size=10)
42+
body = await wrapper.read()
43+
# NOTE: This particular body is not important, but it is important that
44+
# the actual size of the chunk matches the length sent which may not
45+
# always be the configured chunk_size if the read does not return that
46+
# much data.
47+
expected = (
48+
b"9\r\n"
49+
b"abcdefghi\r\n"
50+
b"9\r\n"
51+
b"jklmnopqr\r\n"
52+
b"8\r\n"
53+
b"stuvwxyz\r\n"
54+
b"0\r\n\r\n"
55+
)
56+
assert body == expected
57+
58+
async def test_single_chunk_body_with_checksum(self):
59+
wrapper = AioAwsChunkedWrapper(
60+
BytesIO(b"hello world"),
61+
checksum_cls=Crc32Checksum,
62+
checksum_name="checksum",
63+
)
64+
body = await wrapper.read()
65+
expected = (
66+
b"b\r\n" b"hello world\r\n" b"0\r\n" b"checksum:DUoRhQ==\r\n\r\n"
67+
)
68+
assert body == expected
69+
70+
async def test_multi_chunk_body_with_checksum(self):
71+
wrapper = AioAwsChunkedWrapper(
72+
BytesIO(b"hello world"),
73+
chunk_size=5,
74+
checksum_cls=Crc32Checksum,
75+
checksum_name="checksum",
76+
)
77+
body = await wrapper.read()
78+
expected = (
79+
b"5\r\n"
80+
b"hello\r\n"
81+
b"5\r\n"
82+
b" worl\r\n"
83+
b"1\r\n"
84+
b"d\r\n"
85+
b"0\r\n"
86+
b"checksum:DUoRhQ==\r\n\r\n"
87+
)
88+
assert body == expected
89+
90+
async def test_multi_chunk_body_with_checksum_iter(self):
91+
wrapper = AioAwsChunkedWrapper(
92+
BytesIO(b"hello world"),
93+
chunk_size=5,
94+
checksum_cls=Crc32Checksum,
95+
checksum_name="checksum",
96+
)
97+
expected_chunks = [
98+
b"5\r\nhello\r\n",
99+
b"5\r\n worl\r\n",
100+
b"1\r\nd\r\n",
101+
b"0\r\nchecksum:DUoRhQ==\r\n\r\n",
102+
]
103+
assert expected_chunks == [chunk async for chunk in wrapper]
104+
105+
async def test_wrapper_can_be_reset(self):
106+
wrapper = AioAwsChunkedWrapper(
107+
BytesIO(b"hello world"),
108+
chunk_size=5,
109+
checksum_cls=Crc32Checksum,
110+
checksum_name="checksum",
111+
)
112+
first_read = await wrapper.read()
113+
assert b"" == await wrapper.read()
114+
wrapper.seek(0)
115+
second_read = await wrapper.read()
116+
assert first_read == second_read
117+
assert b"checksum:DUoRhQ==" in first_read
118+
119+
def test_wrapper_can_only_seek_to_start(self):
120+
wrapper = AioAwsChunkedWrapper(BytesIO())
121+
with pytest.raises(AwsChunkedWrapperError):
122+
wrapper.seek(1)
123+
with pytest.raises(AwsChunkedWrapperError):
124+
wrapper.seek(0, whence=1)
125+
with pytest.raises(AwsChunkedWrapperError):
126+
wrapper.seek(1, whence=2)

tests/test_patches.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,9 @@
710710
'0761c4590c6addbe8c674e40fca9f7dd375a184b',
711711
'11dce986975df44f08ff61d7e86cba4f92f7c19f',
712712
},
713+
AwsChunkedWrapper.read: {
714+
'226db2259073a2b2e05f999e8ef55210394693d8',
715+
},
713716
AwsChunkedWrapper._make_chunk: {
714717
'097361692f0fd6c863a17dd695739629982ef7e4'
715718
},

0 commit comments

Comments
 (0)