Skip to content

Commit 7bb8065

Browse files
authored
feat: support includeFoldersAsPrefixes (#1223)
* feat: support includeFoldersAsPrefixes * sys test * update sys test with cleanup
1 parent 3928aa0 commit 7bb8065

File tree

5 files changed

+66
-0
lines changed

5 files changed

+66
-0
lines changed

google/cloud/storage/bucket.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,6 +1306,7 @@ def list_blobs(
13061306
timeout=_DEFAULT_TIMEOUT,
13071307
retry=DEFAULT_RETRY,
13081308
match_glob=None,
1309+
include_folders_as_prefixes=None,
13091310
soft_deleted=None,
13101311
):
13111312
"""Return an iterator used to find blobs in the bucket.
@@ -1388,6 +1389,11 @@ def list_blobs(
13881389
The string value must be UTF-8 encoded. See:
13891390
https://cloud.google.com/storage/docs/json_api/v1/objects/list#list-object-glob
13901391
1392+
:type include_folders_as_prefixes: bool
1393+
(Optional) If true, includes Folders and Managed Folders in the set of
1394+
``prefixes`` returned by the query. Only applicable if ``delimiter`` is set to /.
1395+
See: https://cloud.google.com/storage/docs/managed-folders
1396+
13911397
:type soft_deleted: bool
13921398
:param soft_deleted:
13931399
(Optional) If true, only soft-deleted objects will be listed as distinct results in order of increasing
@@ -1415,6 +1421,7 @@ def list_blobs(
14151421
timeout=timeout,
14161422
retry=retry,
14171423
match_glob=match_glob,
1424+
include_folders_as_prefixes=include_folders_as_prefixes,
14181425
soft_deleted=soft_deleted,
14191426
)
14201427

google/cloud/storage/client.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1184,6 +1184,7 @@ def list_blobs(
11841184
timeout=_DEFAULT_TIMEOUT,
11851185
retry=DEFAULT_RETRY,
11861186
match_glob=None,
1187+
include_folders_as_prefixes=None,
11871188
soft_deleted=None,
11881189
):
11891190
"""Return an iterator used to find blobs in the bucket.
@@ -1283,6 +1284,11 @@ def list_blobs(
12831284
The string value must be UTF-8 encoded. See:
12841285
https://cloud.google.com/storage/docs/json_api/v1/objects/list#list-object-glob
12851286
1287+
include_folders_as_prefixes (bool):
1288+
(Optional) If true, includes Folders and Managed Folders in the set of
1289+
``prefixes`` returned by the query. Only applicable if ``delimiter`` is set to /.
1290+
See: https://cloud.google.com/storage/docs/managed-folders
1291+
12861292
soft_deleted (bool):
12871293
(Optional) If true, only soft-deleted objects will be listed as distinct results in order of increasing
12881294
generation number. This parameter can only be used successfully if the bucket has a soft delete policy.
@@ -1325,6 +1331,9 @@ def list_blobs(
13251331
if fields is not None:
13261332
extra_params["fields"] = fields
13271333

1334+
if include_folders_as_prefixes is not None:
1335+
extra_params["includeFoldersAsPrefixes"] = include_folders_as_prefixes
1336+
13281337
if soft_deleted is not None:
13291338
extra_params["softDeleted"] = soft_deleted
13301339

tests/system/test_bucket.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,47 @@ def test_bucket_list_blobs_w_match_glob(
653653
assert [blob.name for blob in blobs] == expected_names
654654

655655

656+
def test_bucket_list_blobs_include_managed_folders(
657+
storage_client,
658+
buckets_to_delete,
659+
blobs_to_delete,
660+
hierarchy_filenames,
661+
):
662+
bucket_name = _helpers.unique_name("ubla-mf")
663+
bucket = storage_client.bucket(bucket_name)
664+
bucket.iam_configuration.uniform_bucket_level_access_enabled = True
665+
_helpers.retry_429_503(bucket.create)()
666+
buckets_to_delete.append(bucket)
667+
668+
payload = b"helloworld"
669+
for filename in hierarchy_filenames:
670+
blob = bucket.blob(filename)
671+
blob.upload_from_string(payload)
672+
blobs_to_delete.append(blob)
673+
674+
# Make API call to create a managed folder.
675+
# TODO: change to use storage control client once available.
676+
path = f"/b/{bucket_name}/managedFolders"
677+
properties = {"name": "managedfolder1"}
678+
storage_client._post_resource(path, properties)
679+
680+
expected_prefixes = set(["parent/"])
681+
blob_iter = bucket.list_blobs(delimiter="/")
682+
list(blob_iter)
683+
assert blob_iter.prefixes == expected_prefixes
684+
685+
# Test that managed folders are only included when IncludeFoldersAsPrefixes is set.
686+
expected_prefixes = set(["parent/", "managedfolder1/"])
687+
blob_iter = bucket.list_blobs(delimiter="/", include_folders_as_prefixes=True)
688+
list(blob_iter)
689+
assert blob_iter.prefixes == expected_prefixes
690+
691+
# Cleanup: API call to delete a managed folder.
692+
# TODO: change to use storage control client once available.
693+
path = f"/b/{bucket_name}/managedFolders/managedfolder1"
694+
storage_client._delete_resource(path)
695+
696+
656697
def test_bucket_update_retention_period(
657698
storage_client,
658699
buckets_to_delete,

tests/unit/test_bucket.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,7 @@ def test_list_blobs_w_defaults(self):
11771177
expected_versions = None
11781178
expected_projection = "noAcl"
11791179
expected_fields = None
1180+
expected_include_folders_as_prefixes = None
11801181
soft_deleted = None
11811182
client.list_blobs.assert_called_once_with(
11821183
bucket,
@@ -1193,6 +1194,7 @@ def test_list_blobs_w_defaults(self):
11931194
timeout=self._get_default_timeout(),
11941195
retry=DEFAULT_RETRY,
11951196
match_glob=expected_match_glob,
1197+
include_folders_as_prefixes=expected_include_folders_as_prefixes,
11961198
soft_deleted=soft_deleted,
11971199
)
11981200

@@ -1206,6 +1208,7 @@ def test_list_blobs_w_explicit(self):
12061208
start_offset = "c"
12071209
end_offset = "g"
12081210
include_trailing_delimiter = True
1211+
include_folders_as_prefixes = True
12091212
versions = True
12101213
soft_deleted = True
12111214
projection = "full"
@@ -1231,6 +1234,7 @@ def test_list_blobs_w_explicit(self):
12311234
timeout=timeout,
12321235
retry=retry,
12331236
match_glob=match_glob,
1237+
include_folders_as_prefixes=include_folders_as_prefixes,
12341238
soft_deleted=soft_deleted,
12351239
)
12361240

@@ -1247,6 +1251,7 @@ def test_list_blobs_w_explicit(self):
12471251
expected_versions = versions
12481252
expected_projection = projection
12491253
expected_fields = fields
1254+
expected_include_folders_as_prefixes = include_folders_as_prefixes
12501255
expected_soft_deleted = soft_deleted
12511256
other_client.list_blobs.assert_called_once_with(
12521257
bucket,
@@ -1263,6 +1268,7 @@ def test_list_blobs_w_explicit(self):
12631268
timeout=timeout,
12641269
retry=retry,
12651270
match_glob=expected_match_glob,
1271+
include_folders_as_prefixes=expected_include_folders_as_prefixes,
12661272
soft_deleted=expected_soft_deleted,
12671273
)
12681274

tests/unit/test_client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2015,6 +2015,7 @@ def test_list_blobs_w_explicit_w_user_project(self):
20152015
start_offset = "c"
20162016
end_offset = "g"
20172017
include_trailing_delimiter = True
2018+
include_folders_as_prefixes = True
20182019
soft_deleted = False
20192020
versions = True
20202021
projection = "full"
@@ -2048,6 +2049,7 @@ def test_list_blobs_w_explicit_w_user_project(self):
20482049
timeout=timeout,
20492050
retry=retry,
20502051
match_glob=match_glob,
2052+
include_folders_as_prefixes=include_folders_as_prefixes,
20512053
soft_deleted=soft_deleted,
20522054
)
20532055

@@ -2070,6 +2072,7 @@ def test_list_blobs_w_explicit_w_user_project(self):
20702072
"versions": versions,
20712073
"fields": fields,
20722074
"userProject": user_project,
2075+
"includeFoldersAsPrefixes": include_folders_as_prefixes,
20732076
"softDeleted": soft_deleted,
20742077
}
20752078
expected_page_start = _blobs_page_start

0 commit comments

Comments
 (0)