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: add if*generation*match args into Bucket.delete_blobs() #130

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3fa6245
feat: add ifMetageneration*Match support, pt1
May 1, 2020
4cbad43
fix unit tests, add test for helper
May 1, 2020
b905487
fix unit tests
May 1, 2020
ddecf53
add generation match args into more methods
May 1, 2020
74961fa
feat: add if*generation*Match support, pt2
May 4, 2020
7e08361
Lint fix.
May 4, 2020
6f9f910
delete "more than one set "checks
May 5, 2020
ff26d2f
del excess import
May 5, 2020
551df43
Merge branch 'metageneration_match_pt1' into metageneration_match_pt2
May 5, 2020
1f4d9c1
delete "more than one set" checks
May 5, 2020
42fd268
feat: add if*generation*match args into Bucket.delete_blobs()
May 5, 2020
0a34086
Merge branch 'master' into metageneration_match_pt1
frankyn May 5, 2020
6deb7ba
feat: add helper for bucket bound hostname URLs
May 7, 2020
ae516da
Revert "feat: add helper for bucket bound hostname URLs"
May 7, 2020
8ecf93a
lint fix
May 7, 2020
e1b67b5
Merge branch 'master' into metageneration_match_pt1
May 12, 2020
bd5b54f
rename the helper; add error raising in case of wront parameters type
May 12, 2020
ea37318
Merge branch 'metageneration_match_pt2' into metageneration_match_pt1
May 12, 2020
9d19e15
Merge branch 'metageneration_match_pt1' of https://github.com/q-logic…
May 12, 2020
29c435e
Merge branch 'metageneration_match_pt1' into delete_blobs_generation_…
May 12, 2020
d8ca98d
Merge branch 'master' into delete_blobs_generation_match
May 21, 2020
fe0bb8c
add args length check, add more unit tests
May 21, 2020
fabbfbf
erase empty line left from conflict resolving
May 21, 2020
f0190bd
add arg name
Jun 3, 2020
90da4de
Merge branch 'master' into delete_blobs_generation_match
frankyn Jun 9, 2020
5f696bf
Merge branch 'master' into delete_blobs_generation_match
frankyn Jun 15, 2020
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
171 changes: 165 additions & 6 deletions google/cloud/storage/_helpers.py
Expand Up @@ -30,6 +30,18 @@

_DEFAULT_STORAGE_HOST = u"https://storage.googleapis.com"

# generation match parameters in camel and snake cases
_GENERATION_MATCH_PARAMETERS = (
("if_generation_match", "ifGenerationMatch"),
("if_generation_not_match", "ifGenerationNotMatch"),
("if_metageneration_match", "ifMetagenerationMatch"),
("if_metageneration_not_match", "ifMetagenerationNotMatch"),
("if_source_generation_match", "ifSourceGenerationMatch"),
("if_source_generation_not_match", "ifSourceGenerationNotMatch"),
("if_source_metageneration_match", "ifSourceMetagenerationMatch"),
("if_source_metageneration_not_match", "ifSourceMetagenerationNotMatch"),
)


def _get_storage_host():
return os.environ.get(STORAGE_EMULATOR_ENV_VAR, _DEFAULT_STORAGE_HOST)
Expand Down Expand Up @@ -121,27 +133,64 @@ def _query_params(self):
params["userProject"] = self.user_project
return params

