Skip to content

Commit c1d2577

Browse files
authored
Chunk file streaming for improved performance (#44)
1 parent c204699 commit c1d2577

File tree

3 files changed

+28
-4
lines changed

3 files changed

+28
-4
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,15 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## Unreleased
9+
10+
- Improve performance of streamed file downloads changed in v0.8 ([#44](https://github.com/torchbox/wagtail-bynder/pull/44/) @alxbridge
11+
812
## [0.8] - 2025-11-12
913

1014
### Fixed
1115

12-
- Improved handling for unexpected server error responses when downloading Bynder assets ([#43](hhttps://github.com/torchbox/wagtail-bynder/pull/43/)) @alxbridge
16+
- Improved handling for unexpected server error responses when downloading Bynder assets ([#43](https://github.com/torchbox/wagtail-bynder/pull/43/)) @alxbridge
1317

1418
## [0.7] - 2025-01-15
1519

src/wagtail_bynder/utils.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ def download_file(
3636
file = BytesIO()
3737
# Stream the file to memory. We use iter_content() instead of the default iterator for requests.Response,
3838
# as the latter uses iter_lines() which isn't suitable for streaming binary data.
39-
for chunk in response.iter_content():
40-
if not chunk:
41-
continue
39+
# Get data in largish 8KB chunks, for more performant streaming while staying within CPU cache limits
40+
for chunk in response.iter_content(chunk_size=8192):
4241
file.write(chunk)
4342
if file.tell() > max_filesize:
4443
file.truncate(0)
@@ -47,6 +46,11 @@ def download_file(
4746
)
4847

4948
size = file.tell()
49+
# Catch empty case where iter_content wouldn't have iterated
50+
if size == 0:
51+
raise BynderAssetDownloadError(
52+
f"Downloaded file '{name}' from Bynder is empty."
53+
)
5054
file.seek(0)
5155

5256
content_type, charset = mimetypes.guess_type(name)

tests/test_utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@ def test_download_file_raises_error_on_non_200_status(self):
2424
self.assertIn("file.jpg", str(cm.exception))
2525
self.assertIn("Server error downloading", str(cm.exception))
2626

27+
def test_download_file_raises_error_on_empty_response(self):
28+
"""Test that download_file raises BynderAssetDownloadError for successful-but-empty responses"""
29+
mock_response = mock.Mock()
30+
mock_response.status_code = 200
31+
# Mock iter_content to return no chunks (empty file)
32+
mock_response.iter_content = mock.Mock(return_value=[])
33+
34+
with (
35+
mock.patch("wagtail_bynder.utils.requests.get", return_value=mock_response),
36+
self.assertRaises(BynderAssetDownloadError) as cm,
37+
):
38+
download_file("https://example.com/empty.jpg", 5242880, "TEST_SETTING")
39+
40+
self.assertIn("empty.jpg", str(cm.exception))
41+
self.assertIn("empty", str(cm.exception).lower())
42+
2743
def test_download_file_succeeds_on_200(self):
2844
"""Test that download_file works correctly with 200 status"""
2945
mock_response = mock.Mock()

0 commit comments

Comments
 (0)