Skip to content

Commit 047c9ed

Browse files
authored
refactor: add / use 'Client._put_resource' method (#441)
Toward #38.
1 parent 8a13492 commit 047c9ed

File tree

10 files changed

+446
-275
lines changed

10 files changed

+446
-275
lines changed

packages/google-cloud-storage/google/cloud/storage/_helpers.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -416,14 +416,13 @@ def update(
416416
if_metageneration_not_match=if_metageneration_not_match,
417417
)
418418

419-
api_response = client._connection.api_request(
420-
method="PUT",
421-
path=self.path,
422-
data=self._properties,
419+
api_response = client._put_resource(
420+
self.path,
421+
self._properties,
423422
query_params=query_params,
424-
_target_object=self,
425423
timeout=timeout,
426424
retry=retry,
425+
_target_object=self,
427426
)
428427
self._set_properties(api_response)
429428

packages/google-cloud-storage/google/cloud/storage/blob.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2898,16 +2898,16 @@ def set_iam_policy(
28982898
if self.user_project is not None:
28992899
query_params["userProject"] = self.user_project
29002900

2901+
path = "{}/iam".format(self.path)
29012902
resource = policy.to_api_repr()
29022903
resource["resourceId"] = self.path
2903-
info = client._connection.api_request(
2904-
method="PUT",
2905-
path="%s/iam" % (self.path,),
2904+
info = client._put_resource(
2905+
path,
2906+
resource,
29062907
query_params=query_params,
2907-
data=resource,
2908-
_target_object=None,
29092908
timeout=timeout,
29102909
retry=retry,
2910+
_target_object=None,
29112911
)
29122912
return Policy.from_api_repr(info)
29132913

packages/google-cloud-storage/google/cloud/storage/bucket.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2943,17 +2943,19 @@ def set_iam_policy(
29432943
if self.user_project is not None:
29442944
query_params["userProject"] = self.user_project
29452945

2946+
path = "{}/iam".format(self.path)
29462947
resource = policy.to_api_repr()
29472948
resource["resourceId"] = self.path
2948-
info = client._connection.api_request(
2949-
method="PUT",
2950-
path="%s/iam" % (self.path,),
2949+
2950+
info = client._put_resource(
2951+
path,
2952+
resource,
29512953
query_params=query_params,
2952-
data=resource,
2953-
_target_object=None,
29542954
timeout=timeout,
29552955
retry=retry,
2956+
_target_object=None,
29562957
)
2958+
29572959
return Policy.from_api_repr(info)
29582960

29592961
def test_iam_permissions(

packages/google-cloud-storage/google/cloud/storage/client.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,77 @@ def _patch_resource(
457457
_target_object=_target_object,
458458
)
459459

460+
def _put_resource(
461+
self,
462+
path,
463+
data,
464+
query_params=None,
465+
headers=None,
466+
timeout=_DEFAULT_TIMEOUT,
467+
retry=DEFAULT_RETRY,
468+
_target_object=None,
469+
):
470+
"""Helper for bucket / blob methods making API 'PUT' calls.
471+
472+
Args:
473+
path str:
474+
The path of the resource to fetch.
475+
476+
data dict:
477+
The data to be patched.
478+
479+
query_params Optional[dict]:
480+
HTTP query parameters to be passed
481+
482+
headers Optional[dict]:
483+
HTTP headers to be passed
484+
485+
timeout (Optional[Union[float, Tuple[float, float]]]):
486+
The amount of time, in seconds, to wait for the server response.
487+
488+
Can also be passed as a tuple (connect_timeout, read_timeout).
489+
See :meth:`requests.Session.request` documentation for details.
490+
491+
retry (Optional[Union[google.api_core.retry.Retry, google.cloud.storage.retry.ConditionalRetryPolicy]]):
492+
How to retry the RPC. A None value will disable retries.
493+
A google.api_core.retry.Retry value will enable retries, and the object will
494+
define retriable response codes and errors and configure backoff and timeout options.
495+
496+
A google.cloud.storage.retry.ConditionalRetryPolicy value wraps a Retry object and
497+
activates it only if certain conditions are met. This class exists to provide safe defaults
498+
for RPC calls that are not technically safe to retry normally (due to potential data
499+
duplication or other side-effects) but become safe to retry if a condition such as
500+
if_metageneration_match is set.
501+
502+
See the retry.py source code and docstrings in this package (google.cloud.storage.retry) for
503+
information on retry types and how to configure them.
504+
505+
_target_object (Union[ \
506+
:class:`~google.cloud.storage.bucket.Bucket`, \
507+
:class:`~google.cloud.storage.bucket.blob`, \
508+
]):
509+
Object to which future data is to be applied -- only relevant
510+
in the context of a batch.
511+
512+
Returns:
513+
dict
514+
The JSON resource fetched
515+
516+
Raises:
517+
google.cloud.exceptions.NotFound
518+
If the bucket is not found.
519+
"""
520+
return self._connection.api_request(
521+
method="PUT",
522+
path=path,
523+
data=data,
524+
query_params=query_params,
525+
headers=headers,
526+
timeout=timeout,
527+
retry=retry,
528+
_target_object=_target_object,
529+
)
530+
460531
def get_bucket(
461532
self,
462533
bucket_or_name,

packages/google-cloud-storage/google/cloud/storage/hmac_key.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,8 @@ def update(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY_IF_ETAG_IN_JSON):
298298
qs_params["userProject"] = self.user_project
299299

300300
payload = {"state": self.state}
301-
self._properties = self._client._connection.api_request(
302-
method="PUT",
303-
path=self.path,
304-
data=payload,
305-
query_params=qs_params,
306-
timeout=timeout,
307-
retry=retry,
301+
self._properties = self._client._put_resource(
302+
self.path, payload, query_params=qs_params, timeout=timeout, retry=retry,
308303
)
309304

310305
def delete(self, timeout=_DEFAULT_TIMEOUT, retry=DEFAULT_RETRY):

packages/google-cloud-storage/tests/unit/test__helpers.py

Lines changed: 75 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -349,82 +349,100 @@ def test_patch_w_user_project_w_explicit_client(self):
349349
_target_object=derived,
350350
)
351351

352-
def test_update(self):
353-
connection = _Connection({"foo": "Foo"})
354-
client = _Client(connection)
355-
derived = self._derivedClass("/path")()
352+
def test_update_w_defaults(self):
353+
path = "/path"
354+
api_response = {"foo": "Foo"}
355+
derived = self._derivedClass(path)()
356356
# Make sure changes is non-empty, so we can observe a change.
357-
BAR = object()
358-
BAZ = object()
359-
derived._properties = {"bar": BAR, "baz": BAZ}
357+
bar = object()
358+
baz = object()
359+
expected_data = derived._properties = {"bar": bar, "baz": baz}
360360
derived._changes = set(["bar"]) # Update sends 'baz' anyway.
361-
derived.update(client=client, timeout=42)
362-
self.assertEqual(derived._properties, {"foo": "Foo"})
363-
kw = connection._requested
364-
self.assertEqual(len(kw), 1)
365-
self.assertEqual(kw[0]["method"], "PUT")
366-
self.assertEqual(kw[0]["path"], "/path")
367-
self.assertEqual(kw[0]["query_params"], {"projection": "full"})
368-
self.assertEqual(kw[0]["data"], {"bar": BAR, "baz": BAZ})
369-
self.assertEqual(kw[0]["timeout"], 42)
370-
self.assertEqual(kw[0]["retry"], DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED)
371-
# Make sure changes get reset by patch().
361+
client = derived.client = mock.Mock(spec=["_put_resource"])
362+
client._put_resource.return_value = api_response
363+
364+
derived.update()
365+
366+
self.assertEqual(derived._properties, api_response)
367+
# Make sure changes get reset by update().
372368
self.assertEqual(derived._changes, set())
373369

374-
def test_update_with_metageneration_not_match(self):
375-
GENERATION_NUMBER = 6
370+
expected_query_params = {"projection": "full"}
371+
client._put_resource.assert_called_once_with(
372+
path,
373+
expected_data,
374+
query_params=expected_query_params,
375+
timeout=self._get_default_timeout(),
376+
retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
377+
_target_object=derived,
378+
)
376379

377-
connection = _Connection({"foo": "Foo"})
378-
client = _Client(connection)
379-
derived = self._derivedClass("/path")()
380+
def test_update_with_metageneration_not_match_w_timeout_w_retry(self):
381+
path = "/path"
382+
generation_number = 6
383+
api_response = {"foo": "Foo"}
384+
derived = self._derivedClass(path)()
380385
# Make sure changes is non-empty, so we can observe a change.
381-
BAR = object()
382-
BAZ = object()
383-
derived._properties = {"bar": BAR, "baz": BAZ}
386+
bar = object()
387+
baz = object()
388+
expected_data = derived._properties = {"bar": bar, "baz": baz}
384389
derived._changes = set(["bar"]) # Update sends 'baz' anyway.
390+
client = derived.client = mock.Mock(spec=["_put_resource"])
391+
client._put_resource.return_value = api_response
392+
timeout = 42
393+
385394
derived.update(
386-
client=client, timeout=42, if_metageneration_not_match=GENERATION_NUMBER
395+
if_metageneration_not_match=generation_number, timeout=timeout,
387396
)
397+
388398
self.assertEqual(derived._properties, {"foo": "Foo"})
389-
kw = connection._requested
390-
self.assertEqual(len(kw), 1)
391-
self.assertEqual(kw[0]["method"], "PUT")
392-
self.assertEqual(kw[0]["path"], "/path")
393-
self.assertEqual(
394-
kw[0]["query_params"],
395-
{"projection": "full", "ifMetagenerationNotMatch": GENERATION_NUMBER},
396-
)
397-
self.assertEqual(kw[0]["data"], {"bar": BAR, "baz": BAZ})
398-
self.assertEqual(kw[0]["timeout"], 42)
399-
self.assertEqual(kw[0]["retry"], DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED)
400399
# Make sure changes get reset by patch().
401400
self.assertEqual(derived._changes, set())
402401

403-
def test_update_w_user_project(self):
402+
expected_query_params = {
403+
"projection": "full",
404+
"ifMetagenerationNotMatch": generation_number,
405+
}
406+
client._put_resource.assert_called_once_with(
407+
path,
408+
expected_data,
409+
query_params=expected_query_params,
410+
timeout=timeout,
411+
retry=DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED,
412+
_target_object=derived,
413+
)
414+
415+
def test_update_w_user_project_w_retry_w_explicit_client(self):
404416
user_project = "user-project-123"
405-
connection = _Connection({"foo": "Foo"})
406-
client = _Client(connection)
407-
derived = self._derivedClass("/path", user_project)()
417+
path = "/path"
418+
api_response = {"foo": "Foo"}
419+
derived = self._derivedClass(path, user_project)()
408420
# Make sure changes is non-empty, so we can observe a change.
409-
BAR = object()
410-
BAZ = object()
411-
derived._properties = {"bar": BAR, "baz": BAZ}
421+
bar = object()
422+
baz = object()
423+
expected_data = derived._properties = {"bar": bar, "baz": baz}
412424
derived._changes = set(["bar"]) # Update sends 'baz' anyway.
413-
derived.update(client=client)
414-
self.assertEqual(derived._properties, {"foo": "Foo"})
415-
kw = connection._requested
416-
self.assertEqual(len(kw), 1)
417-
self.assertEqual(kw[0]["method"], "PUT")
418-
self.assertEqual(kw[0]["path"], "/path")
419-
self.assertEqual(
420-
kw[0]["query_params"], {"projection": "full", "userProject": user_project}
421-
)
422-
self.assertEqual(kw[0]["data"], {"bar": BAR, "baz": BAZ})
423-
self.assertEqual(kw[0]["timeout"], self._get_default_timeout())
424-
self.assertEqual(kw[0]["retry"], DEFAULT_RETRY_IF_METAGENERATION_SPECIFIED)
425+
client = mock.Mock(spec=["_put_resource"])
426+
client._put_resource.return_value = api_response
427+
retry = mock.Mock(spec=[])
428+
429+
derived.update(client=client, retry=retry)
425430
# Make sure changes get reset by patch().
426431
self.assertEqual(derived._changes, set())
427432

433+
expected_query_params = {
434+
"projection": "full",
435+
"userProject": user_project,
436+
}
437+
client._put_resource.assert_called_once_with(
438+
path,
439+
expected_data,
440+
query_params=expected_query_params,
441+
timeout=self._get_default_timeout(),
442+
retry=retry,
443+
_target_object=derived,
444+
)
445+
428446

429447
class Test__scalar_property(unittest.TestCase):
430448
def _call_fut(self, fieldName):
@@ -575,17 +593,6 @@ def test_hostname_and_scheme(self):
575593
self.assertEqual(self._call_fut(host=HOST, scheme=SCHEME), EXPECTED_URL)
576594

577595

578-
class _Connection(object):
579-
def __init__(self, *responses):
580-
self._responses = responses
581-
self._requested = []
582-
583-
def api_request(self, **kw):
584-
self._requested.append(kw)
585-
response, self._responses = self._responses[0], self._responses[1:]
586-
return response
587-
588-
589596
class _MD5Hash(object):
590597
def __init__(self, digest_val):
591598
self.digest_val = digest_val
@@ -617,8 +624,3 @@ def __init__(self):
617624
def b64encode(self, value):
618625
self._called_b64encode.append(value)
619626
return value
620-
621-
622-
class _Client(object):
623-
def __init__(self, connection):
624-
self._connection = connection

0 commit comments

Comments
 (0)