27
27
import time
28
28
29
29
import httplib2
30
- import six
31
30
from six .moves .urllib .parse import quote
32
31
33
32
import google .auth .transport .requests
50
49
51
50
52
51
_API_ACCESS_ENDPOINT = 'https://storage.googleapis.com'
52
+ _DEFAULT_CONTENT_TYPE = u'application/octet-stream'
53
53
_DOWNLOAD_URL_TEMPLATE = (
54
54
u'https://www.googleapis.com/download/storage/v1{path}?alt=media' )
55
+ _CONTENT_TYPE = 'contentType'
55
56
56
57
57
58
class Blob (_PropertyMixin ):
@@ -192,7 +193,7 @@ def public_url(self):
192
193
:returns: The public URL for this blob.
193
194
"""
194
195
return '{storage_base_url}/{bucket_name}/{quoted_name}' .format (
195
- storage_base_url = 'https://storage.googleapis.com' ,
196
+ storage_base_url = _API_ACCESS_ENDPOINT ,
196
197
bucket_name = self .bucket .name ,
197
198
quoted_name = _quote (self .name ))
198
199
@@ -269,7 +270,7 @@ def generate_signed_url(self, expiration, method='GET',
269
270
270
271
if credentials is None :
271
272
client = self ._require_client (client )
272
- credentials = client ._base_connection . credentials
273
+ credentials = client ._credentials
273
274
274
275
return generate_signed_url (
275
276
credentials , resource = resource ,
@@ -324,6 +325,23 @@ def delete(self, client=None):
324
325
"""
325
326
return self .bucket .delete_blob (self .name , client = client )
326
327
328
+ def _make_transport (self , client ):
329
+ """Make an authenticated transport with a client's credentials.
330
+
331
+ :type client: :class:`~google.cloud.storage.client.Client`
332
+ :param client: (Optional) The client to use. If not passed, falls back
333
+ to the ``client`` stored on the blob's bucket.
334
+ :rtype transport:
335
+ :class:`~google.auth.transport.requests.AuthorizedSession`
336
+ :returns: The transport (with credentials) that will
337
+ make authenticated requests.
338
+ """
339
+ client = self ._require_client (client )
340
+ # Create a ``requests`` transport with the client's credentials.
341
+ transport = google .auth .transport .requests .AuthorizedSession (
342
+ client ._credentials )
343
+ return transport
344
+
327
345
def _get_download_url (self ):
328
346
"""Get the download URL for the current blob.
329
347
@@ -403,14 +421,9 @@ def download_to_file(self, file_obj, client=None):
403
421
404
422
:raises: :class:`google.cloud.exceptions.NotFound`
405
423
"""
406
- client = self ._require_client (client )
407
- # Get the download URL.
408
424
download_url = self ._get_download_url ()
409
- # Get any extra headers for the request.
410
425
headers = _get_encryption_headers (self ._encryption_key )
411
- # Create a ``requests`` transport with the client's credentials.
412
- transport = google .auth .transport .requests .AuthorizedSession (
413
- client ._credentials )
426
+ transport = self ._make_transport (client )
414
427
415
428
try :
416
429
self ._do_download (transport , file_obj , download_url , headers )
@@ -457,6 +470,36 @@ def download_as_string(self, client=None):
457
470
self .download_to_file (string_buffer , client = client )
458
471
return string_buffer .getvalue ()
459
472
473
+ def _get_content_type (self , content_type , filename = None ):
474
+ """Determine the content type from the current object.
475
+
476
+ The return value will be determined in order of precedence:
477
+
478
+ - The value passed in to this method (if not :data:`None`)
479
+ - The value stored on the current blob
480
+ - The default value ('application/octet-stream')
481
+
482
+ :type content_type: str
483
+ :param content_type: (Optional) type of content.
484
+
485
+ :type filename: str
486
+ :param filename: (Optional) The name of the file where the content
487
+ is stored.
488
+
489
+ :rtype: str
490
+ :returns: Type of content gathered from the object.
491
+ """
492
+ if content_type is None :
493
+ content_type = self .content_type
494
+
495
+ if content_type is None and filename is not None :
496
+ content_type , _ = mimetypes .guess_type (filename )
497
+
498
+ if content_type is None :
499
+ content_type = _DEFAULT_CONTENT_TYPE
500
+
501
+ return content_type
502
+
460
503
def _create_upload (
461
504
self , client , file_obj = None , size = None , content_type = None ,
462
505
chunk_size = None , strategy = None , extra_headers = None ):
@@ -509,8 +552,7 @@ def _create_upload(
509
552
# API_BASE_URL and build_api_url).
510
553
connection = client ._base_connection
511
554
512
- content_type = (content_type or self ._properties .get ('contentType' ) or
513
- 'application/octet-stream' )
555
+ content_type = self ._get_content_type (content_type )
514
556
515
557
headers = {
516
558
'Accept' : 'application/json' ,
@@ -575,10 +617,12 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
575
617
content_type = None , num_retries = 6 , client = None ):
576
618
"""Upload the contents of this blob from a file-like object.
577
619
578
- The content type of the upload will either be
579
- - The value passed in to the function (if any)
620
+ The content type of the upload will be determined in order
621
+ of precedence:
622
+
623
+ - The value passed in to this method (if not :data:`None`)
580
624
- The value stored on the current blob
581
- - The default value of 'application/octet-stream'
625
+ - The default value ( 'application/octet-stream')
582
626
583
627
.. note::
584
628
The effect of uploading to an existing blob depends on the
@@ -640,10 +684,7 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
640
684
# API_BASE_URL and build_api_url).
641
685
connection = client ._base_connection
642
686
643
- # Rewind the file if desired.
644
- if rewind :
645
- file_obj .seek (0 , os .SEEK_SET )
646
-
687
+ _maybe_rewind (file_obj , rewind = rewind )
647
688
# Get the basic stats about the file.
648
689
total_bytes = size
649
690
if total_bytes is None :
@@ -679,18 +720,19 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
679
720
self ._check_response_error (request , http_response )
680
721
response_content = http_response .content
681
722
682
- if not isinstance (response_content ,
683
- six .string_types ): # pragma: NO COVER Python3
684
- response_content = response_content .decode ('utf-8' )
723
+ response_content = _bytes_to_unicode (response_content )
685
724
self ._set_properties (json .loads (response_content ))
686
725
687
726
def upload_from_filename (self , filename , content_type = None , client = None ):
688
727
"""Upload this blob's contents from the content of a named file.
689
728
690
- The content type of the upload will either be
691
- - The value passed in to the function (if any)
729
+ The content type of the upload will be determined in order
730
+ of precedence:
731
+
732
+ - The value passed in to this method (if not :data:`None`)
692
733
- The value stored on the current blob
693
- - The value given by mimetypes.guess_type
734
+ - The value given by ``mimetypes.guess_type``
735
+ - The default value ('application/octet-stream')
694
736
695
737
.. note::
696
738
The effect of uploading to an existing blob depends on the
@@ -714,9 +756,7 @@ def upload_from_filename(self, filename, content_type=None, client=None):
714
756
:param client: Optional. The client to use. If not passed, falls back
715
757
to the ``client`` stored on the blob's bucket.
716
758
"""
717
- content_type = content_type or self ._properties .get ('contentType' )
718
- if content_type is None :
719
- content_type , _ = mimetypes .guess_type (filename )
759
+ content_type = self ._get_content_type (content_type , filename = filename )
720
760
721
761
with open (filename , 'rb' ) as file_obj :
722
762
self .upload_from_file (
@@ -749,8 +789,7 @@ def upload_from_string(self, data, content_type='text/plain', client=None):
749
789
:param client: Optional. The client to use. If not passed, falls back
750
790
to the ``client`` stored on the blob's bucket.
751
791
"""
752
- if isinstance (data , six .text_type ):
753
- data = data .encode ('utf-8' )
792
+ data = _to_bytes (data , encoding = 'utf-8' )
754
793
string_buffer = BytesIO ()
755
794
string_buffer .write (data )
756
795
self .upload_from_file (
@@ -777,10 +816,12 @@ def create_resumable_upload_session(
777
816
.. _documentation on signed URLs: https://cloud.google.com/storage\
778
817
/docs/access-control/signed-urls#signing-resumable
779
818
780
- The content type of the upload will either be
781
- - The value passed in to the function (if any)
819
+ The content type of the upload will be determined in order
820
+ of precedence:
821
+
822
+ - The value passed in to this method (if not :data:`None`)
782
823
- The value stored on the current blob
783
- - The default value of 'application/octet-stream'
824
+ - The default value ( 'application/octet-stream')
784
825
785
826
.. note::
786
827
The effect of uploading to an existing blob depends on the
@@ -1080,7 +1121,7 @@ def update_storage_class(self, new_class, client=None):
1080
1121
:rtype: str or ``NoneType``
1081
1122
"""
1082
1123
1083
- content_type = _scalar_property ('contentType' )
1124
+ content_type = _scalar_property (_CONTENT_TYPE )
1084
1125
"""HTTP 'Content-Type' header for this object.
1085
1126
1086
1127
See: https://tools.ietf.org/html/rfc2616#section-14.17 and
@@ -1353,8 +1394,8 @@ def _get_encryption_headers(key, source=False):
1353
1394
1354
1395
key = _to_bytes (key )
1355
1396
key_hash = hashlib .sha256 (key ).digest ()
1356
- key_hash = base64 .b64encode (key_hash ). rstrip ()
1357
- key = base64 .b64encode (key ). rstrip ()
1397
+ key_hash = base64 .b64encode (key_hash )
1398
+ key = base64 .b64encode (key )
1358
1399
1359
1400
if source :
1360
1401
prefix = 'X-Goog-Copy-Source-Encryption-'
@@ -1384,3 +1425,16 @@ def _quote(value):
1384
1425
"""
1385
1426
value = _to_bytes (value , encoding = 'utf-8' )
1386
1427
return quote (value , safe = '' )
1428
+
1429
+
1430
+ def _maybe_rewind (stream , rewind = False ):
1431
+ """Rewind the stream if desired.
1432
+
1433
+ :type stream: IO[Bytes]
1434
+ :param stream: A bytes IO object open for reading.
1435
+
1436
+ :type rewind: bool
1437
+ :param rewind: Indicates if we should seek to the beginning of the stream.
1438
+ """
1439
+ if rewind :
1440
+ stream .seek (0 , os .SEEK_SET )
0 commit comments