diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index e4e943e02..81f87c569 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,5 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-python:latest - digest: sha256:98f3afd11308259de6e828e37376d18867fd321aba07826e29e4f8d9cab56bad -# created: 2024-02-27T15:56:18.442440378Z + digest: sha256:5a4c19d17e597b92d786e569be101e636c9c2817731f80a5adec56b2aa8fe070 +# created: 2024-04-12T11:35:58.922854369Z diff --git a/.github/auto-label.yaml b/.github/auto-label.yaml index b2016d119..8b37ee897 100644 --- a/.github/auto-label.yaml +++ b/.github/auto-label.yaml @@ -13,3 +13,8 @@ # limitations under the License. requestsize: enabled: true + +path: + pullrequest: true + paths: + samples: "samples" diff --git a/.github/blunderbuss.yml b/.github/blunderbuss.yml new file mode 100644 index 000000000..d0b41f786 --- /dev/null +++ b/.github/blunderbuss.yml @@ -0,0 +1,17 @@ +# Blunderbuss config +# +# This file controls who is assigned for pull requests and issues. +# Note: This file is autogenerated. To make changes to the assignee +# team, please update `codeowner_team` in `.repo-metadata.json`. +assign_issues: + - googleapis/cloud-storage-dpe + +assign_issues_by: + - labels: + - "samples" + to: + - googleapis/python-samples-reviewers + - googleapis/cloud-storage-dpe + +assign_prs: + - googleapis/cloud-storage-dpe diff --git a/.kokoro/build.sh b/.kokoro/build.sh index 4e816ecf6..99690a243 100755 --- a/.kokoro/build.sh +++ b/.kokoro/build.sh @@ -43,13 +43,6 @@ export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/service-account.json # Setup project id. export PROJECT_ID=$(cat "${KOKORO_GFILE_DIR}/project-id.json") -# Remove old nox -python3 -m pip uninstall --yes --quiet nox-automation - -# Install nox -python3 -m pip install --upgrade --quiet nox -python3 -m nox --version - # If this is a continuous build, send the test log to the FlakyBot. # See https://github.com/googleapis/repo-automation-bots/tree/main/packages/flakybot. if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]]; then diff --git a/.kokoro/docker/docs/Dockerfile b/.kokoro/docker/docs/Dockerfile index 8e39a2cc4..bdaf39fe2 100644 --- a/.kokoro/docker/docs/Dockerfile +++ b/.kokoro/docker/docs/Dockerfile @@ -80,4 +80,8 @@ RUN wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \ # Test pip RUN python3 -m pip +# Install build requirements +COPY requirements.txt /requirements.txt +RUN python3 -m pip install --require-hashes -r requirements.txt + CMD ["python3.8"] diff --git a/.kokoro/docker/docs/requirements.in b/.kokoro/docker/docs/requirements.in new file mode 100644 index 000000000..816817c67 --- /dev/null +++ b/.kokoro/docker/docs/requirements.in @@ -0,0 +1 @@ +nox diff --git a/.kokoro/docker/docs/requirements.txt b/.kokoro/docker/docs/requirements.txt new file mode 100644 index 000000000..0e5d70f20 --- /dev/null +++ b/.kokoro/docker/docs/requirements.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --generate-hashes requirements.in +# +argcomplete==3.2.3 \ + --hash=sha256:bf7900329262e481be5a15f56f19736b376df6f82ed27576fa893652c5de6c23 \ + --hash=sha256:c12355e0494c76a2a7b73e3a59b09024ca0ba1e279fb9ed6c1b82d5b74b6a70c + # via nox +colorlog==6.8.2 \ + --hash=sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44 \ + --hash=sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33 + # via nox +distlib==0.3.8 \ + --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ + --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 + # via virtualenv +filelock==3.13.1 \ + --hash=sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e \ + --hash=sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c + # via virtualenv +nox==2024.3.2 \ + --hash=sha256:e53514173ac0b98dd47585096a55572fe504fecede58ced708979184d05440be \ + --hash=sha256:f521ae08a15adbf5e11f16cb34e8d0e6ea521e0b92868f684e91677deb974553 + # via -r requirements.in +packaging==24.0 \ + --hash=sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5 \ + --hash=sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9 + # via nox +platformdirs==4.2.0 \ + --hash=sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068 \ + --hash=sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768 + # via virtualenv +virtualenv==20.25.1 \ + --hash=sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a \ + --hash=sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197 + # via nox diff --git a/.kokoro/requirements.in b/.kokoro/requirements.in index ec867d9fd..fff4d9ce0 100644 --- a/.kokoro/requirements.in +++ b/.kokoro/requirements.in @@ -1,5 +1,5 @@ gcp-docuploader -gcp-releasetool>=1.10.5 # required for compatibility with cryptography>=39.x +gcp-releasetool>=2 # required for compatibility with cryptography>=42.x importlib-metadata typing-extensions twine @@ -8,3 +8,4 @@ setuptools nox>=2022.11.21 # required to remove dependency on py charset-normalizer<3 click<8.1.0 +cryptography>=42.0.5 diff --git a/.kokoro/requirements.txt b/.kokoro/requirements.txt index bda8e38c4..51f92b8e1 100644 --- a/.kokoro/requirements.txt +++ b/.kokoro/requirements.txt @@ -93,40 +93,41 @@ colorlog==6.7.0 \ # via # gcp-docuploader # nox -cryptography==42.0.4 \ - --hash=sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b \ - --hash=sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce \ - --hash=sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88 \ - --hash=sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7 \ - --hash=sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20 \ - --hash=sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9 \ - --hash=sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff \ - --hash=sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1 \ - --hash=sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764 \ - --hash=sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b \ - --hash=sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298 \ - --hash=sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1 \ - --hash=sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824 \ - --hash=sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257 \ - --hash=sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a \ - --hash=sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129 \ - --hash=sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb \ - --hash=sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929 \ - --hash=sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854 \ - --hash=sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52 \ - --hash=sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923 \ - --hash=sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885 \ - --hash=sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0 \ - --hash=sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd \ - --hash=sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2 \ - --hash=sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18 \ - --hash=sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b \ - --hash=sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992 \ - --hash=sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74 \ - --hash=sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660 \ - --hash=sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925 \ - --hash=sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449 +cryptography==42.0.5 \ + --hash=sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee \ + --hash=sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576 \ + --hash=sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d \ + --hash=sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30 \ + --hash=sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413 \ + --hash=sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb \ + --hash=sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da \ + --hash=sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4 \ + --hash=sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd \ + --hash=sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc \ + --hash=sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8 \ + --hash=sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1 \ + --hash=sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc \ + --hash=sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e \ + --hash=sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8 \ + --hash=sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940 \ + --hash=sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400 \ + --hash=sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7 \ + --hash=sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16 \ + --hash=sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278 \ + --hash=sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74 \ + --hash=sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec \ + --hash=sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1 \ + --hash=sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2 \ + --hash=sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c \ + --hash=sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922 \ + --hash=sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a \ + --hash=sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6 \ + --hash=sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1 \ + --hash=sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e \ + --hash=sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac \ + --hash=sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7 # via + # -r requirements.in # gcp-releasetool # secretstorage distlib==0.3.7 \ @@ -145,9 +146,9 @@ gcp-docuploader==0.6.5 \ --hash=sha256:30221d4ac3e5a2b9c69aa52fdbef68cc3f27d0e6d0d90e220fc024584b8d2318 \ --hash=sha256:b7458ef93f605b9d46a4bf3a8dc1755dad1f31d030c8679edf304e343b347eea # via -r requirements.in -gcp-releasetool==1.16.0 \ - --hash=sha256:27bf19d2e87aaa884096ff941aa3c592c482be3d6a2bfe6f06afafa6af2353e3 \ - --hash=sha256:a316b197a543fd036209d0caba7a8eb4d236d8e65381c80cbc6d7efaa7606d63 +gcp-releasetool==2.0.0 \ + --hash=sha256:3d73480b50ba243f22d7c7ec08b115a30e1c7817c4899781840c26f9c55b8277 \ + --hash=sha256:7aa9fd935ec61e581eb8458ad00823786d91756c25e492f372b2b30962f3c28f # via -r requirements.in google-api-core==2.12.0 \ --hash=sha256:c22e01b1e3c4dcd90998494879612c38d0a3411d1f7b679eb89e2abe3ce1f553 \ @@ -251,9 +252,9 @@ googleapis-common-protos==1.61.0 \ --hash=sha256:22f1915393bb3245343f6efe87f6fe868532efc12aa26b391b15132e1279f1c0 \ --hash=sha256:8a64866a97f6304a7179873a465d6eee97b7a24ec6cfd78e0f575e96b821240b # via google-api-core -idna==3.4 \ - --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ - --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 +idna==3.7 \ + --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ + --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 # via requests importlib-metadata==6.8.0 \ --hash=sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb \ @@ -392,29 +393,18 @@ platformdirs==3.11.0 \ --hash=sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3 \ --hash=sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e # via virtualenv -protobuf==3.20.3 \ - --hash=sha256:03038ac1cfbc41aa21f6afcbcd357281d7521b4157926f30ebecc8d4ea59dcb7 \ - --hash=sha256:28545383d61f55b57cf4df63eebd9827754fd2dc25f80c5253f9184235db242c \ - --hash=sha256:2e3427429c9cffebf259491be0af70189607f365c2f41c7c3764af6f337105f2 \ - --hash=sha256:398a9e0c3eaceb34ec1aee71894ca3299605fa8e761544934378bbc6c97de23b \ - --hash=sha256:44246bab5dd4b7fbd3c0c80b6f16686808fab0e4aca819ade6e8d294a29c7050 \ - --hash=sha256:447d43819997825d4e71bf5769d869b968ce96848b6479397e29fc24c4a5dfe9 \ - --hash=sha256:67a3598f0a2dcbc58d02dd1928544e7d88f764b47d4a286202913f0b2801c2e7 \ - --hash=sha256:74480f79a023f90dc6e18febbf7b8bac7508420f2006fabd512013c0c238f454 \ - --hash=sha256:819559cafa1a373b7096a482b504ae8a857c89593cf3a25af743ac9ecbd23480 \ - --hash=sha256:899dc660cd599d7352d6f10d83c95df430a38b410c1b66b407a6b29265d66469 \ - --hash=sha256:8c0c984a1b8fef4086329ff8dd19ac77576b384079247c770f29cc8ce3afa06c \ - --hash=sha256:9aae4406ea63d825636cc11ffb34ad3379335803216ee3a856787bcf5ccc751e \ - --hash=sha256:a7ca6d488aa8ff7f329d4c545b2dbad8ac31464f1d8b1c87ad1346717731e4db \ - --hash=sha256:b6cc7ba72a8850621bfec987cb72623e703b7fe2b9127a161ce61e61558ad905 \ - --hash=sha256:bf01b5720be110540be4286e791db73f84a2b721072a3711efff6c324cdf074b \ - --hash=sha256:c02ce36ec760252242a33967d51c289fd0e1c0e6e5cc9397e2279177716add86 \ - --hash=sha256:d9e4432ff660d67d775c66ac42a67cf2453c27cb4d738fc22cb53b5d84c135d4 \ - --hash=sha256:daa564862dd0d39c00f8086f88700fdbe8bc717e993a21e90711acfed02f2402 \ - --hash=sha256:de78575669dddf6099a8a0f46a27e82a1783c557ccc38ee620ed8cc96d3be7d7 \ - --hash=sha256:e64857f395505ebf3d2569935506ae0dfc4a15cb80dc25261176c784662cdcc4 \ - --hash=sha256:f4bd856d702e5b0d96a00ec6b307b0f51c1982c2bf9c0052cf9019e9a544ba99 \ - --hash=sha256:f4c42102bc82a51108e449cbb32b19b180022941c727bac0cfd50170341f16ee +protobuf==4.25.3 \ + --hash=sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4 \ + --hash=sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8 \ + --hash=sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c \ + --hash=sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d \ + --hash=sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4 \ + --hash=sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa \ + --hash=sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c \ + --hash=sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019 \ + --hash=sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9 \ + --hash=sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c \ + --hash=sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2 # via # gcp-docuploader # gcp-releasetool @@ -518,7 +508,7 @@ zipp==3.17.0 \ # via importlib-metadata # The following packages are considered to be unsafe in a requirements file: -setuptools==68.2.2 \ - --hash=sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87 \ - --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a +setuptools==69.2.0 \ + --hash=sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e \ + --hash=sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c # via -r requirements.in diff --git a/CHANGELOG.md b/CHANGELOG.md index 25b6fe162..4a7fefeb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ [1]: https://pypi.org/project/google-cloud-storage/#history +## [2.17.0](https://github.com/googleapis/python-storage/compare/v2.16.0...v2.17.0) (2024-05-22) + + +### Features + +* Support HNS enablement in bucket metadata ([#1278](https://github.com/googleapis/python-storage/issues/1278)) ([add3c01](https://github.com/googleapis/python-storage/commit/add3c01f0974e22df7f0b50504d5e83e4235fd81)) +* Support page_size in bucket.list_blobs ([#1275](https://github.com/googleapis/python-storage/issues/1275)) ([c52e882](https://github.com/googleapis/python-storage/commit/c52e882f65583a7739392926308cc34984561165)) + + +### Bug Fixes + +* Remove deprecated methods in samples and tests ([#1274](https://github.com/googleapis/python-storage/issues/1274)) ([4db96c9](https://github.com/googleapis/python-storage/commit/4db96c960b07e503c1031c9fa879cf2af195f513)) + + +### Documentation + +* Reference Storage Control in readme ([#1254](https://github.com/googleapis/python-storage/issues/1254)) ([3d6d369](https://github.com/googleapis/python-storage/commit/3d6d3693d5c1b24cd3d2bbdeabfd78b8bfd4161a)) +* Update DEFAULT_RETRY_IF_GENERATION_SPECIFIED docstrings ([#1234](https://github.com/googleapis/python-storage/issues/1234)) ([bdd426a](https://github.com/googleapis/python-storage/commit/bdd426adf5901faa36115885af868ef50e356a36)) + ## [2.16.0](https://github.com/googleapis/python-storage/compare/v2.15.0...v2.16.0) (2024-03-18) diff --git a/README.rst b/README.rst index 61b5a62eb..9eef57645 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,11 @@ A comprehensive list of changes in each version may be found in the `CHANGELOG`_ - `Client Library Documentation`_ - `github.com/googleapis/python-storage`_ +Certain control plane and long-running operations for Cloud Storage (including Folder +and Managed Folder operations) are supported via the `Storage Control Client`_. +The `Storage Control API`_ creates one space to perform metadata-specific, control plane, +and long-running operations apart from the Storage API. + Read more about the client libraries for Cloud APIs, including the older Google APIs Client Libraries, in `Client Libraries Explained`_. @@ -28,6 +33,8 @@ Google APIs Client Libraries, in `Client Libraries Explained`_. .. _Product Documentation: https://cloud.google.com/storage .. _CHANGELOG: https://github.com/googleapis/python-storage/blob/main/CHANGELOG.md .. _github.com/googleapis/python-storage: https://github.com/googleapis/python-storage +.. _Storage Control Client: https://cloud.google.com/python/docs/reference/google-cloud-storage-control/latest +.. _Storage Control API: https://cloud.google.com/storage/docs/reference/rpc/google.storage.control.v2 .. _Client Libraries Explained: https://cloud.google.com/apis/docs/client-libraries-explained Quick Start diff --git a/docs/index.rst b/docs/index.rst index 1dd08278a..7772500bb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -49,3 +49,8 @@ Changelog :maxdepth: 2 changelog + +.. toctree:: + :hidden: + + summary_overview.md diff --git a/docs/snippets.py b/docs/snippets.py index 93884900f..631dca468 100644 --- a/docs/snippets.py +++ b/docs/snippets.py @@ -39,7 +39,7 @@ def storage_get_started(to_delete): bucket = client.get_bucket("bucket-id-here") # Then do other things... blob = bucket.get_blob("/remote/path/to/file.txt") - assert blob.download_as_string() == b"My old contents!" + assert blob.download_as_bytes() == b"My old contents!" blob.upload_from_string("New contents!") blob2 = bucket.blob("/remote/path/storage.txt") blob2.upload_from_filename(filename="/local/path.txt") diff --git a/docs/summary_overview.md b/docs/summary_overview.md new file mode 100644 index 000000000..e735f9658 --- /dev/null +++ b/docs/summary_overview.md @@ -0,0 +1,22 @@ +[ +This is a templated file. Adding content to this file may result in it being +reverted. Instead, if you want to place additional content, create an +"overview_content.md" file in `docs/` directory. The Sphinx tool will +pick up on the content and merge the content. +]: # + +# Google Cloud Storage API + +Overview of the APIs available for Google Cloud Storage API. + +## All entries + +Classes, methods and properties & attributes for +Google Cloud Storage API. + +[classes](https://cloud.google.com/python/docs/reference/storage/latest/summary_class.html) + +[methods](https://cloud.google.com/python/docs/reference/storage/latest/summary_method.html) + +[properties and +attributes](https://cloud.google.com/python/docs/reference/storage/latest/summary_property.html) diff --git a/google/cloud/storage/blob.py b/google/cloud/storage/blob.py index 9c0cf33ab..b0b4a663f 100644 --- a/google/cloud/storage/blob.py +++ b/google/cloud/storage/blob.py @@ -787,7 +787,13 @@ def delete( :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy :param retry: - (Optional) How to retry the RPC. See: :ref:`configuring_retries` + (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). :raises: :class:`google.cloud.exceptions.NotFound` (propagated from @@ -2769,26 +2775,17 @@ def upload_from_file( "md5", "crc32c" and None. The default is None. :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy - :param retry: (Optional) How to retry the RPC. A None value will disable - retries. A google.api_core.retry.Retry value will enable retries, - and the object will define retriable response codes and errors and - configure backoff and timeout options. - - A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a - Retry object and activates it only if certain conditions are met. - This class exists to provide safe defaults for RPC calls that are - not technically safe to retry normally (due to potential data - duplication or other side-effects) but become safe to retry if a - condition such as if_generation_match is set. - - See the retry.py source code and docstrings in this package - (google.cloud.storage.retry) for information on retry types and how - to configure them. + :param retry: (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). Media operations (downloads and uploads) do not support non-default - predicates in a Retry object. The default will always be used. Other - configuration changes for Retry objects such as delays and deadlines - are respected. + predicates in a Retry object. Other configuration changes for Retry objects + such as delays and deadlines are respected. :raises: :class:`~google.cloud.exceptions.GoogleCloudError` if the upload response returns an error status. @@ -2934,26 +2931,17 @@ def upload_from_filename( "md5", "crc32c" and None. The default is None. :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy - :param retry: (Optional) How to retry the RPC. A None value will disable - retries. A google.api_core.retry.Retry value will enable retries, - and the object will define retriable response codes and errors and - configure backoff and timeout options. - - A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a - Retry object and activates it only if certain conditions are met. - This class exists to provide safe defaults for RPC calls that are - not technically safe to retry normally (due to potential data - duplication or other side-effects) but become safe to retry if a - condition such as if_generation_match is set. - - See the retry.py source code and docstrings in this package - (google.cloud.storage.retry) for information on retry types and how - to configure them. + :param retry: (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). Media operations (downloads and uploads) do not support non-default - predicates in a Retry object. The default will always be used. Other - configuration changes for Retry objects such as delays and deadlines - are respected. + predicates in a Retry object. Other configuration changes for Retry objects + such as delays and deadlines are respected. """ self._handle_filename_and_upload( @@ -3063,26 +3051,17 @@ def upload_from_string( "md5", "crc32c" and None. The default is None. :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy - :param retry: (Optional) How to retry the RPC. A None value will disable - retries. A google.api_core.retry.Retry value will enable retries, - and the object will define retriable response codes and errors and - configure backoff and timeout options. - - A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a - Retry object and activates it only if certain conditions are met. - This class exists to provide safe defaults for RPC calls that are - not technically safe to retry normally (due to potential data - duplication or other side-effects) but become safe to retry if a - condition such as if_generation_match is set. - - See the retry.py source code and docstrings in this package - (google.cloud.storage.retry) for information on retry types and how - to configure them. + :param retry: (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). Media operations (downloads and uploads) do not support non-default - predicates in a Retry object. The default will always be used. Other - configuration changes for Retry objects such as delays and deadlines - are respected. + predicates in a Retry object. Other configuration changes for Retry objects + such as delays and deadlines are respected. """ data = _to_bytes(data, encoding="utf-8") string_buffer = BytesIO(data) @@ -3209,23 +3188,17 @@ def create_resumable_upload_session( (Optional) See :ref:`using-if-metageneration-not-match` :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy - :param retry: (Optional) How to retry the RPC. A None value will disable - retries. A google.api_core.retry.Retry value will enable retries, - and the object will define retriable response codes and errors and - configure backoff and timeout options. - A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a - Retry object and activates it only if certain conditions are met. - This class exists to provide safe defaults for RPC calls that are - not technically safe to retry normally (due to potential data - duplication or other side-effects) but become safe to retry if a - condition such as if_generation_match is set. - See the retry.py source code and docstrings in this package - (google.cloud.storage.retry) for information on retry types and how - to configure them. + :param retry: (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). + Media operations (downloads and uploads) do not support non-default - predicates in a Retry object. The default will always be used. Other - configuration changes for Retry objects such as delays and deadlines - are respected. + predicates in a Retry object. Other configuration changes for Retry objects + such as delays and deadlines are respected. :rtype: str :returns: The resumable upload session URL. The upload can be @@ -3631,7 +3604,13 @@ def compose( :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy :param retry: - (Optional) How to retry the RPC. See: :ref:`configuring_retries` + (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). """ sources_len = len(sources) client = self._require_client(client) @@ -3793,7 +3772,13 @@ def rewrite( :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy :param retry: - (Optional) How to retry the RPC. See: :ref:`configuring_retries` + (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). :rtype: tuple :returns: ``(token, bytes_rewritten, total_bytes)``, where ``token`` @@ -3953,7 +3938,13 @@ def update_storage_class( :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy :param retry: - (Optional) How to retry the RPC. See: :ref:`configuring_retries` + (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). """ # 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 c83e2a958..7b6421d29 100644 --- a/google/cloud/storage/bucket.py +++ b/google/cloud/storage/bucket.py @@ -1308,6 +1308,7 @@ def list_blobs( match_glob=None, include_folders_as_prefixes=None, soft_deleted=None, + page_size=None, ): """Return an iterator used to find blobs in the bucket. @@ -1401,6 +1402,11 @@ def list_blobs( Note ``soft_deleted`` and ``versions`` cannot be set to True simultaneously. See: https://cloud.google.com/storage/docs/soft-delete + :type page_size: int + :param page_size: + (Optional) Maximum number of blobs to return in each page. + Defaults to a value set by the API. + :rtype: :class:`~google.api_core.page_iterator.Iterator` :returns: Iterator of all :class:`~google.cloud.storage.blob.Blob` in this bucket matching the arguments. @@ -1418,6 +1424,7 @@ def list_blobs( versions=versions, projection=projection, fields=fields, + page_size=page_size, timeout=timeout, retry=retry, match_glob=match_glob, @@ -1656,7 +1663,13 @@ def delete_blob( :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy :param retry: - (Optional) How to retry the RPC. See: :ref:`configuring_retries` + (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). :raises: :class:`google.cloud.exceptions.NotFound` Raises a NotFound if the blob isn't found. To suppress @@ -1757,7 +1770,13 @@ def delete_blobs( :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy :param retry: - (Optional) How to retry the RPC. See: :ref:`configuring_retries` + (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). :raises: :class:`~google.cloud.exceptions.NotFound` (if `on_error` is not passed). @@ -1902,7 +1921,13 @@ def copy_blob( :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy :param retry: - (Optional) How to retry the RPC. See: :ref:`configuring_retries` + (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). :rtype: :class:`google.cloud.storage.blob.Blob` :returns: The new Blob. @@ -2049,7 +2074,13 @@ def rename_blob( :type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy :param retry: - (Optional) How to retry the RPC. See: :ref:`configuring_retries` + (Optional) How to retry the RPC. + The default value is ``DEFAULT_RETRY_IF_GENERATION_SPECIFIED``, a conditional retry + policy which will only enable retries if ``if_generation_match`` or ``generation`` + is set, in order to ensure requests are idempotent before retrying them. + Change the value to ``DEFAULT_RETRY`` or another `google.api_core.retry.Retry` object + to enable retries regardless of generation precondition setting. + See [Configuring Retries](https://cloud.google.com/python/docs/reference/storage/latest/retry_timeout). :rtype: :class:`Blob` :returns: The newly-renamed blob. @@ -2926,6 +2957,35 @@ def object_retention_mode(self): if object_retention is not None: return object_retention.get("mode") + @property + def hierarchical_namespace_enabled(self): + """Whether hierarchical namespace is enabled for this bucket. + + :setter: Update whether hierarchical namespace is enabled for this bucket. + :getter: Query whether hierarchical namespace is enabled for this bucket. + + :rtype: bool + :returns: True if enabled, else False. + """ + hns = self._properties.get("hierarchicalNamespace", {}) + return hns.get("enabled") + + @hierarchical_namespace_enabled.setter + def hierarchical_namespace_enabled(self, value): + """Enable or disable hierarchical namespace at the bucket-level. + + :type value: convertible to boolean + :param value: If true, enable hierarchical namespace for this bucket. + If false, disable hierarchical namespace for this bucket. + + .. note:: + To enable hierarchical namespace, you must set it at bucket creation time. + Currently, hierarchical namespace configuration cannot be changed after bucket creation. + """ + hns = self._properties.get("hierarchicalNamespace", {}) + hns["enabled"] = bool(value) + self._patch_property("hierarchicalNamespace", hns) + def configure_website(self, main_page_suffix=None, not_found_page=None): """Configure website-related properties. diff --git a/google/cloud/storage/version.py b/google/cloud/storage/version.py index a93d72c2b..422b383cc 100644 --- a/google/cloud/storage/version.py +++ b/google/cloud/storage/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.16.0" +__version__ = "2.17.0" diff --git a/samples/snippets/encryption_test.py b/samples/snippets/encryption_test.py index ff7a568e0..9039b1fad 100644 --- a/samples/snippets/encryption_test.py +++ b/samples/snippets/encryption_test.py @@ -125,4 +125,4 @@ def test_object_csek_to_cmek(test_blob): BUCKET, test_blob_name, TEST_ENCRYPTION_KEY_2, KMS_KEY ) - assert cmek_blob.download_as_string(), test_blob_content + assert cmek_blob.download_as_bytes(), test_blob_content diff --git a/samples/snippets/requirements-test.txt b/samples/snippets/requirements-test.txt index 9035a0f91..86a3ade30 100644 --- a/samples/snippets/requirements-test.txt +++ b/samples/snippets/requirements-test.txt @@ -1,3 +1,4 @@ -pytest==7.4.4 +pytest===7.4.4; python_version == '3.7' +pytest==8.1.1; python_version >= '3.8' mock==5.1.0 -backoff==2.2.1 \ No newline at end of file +backoff==2.2.1 diff --git a/samples/snippets/requirements.txt b/samples/snippets/requirements.txt index 15a684973..b0e41fa84 100644 --- a/samples/snippets/requirements.txt +++ b/samples/snippets/requirements.txt @@ -1,5 +1,5 @@ -google-cloud-pubsub==2.19.0 -google-cloud-storage==2.14.0 +google-cloud-pubsub==2.21.1 +google-cloud-storage==2.16.0 pandas===1.3.5; python_version == '3.7' pandas===2.0.3; python_version == '3.8' -pandas==2.1.4; python_version >= '3.9' +pandas==2.2.2; python_version >= '3.9' diff --git a/samples/snippets/rpo_test.py b/samples/snippets/rpo_test.py index befc0334a..0dcf15746 100644 --- a/samples/snippets/rpo_test.py +++ b/samples/snippets/rpo_test.py @@ -27,11 +27,11 @@ def dual_region_bucket(): """Yields a dual region bucket that is deleted after the test completes.""" bucket = None + location = "NAM4" while bucket is None or bucket.exists(): bucket_name = f"bucket-lock-{uuid.uuid4()}" bucket = storage.Client().bucket(bucket_name) - bucket.location = "NAM4" - bucket.create() + bucket.create(location=location) yield bucket bucket.delete(force=True) diff --git a/samples/snippets/snippets_test.py b/samples/snippets/snippets_test.py index ff1d23005..f9851b0ec 100644 --- a/samples/snippets/snippets_test.py +++ b/samples/snippets/snippets_test.py @@ -37,6 +37,7 @@ import storage_cors_configuration import storage_create_bucket_class_location import storage_create_bucket_dual_region +import storage_create_bucket_object_retention import storage_define_bucket_website_configuration import storage_delete_file import storage_delete_file_archived_generation @@ -71,6 +72,7 @@ import storage_set_autoclass import storage_set_bucket_default_kms_key import storage_set_client_endpoint +import storage_set_object_retention_policy import storage_set_metadata import storage_transfer_manager_download_bucket import storage_transfer_manager_download_chunks_concurrently @@ -818,3 +820,24 @@ def test_transfer_manager_upload_chunks_concurrently(test_bucket, capsys): out, _ = capsys.readouterr() assert "File {} uploaded to {}".format(file.name, BLOB_NAME) in out + + +def test_object_retention_policy(test_bucket_create, capsys): + storage_create_bucket_object_retention.create_bucket_object_retention( + test_bucket_create.name + ) + out, _ = capsys.readouterr() + assert f"Created bucket {test_bucket_create.name} with object retention enabled setting" in out + + blob_name = "test_object_retention" + storage_set_object_retention_policy.set_object_retention_policy( + test_bucket_create.name, "hello world", blob_name + ) + out, _ = capsys.readouterr() + assert f"Retention policy for file {blob_name}" in out + + # Remove retention policy for test cleanup + blob = test_bucket_create.blob(blob_name) + blob.retention.mode = None + blob.retention.retain_until_time = None + blob.patch(override_unlocked_retention=True) diff --git a/samples/snippets/storage_create_bucket_object_retention.py b/samples/snippets/storage_create_bucket_object_retention.py new file mode 100644 index 000000000..4ebc32c0a --- /dev/null +++ b/samples/snippets/storage_create_bucket_object_retention.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys + +# [START storage_create_bucket_with_object_retention] +from google.cloud import storage + + +def create_bucket_object_retention(bucket_name): + """Creates a bucket with object retention enabled.""" + # The ID of your GCS bucket + # bucket_name = "your-bucket-name" + + storage_client = storage.Client() + bucket = storage_client.create_bucket(bucket_name, enable_object_retention=True) + + print(f"Created bucket {bucket_name} with object retention enabled setting: {bucket.object_retention_mode}") + + +# [END storage_create_bucket_with_object_retention] + + +if __name__ == "__main__": + create_bucket_object_retention(bucket_name=sys.argv[1]) diff --git a/samples/snippets/storage_create_bucket_turbo_replication.py b/samples/snippets/storage_create_bucket_turbo_replication.py index 3d26616ec..bc0559795 100644 --- a/samples/snippets/storage_create_bucket_turbo_replication.py +++ b/samples/snippets/storage_create_bucket_turbo_replication.py @@ -35,9 +35,9 @@ def create_bucket_turbo_replication(bucket_name): storage_client = storage.Client() bucket = storage_client.bucket(bucket_name) - bucket.location = "NAM4" + bucket_location = "NAM4" bucket.rpo = RPO_ASYNC_TURBO - bucket.create() + bucket.create(location=bucket_location) print(f"{bucket.name} created with the recovery point objective (RPO) set to {bucket.rpo} in {bucket.location}.") diff --git a/samples/snippets/storage_get_bucket_metadata.py b/samples/snippets/storage_get_bucket_metadata.py index 87cd5eddc..c86e154de 100644 --- a/samples/snippets/storage_get_bucket_metadata.py +++ b/samples/snippets/storage_get_bucket_metadata.py @@ -44,6 +44,7 @@ def bucket_metadata(bucket_name): print(f"Retention Effective Time: {bucket.retention_policy_effective_time}") print(f"Retention Period: {bucket.retention_period}") print(f"Retention Policy Locked: {bucket.retention_policy_locked}") + print(f"Object Retention Mode: {bucket.object_retention_mode}") print(f"Requester Pays: {bucket.requester_pays}") print(f"Self Link: {bucket.self_link}") print(f"Time Created: {bucket.time_created}") diff --git a/samples/snippets/storage_get_metadata.py b/samples/snippets/storage_get_metadata.py index eece8028a..7216efdb4 100644 --- a/samples/snippets/storage_get_metadata.py +++ b/samples/snippets/storage_get_metadata.py @@ -59,6 +59,8 @@ def blob_metadata(bucket_name, blob_name): "Event based hold: ", "enabled" if blob.event_based_hold else "disabled", ) + print(f"Retention mode: {blob.retention.mode}") + print(f"Retention retain until time: {blob.retention.retain_until_time}") if blob.retention_expiration_time: print( f"retentionExpirationTime: {blob.retention_expiration_time}" diff --git a/samples/snippets/storage_set_object_retention_policy.py b/samples/snippets/storage_set_object_retention_policy.py new file mode 100644 index 000000000..d0d3a54ec --- /dev/null +++ b/samples/snippets/storage_set_object_retention_policy.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python + +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import sys + +# [START storage_set_object_retention_policy] +from google.cloud import storage + + +def set_object_retention_policy(bucket_name, contents, destination_blob_name): + """Set the object retention policy of a file.""" + + # The ID of your GCS bucket + # bucket_name = "your-bucket-name" + + # The contents to upload to the file + # contents = "these are my contents" + + # The ID of your GCS object + # destination_blob_name = "storage-object-name" + + storage_client = storage.Client() + bucket = storage_client.bucket(bucket_name) + blob = bucket.blob(destination_blob_name) + blob.upload_from_string(contents) + + # Set the retention policy for the file. + blob.retention.mode = "Unlocked" + retention_date = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=10) + blob.retention.retain_until_time = retention_date + blob.patch() + print( + f"Retention policy for file {destination_blob_name} was set to: {blob.retention.mode}." + ) + + # To modify an existing policy on an unlocked file object, pass in the override parameter. + new_retention_date = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=9) + blob.retention.retain_until_time = new_retention_date + blob.patch(override_unlocked_retention=True) + print( + f"Retention policy for file {destination_blob_name} was updated to: {blob.retention.retain_until_time}." + ) + + +# [END storage_set_object_retention_policy] + + +if __name__ == "__main__": + set_object_retention_policy( + bucket_name=sys.argv[1], + contents=sys.argv[2], + destination_blob_name=sys.argv[3], + ) diff --git a/tests/system/test_blob.py b/tests/system/test_blob.py index a35c047b1..6069725ce 100644 --- a/tests/system/test_blob.py +++ b/tests/system/test_blob.py @@ -761,7 +761,7 @@ def test_blob_upload_download_crc32_md5_hash( download_blob = shared_bucket.blob("MyBuffer") - assert download_blob.download_as_string() == payload + assert download_blob.download_as_bytes() == payload assert download_blob.crc32c == blob.crc32c assert download_blob.md5_hash == blob.md5_hash diff --git a/tests/system/test_bucket.py b/tests/system/test_bucket.py index 9b2fcd614..270a77ad1 100644 --- a/tests/system/test_bucket.py +++ b/tests/system/test_bucket.py @@ -410,9 +410,9 @@ def test_bucket_copy_blob_w_metageneration_match( ): payload = b"DEADBEEF" bucket_name = _helpers.unique_name("generation-match") - created = _helpers.retry_429_503(storage_client.create_bucket)( - bucket_name, requester_pays=True - ) + bucket = storage_client.bucket(bucket_name) + bucket.requester_pays = True + created = _helpers.retry_429_503(storage_client.create_bucket)(bucket) buckets_to_delete.append(created) assert created.name == bucket_name @@ -1236,3 +1236,32 @@ def test_soft_delete_policy( bucket.soft_delete_policy.retention_duration_seconds = new_duration_secs bucket.patch() assert bucket.soft_delete_policy.retention_duration_seconds == new_duration_secs + + +def test_new_bucket_with_hierarchical_namespace( + storage_client, + buckets_to_delete, +): + # Test new bucket without specifying hierarchical namespace + bucket_name = _helpers.unique_name("new-wo-hns") + bucket_obj = storage_client.bucket(bucket_name) + bucket = storage_client.create_bucket(bucket_obj) + buckets_to_delete.append(bucket) + assert bucket.hierarchical_namespace_enabled is None + + # Test new bucket with hierarchical namespace disabled + bucket_name = _helpers.unique_name("new-hns-disabled") + bucket_obj = storage_client.bucket(bucket_name) + bucket_obj.hierarchical_namespace_enabled = False + bucket = storage_client.create_bucket(bucket_obj) + buckets_to_delete.append(bucket) + assert bucket.hierarchical_namespace_enabled is False + + # Test new bucket with hierarchical namespace enabled + bucket_name = _helpers.unique_name("new-hns-enabled") + bucket_obj = storage_client.bucket(bucket_name) + bucket_obj.hierarchical_namespace_enabled = True + bucket_obj.iam_configuration.uniform_bucket_level_access_enabled = True + bucket = storage_client.create_bucket(bucket_obj) + buckets_to_delete.append(bucket) + assert bucket.hierarchical_namespace_enabled is True diff --git a/tests/unit/test_bucket.py b/tests/unit/test_bucket.py index d8ce1e0f5..030fba72b 100644 --- a/tests/unit/test_bucket.py +++ b/tests/unit/test_bucket.py @@ -1179,6 +1179,7 @@ def test_list_blobs_w_defaults(self): expected_fields = None expected_include_folders_as_prefixes = None soft_deleted = None + page_size = None client.list_blobs.assert_called_once_with( bucket, max_results=expected_max_results, @@ -1196,6 +1197,7 @@ def test_list_blobs_w_defaults(self): match_glob=expected_match_glob, include_folders_as_prefixes=expected_include_folders_as_prefixes, soft_deleted=soft_deleted, + page_size=page_size, ) def test_list_blobs_w_explicit(self): @@ -1211,6 +1213,7 @@ def test_list_blobs_w_explicit(self): include_folders_as_prefixes = True versions = True soft_deleted = True + page_size = 2 projection = "full" fields = "items/contentLanguage,nextPageToken" bucket = self._make_one(client=None, name=name) @@ -1236,6 +1239,7 @@ def test_list_blobs_w_explicit(self): match_glob=match_glob, include_folders_as_prefixes=include_folders_as_prefixes, soft_deleted=soft_deleted, + page_size=page_size, ) self.assertIs(iterator, other_client.list_blobs.return_value) @@ -1253,6 +1257,7 @@ def test_list_blobs_w_explicit(self): expected_fields = fields expected_include_folders_as_prefixes = include_folders_as_prefixes expected_soft_deleted = soft_deleted + expected_page_size = page_size other_client.list_blobs.assert_called_once_with( bucket, max_results=expected_max_results, @@ -1270,6 +1275,7 @@ def test_list_blobs_w_explicit(self): match_glob=expected_match_glob, include_folders_as_prefixes=expected_include_folders_as_prefixes, soft_deleted=expected_soft_deleted, + page_size=expected_page_size, ) def test_list_notifications_w_defaults(self): @@ -3157,6 +3163,19 @@ def test_soft_delete_policy_setter(self): self.assertTrue("softDeletePolicy" in bucket._changes) self.assertEqual(bucket.soft_delete_policy.retention_duration_seconds, seconds) + def test_hierarchical_namespace_enabled_getter_and_setter(self): + # Test hierarchical_namespace configuration unset + bucket = self._make_one() + self.assertIsNone(bucket.hierarchical_namespace_enabled) + + # Test hierarchical_namespace configuration explicitly set + properties = {"hierarchicalNamespace": {"enabled": True}} + bucket = self._make_one(properties=properties) + self.assertTrue(bucket.hierarchical_namespace_enabled) + bucket.hierarchical_namespace_enabled = False + self.assertIn("hierarchicalNamespace", bucket._changes) + self.assertFalse(bucket.hierarchical_namespace_enabled) + def test_configure_website_defaults(self): NAME = "name" UNSET = {"website": {"mainPageSuffix": None, "notFoundPage": None}}