Skip to content

Commit 0944442

Browse files
Gurov Ilyafrankyn
andauthored
feat: add if*generation*Match support, pt1 (#123)
* feat: add ifMetageneration*Match support, pt1 * fix unit tests, add test for helper * fix unit tests * add generation match args into more methods * feat: add if*generation*Match support, pt2 * Lint fix. * delete "more than one set "checks * del excess import * delete "more than one set" checks * rename the helper; add error raising in case of wront parameters type * add more system tests * system tests fixes * cleanup system test * fix comments * delete excess checks Co-authored-by: Frank Natividad <frankyn@users.noreply.github.com>
1 parent 304024d commit 0944442

File tree

9 files changed

+1686
-77
lines changed

9 files changed

+1686
-77
lines changed

google/cloud/storage/_helpers.py

Lines changed: 177 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,18 @@
3030

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

33+
# generation match parameters in camel and snake cases
34+
_GENERATION_MATCH_PARAMETERS = (
35+
("if_generation_match", "ifGenerationMatch"),
36+
("if_generation_not_match", "ifGenerationNotMatch"),
37+
("if_metageneration_match", "ifMetagenerationMatch"),
38+
("if_metageneration_not_match", "ifMetagenerationNotMatch"),
39+
("if_source_generation_match", "ifSourceGenerationMatch"),
40+
("if_source_generation_not_match", "ifSourceGenerationNotMatch"),
41+
("if_source_metageneration_match", "ifSourceMetagenerationMatch"),
42+
("if_source_metageneration_not_match", "ifSourceMetagenerationNotMatch"),
43+
)
44+
3345

3446
def _get_storage_host():
3547
return os.environ.get(STORAGE_EMULATOR_ENV_VAR, _DEFAULT_STORAGE_HOST)
@@ -121,27 +133,64 @@ def _query_params(self):
121133
params["userProject"] = self.user_project
122134
return params
123135

124-
def reload(self, client=None, timeout=_DEFAULT_TIMEOUT):
136+
def reload(
137+
self,
138+
client=None,
139+
timeout=_DEFAULT_TIMEOUT,
140+
if_generation_match=None,
141+
if_generation_not_match=None,
142+
if_metageneration_match=None,
143+
if_metageneration_not_match=None,
144+
):
125145
"""Reload properties from Cloud Storage.
126146
127147
If :attr:`user_project` is set, bills the API request to that project.
128148
129149
:type client: :class:`~google.cloud.storage.client.Client` or
130150
``NoneType``
131-
:param client: the client to use. If not passed, falls back to the
151+
:param client: the client to use. If not passed, falls back to the
132152
``client`` stored on the current object.
153+
133154
:type timeout: float or tuple
134155
:param timeout: (Optional) The amount of time, in seconds, to wait
135156
for the server response.
136157
137158
Can also be passed as a tuple (connect_timeout, read_timeout).
138159
See :meth:`requests.Session.request` documentation for details.
160+
161+
:type if_generation_match: long
162+
:param if_generation_match: (Optional) Make the operation conditional on whether
163+
the blob's current generation matches the given value.
164+
Setting to 0 makes the operation succeed only if there
165+
are no live versions of the blob.
166+
167+
:type if_generation_not_match: long
168+
:param if_generation_not_match: (Optional) Make the operation conditional on whether
169+
the blob's current generation does not match the given
170+
value. If no live blob exists, the precondition fails.
171+
Setting to 0 makes the operation succeed only if there
172+
is a live version of the blob.
173+
174+
:type if_metageneration_match: long
175+
:param if_metageneration_match: (Optional) Make the operation conditional on whether the
176+
blob's current metageneration matches the given value.
177+
178+
:type if_metageneration_not_match: long
179+
:param if_metageneration_not_match: (Optional) Make the operation conditional on whether the
180+
blob's current metageneration does not match the given value.
139181
"""
140182
client = self._require_client(client)
141183
query_params = self._query_params
142184
# Pass only '?projection=noAcl' here because 'acl' and related
143185
# are handled via custom endpoints.
144186
query_params["projection"] = "noAcl"
187+
_add_generation_match_parameters(
188+
query_params,
189+
if_generation_match=if_generation_match,
190+
if_generation_not_match=if_generation_not_match,
191+
if_metageneration_match=if_metageneration_match,
192+
if_metageneration_not_match=if_metageneration_not_match,
193+
)
145194
api_response = client._connection.api_request(
146195
method="GET",
147196
path=self.path,
@@ -180,7 +229,15 @@ def _set_properties(self, value):
180229
# If the values are reset, the changes must as well.
181230
self._changes = set()
182231

183-
def patch(self, client=None, timeout=_DEFAULT_TIMEOUT):
232+
def patch(
233+
self,
234+
client=None,
235+
timeout=_DEFAULT_TIMEOUT,
236+
if_generation_match=None,
237+
if_generation_not_match=None,
238+
if_metageneration_match=None,
239+
if_metageneration_not_match=None,
240+
):
184241
"""Sends all changed properties in a PATCH request.
185242
186243
Updates the ``_properties`` with the response from the backend.
@@ -189,20 +246,49 @@ def patch(self, client=None, timeout=_DEFAULT_TIMEOUT):
189246
190247
:type client: :class:`~google.cloud.storage.client.Client` or
191248
``NoneType``
192-
:param client: the client to use. If not passed, falls back to the
249+
:param client: the client to use. If not passed, falls back to the
193250
``client`` stored on the current object.
251+
194252
:type timeout: float or tuple
195253
:param timeout: (Optional) The amount of time, in seconds, to wait
196254
for the server response.
197255
198256
Can also be passed as a tuple (connect_timeout, read_timeout).
199257
See :meth:`requests.Session.request` documentation for details.
258+
259+
:type if_generation_match: long
260+
:param if_generation_match: (Optional) Make the operation conditional on whether
261+
the blob's current generation matches the given value.
262+
Setting to 0 makes the operation succeed only if there
263+
are no live versions of the blob.
264+
265+
:type if_generation_not_match: long
266+
:param if_generation_not_match: (Optional) Make the operation conditional on whether
267+
the blob's current generation does not match the given
268+
value. If no live blob exists, the precondition fails.
269+
Setting to 0 makes the operation succeed only if there
270+
is a live version of the blob.
271+
272+
:type if_metageneration_match: long
273+
:param if_metageneration_match: (Optional) Make the operation conditional on whether the
274+
blob's current metageneration matches the given value.
275+
276+
:type if_metageneration_not_match: long
277+
:param if_metageneration_not_match: (Optional) Make the operation conditional on whether the
278+
blob's current metageneration does not match the given value.
200279
"""
201280
client = self._require_client(client)
202281
query_params = self._query_params
203282
# Pass '?projection=full' here because 'PATCH' documented not
204283
# to work properly w/ 'noAcl'.
205284
query_params["projection"] = "full"
285+
_add_generation_match_parameters(
286+
query_params,
287+
if_generation_match=if_generation_match,
288+
if_generation_not_match=if_generation_not_match,
289+
if_metageneration_match=if_metageneration_match,
290+
if_metageneration_not_match=if_metageneration_not_match,
291+
)
206292
update_properties = {key: self._properties[key] for key in self._changes}
207293

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

219-
def update(self, client=None, timeout=_DEFAULT_TIMEOUT):
305+
def update(
306+
self,
307+
client=None,
308+
timeout=_DEFAULT_TIMEOUT,
309+
if_generation_match=None,
310+
if_generation_not_match=None,
311+
if_metageneration_match=None,
312+
if_metageneration_not_match=None,
313+
):
220314
"""Sends all properties in a PUT request.
221315
222316
Updates the ``_properties`` with the response from the backend.
@@ -225,18 +319,46 @@ def update(self, client=None, timeout=_DEFAULT_TIMEOUT):
225319
226320
:type client: :class:`~google.cloud.storage.client.Client` or
227321
``NoneType``
228-
:param client: the client to use. If not passed, falls back to the
322+
:param client: the client to use. If not passed, falls back to the
229323
``client`` stored on the current object.
324+
230325
:type timeout: float or tuple
231326
:param timeout: (Optional) The amount of time, in seconds, to wait
232327
for the server response.
233328
234329
Can also be passed as a tuple (connect_timeout, read_timeout).
235330
See :meth:`requests.Session.request` documentation for details.
331+
332+
:type if_generation_match: long
333+
:param if_generation_match: (Optional) Make the operation conditional on whether
334+
the blob's current generation matches the given value.
335+
Setting to 0 makes the operation succeed only if there
336+
are no live versions of the blob.
337+
338+
:type if_generation_not_match: long
339+
:param if_generation_not_match: (Optional) Make the operation conditional on whether
340+
the blob's current generation does not match the given
341+
value. If no live blob exists, the precondition fails.
342+
Setting to 0 makes the operation succeed only if there
343+
is a live version of the blob.
344+
345+
:type if_metageneration_match: long
346+
:param if_metageneration_match: (Optional) Make the operation conditional on whether the
347+
blob's current metageneration matches the given value.
348+
349+
:type if_metageneration_not_match: long
350+
:param if_metageneration_not_match: (Optional) Make the operation conditional on whether the
351+
blob's current metageneration does not match the given value.
236352
"""
237353
client = self._require_client(client)
354+
238355
query_params = self._query_params
239356
query_params["projection"] = "full"
357+
_add_generation_match_parameters(
358+
query_params,
359+
if_metageneration_match=if_metageneration_match,
360+
if_metageneration_not_match=if_metageneration_not_match,
361+
)
240362
api_response = client._connection.api_request(
241363
method="PUT",
242364
path=self.path,
@@ -312,3 +434,52 @@ def _convert_to_timestamp(value):
312434
utc_naive = value.replace(tzinfo=None) - value.utcoffset()
313435
mtime = (utc_naive - datetime(1970, 1, 1)).total_seconds()
314436
return mtime
437+
438+
439+
def _add_generation_match_parameters(parameters, **match_parameters):
440+
"""Add generation match parameters into the given parameters list.
441+
442+
:type parameters: list or dict
443+
:param parameters: Parameters list or dict.
444+
445+
:type match_parameters: dict
446+
:param match_parameters: if*generation*match parameters to add.
447+
448+
:raises: :exc:`ValueError` if ``parameters`` is not a ``list()``
449+
or a ``dict()``.
450+
"""
451+
for snakecase_name, camelcase_name in _GENERATION_MATCH_PARAMETERS:
452+
value = match_parameters.get(snakecase_name)
453+
454+
if value is not None:
455+
if isinstance(parameters, list):
456+
parameters.append((camelcase_name, value))
457+
458+
elif isinstance(parameters, dict):
459+
parameters[camelcase_name] = value
460+
461+
else:
462+
raise ValueError(
463+
"`parameters` argument should be a dict() or a list()."
464+
)
465+
466+
467+
def _raise_if_more_than_one_set(**kwargs):
468+
"""Raise ``ValueError`` exception if more than one parameter was set.
469+
470+
:type error: :exc:`ValueError`
471+
:param error: Description of which fields were set
472+
473+
:raises: :class:`~ValueError` containing the fields that were set
474+
"""
475+
if sum(arg is not None for arg in kwargs.values()) > 1:
476+
escaped_keys = ["'%s'" % name for name in kwargs.keys()]
477+
478+
keys_but_last = ", ".join(escaped_keys[:-1])
479+
last_key = escaped_keys[-1]
480+
481+
msg = "Pass at most one of {keys_but_last} and {last_key}".format(
482+
keys_but_last=keys_but_last, last_key=last_key
483+
)
484+
485+
raise ValueError(msg)

0 commit comments

Comments
 (0)