# Copyright 2015 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.
"""Client for interacting with the Google Cloud Storage API."""
from six.moves.urllib.parse import urlsplit
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
from google.cloud.exceptions import NotFound
from google.cloud.storage._http import Connection
from google.cloud.storage.batch import Batch
from google.cloud.storage.bucket import Bucket
from google.cloud.storage.blob import Blob
_marker = object()
[docs]class Client(ClientWithProject):
"""Client to bundle configuration needed for API requests.
:type project: str or None
:param project: the project which the client acts on behalf of. Will be
passed when creating a topic. If not passed,
falls back to the default inferred from the environment.
:type credentials: :class:`~google.auth.credentials.Credentials`
:param credentials: (Optional) The OAuth2 Credentials to use for this
client. If not passed (and if no ``_http`` object is
passed), falls back to the default inferred from the
environment.
:type _http: :class:`~requests.Session`
:param _http: (Optional) HTTP object to make requests. Can be any object
that defines ``request()`` with the same interface as
:meth:`requests.Session.request`. If not passed, an
``_http`` object is created that is bound to the
``credentials`` for the current object.
This parameter should be considered private, and could
change in the future.
:type client_info: :class:`~google.api_core.client_info.ClientInfo`
:param client_info:
The client info used to send a user-agent string along with API
requests. If ``None``, then default info will be used. Generally,
you only need to set this if you're developing your own library
or partner tool.
"""
SCOPE = (
"https://www.googleapis.com/auth/devstorage.full_control",
"https://www.googleapis.com/auth/devstorage.read_only",
"https://www.googleapis.com/auth/devstorage.read_write",
)
"""The scopes required for authenticating as a Cloud Storage consumer."""
def __init__(self, project=_marker, credentials=None, _http=None, client_info=None):
self._base_connection = None
if project is None:
no_project = True
project = "<none>"
else:
no_project = False
if project is _marker:
project = None
super(Client, self).__init__(
project=project, credentials=credentials, _http=_http
)
if no_project:
self.project = None
self._connection = Connection(self, client_info=client_info)
self._batch_stack = _LocalStack()
[docs] @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.
:rtype: :class:`google.cloud.storage._http.Connection`
:returns: The connection set on the client, or the batch
if one is set.
"""
if self.current_batch is not None:
return self.current_batch
else:
return self._base_connection
@_connection.setter
def _connection(self, value):
"""Set connection on the client.
Intended to be used by constructor (since the base class calls)
self._connection = connection
Will raise if the connection is set more than once.
:type value: :class:`google.cloud.storage._http.Connection`
:param value: The connection set on the client.
:raises: :class:`ValueError` if connection has already been set.
"""
if self._base_connection is not None:
raise ValueError("Connection already set on client")
self._base_connection = value
def _push_batch(self, batch):
"""Push a batch onto our stack.
"Protected", intended for use by batch context mgrs.
:type batch: :class:`google.cloud.storage.batch.Batch`
:param batch: newly-active batch
"""
self._batch_stack.push(batch)
def _pop_batch(self):
"""Pop a batch from our stack.
"Protected", intended for use by batch context mgrs.
:raises: IndexError if the stack is empty.
:rtype: :class:`google.cloud.storage.batch.Batch`
:returns: the top-most batch/transaction, after removing it.
"""
return self._batch_stack.pop()
def _bucket_arg_to_bucket(self, bucket_or_name):
"""Helper to return given bucket or create new by name.
Args:
bucket_or_name (Union[ \
:class:`~google.cloud.storage.bucket.Bucket`, \
str, \
]):
The bucket resource to pass or name to create.
Returns:
google.cloud.storage.bucket.Bucket
The newly created bucket or the given one.
"""
if isinstance(bucket_or_name, Bucket):
bucket = bucket_or_name
else:
bucket = Bucket(self, name=bucket_or_name)
return bucket
@property
def current_batch(self):
"""Currently-active batch.
:rtype: :class:`google.cloud.storage.batch.Batch` or ``NoneType`` (if
no batch is active).
:returns: The batch at the top of the batch stack.
"""
return self._batch_stack.top
[docs] def get_service_account_email(self, project=None):
"""Get the email address of the project's GCS service account
:type project: str
:param project:
(Optional) Project ID to use for retreiving GCS service account
email address. Defaults to the client's project.
:rtype: str
:returns: service account email address
"""
if project is None:
project = self.project
path = "/projects/%s/serviceAccount" % (project,)
api_response = self._base_connection.api_request(method="GET", path=path)
return api_response["email_address"]
[docs] def bucket(self, bucket_name, user_project=None):
"""Factory constructor for bucket object.
.. note::
This will not make an HTTP request; it simply instantiates
a bucket object owned by this client.
:type bucket_name: str
:param bucket_name: The name of the bucket to be instantiated.
:type user_project: str
:param user_project: (Optional) the project ID to be billed for API
requests made via the bucket.
:rtype: :class:`google.cloud.storage.bucket.Bucket`
:returns: The bucket object created.
"""
return Bucket(client=self, name=bucket_name, user_project=user_project)
[docs] def batch(self):
"""Factory constructor for batch object.
.. note::
This will not make an HTTP request; it simply instantiates
a batch object owned by this client.
:rtype: :class:`google.cloud.storage.batch.Batch`
:returns: The batch object created.
"""
return Batch(client=self)
[docs] def get_bucket(self, bucket_or_name):
"""API call: retrieve a bucket via a GET request.
See
https://cloud.google.com/storage/docs/json_api/v1/buckets/get
Args:
bucket_or_name (Union[ \
:class:`~google.cloud.storage.bucket.Bucket`, \
str, \
]):
The bucket resource to pass or name to create.
Returns:
google.cloud.storage.bucket.Bucket
The bucket matching the name provided.
Raises:
google.cloud.exceptions.NotFound
If the bucket is not found.
Examples:
Retrieve a bucket using a string.
.. literalinclude:: snippets.py
:start-after: [START get_bucket]
:end-before: [END get_bucket]
Get a bucket using a resource.
>>> from google.cloud import storage
>>> client = storage.Client()
>>> # Set properties on a plain resource object.
>>> bucket = client.get_bucket("my-bucket-name")
>>> # Time passes. Another program may have modified the bucket
... # in the meantime, so you want to get the latest state.
>>> bucket = client.get_bucket(bucket) # API request.
"""
bucket = self._bucket_arg_to_bucket(bucket_or_name)
bucket.reload(client=self)
return bucket
[docs] def lookup_bucket(self, bucket_name):
"""Get a bucket by name, returning None if not found.
You can use this if you would rather check for a None value
than catching an exception:
.. literalinclude:: snippets.py
:start-after: [START lookup_bucket]
:end-before: [END lookup_bucket]
:type bucket_name: str
:param bucket_name: The name of the bucket to get.
:rtype: :class:`google.cloud.storage.bucket.Bucket`
:returns: The bucket matching the name provided or None if not found.
"""
try:
return self.get_bucket(bucket_name)
except NotFound:
return None
[docs] def create_bucket(self, bucket_or_name, requester_pays=None, project=None):
"""API call: create a new bucket via a POST request.
See
https://cloud.google.com/storage/docs/json_api/v1/buckets/insert
Args:
bucket_or_name (Union[ \
:class:`~google.cloud.storage.bucket.Bucket`, \
str, \
]):
The bucket resource to pass or name to create.
requester_pays (bool):
Optional. Whether requester pays for API requests for this
bucket and its blobs.
project (str):
Optional. the project under which the bucket is to be created.
If not passed, uses the project set on the client.
Returns:
google.cloud.storage.bucket.Bucket
The newly created bucket.
Raises:
google.cloud.exceptions.Conflict
If the bucket already exists.
Examples:
Create a bucket using a string.
.. literalinclude:: snippets.py
:start-after: [START create_bucket]
:end-before: [END create_bucket]
Create a bucket using a resource.
>>> from google.cloud import storage
>>> client = storage.Client()
>>> # Set properties on a plain resource object.
>>> bucket = storage.Bucket("my-bucket-name")
>>> bucket.location = "europe-west6"
>>> bucket.storage_class = "COLDLINE"
>>> # Pass that resource object to the client.
>>> bucket = client.create_bucket(bucket) # API request.
"""
bucket = self._bucket_arg_to_bucket(bucket_or_name)
if requester_pays is not None:
bucket.requester_pays = requester_pays
bucket.create(client=self, project=project)
return bucket
[docs] def download_blob_to_file(self, blob_or_uri, file_obj, start=None, end=None):
"""Download the contents of a blob object or blob URI into a file-like object.
Args:
blob_or_uri (Union[ \
:class:`~google.cloud.storage.blob.Blob`, \
str, \
]):
The blob resource to pass or URI to download.
file_obj (file):
A file handle to which to write the blob's data.
start (int):
Optional. The first byte in a range to be downloaded.
end (int):
Optional. The last byte in a range to be downloaded.
Examples:
Download a blob using using a blob resource.
>>> from google.cloud import storage
>>> client = storage.Client()
>>> bucket = client.get_bucket('my-bucket-name')
>>> blob = storage.Blob('path/to/blob', bucket)
>>> with open('file-to-download-to') as file_obj:
>>> client.download_blob_to_file(blob, file_obj) # API request.
Download a blob using a URI.
>>> from google.cloud import storage
>>> client = storage.Client()
>>> with open('file-to-download-to') as file_obj:
>>> client.download_blob_to_file(
>>> 'gs://bucket_name/path/to/blob', file_obj)
"""
try:
blob_or_uri.download_to_file(file_obj, client=self, start=start, end=end)
except AttributeError:
scheme, netloc, path, query, frag = urlsplit(blob_or_uri)
if scheme != "gs":
raise ValueError("URI scheme must be gs")
bucket = Bucket(self, name=netloc)
blob_or_uri = Blob(path[1:], bucket)
blob_or_uri.download_to_file(file_obj, client=self, start=start, end=end)
[docs] def list_blobs(
self,
bucket_or_name,
max_results=None,
page_token=None,
prefix=None,
delimiter=None,
versions=None,
projection="noAcl",
fields=None,
):
"""Return an iterator used to find blobs in the bucket.
If :attr:`user_project` is set, bills the API request to that project.
Args:
bucket_or_name (Union[ \
:class:`~google.cloud.storage.bucket.Bucket`, \
str, \
]):
The bucket resource to pass or name to create.
max_results (int):
(Optional) The maximum number of blobs in each page of results
from this request. Non-positive values are ignored. Defaults to
a sensible value set by the API.
page_token (str):
(Optional) If present, return the next batch of blobs, using the
value, which must correspond to the ``nextPageToken`` value
returned in the previous response. Deprecated: use the ``pages``
property of the returned iterator instead of manually passing the
token.
prefix (str):
(Optional) prefix used to filter blobs.
delimiter (str):
(Optional) Delimiter, used with ``prefix`` to
emulate hierarchy.
versions (bool):
(Optional) Whether object versions should be returned
as separate blobs.
projection (str):
(Optional) If used, must be 'full' or 'noAcl'.
Defaults to ``'noAcl'``. Specifies the set of
properties to return.
fields (str):
(Optional) Selector specifying which fields to include
in a partial response. Must be a list of fields. For
example to get a partial response with just the next
page token and the name and language of each blob returned:
``'items(name,contentLanguage),nextPageToken'``.
See: https://cloud.google.com/storage/docs/json_api/v1/parameters#fields
Returns:
Iterator of all :class:`~google.cloud.storage.blob.Blob`
in this bucket matching the arguments.
"""
bucket = self._bucket_arg_to_bucket(bucket_or_name)
return bucket.list_blobs(
max_results=max_results,
page_token=page_token,
prefix=prefix,
delimiter=delimiter,
versions=versions,
projection=projection,
fields=fields,
client=self
)
[docs] def list_buckets(
self,
max_results=None,
page_token=None,
prefix=None,
projection="noAcl",
fields=None,
project=None,
):
"""Get all buckets in the project associated to the client.
This will not populate the list of blobs available in each
bucket.
.. literalinclude:: snippets.py
:start-after: [START list_buckets]
:end-before: [END list_buckets]
This implements "storage.buckets.list".
:type max_results: int
:param max_results: Optional. The maximum number of buckets to return.
:type page_token: str
:param page_token:
Optional. If present, return the next batch of buckets, using the
value, which must correspond to the ``nextPageToken`` value
returned in the previous response. Deprecated: use the ``pages``
property of the returned iterator instead of manually passing the
token.
:type prefix: str
:param prefix: Optional. Filter results to buckets whose names begin
with this prefix.
:type projection: str
:param projection:
(Optional) Specifies the set of properties to return. If used, must
be 'full' or 'noAcl'. Defaults to 'noAcl'.
:type fields: str
:param fields:
(Optional) Selector specifying which fields to include in a partial
response. Must be a list of fields. For example to get a partial
response with just the next page token and the language of each
bucket returned: 'items/id,nextPageToken'
:type project: str
:param project: (Optional) the project whose buckets are to be listed.
If not passed, uses the project set on the client.
:rtype: :class:`~google.api_core.page_iterator.Iterator`
:raises ValueError: if both ``project`` is ``None`` and the client's
project is also ``None``.
:returns: Iterator of all :class:`~google.cloud.storage.bucket.Bucket`
belonging to this project.
"""
if project is None:
project = self.project
if project is None:
raise ValueError("Client project not set: pass an explicit project.")
extra_params = {"project": project}
if prefix is not None:
extra_params["prefix"] = prefix
extra_params["projection"] = projection
if fields is not None:
extra_params["fields"] = fields
return page_iterator.HTTPIterator(
client=self,
api_request=self._connection.api_request,
path="/b",
item_to_value=_item_to_bucket,
page_token=page_token,
max_results=max_results,
extra_params=extra_params,
)
def _item_to_bucket(iterator, item):
"""Convert a JSON bucket to the native object.
:type iterator: :class:`~google.api_core.page_iterator.Iterator`
:param iterator: The iterator that has retrieved the item.
:type item: dict
:param item: An item to be converted to a bucket.
:rtype: :class:`.Bucket`
:returns: The next bucket in the page.
"""
name = item.get("name")
bucket = Bucket(iterator.client, name)
bucket._set_properties(item)
return bucket