Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send copy of ContentFile.file to upload_fileobj. #376

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -31,6 +31,7 @@ By order of apparition, thanks:
* Jody McIntyre (Google Cloud Storage native support)
* Stanislav Kaledin (Bug fixes in SFTPStorage)
* Filip Vavera (Google Cloud MIME types support)
* João Sampaio (S3 with Boto3)

Extra thanks to Marty for adding this in Django,
you can buy his very interesting book (Pro Django).
7 changes: 4 additions & 3 deletions storages/backends/s3boto3.py
Expand Up @@ -444,10 +444,11 @@ def _save(self, name, content):
# provided. `File.__bool__` method is Django-specific and depends on
# file name, for this reason`botocore.handlers.calculate_md5` can fail
# even if wrapped file-like object exists. To avoid Django-specific
# logic, pass internal file-like object if `content` is `File`
# class instance.
# logic, pass a copy of internal file-like object if `content` is
# `File` class instance.
if isinstance(content, File):
content = content.file
content.file.seek(0)
content = BytesIO(force_bytes(content.file.read()))

self._save_content(obj, content, parameters=parameters)
# Note: In boto3, after a put, last_modified is automatically reloaded
Expand Down
16 changes: 12 additions & 4 deletions tests/test_s3boto3.py
Expand Up @@ -71,34 +71,42 @@ def test_storage_url_slashes(self):
self.assertEqual(self.storage.url('path/1'), 'https://example.com/path/1')
self.assertEqual(self.storage.url('path/1/'), 'https://example.com/path/1/')

def test_storage_save(self):
@mock.patch('storages.backends.s3boto3.BytesIO')
def test_storage_save(self, mocked_BytesIO):
"""
Test saving a file
"""
content_file = mock.Mock()
mocked_BytesIO.return_value = content_file

name = 'test_storage_save.txt'
content = ContentFile('new content')
self.storage.save(name, content)
self.storage.bucket.Object.assert_called_once_with(name)

obj = self.storage.bucket.Object.return_value
obj.upload_fileobj.assert_called_with(
content.file,
content_file,
ExtraArgs={
'ContentType': 'text/plain',
'ACL': self.storage.default_acl,
}
)

def test_storage_save_gzipped(self):
@mock.patch('storages.backends.s3boto3.BytesIO')
def test_storage_save_gzipped(self, mocked_BytesIO):
"""
Test saving a gzipped file
"""
content_file = mock.Mock()
mocked_BytesIO.return_value = content_file

name = 'test_storage_save.gz'
content = ContentFile("I am gzip'd")
self.storage.save(name, content)
obj = self.storage.bucket.Object.return_value
obj.upload_fileobj.assert_called_with(
content.file,
content_file,
ExtraArgs={
'ContentType': 'application/octet-stream',
'ContentEncoding': 'gzip',
Expand Down