Skip to content

Commit cfe477c

Browse files
committed
Merge pull request #629 from dhermes/fix-536
DISCUSSION PR: Uses Blob's current content type (in an upload) when present.
2 parents bdc591e + 83c0fc1 commit cfe477c

File tree

2 files changed

+92
-15
lines changed

2 files changed

+92
-15
lines changed

gcloud/storage/blob.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
274274
content_type=None, num_retries=6):
275275
"""Upload the contents of this blob from a file-like object.
276276
277+
The content type of the upload will either be
278+
- The value passed in to the function (if any)
279+
- The value stored on the current blob
280+
- The default value of 'application/octet-stream'
281+
277282
.. note::
278283
The effect of uploading to an existing blob depends on the
279284
"versioning" and "lifecycle" policies defined on the blob's
@@ -296,7 +301,16 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
296301
:param size: The number of bytes to read from the file handle.
297302
If not provided, we'll try to guess the size using
298303
:func:`os.fstat`
304+
305+
:type content_type: string or ``NoneType``
306+
:param content_type: Optional type of content being uploaded.
307+
308+
:type num_retries: integer
309+
:param num_retries: Number of upload retries. Defaults to 6.
299310
"""
311+
content_type = (content_type or self._properties.get('contentType') or
312+
'application/octet-stream')
313+
300314
# Rewind the file if desired.
301315
if rewind:
302316
file_obj.seek(0, os.SEEK_SET)
@@ -310,9 +324,8 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
310324
'User-Agent': conn.USER_AGENT,
311325
}
312326

313-
upload = transfer.Upload(file_obj,
314-
content_type or 'application/unknown',
315-
total_bytes, auto_transfer=False,
327+
upload = transfer.Upload(file_obj, content_type, total_bytes,
328+
auto_transfer=False,
316329
chunksize=self.CHUNK_SIZE)
317330

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

345-
def upload_from_filename(self, filename):
358+
def upload_from_filename(self, filename, content_type=None):
346359
"""Upload this blob's contents from the content of a named file.
347360
361+
The content type of the upload will either be
362+
- The value passed in to the function (if any)
363+
- The value stored on the current blob
364+
- The value given by mimetypes.guess_type
365+
348366
.. note::
349367
The effect of uploading to an existing blob depends on the
350368
"versioning" and "lifecycle" policies defined on the blob's
@@ -358,8 +376,13 @@ def upload_from_filename(self, filename):
358376
359377
:type filename: string
360378
:param filename: The path to the file.
379+
380+
:type content_type: string or ``NoneType``
381+
:param content_type: Optional type of content being uploaded.
361382
"""
362-
content_type, _ = mimetypes.guess_type(filename)
383+
content_type = content_type or self._properties.get('contentType')
384+
if content_type is None:
385+
content_type, _ = mimetypes.guess_type(filename)
363386

364387
with open(filename, 'rb') as file_obj:
365388
self.upload_from_file(file_obj, content_type=content_type)

gcloud/storage/test_blob.py

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,9 @@ def test_download_as_string(self):
327327
fetched = blob.download_as_string()
328328
self.assertEqual(fetched, b'abcdef')
329329

330-
def test_upload_from_file_simple(self):
330+
def _upload_from_file_simple_test_helper(self, properties=None,
331+
content_type_arg=None,
332+
expected_content_type=None):
331333
from six.moves.http_client import OK
332334
from six.moves.urllib.parse import parse_qsl
333335
from six.moves.urllib.parse import urlsplit
@@ -339,12 +341,13 @@ def test_upload_from_file_simple(self):
339341
(response, b''),
340342
)
341343
bucket = _Bucket(connection)
342-
blob = self._makeOne(BLOB_NAME, bucket=bucket)
344+
blob = self._makeOne(BLOB_NAME, bucket=bucket, properties=properties)
343345
blob.CHUNK_SIZE = 5
344346
with NamedTemporaryFile() as fh:
345347
fh.write(DATA)
346348
fh.flush()
347-
blob.upload_from_file(fh, rewind=True)
349+
blob.upload_from_file(fh, rewind=True,
350+
content_type=content_type_arg)
348351
rq = connection.http._requested
349352
self.assertEqual(len(rq), 1)
350353
self.assertEqual(rq[0]['method'], 'POST')
@@ -358,7 +361,31 @@ def test_upload_from_file_simple(self):
358361
headers = dict(
359362
[(x.title(), str(y)) for x, y in rq[0]['headers'].items()])
360363
self.assertEqual(headers['Content-Length'], '6')
361-
self.assertEqual(headers['Content-Type'], 'application/unknown')
364+
self.assertEqual(headers['Content-Type'], expected_content_type)
365+
366+
def test_upload_from_file_simple(self):
367+
self._upload_from_file_simple_test_helper(
368+
expected_content_type='application/octet-stream')
369+
370+
def test_upload_from_file_simple_with_content_type(self):
371+
EXPECTED_CONTENT_TYPE = 'foo/bar'
372+
self._upload_from_file_simple_test_helper(
373+
properties={'contentType': EXPECTED_CONTENT_TYPE},
374+
expected_content_type=EXPECTED_CONTENT_TYPE)
375+
376+
def test_upload_from_file_simple_with_content_type_passed(self):
377+
EXPECTED_CONTENT_TYPE = 'foo/bar'
378+
self._upload_from_file_simple_test_helper(
379+
content_type_arg=EXPECTED_CONTENT_TYPE,
380+
expected_content_type=EXPECTED_CONTENT_TYPE)
381+
382+
def test_upload_from_file_simple_both_content_type_sources(self):
383+
EXPECTED_CONTENT_TYPE = 'foo/bar'
384+
ALT_CONTENT_TYPE = 'foo/baz'
385+
self._upload_from_file_simple_test_helper(
386+
properties={'contentType': ALT_CONTENT_TYPE},
387+
content_type_arg=EXPECTED_CONTENT_TYPE,
388+
expected_content_type=EXPECTED_CONTENT_TYPE)
362389

