Merge pull request #2168 from Sanjays2402/fix/s3-stream-chunk-size
[S3] Respect chunk_size in download_object_as_stream
diff --git a/CHANGES.rst b/CHANGES.rst
index 5b208a8..f1538e8 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -57,11 +57,20 @@
Storage
~~~~~~~
+- [S3] Fix ``chunk_size`` argument being ignored by
+ ``download_object_as_stream`` and ``download_object_range_as_stream``. The
+ requested chunk size is now passed through to the underlying iterator
+ instead of the hardcoded default.
+ (GITHUB-1798)
+
+ [Sanjay Santhanam - @Sanjays2402]
+
- [Azure Blobs] Fix ``chunk_size`` argument being ignored by
``download_object_as_stream`` and ``download_object_range_as_stream``. The
requested chunk size is now forwarded to the underlying iterator instead of
always using ``AZURE_DOWNLOAD_CHUNK_SIZE``.
(GITHUB-1698)
+
[Sanjay Santhanam - @Sanjays2402]
DNS
diff --git a/libcloud/storage/drivers/s3.py b/libcloud/storage/drivers/s3.py
index bd9212e..4b59e2d 100644
--- a/libcloud/storage/drivers/s3.py
+++ b/libcloud/storage/drivers/s3.py
@@ -521,7 +521,7 @@
callback=read_in_chunks,
response=response,
callback_kwargs={
- "iterator": response.iter_content(CHUNK_SIZE),
+ "iterator": response.iter_content(chunk_size or CHUNK_SIZE),
"chunk_size": chunk_size,
},
success_status_code=httplib.OK,
@@ -573,7 +573,7 @@
callback=read_in_chunks,
response=response,
callback_kwargs={
- "iterator": response.iter_content(CHUNK_SIZE),
+ "iterator": response.iter_content(chunk_size or CHUNK_SIZE),
"chunk_size": chunk_size,
},
success_status_code=httplib.PARTIAL_CONTENT,
diff --git a/libcloud/test/storage/test_s3.py b/libcloud/test/storage/test_s3.py
index be70898..f8b4607 100644
--- a/libcloud/test/storage/test_s3.py
+++ b/libcloud/test/storage/test_s3.py
@@ -861,6 +861,59 @@
finally:
self.driver_type._get_object = old_func
+ def test_download_object_as_stream_uses_chunk_size(self):
+ # Regression test: the chunk_size passed by the caller must be
+ # forwarded to iter_content instead of the hardcoded CHUNK_SIZE.
+ container = Container(name="foo_bar_container", extra={}, driver=self.driver)
+ obj = Object(
+ name="foo_bar_object",
+ size=1000,
+ hash=None,
+ extra={},
+ container=container,
+ meta_data=None,
+ driver=self.driver_type,
+ )
+
+ requested_chunk_size = CHUNK_SIZE * 2
+ mock_response = Mock(name="mock response")
+ mock_response.iter_content.return_value = iter([b"a"])
+
+ with mock.patch.object(self.driver.connection, "request", return_value=mock_response):
+ with mock.patch.object(self.driver, "_get_object", side_effect=lambda **kw: kw):
+ self.driver.download_object_as_stream(obj=obj, chunk_size=requested_chunk_size)
+
+ mock_response.iter_content.assert_called_once_with(requested_chunk_size)
+
+ def test_download_object_range_as_stream_uses_chunk_size(self):
+ # Same regression as above, but for the ranged variant which goes
+ # through the same _get_object() code path.
+ container = Container(name="foo_bar_container", extra={}, driver=self.driver)
+ obj = Object(
+ name="foo_bar_object",
+ size=1000,
+ hash=None,
+ extra={},
+ container=container,
+ meta_data=None,
+ driver=self.driver_type,
+ )
+
+ requested_chunk_size = CHUNK_SIZE * 2
+ mock_response = Mock(name="mock response")
+ mock_response.iter_content.return_value = iter([b"a"])
+
+ with mock.patch.object(self.driver.connection, "request", return_value=mock_response):
+ with mock.patch.object(self.driver, "_get_object", side_effect=lambda **kw: kw):
+ self.driver.download_object_range_as_stream(
+ obj=obj,
+ start_bytes=0,
+ end_bytes=100,
+ chunk_size=requested_chunk_size,
+ )
+
+ mock_response.iter_content.assert_called_once_with(requested_chunk_size)
+
def test_upload_object_invalid_ex_storage_class(self):
# Invalid hash is detected on the amazon side and BAD_REQUEST is
# returned