1414
1515# pylint: disable=too-many-lines
1616
17- """Create / interact with Google Cloud Storage blobs.
18- """
17+ """Create / interact with Google Cloud Storage blobs."""
1918
2019import base64
2120import copy
142141 r"(?P<scheme>gs)://(?P<bucket_name>[a-z0-9_.-]+)/(?P<object_name>.+)"
143142)
144143
145- _DEFAULT_CHUNKSIZE = 104857600 # 1024 * 1024 B * 100 = 100 MB
146- _MAX_MULTIPART_SIZE = 8388608 # 8 MB
144+ _DEFAULT_CHUNKSIZE = 104857600 # 1024 * 1024 B * 100 = 100 MiB
145+ _MAX_MULTIPART_SIZE = 8388608 # 8 MiB
147146
148147_logger = logging .getLogger (__name__ )
149148
@@ -181,6 +180,14 @@ class Blob(_PropertyMixin):
181180 :type generation: long
182181 :param generation:
183182 (Optional) If present, selects a specific revision of this object.
183+
184+ :type crc32c_checksum: str
185+ :param crc32c_checksum:
186+ (Optional) If set, the CRC32C checksum of the blob's content.
187+ CRC32c checksum, as described in RFC 4960, Appendix B; encoded using
188+ base64 in big-endian byte order. See
189+ Apenndix B: https://datatracker.ietf.org/doc/html/rfc4960#appendix-B
190+ base64: https://datatracker.ietf.org/doc/html/rfc4648#section-4
184191 """
185192
186193 _chunk_size = None # Default value for each instance.
@@ -214,6 +221,7 @@ def __init__(
214221 encryption_key = None ,
215222 kms_key_name = None ,
216223 generation = None ,
224+ crc32c_checksum = None ,
217225 ):
218226 """
219227 property :attr:`name`
@@ -237,6 +245,9 @@ def __init__(
237245 if generation is not None :
238246 self ._properties ["generation" ] = generation
239247
248+ if crc32c_checksum is not None :
249+ self ._properties ["crc32c" ] = crc32c_checksum
250+
240251 @property
241252 def bucket (self ):
242253 """Bucket which contains the object.
@@ -1643,7 +1654,9 @@ def download_as_string(
16431654 :raises: :class:`google.cloud.exceptions.NotFound`
16441655 """
16451656 warnings .warn (
1646- _DOWNLOAD_AS_STRING_DEPRECATED , PendingDeprecationWarning , stacklevel = 2
1657+ _DOWNLOAD_AS_STRING_DEPRECATED ,
1658+ PendingDeprecationWarning ,
1659+ stacklevel = 2 ,
16471660 )
16481661 with create_trace_span (name = "Storage.Blob.downloadAsString" ):
16491662 return self .download_as_bytes (
@@ -1999,12 +2012,18 @@ def _do_multipart_upload(
19992012 transport = self ._get_transport (client )
20002013 if "metadata" in self ._properties and "metadata" not in self ._changes :
20012014 self ._changes .add ("metadata" )
2015+
20022016 info = self ._get_upload_arguments (client , content_type , command = command )
20032017 headers , object_metadata , content_type = info
20042018
2019+ if "crc32c" in self ._properties :
2020+ object_metadata ["crc32c" ] = self ._properties ["crc32c" ]
2021+
20052022 hostname = _get_host_name (client ._connection )
20062023 base_url = _MULTIPART_URL_TEMPLATE .format (
2007- hostname = hostname , bucket_path = self .bucket .path , api_version = _API_VERSION
2024+ hostname = hostname ,
2025+ bucket_path = self .bucket .path ,
2026+ api_version = _API_VERSION ,
20082027 )
20092028 name_value_pairs = []
20102029
@@ -2195,9 +2214,14 @@ def _initiate_resumable_upload(
21952214 if extra_headers is not None :
21962215 headers .update (extra_headers )
21972216
2217+ if "crc32c" in self ._properties :
2218+ object_metadata ["crc32c" ] = self ._properties ["crc32c" ]
2219+
21982220 hostname = _get_host_name (client ._connection )
21992221 base_url = _RESUMABLE_URL_TEMPLATE .format (
2200- hostname = hostname , bucket_path = self .bucket .path , api_version = _API_VERSION
2222+ hostname = hostname ,
2223+ bucket_path = self .bucket .path ,
2224+ api_version = _API_VERSION ,
22012225 )
22022226 name_value_pairs = []
22032227
@@ -2234,7 +2258,11 @@ def _initiate_resumable_upload(
22342258
22352259 upload_url = _add_query_parameters (base_url , name_value_pairs )
22362260 upload = ResumableUpload (
2237- upload_url , chunk_size , headers = headers , checksum = checksum , retry = retry
2261+ upload_url ,
2262+ chunk_size ,
2263+ headers = headers ,
2264+ checksum = checksum ,
2265+ retry = retry ,
22382266 )
22392267
22402268 upload .initiate (
@@ -3426,7 +3454,11 @@ def set_iam_policy(
34263454 return Policy .from_api_repr (info )
34273455
34283456 def test_iam_permissions (
3429- self , permissions , client = None , timeout = _DEFAULT_TIMEOUT , retry = DEFAULT_RETRY
3457+ self ,
3458+ permissions ,
3459+ client = None ,
3460+ timeout = _DEFAULT_TIMEOUT ,
3461+ retry = DEFAULT_RETRY ,
34303462 ):
34313463 """API call: test permissions
34323464
@@ -3693,7 +3725,10 @@ def compose(
36933725
36943726 source_objects = []
36953727 for source , source_generation in zip (sources , if_source_generation_match ):
3696- source_object = {"name" : source .name , "generation" : source .generation }
3728+ source_object = {
3729+ "name" : source .name ,
3730+ "generation" : source .generation ,
3731+ }
36973732
36983733 preconditions = {}
36993734 if source_generation is not None :
@@ -4154,7 +4189,10 @@ def open(
41544189 "encoding, errors and newline arguments are for text mode only"
41554190 )
41564191 return BlobWriter (
4157- self , chunk_size = chunk_size , ignore_flush = ignore_flush , ** kwargs
4192+ self ,
4193+ chunk_size = chunk_size ,
4194+ ignore_flush = ignore_flush ,
4195+ ** kwargs ,
41584196 )
41594197 elif mode in ("r" , "rt" ):
41604198 if ignore_flush :
0 commit comments