Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions storage/google/cloud/storage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"""Client for interacting with the Google Cloud Storage API."""


from google.auth.credentials import AnonymousCredentials

from google.api_core import page_iterator
from google.cloud._helpers import _LocalStack
from google.cloud.client import ClientWithProject
Expand Down Expand Up @@ -60,6 +62,22 @@ def __init__(self, project=None, credentials=None, _http=None):
self._connection = Connection(self)
self._batch_stack = _LocalStack()

@classmethod
def create_anonymous_client(cls):
"""Factory: return client with anonymous credentials.

.. note::

Such a client has only limited access to "public" buckets:
listing their contents and downloading their blobs.

:rtype: :class:`google.cloud.storage.client.Client`
:returns: Instance w/ anonymous credentials and no project.
"""
client = cls(project='<none>', credentials=AnonymousCredentials())
client.project = None
return client

@property
def _connection(self):
"""Get connection or batch on the client.
Expand Down
2 changes: 1 addition & 1 deletion storage/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
REQUIREMENTS = [
'google-cloud-core >= 0.28.0, < 0.29dev',
'google-api-core >= 0.1.1, < 0.2.0dev',
'google-auth >= 1.0.0',
'google-auth >= 1.2.0',

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

'google-resumable-media >= 0.3.1',
'requests >= 2.18.0',
]
Expand Down
12 changes: 12 additions & 0 deletions storage/tests/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -952,3 +952,15 @@ def test_notification_w_user_project(self):
self.assertEqual(notifications[0].topic_name, self.TOPIC_NAME)
finally:
notification.delete()


class TestAnonymousClient(unittest.TestCase):

PUBLIC_BUCKET = 'gcp-public-data-landsat'

def test_access_to_public_bucket(self):
anonymous = storage.Client.create_anonymous_client()
bucket = anonymous.bucket(self.PUBLIC_BUCKET)
blob, = bucket.list_blobs(max_results=1)
with tempfile.TemporaryFile() as stream:
blob.download_to_file(stream)
13 changes: 13 additions & 0 deletions storage/tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,25 @@ def test_ctor_connection_type(self):
CREDENTIALS = _make_credentials()

client = self._make_one(project=PROJECT, credentials=CREDENTIALS)

self.assertEqual(client.project, PROJECT)
self.assertIsInstance(client._connection, Connection)
self.assertIs(client._connection.credentials, CREDENTIALS)
self.assertIsNone(client.current_batch)
self.assertEqual(list(client._batch_stack), [])

def test_create_anonymous_client(self):
from google.auth.credentials import AnonymousCredentials
from google.cloud.storage._http import Connection

klass = self._get_target_class()
client = klass.create_anonymous_client()

self.assertIsNone(client.project)
self.assertIsInstance(client._connection, Connection)
self.assertIsInstance(
client._connection.credentials, AnonymousCredentials)

def test__push_batch_and__pop_batch(self):
from google.cloud.storage.batch import Batch

Expand Down