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

DISCUSSION PR: Uses Blob's current content type (in an upload) when present. #629

Merged
merged 1 commit into from
Feb 13, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions gcloud/storage/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
content_type=None, num_retries=6):
"""Upload the contents of this blob from a file-like object.

The content type of the upload will either be
- The value passed in to the function (if any)
- The value stored on the current blob
- The default value of 'application/octet-stream'

.. note::
The effect of uploading to an existing blob depends on the
"versioning" and "lifecycle" policies defined on the blob's
Expand All @@ -296,7 +301,16 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
:param size: The number of bytes to read from the file handle.
If not provided, we'll try to guess the size using
:func:`os.fstat`

:type content_type: string or ``NoneType``
:param content_type: Optional type of content being uploaded.

:type num_retries: integer
:param num_retries: Number of upload retries. Defaults to 6.
"""
content_type = (content_type or self._properties.get('contentType') or
'application/octet-stream')

# Rewind the file if desired.
if rewind:
file_obj.seek(0, os.SEEK_SET)
Expand All @@ -310,9 +324,8 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
'User-Agent': conn.USER_AGENT,
}

upload = transfer.Upload(file_obj,
content_type or 'application/unknown',
total_bytes, auto_transfer=False,
upload = transfer.Upload(file_obj, content_type, total_bytes,
auto_transfer=False,
chunksize=self.CHUNK_SIZE)

url_builder = _UrlBuilder(bucket_name=self.bucket.name,
Expand Down Expand Up @@ -342,9 +355,14 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
else:
http_wrapper.MakeRequest(conn.http, request, retries=num_retries)

def upload_from_filename(self, filename):
def upload_from_filename(self, filename, content_type=None):
"""Upload this blob's contents from the content of a named file.

The content type of the upload will either be
- The value passed in to the function (if any)
- The value stored on the current blob
- The value given by mimetypes.guess_type

.. note::
The effect of uploading to an existing blob depends on the
"versioning" and "lifecycle" policies defined on the blob's
Expand All @@ -358,8 +376,13 @@ def upload_from_filename(self, filename):

:type filename: string
:param filename: The path to the file.

:type content_type: string or ``NoneType``
:param content_type: Optional type of content being uploaded.
"""
content_type, _ = mimetypes.guess_type(filename)
content_type = content_type or self._properties.get('contentType')
if content_type is None:
content_type, _ = mimetypes.guess_type(filename)

with open(filename, 'rb') as file_obj:
self.upload_from_file(file_obj, content_type=content_type)
Expand Down
74 changes: 64 additions & 10 deletions gcloud/storage/test_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,9 @@ def test_download_as_string(self):
fetched = blob.download_as_string()
self.assertEqual(fetched, b'abcdef')

def test_upload_from_file_simple(self):
def _upload_from_file_simple_test_helper(self, properties=None,
content_type_arg=None,
expected_content_type=None):
from six.moves.http_client import OK
from six.moves.urllib.parse import parse_qsl
from six.moves.urllib.parse import urlsplit
Expand All @@ -339,12 +341,13 @@ def test_upload_from_file_simple(self):
(response, b''),
)
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob = self._makeOne(BLOB_NAME, bucket=bucket, properties=properties)
blob.CHUNK_SIZE = 5
with NamedTemporaryFile() as fh:
fh.write(DATA)
fh.flush()
blob.upload_from_file(fh, rewind=True)
blob.upload_from_file(fh, rewind=True,
content_type=content_type_arg)
rq = connection.http._requested
self.assertEqual(len(rq), 1)
self.assertEqual(rq[0]['method'], 'POST')
Expand All @@ -358,7 +361,31 @@ def test_upload_from_file_simple(self):
headers = dict(
[(x.title(), str(y)) for x, y in rq[0]['headers'].items()])
self.assertEqual(headers['Content-Length'], '6')
self.assertEqual(headers['Content-Type'], 'application/unknown')
self.assertEqual(headers['Content-Type'], expected_content_type)

def test_upload_from_file_simple(self):
self._upload_from_file_simple_test_helper(
expected_content_type='application/octet-stream')

def test_upload_from_file_simple_with_content_type(self):
EXPECTED_CONTENT_TYPE = 'foo/bar'
self._upload_from_file_simple_test_helper(
properties={'contentType': EXPECTED_CONTENT_TYPE},
expected_content_type=EXPECTED_CONTENT_TYPE)

def test_upload_from_file_simple_with_content_type_passed(self):
EXPECTED_CONTENT_TYPE = 'foo/bar'
self._upload_from_file_simple_test_helper(
content_type_arg=EXPECTED_CONTENT_TYPE,
expected_content_type=EXPECTED_CONTENT_TYPE)

def test_upload_from_file_simple_both_content_type_sources(self):
EXPECTED_CONTENT_TYPE = 'foo/bar'
ALT_CONTENT_TYPE = 'foo/baz'
self._upload_from_file_simple_test_helper(
properties={'contentType': ALT_CONTENT_TYPE},
content_type_arg=EXPECTED_CONTENT_TYPE,
expected_content_type=EXPECTED_CONTENT_TYPE)

def test_upload_from_file_resumable(self):
from six.moves.http_client import OK
Expand Down Expand Up @@ -403,7 +430,7 @@ def test_upload_from_file_resumable(self):
[(x.title(), str(y)) for x, y in rq[0]['headers'].items()])
self.assertEqual(headers['X-Upload-Content-Length'], '6')
self.assertEqual(headers['X-Upload-Content-Type'],
'application/unknown')
'application/octet-stream')
self.assertEqual(rq[1]['method'], 'PUT')
self.assertEqual(rq[1]['uri'], UPLOAD_URL)
headers = dict(
Expand Down Expand Up @@ -457,9 +484,11 @@ def test_upload_from_file_w_slash_in_name(self):
headers = dict(
[(x.title(), str(y)) for x, y in rq[0]['headers'].items()])
self.assertEqual(headers['Content-Length'], '6')
self.assertEqual(headers['Content-Type'], 'application/unknown')
self.assertEqual(headers['Content-Type'], 'application/octet-stream')

def test_upload_from_filename(self):
def _upload_from_filename_test_helper(self, properties=None,
content_type_arg=None,
expected_content_type=None):
from six.moves.http_client import OK
from six.moves.urllib.parse import parse_qsl
from six.moves.urllib.parse import urlsplit
Expand All @@ -478,12 +507,13 @@ def test_upload_from_filename(self):
(chunk2_response, ''),
)
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob = self._makeOne(BLOB_NAME, bucket=bucket,
properties=properties)
blob.CHUNK_SIZE = 5
with NamedTemporaryFile(suffix='.jpeg') as fh:
fh.write(DATA)
fh.flush()
blob.upload_from_filename(fh.name)
blob.upload_from_filename(fh.name, content_type=content_type_arg)
rq = connection.http._requested
self.assertEqual(len(rq), 1)
self.assertEqual(rq[0]['method'], 'POST')
Expand All @@ -497,7 +527,31 @@ def test_upload_from_filename(self):
headers = dict(
[(x.title(), str(y)) for x, y in rq[0]['headers'].items()])
self.assertEqual(headers['Content-Length'], '6')
self.assertEqual(headers['Content-Type'], 'image/jpeg')
self.assertEqual(headers['Content-Type'], expected_content_type)

def test_upload_from_filename(self):
self._upload_from_filename_test_helper(
expected_content_type='image/jpeg')

def test_upload_from_filename_with_content_type(self):
EXPECTED_CONTENT_TYPE = 'foo/bar'
self._upload_from_filename_test_helper(
properties={'contentType': EXPECTED_CONTENT_TYPE},
expected_content_type=EXPECTED_CONTENT_TYPE)

def test_upload_from_filename_with_content_type_passed(self):
EXPECTED_CONTENT_TYPE = 'foo/bar'
self._upload_from_filename_test_helper(
content_type_arg=EXPECTED_CONTENT_TYPE,
expected_content_type=EXPECTED_CONTENT_TYPE)

def test_upload_from_filename_both_content_type_sources(self):
EXPECTED_CONTENT_TYPE = 'foo/bar'
ALT_CONTENT_TYPE = 'foo/baz'
self._upload_from_filename_test_helper(
properties={'contentType': ALT_CONTENT_TYPE},
content_type_arg=EXPECTED_CONTENT_TYPE,
expected_content_type=EXPECTED_CONTENT_TYPE)

def test_upload_from_string_w_bytes(self):
from six.moves.http_client import OK
Expand Down