Skip to content

Commit

Permalink
refactor: add / use 'Client._patch_resource' method (#436)
Browse files Browse the repository at this point in the history
Add retry support to 'ACL.save', 'ACL.save_predefined', and 'ACL.clear'.

Add 'timeout' and 'retry' support to 'Blob.make_private'.

Toward #38.
  • Loading branch information
tseaver authored Jun 7, 2021
1 parent e0f1b71 commit e96f852
Show file tree
Hide file tree
Showing 10 changed files with 936 additions and 497 deletions.
7 changes: 3 additions & 4 deletions google/cloud/storage/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,9 @@ def patch(
update_properties = {key: self._properties[key] for key in self._changes}

# Make the API call.
api_response = client._connection.api_request(
method="PATCH",
path=self.path,
data=update_properties,
api_response = client._patch_resource(
self.path,
update_properties,
query_params=query_params,
_target_object=self,
timeout=timeout,
Expand Down
70 changes: 58 additions & 12 deletions google/cloud/storage/acl.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,9 @@ def reload(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
for entry in found.get("items", ()):
self.add_entity(self.entity_from_dict(entry))

def _save(self, acl, predefined, client, timeout=_DEFAULT_TIMEOUT):
def _save(
self, acl, predefined, client, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY,
):
"""Helper for :meth:`save` and :meth:`save_predefined`.
:type acl: :class:`google.cloud.storage.acl.ACL`, or a compatible list.
Expand All @@ -495,8 +497,19 @@ def _save(self, acl, predefined, client, timeout=_DEFAULT_TIMEOUT):
Can also be passed as a tuple (connect_timeout, read_timeout).
See :meth:`requests.Session.request` documentation for details.
:type retry: :class:`~google.api_core.retry.Retry`
:param retry: (Optional) How to retry the RPC.
A None value will disable retries.
A google.api_core.retry.Retry value will enable retries,
and the object will define retriable response codes and errors
and configure backoff and timeout options.
"""
client = self._require_client(client)
query_params = {"projection": "full"}

if predefined is not None:
acl = []
query_params[self._PREDEFINED_QUERY_PARAM] = predefined
Expand All @@ -505,21 +518,25 @@ def _save(self, acl, predefined, client, timeout=_DEFAULT_TIMEOUT):
query_params["userProject"] = self.user_project

path = self.save_path
client = self._require_client(client)

result = client._connection.api_request(
method="PATCH",
path=path,
data={self._URL_PATH_ELEM: list(acl)},
result = client._patch_resource(
path,
{self._URL_PATH_ELEM: list(acl)},
query_params=query_params,
timeout=timeout,
retry=retry,
)

self.entities.clear()

for entry in result.get(self._URL_PATH_ELEM, ()):
self.add_entity(self.entity_from_dict(entry))

self.loaded = True

def save(self, acl=None, client=None, timeout=_DEFAULT_TIMEOUT):
def save(
self, acl=None, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY
):
"""Save this ACL for the current bucket.
If :attr:`user_project` is set, bills the API request to that project.
Expand All @@ -538,6 +555,15 @@ def save(self, acl=None, client=None, timeout=_DEFAULT_TIMEOUT):
Can also be passed as a tuple (connect_timeout, read_timeout).
See :meth:`requests.Session.request` documentation for details.
:type retry: :class:`~google.api_core.retry.Retry`
:param retry: (Optional) How to retry the RPC.
A None value will disable retries.
A google.api_core.retry.Retry value will enable retries,
and the object will define retriable response codes and errors
and configure backoff and timeout options.
"""
if acl is None:
acl = self
Expand All @@ -546,9 +572,11 @@ def save(self, acl=None, client=None, timeout=_DEFAULT_TIMEOUT):
save_to_backend = True

if save_to_backend:
self._save(acl, None, client, timeout=timeout)
self._save(acl, None, client, timeout=timeout, retry=retry)

def save_predefined(self, predefined, client=None, timeout=_DEFAULT_TIMEOUT):
def save_predefined(
self, predefined, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY,
):
"""Save this ACL for the current bucket using a predefined ACL.
If :attr:`user_project` is set, bills the API request to that project.
Expand All @@ -570,11 +598,20 @@ def save_predefined(self, predefined, client=None, timeout=_DEFAULT_TIMEOUT):
Can also be passed as a tuple (connect_timeout, read_timeout).
See :meth:`requests.Session.request` documentation for details.
:type retry: :class:`~google.api_core.retry.Retry`
:param retry: (Optional) How to retry the RPC.
A None value will disable retries.
A google.api_core.retry.Retry value will enable retries,
and the object will define retriable response codes and errors
and configure backoff and timeout options.
"""
predefined = self.validate_predefined(predefined)
self._save(None, predefined, client, timeout=timeout)
self._save(None, predefined, client, timeout=timeout, retry=retry)

def clear(self, client=None, timeout=_DEFAULT_TIMEOUT):
def clear(self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):
"""Remove all ACL entries.
If :attr:`user_project` is set, bills the API request to that project.
Expand All @@ -594,8 +631,17 @@ def clear(self, client=None, timeout=_DEFAULT_TIMEOUT):
Can also be passed as a tuple (connect_timeout, read_timeout).
See :meth:`requests.Session.request` documentation for details.
:type retry: :class:`~google.api_core.retry.Retry`
:param retry: (Optional) How to retry the RPC.
A None value will disable retries.
A google.api_core.retry.Retry value will enable retries,
and the object will define retriable response codes and errors
and configure backoff and timeout options.
"""
self.save([], client=client, timeout=timeout)
self.save([], client=client, timeout=timeout, retry=retry)


class BucketACL(ACL):
Expand Down
56 changes: 52 additions & 4 deletions google/cloud/storage/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -2978,27 +2978,75 @@ def test_iam_permissions(

return resp.get("permissions", [])

def make_public(self, client=None):
def make_public(
self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY,
):
"""Update blob's ACL, granting read access to anonymous users.
:type client: :class:`~google.cloud.storage.client.Client` or
``NoneType``
:param client: (Optional) The client to use. If not passed, falls back
to the ``client`` stored on the blob's bucket.
:type timeout: float or tuple
:param timeout: (Optional) The amount of time, in seconds, to wait
for the server response. The timeout applies to each underlying
request.
Can also be passed as a tuple (connect_timeout, read_timeout).
See :meth:`requests.Session.request` documentation for details.
:type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
:param retry: (Optional) How to retry the RPC. A None value will disable retries.
A google.api_core.retry.Retry value will enable retries, and the object will
define retriable response codes and errors and configure backoff and timeout options.
A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a Retry object and
activates it only if certain conditions are met. This class exists to provide safe defaults
for RPC calls that are not technically safe to retry normally (due to potential data
duplication or other side-effects) but become safe to retry if a condition such as
if_metageneration_match is set.
See the retry.py source code and docstrings in this package (google.cloud.storage.retry) for
information on retry types and how to configure them.
"""
self.acl.all().grant_read()
self.acl.save(client=client)
self.acl.save(client=client, timeout=timeout, retry=retry)

def make_private(self, client=None):
def make_private(
self, client=None, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY,
):
"""Update blob's ACL, revoking read access for anonymous users.
:type client: :class:`~google.cloud.storage.client.Client` or
``NoneType``
:param client: (Optional) The client to use. If not passed, falls back
to the ``client`` stored on the blob's bucket.
:type timeout: float or tuple
:param timeout: (Optional) The amount of time, in seconds, to wait
for the server response. The timeout applies to each underlying
request.
Can also be passed as a tuple (connect_timeout, read_timeout).
See :meth:`requests.Session.request` documentation for details.
:type retry: google.api_core.retry.Retry or google.cloud.storage.retry.ConditionalRetryPolicy
:param retry: (Optional) How to retry the RPC. A None value will disable retries.
A google.api_core.retry.Retry value will enable retries, and the object will
define retriable response codes and errors and configure backoff and timeout options.
A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a Retry object and
activates it only if certain conditions are met. This class exists to provide safe defaults
for RPC calls that are not technically safe to retry normally (due to potential data
duplication or other side-effects) but become safe to retry if a condition such as
if_metageneration_match is set.
See the retry.py source code and docstrings in this package (google.cloud.storage.retry) for
information on retry types and how to configure them.
"""
self.acl.all().revoke_read()
self.acl.save(client=client)
self.acl.save(client=client, timeout=timeout, retry=retry)

def compose(
self,
Expand Down
12 changes: 6 additions & 6 deletions google/cloud/storage/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -1065,9 +1065,9 @@ def patch(
# Call the superclass method.
super(Bucket, self).patch(
client=client,
timeout=timeout,
if_metageneration_match=if_metageneration_match,
if_metageneration_not_match=if_metageneration_not_match,
timeout=timeout,
retry=retry,
)

Expand Down Expand Up @@ -1993,7 +1993,7 @@ def copy_blob(
)

if not preserve_acl:
new_blob.acl.save(acl={}, client=client, timeout=timeout)
new_blob.acl.save(acl={}, client=client, timeout=timeout, retry=retry)

new_blob._set_properties(copy_result)
return new_blob
Expand Down Expand Up @@ -3068,7 +3068,7 @@ def make_public(
for each blob.
"""
self.acl.all().grant_read()
self.acl.save(client=client, timeout=timeout)
self.acl.save(client=client, timeout=timeout, retry=retry)

if future:
doa = self.default_object_acl
Expand Down Expand Up @@ -3099,7 +3099,7 @@ def make_public(

for blob in blobs:
blob.acl.all().grant_read()
blob.acl.save(client=client, timeout=timeout)
blob.acl.save(client=client, timeout=timeout, retry=retry)

def make_private(
self,
Expand Down Expand Up @@ -3155,7 +3155,7 @@ def make_private(
for each blob.
"""
self.acl.all().revoke_read()
self.acl.save(client=client, timeout=timeout)
self.acl.save(client=client, timeout=timeout, retry=retry)

if future:
doa = self.default_object_acl
Expand Down Expand Up @@ -3186,7 +3186,7 @@ def make_private(

for blob in blobs:
blob.acl.all().revoke_read()
blob.acl.save(client=client, timeout=timeout)
blob.acl.save(client=client, timeout=timeout, retry=retry)

def generate_upload_policy(self, conditions, expiration=None, client=None):
"""Create a signed upload policy for uploading objects.
Expand Down
71 changes: 71 additions & 0 deletions google/cloud/storage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,77 @@ def _get_resource(
_target_object=_target_object,
)

def _patch_resource(
self,
path,
data,
query_params=None,
headers=None,
timeout=_DEFAULT_TIMEOUT,
retry=DEFAULT_RETRY,
_target_object=None,
):
"""Helper for bucket / blob methods making API 'PATCH' calls.
Args:
path str:
The path of the resource to fetch.
data dict:
The data to be patched.
query_params Optional[dict]:
HTTP query parameters to be passed
headers Optional[dict]:
HTTP headers to be passed
timeout (Optional[Union[float, Tuple[float, float]]]):
The amount of time, in seconds, to wait for the server response.
Can also be passed as a tuple (connect_timeout, read_timeout).
See :meth:`requests.Session.request` documentation for details.
retry (Optional[Union[google.api_core.retry.Retry, google.cloud.storage.retry.ConditionalRetryPolicy]]):
How to retry the RPC. A None value will disable retries.
A google.api_core.retry.Retry value will enable retries, and the object will
define retriable response codes and errors and configure backoff and timeout options.
A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a Retry object and
activates it only if certain conditions are met. This class exists to provide safe defaults
for RPC calls that are not technically safe to retry normally (due to potential data
duplication or other side-effects) but become safe to retry if a condition such as
if_metageneration_match is set.
See the retry.py source code and docstrings in this package (google.cloud.storage.retry) for
information on retry types and how to configure them.
_target_object (Union[ \
:class:`~google.cloud.storage.bucket.Bucket`, \
:class:`~google.cloud.storage.bucket.blob`, \
]):
Object to which future data is to be applied -- only relevant
in the context of a batch.
Returns:
dict
The JSON resource fetched
Raises:
google.cloud.exceptions.NotFound
If the bucket is not found.
"""
return self._connection.api_request(
method="PATCH",
path=path,
data=data,
query_params=query_params,
headers=headers,
timeout=timeout,
retry=retry,
_target_object=_target_object,
)

def get_bucket(
self,
bucket_or_name,
Expand Down
Loading

0 comments on commit e96f852

Please sign in to comment.