363390
def test_upload_from_file_resumable(self):
364391
from six.moves.http_client import OK
@@ -403,7 +430,7 @@ def test_upload_from_file_resumable(self):
403430
[(x.title(), str(y)) for x, y in rq[0]['headers'].items()])
404431
self.assertEqual(headers['X-Upload-Content-Length'], '6')
405432
self.assertEqual(headers['X-Upload-Content-Type'],
406-
'application/unknown')
433+
'application/octet-stream')
407434
self.assertEqual(rq[1]['method'], 'PUT')
408435
self.assertEqual(rq[1]['uri'], UPLOAD_URL)
409436
headers = dict(
@@ -457,9 +484,11 @@ def test_upload_from_file_w_slash_in_name(self):
457484
headers = dict(
458485
[(x.title(), str(y)) for x, y in rq[0]['headers'].items()])
459486
self.assertEqual(headers['Content-Length'], '6')
460-
self.assertEqual(headers['Content-Type'], 'application/unknown')
487+
self.assertEqual(headers['Content-Type'], 'application/octet-stream')
461488

462-
def test_upload_from_filename(self):
489+
def _upload_from_filename_test_helper(self, properties=None,
490+
content_type_arg=None,
491+
expected_content_type=None):
463492
from six.moves.http_client import OK
464493
from six.moves.urllib.parse import parse_qsl
465494
from six.moves.urllib.parse import urlsplit
@@ -478,12 +507,13 @@ def test_upload_from_filename(self):
478507
(chunk2_response, ''),
479508
)
480509
bucket = _Bucket(connection)
481-
blob = self._makeOne(BLOB_NAME, bucket=bucket)
510+
blob = self._makeOne(BLOB_NAME, bucket=bucket,
511+
properties=properties)
482512
blob.CHUNK_SIZE = 5
483513
with NamedTemporaryFile(suffix='.jpeg') as fh:
484514
fh.write(DATA)
485515
fh.flush()
486-
blob.upload_from_filename(fh.name)
516+
blob.upload_from_filename(fh.name, content_type=content_type_arg)
487517
rq = connection.http._requested
488518
self.assertEqual(len(rq), 1)
489519
self.assertEqual(rq[0]['method'], 'POST')
@@ -497,7 +527,31 @@ def test_upload_from_filename(self):
497527
headers = dict(
498528
[(x.title(), str(y)) for x, y in rq[0]['headers'].items()])
499529
self.assertEqual(headers['Content-Length'], '6')
500-
self.assertEqual(headers['Content-Type'], 'image/jpeg')
530+
self.assertEqual(headers['Content-Type'], expected_content_type)
531+
532+
def test_upload_from_filename(self):
533+
self._upload_from_filename_test_helper(
534+
expected_content_type='image/jpeg')
535+
536+
def test_upload_from_filename_with_content_type(self):
537+
EXPECTED_CONTENT_TYPE = 'foo/bar'
538+
self._upload_from_filename_test_helper(
539+
properties={'contentType': EXPECTED_CONTENT_TYPE},
540+
expected_content_type=EXPECTED_CONTENT_TYPE)
541+
542+
def test_upload_from_filename_with_content_type_passed(self):
543+
EXPECTED_CONTENT_TYPE = 'foo/bar'
544+
self._upload_from_filename_test_helper(
545+
content_type_arg=EXPECTED_CONTENT_TYPE,
546+
expected_content_type=EXPECTED_CONTENT_TYPE)
547+
548+
def test_upload_from_filename_both_content_type_sources(self):
549+
EXPECTED_CONTENT_TYPE = 'foo/bar'
550+
ALT_CONTENT_TYPE = 'foo/baz'
551+
self._upload_from_filename_test_helper(
552+
properties={'contentType': ALT_CONTENT_TYPE},
553+
content_type_arg=EXPECTED_CONTENT_TYPE,
554+
expected_content_type=EXPECTED_CONTENT_TYPE)
501555

502556
def test_upload_from_string_w_bytes(self):
503557
from six.moves.http_client import OK

0 commit comments

Comments
 (0)