def reload(self, client=None, timeout=_DEFAULT_TIMEOUT):
def reload(
self,
client=None,
timeout=_DEFAULT_TIMEOUT,
if_generation_match=None,
if_generation_not_match=None,
if_metageneration_match=None,
if_metageneration_not_match=None,
):
"""Reload properties from Cloud Storage.

If :attr:`user_project` is set, bills the API request to that project.

:type client: :class:`~google.cloud.storage.client.Client` or
``NoneType``
:param client: the client to use. If not passed, falls back to the
:param client: the client to use. If not passed, falls back to the
``client`` stored on the current object.

:type timeout: float or tuple
:param timeout: (Optional) 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.

:type if_generation_match: long
:param if_generation_match: (Optional) Make the operation conditional on whether
the blob's current generation matches the given value.
Setting to 0 makes the operation succeed only if there
are no live versions of the blob.

:type if_generation_not_match: long
:param if_generation_not_match: (Optional) Make the operation conditional on whether
the blob's current generation does not match the given
value. If no live blob exists, the precondition fails.
Setting to 0 makes the operation succeed only if there
is a live version of the blob.

:type if_metageneration_match: long
:param if_metageneration_match: (Optional) Make the operation conditional on whether the
blob's current metageneration matches the given value.

:type if_metageneration_not_match: long
:param if_metageneration_not_match: (Optional) Make the operation conditional on whether the
blob's current metageneration does not match the given value.
"""
client = self._require_client(client)
query_params = self._query_params
# Pass only '?projection=noAcl' here because 'acl' and related
# are handled via custom endpoints.
query_params["projection"] = "noAcl"
_add_generation_match_parameters(
query_params,
if_generation_match=if_generation_match,
if_generation_not_match=if_generation_not_match,
if_metageneration_match=if_metageneration_match,
if_metageneration_not_match=if_metageneration_not_match,
)
api_response = client._connection.api_request(
method="GET",
path=self.path,
Expand Down Expand Up @@ -180,7 +229,15 @@ def _set_properties(self, value):
# If the values are reset, the changes must as well.
self._changes = set()

def patch(self, client=None, timeout=_DEFAULT_TIMEOUT):
def patch(
self,
client=None,
timeout=_DEFAULT_TIMEOUT,
if_generation_match=None,
if_generation_not_match=None,
if_metageneration_match=None,
if_metageneration_not_match=None,
):
"""Sends all changed properties in a PATCH request.

Updates the ``_properties`` with the response from the backend.
Expand All @@ -189,20 +246,49 @@ def patch(self, client=None, timeout=_DEFAULT_TIMEOUT):

:type client: :class:`~google.cloud.storage.client.Client` or
``NoneType``
:param client: the client to use. If not passed, falls back to the
:param client: the client to use. If not passed, falls back to the
``client`` stored on the current object.

:type timeout: float or tuple
:param timeout: (Optional) 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.

:type if_generation_match: long
:param if_generation_match: (Optional) Make the operation conditional on whether
the blob's current generation matches the given value.
Setting to 0 makes the operation succeed only if there
are no live versions of the blob.

:type if_generation_not_match: long
:param if_generation_not_match: (Optional) Make the operation conditional on whether
the blob's current generation does not match the given
value. If no live blob exists, the precondition fails.
Setting to 0 makes the operation succeed only if there
is a live version of the blob.

:type if_metageneration_match: long
:param if_metageneration_match: (Optional) Make the operation conditional on whether the
blob's current metageneration matches the given value.

:type if_metageneration_not_match: long
:param if_metageneration_not_match: (Optional) Make the operation conditional on whether the
blob's current metageneration does not match the given value.
"""
client = self._require_client(client)
query_params = self._query_params
# Pass '?projection=full' here because 'PATCH' documented not
# to work properly w/ 'noAcl'.
query_params["projection"] = "full"
_add_generation_match_parameters(
query_params,
if_generation_match=if_generation_match,
if_generation_not_match=if_generation_not_match,
if_metageneration_match=if_metageneration_match,
if_metageneration_not_match=if_metageneration_not_match,
)
update_properties = {key: self._properties[key] for key in self._changes}

# Make the API call.
Expand All @@ -216,7 +302,15 @@ def patch(self, client=None, timeout=_DEFAULT_TIMEOUT):
)
self._set_properties(api_response)

def update(self, client=None, timeout=_DEFAULT_TIMEOUT):
def update(
self,
client=None,
timeout=_DEFAULT_TIMEOUT,
if_generation_match=None,
if_generation_not_match=None,
if_metageneration_match=None,
if_metageneration_not_match=None,
):
"""Sends all properties in a PUT request.

Updates the ``_properties`` with the response from the backend.
Expand All @@ -225,18 +319,42 @@ def update(self, client=None, timeout=_DEFAULT_TIMEOUT):

:type client: :class:`~google.cloud.storage.client.Client` or
``NoneType``
:param client: the client to use. If not passed, falls back to the
:param client: the client to use. If not passed, falls back to the
``client`` stored on the current object.

:type timeout: float or tuple
:param timeout: (Optional) 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.

:type if_generation_match: long
:param if_generation_match: (Optional) Make the operation conditional on whether
the blob's current generation matches the given value.
Setting to 0 makes the operation succeed only if there
are no live versions of the blob.

:type if_generation_not_match: long
:param if_generation_not_match: (Optional) Make the operation conditional on whether
the blob's current generation does not match the given
value. If no live blob exists, the precondition fails.
Setting to 0 makes the operation succeed only if there
is a live version of the blob.

:type if_metageneration_match: long
:param if_metageneration_match: (Optional) Make the operation conditional on whether the
blob's current metageneration matches the given value.
"""
client = self._require_client(client)

query_params = self._query_params
query_params["projection"] = "full"
_add_generation_match_parameters(
query_params,
if_metageneration_match=if_metageneration_match,
if_metageneration_not_match=if_metageneration_not_match,
)
api_response = client._connection.api_request(
method="PUT",
path=self.path,
Expand Down Expand Up @@ -312,3 +430,44 @@ def _convert_to_timestamp(value):
utc_naive = value.replace(tzinfo=None) - value.utcoffset()
mtime = (utc_naive - datetime(1970, 1, 1)).total_seconds()
return mtime


def _add_generation_match_parameters(parameters, **match_parameters):
"""Add generation match parameters into the given parameters list.

:type parameters: list or dict
:param parameters: Parameters list or dict.

:type match_parameters: dict
:param match_parameters: if*generation*match parameters to add.
"""
for snakecase_name, camelcase_name in _GENERATION_MATCH_PARAMETERS:
value = match_parameters.get(snakecase_name)

if value is not None:
if isinstance(parameters, list):
parameters.append((camelcase_name, value))

elif isinstance(parameters, dict):
parameters[camelcase_name] = value


def _raise_for_more_than_one_none(**kwargs):
"""Raise ``ValueError`` exception if more than one parameter was set.

:type error: :exc:`ValueError`
:param error: Description of which fields were set

:raises: :class:`~ValueError` containing the fields that were set
"""
if sum(arg is not None for arg in kwargs.values()) > 1:
escaped_keys = ["'%s'" % name for name in kwargs.keys()]

keys_but_last = ", ".join(escaped_keys[:-1])
last_key = escaped_keys[-1]

msg = "Pass at most one of {keys_but_last} and {last_key}".format(
keys_but_last=keys_but_last, last_key=last_key
)

raise ValueError(msg)