Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: googleapis/python-storage
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.31.0
Choose a base ref
...
head repository: googleapis/python-storage
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.31.1
Choose a head ref
  • 7 commits
  • 10 files changed
  • 8 contributors

Commits on Aug 27, 2020

  1. docs: add docs signed_url expiration take default utc (#250)

    Co-authored-by: Frank Natividad <frankyn@users.noreply.github.com>
    Co-authored-by: Tres Seaver <tseaver@palladion.com>
    3 people authored Aug 27, 2020
    Copy the full SHA
    944ab18 View commit details

Commits on Aug 31, 2020

  1. Copy the full SHA
    e190f1c View commit details

Commits on Sep 2, 2020

  1. fix(blob): base64 includes additional characters (#258)

    Hashes were not being parsed correctly. I forgot that base64 includes the "+" and "/" characters. 
    
    https://en.wikipedia.org/wiki/Base64
    
    <img width="656" alt="image" src="https://user-images.githubusercontent.com/2517065/91491542-3329ee80-e882-11ea-9665-4eaa564b406b.png">
    william-silversmith authored Sep 2, 2020
    Copy the full SHA
    cf0774a View commit details

Commits on Sep 16, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    0642a73 View commit details
  2. fix: add requests as a dependency (#271)

    Co-authored-by: Peter Lamut <plamut@users.noreply.github.com>
    HemangChothani and plamut authored Sep 16, 2020
    Copy the full SHA
    ec52b38 View commit details
  3. fix: preserve existing blob hashes when 'X-Goog-Hash header' is not p…

    …resent (#267)
    
    Co-authored-by: Tres Seaver <tseaver@palladion.com>
    MartinKilonzo and tseaver authored Sep 16, 2020
    Copy the full SHA
    277afb8 View commit details

Commits on Sep 21, 2020

  1. chore: release 1.31.1 (#257)

    Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
    release-please[bot] authored Sep 21, 2020
    Copy the full SHA
    0dd22a4 View commit details
Showing with 143 additions and 24 deletions.
  1. +14 −0 CHANGELOG.md
  2. +12 −4 google/cloud/storage/_signing.py
  3. +12 −9 google/cloud/storage/blob.py
  4. +3 −1 google/cloud/storage/bucket.py
  5. +6 −2 google/cloud/storage/client.py
  6. +1 −1 noxfile.py
  7. +2 −1 setup.py
  8. +65 −2 synth.metadata
  9. +1 −1 synth.py
  10. +27 −3 tests/unit/test_blob.py
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,20 @@

[1]: https://pypi.org/project/google-cloud-storage/#history

### [1.31.1](https://www.github.com/googleapis/python-storage/compare/v1.31.0...v1.31.1) (2020-09-16)


### Bug Fixes

* add requests as a dependency ([#271](https://www.github.com/googleapis/python-storage/issues/271)) ([ec52b38](https://www.github.com/googleapis/python-storage/commit/ec52b38df211fad18a86d7e16d83db79de59d5f5))
* preserve existing blob hashes when 'X-Goog-Hash header' is not present ([#267](https://www.github.com/googleapis/python-storage/issues/267)) ([277afb8](https://www.github.com/googleapis/python-storage/commit/277afb83f464d77b163f2722272092df4180411e))
* **blob:** base64 includes additional characters ([#258](https://www.github.com/googleapis/python-storage/issues/258)) ([cf0774a](https://www.github.com/googleapis/python-storage/commit/cf0774aa8ffd45d340aff9a7d2236d8d65c8ae93))


### Documentation

* add docs signed_url expiration take default utc ([#250](https://www.github.com/googleapis/python-storage/issues/250)) ([944ab18](https://www.github.com/googleapis/python-storage/commit/944ab1827b3ca0bd1d3aafc2829245290e9bde59))

## [1.31.0](https://www.github.com/googleapis/python-storage/compare/v1.30.0...v1.31.0) (2020-08-26)


16 changes: 12 additions & 4 deletions google/cloud/storage/_signing.py
Original file line number Diff line number Diff line change
@@ -91,7 +91,9 @@ def get_expiration_seconds_v2(expiration):
"""Convert 'expiration' to a number of seconds in the future.
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
:param expiration: Point in time when the signed URL should expire.
:param expiration: Point in time when the signed URL should expire. If
a ``datetime`` instance is passed without an explicit
``tzinfo`` set, it will be assumed to be ``UTC``.
:raises: :exc:`TypeError` when expiration is not a valid type.
@@ -123,7 +125,9 @@ def get_expiration_seconds_v4(expiration):
"""Convert 'expiration' to a number of seconds offset from the current time.
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
:param expiration: Point in time when the signed URL should expire.
:param expiration: Point in time when the signed URL should expire. If
a ``datetime`` instance is passed without an explicit
``tzinfo`` set, it will be assumed to be ``UTC``.
:raises: :exc:`TypeError` when expiration is not a valid type.
:raises: :exc:`ValueError` when expiration is too large.
@@ -299,7 +303,9 @@ def generate_signed_url_v2(
Caller should have already URL-encoded the value.
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
:param expiration: Point in time when the signed URL should expire.
:param expiration: Point in time when the signed URL should expire. If
a ``datetime`` instance is passed without an explicit
``tzinfo`` set, it will be assumed to be ``UTC``.
:type api_access_endpoint: str
:param api_access_endpoint: (Optional) URI base. Defaults to empty string.
@@ -461,7 +467,9 @@ def generate_signed_url_v4(
Caller should have already URL-encoded the value.
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
:param expiration: Point in time when the signed URL should expire.
:param expiration: Point in time when the signed URL should expire. If
a ``datetime`` instance is passed without an explicit
``tzinfo`` set, it will be assumed to be ``UTC``.
:type api_access_endpoint: str
:param api_access_endpoint: (Optional) URI base. Defaults to
21 changes: 12 additions & 9 deletions google/cloud/storage/blob.py
Original file line number Diff line number Diff line change
@@ -418,7 +418,9 @@ def generate_signed_url(
log in.
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
:param expiration: Point in time when the signed URL should expire.
:param expiration: Point in time when the signed URL should expire. If
a ``datetime`` instance is passed without an explicit
``tzinfo`` set, it will be assumed to be ``UTC``.
:type api_access_endpoint: str
:param api_access_endpoint: (Optional) URI base.
@@ -809,15 +811,16 @@ def _extract_headers_from_download(self, response):
# '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
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)
self.crc32c = digests.get("crc32c", None)
self.md5_hash = digests.get("md5", None)

def _do_download(
self,
4 changes: 3 additions & 1 deletion google/cloud/storage/bucket.py
Original file line number Diff line number Diff line change
@@ -3020,7 +3020,9 @@ def generate_signed_url(
log in.
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
:param expiration: Point in time when the signed URL should expire.
:param expiration: Point in time when the signed URL should expire. If
a ``datetime`` instance is passed without an explicit
``tzinfo`` set, it will be assumed to be ``UTC``.
:type api_access_endpoint: str
:param api_access_endpoint: (Optional) URI base.
8 changes: 6 additions & 2 deletions google/cloud/storage/client.py
Original file line number Diff line number Diff line change
@@ -962,7 +962,9 @@ def generate_signed_post_policy_v4(
:param blob_name: Object name.
:type expiration: Union[Integer, datetime.datetime, datetime.timedelta]
:param expiration: Policy expiration time.
:param expiration: Policy expiration time. If a ``datetime`` instance is
passed without an explicit ``tzinfo`` set, it will be
assumed to be ``UTC``.
:type conditions: list
:param conditions: (Optional) List of POST policy conditions, which are
@@ -1004,11 +1006,13 @@ def generate_signed_post_policy_v4(
Generate signed POST policy and upload a file.
>>> from google.cloud import storage
>>> import pytz
>>> client = storage.Client()
>>> tz = pytz.timezone('America/New_York')
>>> policy = client.generate_signed_post_policy_v4(
"bucket-name",
"blob-name",
expiration=datetime.datetime(2020, 3, 17),
expiration=datetime.datetime(2020, 3, 17, tzinfo=tz),
conditions=[
["content-length-range", 0, 255]
],
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
@@ -124,7 +124,7 @@ def system(session):
"pytest",
"google-cloud-testutils",
"google-cloud-iam",
"google-cloud-pubsub",
"google-cloud-pubsub < 2.0.0",
"google-cloud-kms < 2.0dev",
)
session.install("-e", ".")
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@

name = "google-cloud-storage"
description = "Google Cloud Storage API client library"
version = "1.31.0"
version = "1.31.1"
# Should be one of:
# 'Development Status :: 3 - Alpha'
# 'Development Status :: 4 - Beta'
@@ -32,6 +32,7 @@
"google-auth >= 1.11.0, < 2.0dev",
"google-cloud-core >= 1.4.1, < 2.0dev",
"google-resumable-media >= 1.0.0, < 2.0dev",
"requests >= 2.18.0, < 3.0.0dev",
]
extras = {}

67 changes: 65 additions & 2 deletions synth.metadata
Original file line number Diff line number Diff line change
@@ -4,15 +4,78 @@
"git": {
"name": ".",
"remote": "https://github.com/googleapis/python-storage.git",
"sha": "e1f91fcca6c001bc3b0c5f759a7a003fcf60c0a6"
"sha": "944ab1827b3ca0bd1d3aafc2829245290e9bde59"
}
},
{
"git": {
"name": "synthtool",
"remote": "https://github.com/googleapis/synthtool.git",
"sha": "4f8f5dc24af79694887385015294e4dbb214c352"
"sha": "80f46100c047bc47efe0025ee537dc8ee413ad04"
}
}
],
"generatedFiles": [
".coveragerc",
".flake8",
".github/CONTRIBUTING.md",
".github/ISSUE_TEMPLATE/bug_report.md",
".github/ISSUE_TEMPLATE/feature_request.md",
".github/ISSUE_TEMPLATE/support_request.md",
".github/PULL_REQUEST_TEMPLATE.md",
".github/release-please.yml",
".gitignore",
".kokoro/build.sh",
".kokoro/continuous/common.cfg",
".kokoro/continuous/continuous.cfg",
".kokoro/docker/docs/Dockerfile",
".kokoro/docker/docs/fetch_gpg_keys.sh",
".kokoro/docs/common.cfg",
".kokoro/docs/docs-presubmit.cfg",
".kokoro/docs/docs.cfg",
".kokoro/presubmit/common.cfg",
".kokoro/presubmit/presubmit.cfg",
".kokoro/publish-docs.sh",
".kokoro/release.sh",
".kokoro/release/common.cfg",
".kokoro/release/release.cfg",
".kokoro/samples/lint/common.cfg",
".kokoro/samples/lint/continuous.cfg",
".kokoro/samples/lint/periodic.cfg",
".kokoro/samples/lint/presubmit.cfg",
".kokoro/samples/python3.6/common.cfg",
".kokoro/samples/python3.6/continuous.cfg",
".kokoro/samples/python3.6/periodic.cfg",
".kokoro/samples/python3.6/presubmit.cfg",
".kokoro/samples/python3.7/common.cfg",
".kokoro/samples/python3.7/continuous.cfg",
".kokoro/samples/python3.7/periodic.cfg",
".kokoro/samples/python3.7/presubmit.cfg",
".kokoro/samples/python3.8/common.cfg",
".kokoro/samples/python3.8/continuous.cfg",
".kokoro/samples/python3.8/periodic.cfg",
".kokoro/samples/python3.8/presubmit.cfg",
".kokoro/test-samples.sh",
".kokoro/trampoline.sh",
".kokoro/trampoline_v2.sh",
".trampolinerc",
"CODE_OF_CONDUCT.md",
"CONTRIBUTING.rst",
"LICENSE",
"MANIFEST.in",
"docs/_static/custom.css",
"docs/_templates/layout.html",
"docs/conf.py",
"noxfile.py",
"renovate.json",
"scripts/decrypt-secrets.sh",
"scripts/readme-gen/readme_gen.py",
"scripts/readme-gen/templates/README.tmpl.rst",
"scripts/readme-gen/templates/auth.tmpl.rst",
"scripts/readme-gen/templates/auth_api_key.tmpl.rst",
"scripts/readme-gen/templates/install_deps.tmpl.rst",
"scripts/readme-gen/templates/install_portaudio.tmpl.rst",
"setup.cfg",
"testing/.gitignore"
]
}
2 changes: 1 addition & 1 deletion synth.py
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@
cov_level=99,
system_test_external_dependencies=[
"google-cloud-iam",
"google-cloud-pubsub",
"google-cloud-pubsub < 2.0.0",
# See: https://github.com/googleapis/python-storage/issues/226
"google-cloud-kms < 2.0dev",
],
30 changes: 27 additions & 3 deletions tests/unit/test_blob.py
Original file line number Diff line number Diff line change
@@ -1522,12 +1522,36 @@ def test_download_as_string_w_response_headers(self):
self.assertEqual(blob.md5_hash, "CS9tHYTtyFntzj7B9nkkJQ==")
self.assertEqual(blob.crc32c, "4gcgLQ==")

response = self._mock_requests_response(
http_client.OK,
headers={
"Content-Type": "application/octet-stream",
"Content-Language": "en-US",
"Cache-Control": "max-age=1337;public",
"Content-Encoding": "gzip",
"X-Goog-Storage-Class": "STANDARD",
"X-Goog-Hash": "crc32c=4/c+LQ==,md5=CS9tHYTt/+ntzj7B9nkkJQ==",
},
content=b"",
)
blob._extract_headers_from_download(response)
self.assertEqual(blob.content_type, "application/octet-stream")
self.assertEqual(blob.content_language, "en-US")
self.assertEqual(blob.md5_hash, "CS9tHYTt/+ntzj7B9nkkJQ==")
self.assertEqual(blob.crc32c, "4/c+LQ==")

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(
@@ -1538,8 +1562,8 @@ def test_download_as_string_w_hash_response_header_none(self):
)
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