Skip to content

Commit

Permalink
Merge pull request #629 from dhermes/fix-536
Browse files Browse the repository at this point in the history
DISCUSSION PR: Uses Blob's current content type (in an upload) when present.
  • Loading branch information
dhermes committed Feb 13, 2015
2 parents bdc591e + 83c0fc1 commit cfe477c
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 15 deletions.
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

0 comments on commit cfe477c

Please sign in to comment.