Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 64d1728

Browse filesBrowse files
Storage: Track deleted labels; make Bucket.patch() send them. (#3737)
1 parent 957f6e0 commit 64d1728
Copy full SHA for 64d1728

File tree

Expand file treeCollapse file tree

4 files changed

+70
-1
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+70
-1
lines changed

‎storage/google/cloud/storage/_helpers.py

Copy file name to clipboardExpand all lines: storage/google/cloud/storage/_helpers.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ def patch(self, client=None):
142142
# to work properly w/ 'noAcl'.
143143
update_properties = {key: self._properties[key]
144144
for key in self._changes}
145+
146+
# Make the API call.
145147
api_response = client._connection.api_request(
146148
method='PATCH', path=self.path, data=update_properties,
147149
query_params={'projection': 'full'}, _target_object=self)

‎storage/google/cloud/storage/bucket.py

Copy file name to clipboardExpand all lines: storage/google/cloud/storage/bucket.py
+40Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ def __init__(self, client, name=None):
115115
self._client = client
116116
self._acl = BucketACL(self)
117117
self._default_object_acl = DefaultObjectACL(self)
118+
self._label_removals = set()
118119

119120
def __repr__(self):
120121
return '<Bucket: %s>' % (self.name,)
@@ -124,6 +125,15 @@ def client(self):
124125
"""The client bound to this bucket."""
125126
return self._client
126127

128+
def _set_properties(self, value):
129+
"""Set the properties for the current object.
130+
131+
:type value: dict or :class:`google.cloud.storage.batch._FutureDict`
132+
:param value: The properties to be set.
133+
"""
134+
self._label_removals.clear()
135+
return super(Bucket, self)._set_properties(value)
136+
127137
def blob(self, blob_name, chunk_size=None, encryption_key=None):
128138
"""Factory constructor for blob object.
129139
@@ -199,6 +209,27 @@ def create(self, client=None):
199209
data=properties, _target_object=self)
200210
self._set_properties(api_response)
201211

212+
def patch(self, client=None):
213+
"""Sends all changed properties in a PATCH request.
214+
215+
Updates the ``_properties`` with the response from the backend.
216+
217+
:type client: :class:`~google.cloud.storage.client.Client` or
218+
``NoneType``
219+
:param client: the client to use. If not passed, falls back to the
220+
``client`` stored on the current object.
221+
"""
222+
# Special case: For buckets, it is possible that labels are being
223+
# removed; this requires special handling.
224+
if self._label_removals:
225+
self._changes.add('labels')
226+
self._properties.setdefault('labels', {})
227+
for removed_label in self._label_removals:
228+
self._properties['labels'][removed_label] = None
229+
230+
# Call the superclass method.
231+
return super(Bucket, self).patch(client=client)
232+
202233
@property
203234
def acl(self):
204235
"""Create our ACL on demand."""
@@ -624,6 +655,15 @@ def labels(self, mapping):
624655
:type mapping: :class:`dict`
625656
:param mapping: Name-value pairs (string->string) labelling the bucket.
626657
"""
658+
# If any labels have been expressly removed, we need to track this
659+
# so that a future .patch() call can do the correct thing.
660+
existing = set([k for k in self.labels.keys()])
661+
incoming = set([k for k in mapping.keys()])
662+
self._label_removals = self._label_removals.union(
663+
existing.difference(incoming),
664+
)
665+
666+
# Actually update the labels on the object.
627667
self._patch_property('labels', copy.deepcopy(mapping))
628668

629669
@property

‎storage/tests/system.py

Copy file name to clipboardExpand all lines: storage/tests/system.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def test_bucket_update_labels(self):
127127

128128
new_labels = {'another-label': 'another-value'}
129129
bucket.labels = new_labels
130-
bucket.update()
130+
bucket.patch()
131131
self.assertEqual(bucket.labels, new_labels)
132132

133133
bucket.labels = {}

‎storage/tests/unit/test_bucket.py

Copy file name to clipboardExpand all lines: storage/tests/unit/test_bucket.py
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,33 @@ def test_labels_setter(self):
710710
self.assertIsNot(bucket._properties['labels'], LABELS)
711711
self.assertIn('labels', bucket._changes)
712712

713+
def test_labels_setter_with_removal(self):
714+
# Make sure the bucket labels look correct and follow the expected
715+
# public structure.
716+
bucket = self._make_one(name='name')
717+
self.assertEqual(bucket.labels, {})
718+
bucket.labels = {'color': 'red', 'flavor': 'cherry'}
719+
self.assertEqual(bucket.labels, {'color': 'red', 'flavor': 'cherry'})
720+
bucket.labels = {'color': 'red'}
721+
self.assertEqual(bucket.labels, {'color': 'red'})
722+
723+
# Make sure that a patch call correctly removes the flavor label.
724+
client = mock.NonCallableMock(spec=('_connection',))
725+
client._connection = mock.NonCallableMock(spec=('api_request',))
726+
bucket.patch(client=client)
727+
client._connection.api_request.assert_called()
728+
_, _, kwargs = client._connection.api_request.mock_calls[0]
729+
self.assertEqual(len(kwargs['data']['labels']), 2)
730+
self.assertEqual(kwargs['data']['labels']['color'], 'red')
731+
self.assertIsNone(kwargs['data']['labels']['flavor'])
732+
733+
# A second patch call should be a no-op for labels.
734+
client._connection.api_request.reset_mock()
735+
bucket.patch(client=client)
736+
client._connection.api_request.assert_called()
737+
_, _, kwargs = client._connection.api_request.mock_calls[0]
738+
self.assertNotIn('labels', kwargs['data'])
739+
713740
def test_get_logging_w_prefix(self):
714741
NAME = 'name'
715742
LOG_BUCKET = 'logs'

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.