Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support includeFoldersAsPrefixes #1223

Merged
merged 4 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Next Next commit
feat: support includeFoldersAsPrefixes
  • Loading branch information
cojenco committed Feb 8, 2024
commit 931e27697c0628cb7065f72348995ade495edae0
7 changes: 7 additions & 0 deletions google/cloud/storage/bucket.py
Expand Up @@ -1297,6 +1297,7 @@ def list_blobs(
timeout=_DEFAULT_TIMEOUT,
retry=DEFAULT_RETRY,
match_glob=None,
include_folders_as_prefixes=None,
):
"""Return an iterator used to find blobs in the bucket.

Expand Down Expand Up @@ -1378,6 +1379,11 @@ def list_blobs(
The string value must be UTF-8 encoded. See:
https://cloud.google.com/storage/docs/json_api/v1/objects/list#list-object-glob

:type include_folders_as_prefixes: bool
(Optional) If true, includes Folders and Managed Folders in the set of
``prefixes`` returned by the query. Only applicable if ``delimiter`` is set to /.
See: https://cloud.google.com/storage/docs/managed-folders

:rtype: :class:`~google.api_core.page_iterator.Iterator`
:returns: Iterator of all :class:`~google.cloud.storage.blob.Blob`
in this bucket matching the arguments.
Expand All @@ -1398,6 +1404,7 @@ def list_blobs(
timeout=timeout,
retry=retry,
match_glob=match_glob,
include_folders_as_prefixes=include_folders_as_prefixes,
)

def list_notifications(
Expand Down
9 changes: 9 additions & 0 deletions google/cloud/storage/client.py
Expand Up @@ -1184,6 +1184,7 @@ def list_blobs(
timeout=_DEFAULT_TIMEOUT,
retry=DEFAULT_RETRY,
match_glob=None,
include_folders_as_prefixes=None,
):
"""Return an iterator used to find blobs in the bucket.

Expand Down Expand Up @@ -1282,6 +1283,11 @@ def list_blobs(
The string value must be UTF-8 encoded. See:
https://cloud.google.com/storage/docs/json_api/v1/objects/list#list-object-glob

include_folders_as_prefixes (bool):
(Optional) If true, includes Folders and Managed Folders in the set of
``prefixes`` returned by the query. Only applicable if ``delimiter`` is set to /.
See: https://cloud.google.com/storage/docs/managed-folders

Returns:
Iterator of all :class:`~google.cloud.storage.blob.Blob`
in this bucket matching the arguments. The RPC call
Expand Down Expand Up @@ -1318,6 +1324,9 @@ def list_blobs(
if fields is not None:
extra_params["fields"] = fields

if include_folders_as_prefixes is not None:
extra_params["includeFoldersAsPrefixes"] = include_folders_as_prefixes

if bucket.user_project is not None:
extra_params["userProject"] = bucket.user_project

Expand Down
36 changes: 36 additions & 0 deletions tests/system/test_bucket.py
Expand Up @@ -653,6 +653,42 @@ def test_bucket_list_blobs_w_match_glob(
assert [blob.name for blob in blobs] == expected_names


def test_bucket_list_blobs_include_managed_folders(
storage_client,
buckets_to_delete,
blobs_to_delete,
hierarchy_filenames,
):
bucket_name = _helpers.unique_name("ubla-mf")
bucket = storage_client.bucket(bucket_name)
bucket.iam_configuration.uniform_bucket_level_access_enabled = True
_helpers.retry_429_503(bucket.create)()
buckets_to_delete.append(bucket)

payload = b"helloworld"
for filename in hierarchy_filenames:
blob = bucket.blob(filename)
blob.upload_from_string(payload)
blobs_to_delete.append(blob)

# Make API call to create a managed folder.
# TODO: change to use storage control client once available.
path = f"/b/{bucket_name}/managedFolders"
properties = {"name": "managedfolder1"}
storage_client._post_resource(path, properties)

expected_prefixes = set(["parent/"])
blob_iter = bucket.list_blobs(delimiter="/")
list(blob_iter)
assert blob_iter.prefixes == expected_prefixes

# Test that managed folders are only included when IncludeFoldersAsPrefixes is set.
expected_prefixes = set(["parent/", "managedfolder1/"])
blob_iter = bucket.list_blobs(delimiter="/", include_folders_as_prefixes=True)
list(blob_iter)
assert blob_iter.prefixes == expected_prefixes


def test_bucket_update_retention_period(
storage_client,
buckets_to_delete,
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/test_bucket.py
Expand Up @@ -1143,6 +1143,7 @@ def test_list_blobs_w_defaults(self):
expected_versions = None
expected_projection = "noAcl"
expected_fields = None
expected_include_folders_as_prefixes = None
client.list_blobs.assert_called_once_with(
bucket,
max_results=expected_max_results,
Expand All @@ -1158,6 +1159,7 @@ def test_list_blobs_w_defaults(self):
timeout=self._get_default_timeout(),
retry=DEFAULT_RETRY,
match_glob=expected_match_glob,
include_folders_as_prefixes=expected_include_folders_as_prefixes,
)

def test_list_blobs_w_explicit(self):
Expand All @@ -1170,6 +1172,7 @@ def test_list_blobs_w_explicit(self):
start_offset = "c"
end_offset = "g"
include_trailing_delimiter = True
include_folders_as_prefixes = True
versions = True
projection = "full"
fields = "items/contentLanguage,nextPageToken"
Expand All @@ -1194,6 +1197,7 @@ def test_list_blobs_w_explicit(self):
timeout=timeout,
retry=retry,
match_glob=match_glob,
include_folders_as_prefixes=include_folders_as_prefixes,
)

self.assertIs(iterator, other_client.list_blobs.return_value)
Expand All @@ -1209,6 +1213,7 @@ def test_list_blobs_w_explicit(self):
expected_versions = versions
expected_projection = projection
expected_fields = fields
expected_include_folders_as_prefixes = include_folders_as_prefixes
other_client.list_blobs.assert_called_once_with(
bucket,
max_results=expected_max_results,
Expand All @@ -1224,6 +1229,7 @@ def test_list_blobs_w_explicit(self):
timeout=timeout,
retry=retry,
match_glob=expected_match_glob,
include_folders_as_prefixes=expected_include_folders_as_prefixes,
)

def test_list_notifications_w_defaults(self):
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/test_client.py
Expand Up @@ -2015,6 +2015,7 @@ def test_list_blobs_w_explicit_w_user_project(self):
start_offset = "c"
end_offset = "g"
include_trailing_delimiter = True
include_folders_as_prefixes = True
versions = True
projection = "full"
page_size = 2
Expand Down Expand Up @@ -2047,6 +2048,7 @@ def test_list_blobs_w_explicit_w_user_project(self):
timeout=timeout,
retry=retry,
match_glob=match_glob,
include_folders_as_prefixes=include_folders_as_prefixes,
)

self.assertIs(iterator, client._list_resource.return_value)
Expand All @@ -2068,6 +2070,7 @@ def test_list_blobs_w_explicit_w_user_project(self):
"versions": versions,
"fields": fields,
"userProject": user_project,
"includeFoldersAsPrefixes": include_folders_as_prefixes,
}
expected_page_start = _blobs_page_start
expected_page_size = 2
Expand Down