Skip to content

Commit c737b74

Browse files
Gurov Ilyatswast
Gurov Ilya
authored andcommitted
Add 'Client.list_blobs(bucket_or_name)'. (#8375)
* Union create-or-use bucket snippets into one helper * Deleted test_list_blobs - looks like it's a full copy of test_list_blobs_defaults * Add new method client.list_blobs that's the same as bucket.list_blobs() * Redact comment and delete unittest runner * Add deprecation for bucket.list_blobs * Changed warn to comment * Bring method docs to Google-style, use mock connection instead of class * Nit on docing Add backticks for sphinx
1 parent dc4834c commit c737b74

File tree

4 files changed

+187
-26
lines changed

4 files changed

+187
-26
lines changed

storage/google/cloud/storage/bucket.py

+3
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,9 @@ def list_blobs(
721721
):
722722
"""Return an iterator used to find blobs in the bucket.
723723
724+
.. note::
725+
Direct use of this method is deprecated. Use ``Client.list_blobs`` instead.
726+
724727
If :attr:`user_project` is set, bills the API request to that project.
725728
726729
:type max_results: int

storage/google/cloud/storage/client.py

+97-13
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,26 @@ def _pop_batch(self):
153153
"""
154154
return self._batch_stack.pop()
155155

156+
def _bucket_arg_to_bucket(self, bucket_or_name):
157+
"""Helper to return given bucket or create new by name.
158+
159+
Args:
160+
bucket_or_name (Union[ \
161+
:class:`~google.cloud.storage.bucket.Bucket`, \
162+
str, \
163+
]):
164+
The bucket resource to pass or name to create.
165+
166+
Returns:
167+
google.cloud.storage.bucket.Bucket
168+
The newly created bucket or the given one.
169+
"""
170+
if isinstance(bucket_or_name, Bucket):
171+
bucket = bucket_or_name
172+
else:
173+
bucket = Bucket(self, name=bucket_or_name)
174+
return bucket
175+
156176
@property
157177
def current_batch(self):
158178
"""Currently-active batch.
@@ -252,12 +272,7 @@ def get_bucket(self, bucket_or_name):
252272
>>> bucket = client.get_bucket(bucket) # API request.
253273
254274
"""
255-
256-
bucket = None
257-
if isinstance(bucket_or_name, Bucket):
258-
bucket = bucket_or_name
259-
else:
260-
bucket = Bucket(self, name=bucket_or_name)
275+
bucket = self._bucket_arg_to_bucket(bucket_or_name)
261276

262277
bucket.reload(client=self)
263278
return bucket
@@ -299,7 +314,7 @@ def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
299314
Optional. Whether requester pays for API requests for this
300315
bucket and its blobs.
301316
project (str):
302-
Optional. the project under which the bucket is to be created.
317+
Optional. the project under which the bucket is to be created.
303318
If not passed, uses the project set on the client.
304319
305320
Returns:
@@ -331,12 +346,7 @@ def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
331346
>>> bucket = client.create_bucket(bucket) # API request.
332347
333348
"""
334-
335-
bucket = None
336-
if isinstance(bucket_or_name, Bucket):
337-
bucket = bucket_or_name
338-
else:
339-
bucket = Bucket(self, name=bucket_or_name)
349+
bucket = self._bucket_arg_to_bucket(bucket_or_name)
340350

341351
if requester_pays is not None:
342352
bucket.requester_pays = requester_pays
@@ -394,6 +404,80 @@ def download_blob_to_file(self, blob_or_uri, file_obj, start=None, end=None):
394404

395405
blob_or_uri.download_to_file(file_obj, client=self, start=start, end=end)
396406

407+
def list_blobs(
408+
self,
409+
bucket_or_name,
410+
max_results=None,
411+
page_token=None,
412+
prefix=None,
413+
delimiter=None,
414+
versions=None,
415+
projection="noAcl",
416+
fields=None,
417+
):
418+
"""Return an iterator used to find blobs in the bucket.
419+
420+
If :attr:`user_project` is set, bills the API request to that project.
421+
422+
Args:
423+
bucket_or_name (Union[ \
424+
:class:`~google.cloud.storage.bucket.Bucket`, \
425+
str, \
426+
]):
427+
The bucket resource to pass or name to create.
428+
429+
max_results (int):
430+
(Optional) The maximum number of blobs in each page of results
431+
from this request. Non-positive values are ignored. Defaults to
432+
a sensible value set by the API.
433+
434+
page_token (str):
435+
(Optional) If present, return the next batch of blobs, using the
436+
value, which must correspond to the ``nextPageToken`` value
437+
returned in the previous response. Deprecated: use the ``pages``
438+
property of the returned iterator instead of manually passing the
439+
token.
440+
441+
prefix (str):
442+
(Optional) prefix used to filter blobs.
443+
444+
delimiter (str):
445+
(Optional) Delimiter, used with ``prefix`` to
446+
emulate hierarchy.
447+
448+
versions (bool):
449+
(Optional) Whether object versions should be returned
450+
as separate blobs.
451+
452+
projection (str):
453+
(Optional) If used, must be 'full' or 'noAcl'.
454+
Defaults to ``'noAcl'``. Specifies the set of
455+
properties to return.
456+
457+
fields (str):
458+
(Optional) Selector specifying which fields to include
459+
in a partial response. Must be a list of fields. For
460+
example to get a partial response with just the next
461+
page token and the name and language of each blob returned:
462+
``'items(name,contentLanguage),nextPageToken'``.
463+
See: https://cloud.google.com/storage/docs/json_api/v1/parameters#fields
464+
465+
Returns:
466+
Iterator of all :class:`~google.cloud.storage.blob.Blob`
467+
in this bucket matching the arguments.
468+
"""
469+
bucket = self._bucket_arg_to_bucket(bucket_or_name)
470+
return bucket.list_blobs(
471+
max_results=max_results,
472+
page_token=page_token,
473+
prefix=prefix,
474+
delimiter=delimiter,
475+
versions=versions,
476+
projection=projection,
477+
fields=fields,
478+
client=self
479+
)
480+
397481
def list_buckets(
398482
self,
399483
max_results=None,

storage/tests/unit/test_bucket.py

-13
Original file line numberDiff line numberDiff line change
@@ -782,19 +782,6 @@ def test_list_blobs_w_all_arguments_and_user_project(self):
782782
self.assertEqual(kw["path"], "/b/%s/o" % NAME)
783783
self.assertEqual(kw["query_params"], EXPECTED)
784784

785-
def test_list_blobs(self):
786-
NAME = "name"
787-
connection = _Connection({"items": []})
788-
client = _Client(connection)
789-
bucket = self._make_one(client=client, name=NAME)
790-
iterator = bucket.list_blobs()
791-
blobs = list(iterator)
792-
self.assertEqual(blobs, [])
793-
kw, = connection._requested
794-
self.assertEqual(kw["method"], "GET")
795-
self.assertEqual(kw["path"], "/b/%s/o" % NAME)
796-
self.assertEqual(kw["query_params"], {"projection": "noAcl"})
797-
798785
def test_list_notifications(self):
799786
from google.cloud.storage.notification import BucketNotification
800787
from google.cloud.storage.notification import _TOPIC_REF_FMT

storage/tests/unit/test_client.py

+87
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ def _make_credentials():
2828
return mock.Mock(spec=google.auth.credentials.Credentials)
2929

3030

31+
def _make_connection(*responses):
32+
import google.cloud.storage._http
33+
from google.cloud.exceptions import NotFound
34+
35+
mock_conn = mock.create_autospec(google.cloud.storage._http.Connection)
36+
mock_conn.user_agent = "testing 1.2.3"
37+
mock_conn.api_request.side_effect = list(responses) + [NotFound("miss")]
38+
return mock_conn
39+
40+
3141
def _make_response(status=http_client.OK, content=b"", headers={}):
3242
response = requests.Response()
3343
response.status_code = status
@@ -639,6 +649,83 @@ def test_download_blob_to_file_with_invalid_uri(self):
639649
"http://bucket_name/path/to/object", file_obj
640650
)
641651

652+
def test_list_blobs(self):
653+
from google.cloud.storage.bucket import Bucket
654+
BUCKET_NAME = "bucket-name"
655+
656+
credentials = _make_credentials()
657+
client = self._make_one(project="PROJECT", credentials=credentials)
658+
connection = _make_connection({"items": []})
659+
660+
with mock.patch(
661+
'google.cloud.storage.client.Client._connection',
662+
new_callable=mock.PropertyMock
663+
) as client_mock:
664+
client_mock.return_value = connection
665+
666+
bucket_obj = Bucket(client, BUCKET_NAME)
667+
iterator = client.list_blobs(bucket_obj)
668+
blobs = list(iterator)
669+
670+
self.assertEqual(blobs, [])
671+
connection.api_request.assert_called_once_with(
672+
method="GET",
673+
path="/b/%s/o" % BUCKET_NAME,
674+
query_params={"projection": "noAcl"}
675+
)
676+
677+
def test_list_blobs_w_all_arguments_and_user_project(self):
678+
from google.cloud.storage.bucket import Bucket
679+
BUCKET_NAME = "name"
680+
USER_PROJECT = "user-project-123"
681+
MAX_RESULTS = 10
682+
PAGE_TOKEN = "ABCD"
683+
PREFIX = "subfolder"
684+
DELIMITER = "/"
685+
VERSIONS = True
686+
PROJECTION = "full"
687+
FIELDS = "items/contentLanguage,nextPageToken"
688+
EXPECTED = {
689+
"maxResults": 10,
690+
"pageToken": PAGE_TOKEN,
691+
"prefix": PREFIX,
692+
"delimiter": DELIMITER,
693+
"versions": VERSIONS,
694+
"projection": PROJECTION,
695+
"fields": FIELDS,
696+
"userProject": USER_PROJECT,
697+
}
698+
699+
credentials = _make_credentials()
700+
client = self._make_one(project=USER_PROJECT, credentials=credentials)
701+
connection = _make_connection({"items": []})
702+
703+
with mock.patch(
704+
'google.cloud.storage.client.Client._connection',
705+
new_callable=mock.PropertyMock
706+
) as client_mock:
707+
client_mock.return_value = connection
708+
709+
bucket = Bucket(client, BUCKET_NAME, user_project=USER_PROJECT)
710+
iterator = client.list_blobs(
711+
bucket_or_name=bucket,
712+
max_results=MAX_RESULTS,
713+
page_token=PAGE_TOKEN,
714+
prefix=PREFIX,
715+
delimiter=DELIMITER,
716+
versions=VERSIONS,
717+
projection=PROJECTION,
718+
fields=FIELDS,
719+
)
720+
blobs = list(iterator)
721+
722+
self.assertEqual(blobs, [])
723+
connection.api_request.assert_called_once_with(
724+
method="GET",
725+
path="/b/%s/o" % BUCKET_NAME,
726+
query_params=EXPECTED
727+
)
728+
642729
def test_list_buckets_wo_project(self):
643730
CREDENTIALS = _make_credentials()
644731
client = self._make_one(project=None, credentials=CREDENTIALS)

0 commit comments

Comments
 (0)