diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index aa547962e..3815c983c 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,4 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:e09366bdf0fd9c8976592988390b24d53583dd9f002d476934da43725adbb978 + digest: sha256:7a40313731a7cb1454eef6b33d3446ebb121836738dc3ab3d2d3ded5268c35b6 diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt index 385f2d4d6..d15994bac 100644 --- a/.kokoro/requirements.txt +++ b/.kokoro/requirements.txt @@ -325,31 +325,30 @@ platformdirs==2.5.2 \ --hash=sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788 \ --hash=sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19 # via virtualenv -protobuf==3.20.1 \ - --hash=sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf \ - --hash=sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f \ - --hash=sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f \ - --hash=sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7 \ - --hash=sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996 \ - --hash=sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067 \ - --hash=sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c \ - --hash=sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7 \ - --hash=sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9 \ - --hash=sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c \ - --hash=sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739 \ - --hash=sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91 \ - --hash=sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c \ - --hash=sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153 \ - --hash=sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9 \ - --hash=sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388 \ - --hash=sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e \ - --hash=sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab \ - --hash=sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde \ - --hash=sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531 \ - --hash=sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8 \ - --hash=sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7 \ - --hash=sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20 \ - --hash=sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3 +protobuf==3.20.2 \ + --hash=sha256:03d76b7bd42ac4a6e109742a4edf81ffe26ffd87c5993126d894fe48a120396a \ + --hash=sha256:09e25909c4297d71d97612f04f41cea8fa8510096864f2835ad2f3b3df5a5559 \ + --hash=sha256:18e34a10ae10d458b027d7638a599c964b030c1739ebd035a1dfc0e22baa3bfe \ + --hash=sha256:291fb4307094bf5ccc29f424b42268640e00d5240bf0d9b86bf3079f7576474d \ + --hash=sha256:2c0b040d0b5d5d207936ca2d02f00f765906622c07d3fa19c23a16a8ca71873f \ + --hash=sha256:384164994727f274cc34b8abd41a9e7e0562801361ee77437099ff6dfedd024b \ + --hash=sha256:3cb608e5a0eb61b8e00fe641d9f0282cd0eedb603be372f91f163cbfbca0ded0 \ + --hash=sha256:5d9402bf27d11e37801d1743eada54372f986a372ec9679673bfcc5c60441151 \ + --hash=sha256:712dca319eee507a1e7df3591e639a2b112a2f4a62d40fe7832a16fd19151750 \ + --hash=sha256:7a5037af4e76c975b88c3becdf53922b5ffa3f2cddf657574a4920a3b33b80f3 \ + --hash=sha256:8228e56a865c27163d5d1d1771d94b98194aa6917bcfb6ce139cbfa8e3c27334 \ + --hash=sha256:84a1544252a933ef07bb0b5ef13afe7c36232a774affa673fc3636f7cee1db6c \ + --hash=sha256:84fe5953b18a383fd4495d375fe16e1e55e0a3afe7b4f7b4d01a3a0649fcda9d \ + --hash=sha256:9c673c8bfdf52f903081816b9e0e612186684f4eb4c17eeb729133022d6032e3 \ + --hash=sha256:9f876a69ca55aed879b43c295a328970306e8e80a263ec91cf6e9189243c613b \ + --hash=sha256:a9e5ae5a8e8985c67e8944c23035a0dff2c26b0f5070b2f55b217a1c33bbe8b1 \ + --hash=sha256:b4fdb29c5a7406e3f7ef176b2a7079baa68b5b854f364c21abe327bbeec01cdb \ + --hash=sha256:c184485e0dfba4dfd451c3bd348c2e685d6523543a0f91b9fd4ae90eb09e8422 \ + --hash=sha256:c9cdf251c582c16fd6a9f5e95836c90828d51b0069ad22f463761d27c6c19019 \ + --hash=sha256:e39cf61bb8582bda88cdfebc0db163b774e7e03364bbf9ce1ead13863e81e359 \ + --hash=sha256:e8fbc522303e09036c752a0afcc5c0603e917222d8bedc02813fd73b4b4ed804 \ + --hash=sha256:f34464ab1207114e73bba0794d1257c150a2b89b7a9faf504e00af7c9fd58978 \ + --hash=sha256:f52dabc96ca99ebd2169dadbe018824ebda08a795c7684a0b7d203a290f3adb0 # via # gcp-docuploader # gcp-releasetool diff --git a/google/cloud/storage/_helpers.py b/google/cloud/storage/_helpers.py index 282d9bcfb..c02be2b45 100644 --- a/google/cloud/storage/_helpers.py +++ b/google/cloud/storage/_helpers.py @@ -546,7 +546,7 @@ def _bucket_bound_hostname_url(host, scheme=None): if url_parts.scheme and url_parts.netloc: return host - return f"{scheme}://{host}/" + return f"{scheme}://{host}" def _api_core_retry_to_resumable_media_retry(retry, num_retries=None): diff --git a/google/cloud/storage/blob.py b/google/cloud/storage/blob.py index 205d4aeb2..d465039ea 100644 --- a/google/cloud/storage/blob.py +++ b/google/cloud/storage/blob.py @@ -2861,6 +2861,7 @@ def create_resumable_upload_session( client=None, timeout=_DEFAULT_TIMEOUT, checksum=None, + predefined_acl=None, if_generation_match=None, if_generation_not_match=None, if_metageneration_match=None, @@ -2942,6 +2943,9 @@ def create_resumable_upload_session( delete the uploaded object automatically. Supported values are "md5", "crc32c" and None. The default is None. + :type predefined_acl: str + :param predefined_acl: (Optional) Predefined access control list + :type if_generation_match: long :param if_generation_match: (Optional) See :ref:`using-if-generation-match` @@ -3015,7 +3019,7 @@ def create_resumable_upload_session( content_type, size, None, - predefined_acl=None, + predefined_acl=predefined_acl, if_generation_match=if_generation_match, if_generation_not_match=if_generation_not_match, if_metageneration_match=if_metageneration_match, @@ -3715,9 +3719,6 @@ def update_storage_class( :param retry: (Optional) How to retry the RPC. See: :ref:`configuring_retries` """ - if new_class not in self.STORAGE_CLASSES: - raise ValueError(f"Invalid storage class: {new_class}") - # Update current blob's storage class prior to rewrite self._patch_property("storageClass", new_class) diff --git a/google/cloud/storage/bucket.py b/google/cloud/storage/bucket.py index 6f133b923..98cbf892b 100644 --- a/google/cloud/storage/bucket.py +++ b/google/cloud/storage/bucket.py @@ -2654,8 +2654,6 @@ def storage_class(self, value): or :attr:`~google.cloud.storage.constants.DURABLE_REDUCED_AVAILABILITY_LEGACY_STORAGE_CLASS`, """ - if value not in self.STORAGE_CLASSES: - raise ValueError(f"Invalid storage class: {value}") self._patch_property("storageClass", value) @property diff --git a/google/cloud/storage/client.py b/google/cloud/storage/client.py index e6c4f33c6..9cccf413b 100644 --- a/google/cloud/storage/client.py +++ b/google/cloud/storage/client.py @@ -1759,7 +1759,7 @@ def generate_signed_post_policy_v4( if virtual_hosted_style: url = f"https://{bucket_name}.storage.googleapis.com/" elif bucket_bound_hostname: - url = _bucket_bound_hostname_url(bucket_bound_hostname, scheme) + url = f"{_bucket_bound_hostname_url(bucket_bound_hostname, scheme)}/" else: url = f"https://storage.googleapis.com/{bucket_name}/" diff --git a/google/cloud/storage/hmac_key.py b/google/cloud/storage/hmac_key.py index 944bc7f87..7f6de7eee 100644 --- a/google/cloud/storage/hmac_key.py +++ b/google/cloud/storage/hmac_key.py @@ -131,11 +131,6 @@ def state(self): @state.setter def state(self, value): - if value not in self._SETTABLE_STATES: - raise ValueError( - f"State may only be set to one of: {', '.join(self._SETTABLE_STATES)}" - ) - self._properties["state"] = value @property @@ -289,9 +284,6 @@ def delete(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY): :raises :class:`~google.api_core.exceptions.NotFound`: if the key does not exist on the back-end. """ - if self.state != self.INACTIVE_STATE: - raise ValueError("Cannot delete key if not in 'INACTIVE' state.") - qs_params = {} if self.user_project is not None: qs_params["userProject"] = self.user_project diff --git a/google/cloud/storage/notification.py b/google/cloud/storage/notification.py index f7e72e710..d9fa79ac6 100644 --- a/google/cloud/storage/notification.py +++ b/google/cloud/storage/notification.py @@ -306,7 +306,7 @@ def exists(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY): :raises ValueError: if the notification has no ID. """ if self.notification_id is None: - raise ValueError("Notification not intialized by server") + raise ValueError("Notification ID not set: set an explicit notification_id") client = self._require_client(client) @@ -352,7 +352,7 @@ def reload(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY): :raises ValueError: if the notification has no ID. """ if self.notification_id is None: - raise ValueError("Notification not intialized by server") + raise ValueError("Notification ID not set: set an explicit notification_id") client = self._require_client(client) @@ -395,7 +395,7 @@ def delete(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY): :raises ValueError: if the notification has no ID. """ if self.notification_id is None: - raise ValueError("Notification not intialized by server") + raise ValueError("Notification ID not set: set an explicit notification_id") client = self._require_client(client) diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index cbcfa2f4f..d999aa86c 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -1,3 +1,3 @@ pytest==7.1.3 mock==4.0.3 -backoff==2.1.2 \ No newline at end of file +backoff==2.2.1 \ No newline at end of file diff --git a/tests/unit/test__helpers.py b/tests/unit/test__helpers.py index dbe0055df..174b96152 100644 --- a/tests/unit/test__helpers.py +++ b/tests/unit/test__helpers.py @@ -675,13 +675,13 @@ def _call_fut(self, **args): return _bucket_bound_hostname_url(**args) def test_full_hostname(self): - HOST = "scheme://domain.tcl/" + HOST = "scheme://domain.tcl" self.assertEqual(self._call_fut(host=HOST), HOST) def test_hostname_and_scheme(self): HOST = "domain.tcl" SCHEME = "scheme" - EXPECTED_URL = SCHEME + "://" + HOST + "/" + EXPECTED_URL = SCHEME + "://" + HOST self.assertEqual(self._call_fut(host=HOST, scheme=SCHEME), EXPECTED_URL) diff --git a/tests/unit/test_blob.py b/tests/unit/test_blob.py index 018ea4505..638db9f4e 100644 --- a/tests/unit/test_blob.py +++ b/tests/unit/test_blob.py @@ -3572,6 +3572,7 @@ def _create_resumable_upload_session_helper( origin=None, side_effect=None, timeout=None, + predefined_acl=None, if_generation_match=None, if_generation_not_match=None, if_metageneration_match=None, @@ -3611,6 +3612,7 @@ def _create_resumable_upload_session_helper( size=size, origin=origin, client=client, + predefined_acl=predefined_acl, if_generation_match=if_generation_match, if_generation_not_match=if_generation_not_match, if_metageneration_match=if_metageneration_match, @@ -3629,6 +3631,9 @@ def _create_resumable_upload_session_helper( ) qs_params = [("uploadType", "resumable")] + if predefined_acl is not None: + qs_params.append(("predefinedAcl", predefined_acl)) + if if_generation_match is not None: qs_params.append(("ifGenerationMatch", if_generation_match)) @@ -3672,6 +3677,9 @@ def test_create_resumable_upload_session_with_custom_timeout(self): def test_create_resumable_upload_session_with_origin(self): self._create_resumable_upload_session_helper(origin="http://google.com") + def test_create_resumable_upload_session_with_predefined_acl(self): + self._create_resumable_upload_session_helper(predefined_acl="private") + def test_create_resumable_upload_session_with_generation_match(self): self._create_resumable_upload_session_helper( if_generation_match=123456, if_metageneration_match=2 @@ -4994,17 +5002,6 @@ def test_rewrite_same_name_w_kms_key_w_version(self): _target_object=dest, ) - def test_update_storage_class_invalid(self): - blob_name = "blob-name" - bucket = _Bucket() - blob = self._make_one(blob_name, bucket=bucket) - blob.rewrite = mock.Mock(spec=[]) - - with self.assertRaises(ValueError): - blob.update_storage_class("BOGUS") - - blob.rewrite.assert_not_called() - def _update_storage_class_multi_pass_helper(self, **kw): blob_name = "blob-name" storage_class = "NEARLINE" @@ -5215,6 +5212,38 @@ def test_update_storage_class_single_pass_w_retry(self): retry = mock.Mock(spec=[]) self._update_storage_class_single_pass_helper(retry=retry) + def test_update_storage_class_invalid(self): + from google.cloud.exceptions import BadRequest + + storage_class = "BOGUS" + blob_name = "blob-name" + client = mock.Mock(spec=[]) + bucket = _Bucket(client=client) + blob = self._make_one(blob_name, bucket=bucket) + blob.rewrite = mock.Mock(spec=[]) + blob.rewrite.side_effect = BadRequest("Invalid storage class") + + with self.assertRaises(BadRequest): + blob.update_storage_class(storage_class) + + # Test that invalid classes are allowed without client side validation. + # Fall back to server side validation and errors. + self.assertEqual(blob.storage_class, storage_class) + + blob.rewrite.assert_called_once_with( + blob, + if_generation_match=None, + if_generation_not_match=None, + if_metageneration_match=None, + if_metageneration_not_match=None, + if_source_generation_match=None, + if_source_generation_not_match=None, + if_source_metageneration_match=None, + if_source_metageneration_not_match=None, + timeout=self._get_default_timeout(), + retry=DEFAULT_RETRY_IF_GENERATION_SPECIFIED, + ) + def test_cache_control_getter(self): BLOB_NAME = "blob-name" bucket = _Bucket() diff --git a/tests/unit/test_bucket.py b/tests/unit/test_bucket.py index 5ff758758..163d31fd6 100644 --- a/tests/unit/test_bucket.py +++ b/tests/unit/test_bucket.py @@ -2813,11 +2813,15 @@ def test_storage_class_getter(self): self.assertEqual(bucket.storage_class, NEARLINE_STORAGE_CLASS) def test_storage_class_setter_invalid(self): + invalid_class = "BOGUS" NAME = "name" bucket = self._make_one(name=NAME) - with self.assertRaises(ValueError): - bucket.storage_class = "BOGUS" - self.assertFalse("storageClass" in bucket._changes) + bucket.storage_class = invalid_class + + # Test that invalid classes are allowed without client side validation. + # Fall back to server side validation and errors. + self.assertEqual(bucket.storage_class, invalid_class) + self.assertTrue("storageClass" in bucket._changes) def test_storage_class_setter_STANDARD(self): from google.cloud.storage.constants import STANDARD_STORAGE_CLASS diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 6769f3020..c100d35b0 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -2572,7 +2572,7 @@ def test_get_signed_policy_v4_bucket_bound_hostname(self): bucket_bound_hostname="https://bucket.bound_hostname", credentials=_create_signing_credentials(), ) - self.assertEqual(policy["url"], "https://bucket.bound_hostname") + self.assertEqual(policy["url"], "https://bucket.bound_hostname/") def test_get_signed_policy_v4_bucket_bound_hostname_with_scheme(self): import datetime diff --git a/tests/unit/test_hmac_key.py b/tests/unit/test_hmac_key.py index 917006b96..b74bc1e7e 100644 --- a/tests/unit/test_hmac_key.py +++ b/tests/unit/test_hmac_key.py @@ -149,11 +149,12 @@ def test_state_getter(self): def test_state_setter_invalid_state(self): metadata = self._make_one() expected = "INVALID" + metadata.state = expected - with self.assertRaises(ValueError): - metadata.state = expected - - self.assertIsNone(metadata.state) + # Test that invalid states are allowed without client side validation. + # Fall back to server side validation and errors. + self.assertEqual(metadata.state, expected) + self.assertEqual(metadata._properties["state"], expected) def test_state_setter_inactive(self): metadata = self._make_one()