Repository: googleapis/python-storage

Issue:
Hashes are no longer returned for partial downloads
#204 Changed behavior for hashes for partial downloads. 
When a partial request is made, we do not return the 'X-Goog-Hash' headers. As a result,
[blob.py:812](https://github.com/googleapis/python-storage/pull/204/files#diff-5d3277a5f0f072a447a1eb89f9fa1ae0R812) overwrites the `blob.crc32c` value with its default, `None`.
[blob.py:813](https://github.com/googleapis/python-storage/pull/204/files#diff-5d3277a5f0f072a447a1eb89f9fa1ae0R813) overwrites the `blob.md5_hash` value with its default, `None`.

#### Environment details

  - OS type and version: Linux 4.19.0-10-cloud-amd64 #1 SMP Debian 4.19.132-1 (2020-07-24) x86_64
  - Python version: `python --version`: 3.7.3, 3.8.2
  - pip version: `pip --version` 18.1
  - `google-cloud-storage` version: `pip show google-cloud-storage` 1.30+

#### Steps to reproduce
1. Get blob
2. issue a partial download (ie `blob.download_to_filename(start=0)`
3. Notice `blob.md5_hash` and `blob.crc32c` are `None`.

#### Code example

```python
client = storage.Client()
bucket = client.get_bucket(BUCKET_ID)
blob = bucket.get_blob(FILE_NAME)
print('blob.md5_hash: {:}'.format(blob.md5_hash)) # blob.md5_hash: 3qgj3i0eVt8mnNjtSErqZQ==
print('blob.crc32c: {:}'.format(blob.crc32c)) # blob.crc32c: AeFI7Q==

# MD5
blob.download_to_filename(FILE_NAME)
print('blob.md5_hash: {:}'.format(blob.md5_hash)) # blob.md5_hash: 3qgj3i0eVt8mnNjtSErqZQ==
blob.download_to_filename(FILE_NAME, start=0)
print('blob.md5_hash: {:}'.format(blob.md5_hash)) # blob.md5_hash: None

# CRC32C
blob.download_to_filename(FILE_NAME)
print('blob.crc32c: {:}'.format(blob.crc32c)) # blob.crc32c: AeFI7Q==
blob.download_to_filename(FILE_NAME, start=0)
print('blob.crc32c: {:}'.format(blob.crc32c)) # blob.crc32c: None
```


Discussion:
google-cla[bot]: We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors.  If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA ([login here](https://cla.developers.google.com/) to double check)?  If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment `@googlebot I fixed it.`. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_cla -->
google-cla[bot]: We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors.  If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA ([login here](https://cla.developers.google.com/) to double check)?  If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment `@googlebot I fixed it.`. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_cla -->
google-cla[bot]: We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors.  If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA ([login here](https://cla.developers.google.com/) to double check)?  If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment `@googlebot I fixed it.`. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_cla -->
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_consent -->
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_consent -->
google-cla[bot]: We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors.  If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA ([login here](https://cla.developers.google.com/) to double check)?  If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment `@googlebot I fixed it.`. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_cla -->
google-cla[bot]: We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors.  If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA ([login here](https://cla.developers.google.com/) to double check)?  If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment `@googlebot I fixed it.`. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_cla -->
googlebot: We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors.  If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA ([login here](https://cla.developers.google.com/) to double check)?  If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
In order to pass this check, please resolve this problem and then comment `@googlebot I fixed it.`. If the bot doesn't comment, it means it doesn't think anything has changed.

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_cla -->
MartinKilonzo: Thanks for the review!
Added the proposed changes. @tseaver, would you mind checking your CLA per @googlebot: 

> We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for all the commit author(s) or Co-authors. If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA ([login here](https://cla.developers.google.com/) to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google.
> In order to pass this check, please resolve this problem and then comment `@googlebot I fixed it.`. If the bot doesn't comment, it means it doesn't think anything has changed.
> 
> ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.
tseaver: @googlebot I consent
google-cla[bot]: All (the pull request submitter and all commit authors) CLAs are signed, **but** one or more commits were authored or co-authored by someone other than the pull request submitter.

We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that by leaving a comment that contains only `@googlebot I consent.` in this pull request.

*Note to project maintainer:* There may be cases where the author cannot leave a comment, or the comment is not properly detected as consent.  In those cases, you can manually confirm consent of the commit author(s), and set the `cla` label to `yes` (if enabled on your project).

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- need_author_consent -->
tseaver: @googlebot I consent
google-cla[bot]: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogleapis%2Fpython-storage%2Fpull%2F267) for more info**.

<!-- ok -->
google-cla[bot]: :frowning_face: Sorry, but only Googlers may change the label `cla: yes`.

Patch:
diff --git a/google/cloud/storage/blob.py b/google/cloud/storage/blob.py
index 2c28f69..a7e6952 100644
--- a/google/cloud/storage/blob.py
+++ b/google/cloud/storage/blob.py
@@ -811,15 +811,16 @@ class Blob(_PropertyMixin):
         #  'X-Goog-Hash': 'crc32c=4gcgLQ==,md5=CS9tHYTtyFntzj7B9nkkJQ==',
         x_goog_hash = response.headers.get("X-Goog-Hash", "")
 
-        digests = {}
-        for encoded_digest in x_goog_hash.split(","):
-            match = re.match(r"(crc32c|md5)=([\w\d/\+/]+={0,3})", encoded_digest)
-            if match:
-                method, digest = match.groups()
-                digests[method] = digest
-
-        self.crc32c = digests.get("crc32c", None)
-        self.md5_hash = digests.get("md5", None)
+        if x_goog_hash:
+            digests = {}
+            for encoded_digest in x_goog_hash.split(","):
+                match = re.match(r"(crc32c|md5)=([\w\d/\+/]+={0,3})", encoded_digest)
+                if match:
+                    method, digest = match.groups()
+                    digests[method] = digest
+
+            self.crc32c = digests.get("crc32c", None)
+            self.md5_hash = digests.get("md5", None)
 
     def _do_download(
         self,


Test patch:
diff --git a/tests/unit/test_blob.py b/tests/unit/test_blob.py
index 0c9e592..f67b650 100644
--- a/tests/unit/test_blob.py
+++ b/tests/unit/test_blob.py
@@ -1542,10 +1542,16 @@ class Test_Blob(unittest.TestCase):
 
     def test_download_as_string_w_hash_response_header_none(self):
         blob_name = "blob-name"
+        md5_hash = "CS9tHYTtyFntzj7B9nkkJQ=="
+        crc32c = "4gcgLQ=="
         client = mock.Mock(spec=["_http"])
         bucket = _Bucket(client)
         media_link = "http://example.com/media/"
-        properties = {"mediaLink": media_link}
+        properties = {
+            "mediaLink": media_link,
+            "md5Hash": md5_hash,
+            "crc32c": crc32c,
+        }
         blob = self._make_one(blob_name, bucket=bucket, properties=properties)
 
         response = self._mock_requests_response(
@@ -1556,8 +1562,8 @@ class Test_Blob(unittest.TestCase):
         )
         blob._extract_headers_from_download(response)
 
-        self.assertIsNone(blob.md5_hash)
-        self.assertIsNone(blob.crc32c)
+        self.assertEqual(blob.md5_hash, md5_hash)
+        self.assertEqual(blob.crc32c, crc32c)
 
     def test_download_as_bytes_w_generation_match(self):
         GENERATION_NUMBER = 6