Skip to content

Commit 6a8d031

Browse files
tseaverlandrito
authored andcommitted
Add 'update' API wrapper for buckets/blobs (googleapis#3714, googleapis#3715)
Turns out some properties (i.e., 'labels', see googleapis#3711) behave differently under 'patch semantics'[1], which makes 'update' useful. [1] https://cloud.google.com/storage/docs/json_api/v1/how-tos/performance#patch Closes googleapis#7311
1 parent 6149cdd commit 6a8d031

4 files changed

Lines changed: 59 additions & 0 deletions

File tree

storage/google/cloud/storage/_helpers.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,22 @@ def patch(self, client=None):
147147
query_params={'projection': 'full'}, _target_object=self)
148148
self._set_properties(api_response)
149149

150+
def update(self, client=None):
151+
"""Sends all properties in a PUT request.
152+
153+
Updates the ``_properties`` with the response from the backend.
154+
155+
:type client: :class:`~google.cloud.storage.client.Client` or
156+
``NoneType``
157+
:param client: the client to use. If not passed, falls back to the
158+
``client`` stored on the current object.
159+
"""
160+
client = self._require_client(client)
161+
api_response = client._connection.api_request(
162+
method='PUT', path=self.path, data=self._properties,
163+
query_params={'projection': 'full'}, _target_object=self)
164+
self._set_properties(api_response)
165+
150166

151167
def _scalar_property(fieldname):
152168
"""Create a property descriptor around the :class:`_PropertyMixin` helpers.

storage/google/cloud/storage/bucket.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ def cors(self):
556556
>>> policies[1]['maxAgeSeconds'] = 3600
557557
>>> del policies[0]
558558
>>> bucket.cors = policies
559+
>>> bucket.update()
559560
560561
:setter: Set CORS policies for this bucket.
561562
:getter: Gets the CORS policies for this bucket.
@@ -595,6 +596,7 @@ def labels(self):
595596
>>> labels['new_key'] = 'some-label'
596597
>>> del labels['old_key']
597598
>>> bucket.labels = labels
599+
>>> bucket.update()
598600
599601
:setter: Set labels for this bucket.
600602
:getter: Gets the labels for this bucket.
@@ -663,6 +665,7 @@ def lifecycle_rules(self):
663665
>>> rules[1]['rule']['action']['type'] = 'Delete'
664666
>>> del rules[0]
665667
>>> bucket.lifecycle_rules = rules
668+
>>> bucket.update()
666669
667670
:setter: Set lifestyle rules for this bucket.
668671
:getter: Gets the lifestyle rules for this bucket.

storage/tests/system.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,26 @@ def test_list_buckets(self):
114114
if bucket.name in buckets_to_create]
115115
self.assertEqual(len(created_buckets), len(buckets_to_create))
116116

117+
def test_bucket_update_labels(self):
118+
bucket_name = 'update-labels' + unique_resource_id('-')
119+
bucket = retry_429(Config.CLIENT.create_bucket)(bucket_name)
120+
self.case_buckets_to_delete.append(bucket_name)
121+
self.assertTrue(bucket.exists())
122+
123+
updated_labels = {'test-label': 'label-value'}
124+
bucket.labels = updated_labels
125+
bucket.update()
126+
self.assertEqual(bucket.labels, updated_labels)
127+
128+
new_labels = {'another-label': 'another-value'}
129+
bucket.labels = new_labels
130+
bucket.update()
131+
self.assertEqual(bucket.labels, new_labels)
132+
133+
bucket.labels = {}
134+
bucket.update()
135+
self.assertEqual(bucket.labels, {})
136+
117137

118138
class TestStorageFiles(unittest.TestCase):
119139

storage/tests/unit/test__helpers.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,26 @@ def test_patch(self):
9595
# Make sure changes get reset by patch().
9696
self.assertEqual(derived._changes, set())
9797

98+
def test_update(self):
99+
connection = _Connection({'foo': 'Foo'})
100+
client = _Client(connection)
101+
derived = self._derivedClass('/path')()
102+
# Make sure changes is non-empty, so we can observe a change.
103+
BAR = object()
104+
BAZ = object()
105+
derived._properties = {'bar': BAR, 'baz': BAZ}
106+
derived._changes = set(['bar']) # Update sends 'baz' anyway.
107+
derived.update(client=client)
108+
self.assertEqual(derived._properties, {'foo': 'Foo'})
109+
kw = connection._requested
110+
self.assertEqual(len(kw), 1)
111+
self.assertEqual(kw[0]['method'], 'PUT')
112+
self.assertEqual(kw[0]['path'], '/path')
113+
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})
114+
self.assertEqual(kw[0]['data'], {'bar': BAR, 'baz': BAZ})
115+
# Make sure changes get reset by patch().
116+
self.assertEqual(derived._changes, set())
117+
98118

99119
class Test__scalar_property(unittest.TestCase):
100120

0 commit comments

Comments
 (0)