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

Moving Bucket._patch_properties (and patch) to _PropertyMixin. #690

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions gcloud/storage/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__(self, name=None, properties=None):
"""
self.name = name
self._properties = {}
self._changes = set()
if properties is not None:
self._properties.update(properties)

Expand Down Expand Up @@ -105,43 +106,53 @@ def _patch_properties(self, properties):
This method will only update the fields provided and will not
touch the other fields.

It will also reload the properties locally based on the server's
response.
It **will not** reload the properties from the server. The behavior is
local only and syncing occurs via :meth:`patch`.

:type properties: dict
:param properties: The dictionary of values to update.

:rtype: :class:`_PropertyMixin`
:returns: The current object.
"""
self._changes.update(properties.keys())

This comment was marked as spam.

This comment was marked as spam.

self._properties.update(properties)
return self

def patch(self):
"""Sends all changed properties in a PATCH request.

Updates the ``properties`` with the response from the backend.

:rtype: :class:`Bucket`
:returns: The current bucket.
"""
# Pass '?projection=full' here because 'PATCH' documented not
# to work properly w/ 'noAcl'.
update_properties = dict((key, self._properties[key])
for key in self._changes)
self._properties = self.connection.api_request(
method='PATCH', path=self.path, data=properties,
method='PATCH', path=self.path, data=update_properties,
query_params={'projection': 'full'})
return self


class _PropertyBatch(object):
"""Context manager: Batch updates to object's ``_patch_properties``
"""Context manager: Batch updates to object.

:type wrapped: class derived from :class:`_PropertyMixin`.
:param wrapped: the instance whose property updates to defer/batch.
:param wrapped: the instance whose property updates to batch.
"""
def __init__(self, wrapped):
self._wrapped = wrapped
self._deferred = {}

def __enter__(self):
"""Intercept / defer property updates."""
self._wrapped._patch_properties = self._deferred.update
"""Do nothing method. Needed to be a context manager."""

def __exit__(self, type, value, traceback):
def __exit__(self, exc_type, value, traceback):
"""Patch deferred property updates if no error."""
del self._wrapped._patch_properties
if type is None:
if self._deferred:
self._wrapped._patch_properties(self._deferred)
if exc_type is None:
self._wrapped.patch()


def _scalar_property(fieldname):
Expand Down
38 changes: 0 additions & 38 deletions gcloud/storage/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ def __init__(self, connection=None, name=None, properties=None):
name = properties.get('name')
super(Bucket, self).__init__(name=name, properties=properties)
self._connection = connection
self._changes = set()

def __repr__(self):
return '<Bucket: %s>' % self.name
Expand Down Expand Up @@ -765,40 +764,3 @@ def make_public(self, recursive=False, future=False):
for blob in self:
blob.acl.all().grant_read()
blob.save_acl()

def _patch_properties(self, properties):
"""Update particular fields of this object's properties.

This method will only update the fields provided and will not
touch the other fields.

It **will not** reload the properties from the server as is done
in :meth:`_PropertyMixin._patch_properties`. The behavior is
local only and syncing occurs via :meth:`patch`.

:type properties: dict
:param properties: The dictionary of values to update.

:rtype: :class:`Bucket`
:returns: The current bucket.
"""
self._changes.update(properties.keys())
self._properties.update(properties)
return self

def patch(self):
"""Sends all changed properties in a PATCH request.

Updates the ``properties`` with the response from the backend.

:rtype: :class:`Bucket`
:returns: The current bucket.
"""
# Pass '?projection=full' here because 'PATCH' documented not
# to work properly w/ 'noAcl'.
update_properties = dict((key, self._properties[key])
for key in self._changes)
self._properties = self.connection.api_request(
method='PATCH', path=self.path, data=update_properties,
query_params={'projection': 'full'})
return self
24 changes: 9 additions & 15 deletions gcloud/storage/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def test__patch_properties(self):
connection = _Connection({'foo': 'Foo'})
derived = self._derivedClass(connection, '/path')()
self.assertTrue(derived._patch_properties({'foo': 'Foo'}) is derived)
derived.patch()
kw = connection._requested
self.assertEqual(len(kw), 1)
self.assertEqual(kw[0]['method'], 'PATCH')
Expand All @@ -95,7 +96,7 @@ def test__patch_properties(self):
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})


class TestPropertyBatch(unittest2.TestCase):
class Test_PropertyBatch(unittest2.TestCase):

def _getTargetClass(self):
from gcloud.storage._helpers import _PropertyBatch
Expand Down Expand Up @@ -127,21 +128,11 @@ def test_ctor_does_not_intercept__patch_properties(self):
self.assertEqual(before, after)
self.assertTrue(batch._wrapped is wrapped)

def test_cm_intercepts_restores__patch_properties(self):
wrapped = self._makeWrapped()
before = wrapped._patch_properties
batch = self._makeOne(wrapped)
with batch:
# No deferred patching -> no call to the real '_patch_properties'
during = wrapped._patch_properties
after = wrapped._patch_properties
self.assertNotEqual(before, during)
self.assertEqual(before, after)

def test___exit___w_error_skips__patch_properties(self):
def test___exit___w_error_skips_patch(self):
class Testing(Exception):
pass
wrapped = self._makeWrapped()
connection = _Connection()
wrapped = self._makeWrapped(connection)
batch = self._makeOne(wrapped)
try:
with batch:
Expand All @@ -152,7 +143,10 @@ class Testing(Exception):
except Testing:
pass

def test___exit___no_error_aggregates__patch_properties(self):
kw = connection._requested
self.assertEqual(len(kw), 0)

def test___exit___no_error_calls_patch(self):
connection = _Connection({'foo': 'Foo'})
wrapped = self._makeWrapped(connection, '/path')
batch = self._makeOne(wrapped)
Expand Down
8 changes: 8 additions & 0 deletions gcloud/storage/test_blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@ def test_cache_control_setter(self):
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob.cache_control = CACHE_CONTROL
blob.patch()
self.assertEqual(blob.cache_control, CACHE_CONTROL)
kw = connection._requested
self.assertEqual(len(kw), 1)
Expand Down Expand Up @@ -701,6 +702,7 @@ def test_content_disposition_setter(self):
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob.content_disposition = CONTENT_DISPOSITION
blob.patch()
self.assertEqual(blob.content_disposition, CONTENT_DISPOSITION)
kw = connection._requested
self.assertEqual(len(kw), 1)
Expand All @@ -727,6 +729,7 @@ def test_content_encoding_setter(self):
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob.content_encoding = CONTENT_ENCODING
blob.patch()
self.assertEqual(blob.content_encoding, CONTENT_ENCODING)
kw = connection._requested
self.assertEqual(len(kw), 1)
Expand All @@ -753,6 +756,7 @@ def test_content_language_setter(self):
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob.content_language = CONTENT_LANGUAGE
blob.patch()
self.assertEqual(blob.content_language, CONTENT_LANGUAGE)
kw = connection._requested
self.assertEqual(len(kw), 1)
Expand All @@ -779,6 +783,7 @@ def test_content_type_setter(self):
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob.content_type = CONTENT_TYPE
blob.patch()
self.assertEqual(blob.content_type, CONTENT_TYPE)
kw = connection._requested
self.assertEqual(len(kw), 1)
Expand All @@ -805,6 +810,7 @@ def test_crc32c_setter(self):
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob.crc32c = CRC32C
blob.patch()
self.assertEqual(blob.crc32c, CRC32C)
kw = connection._requested
self.assertEqual(len(kw), 1)
Expand Down Expand Up @@ -858,6 +864,7 @@ def test_md5_hash_setter(self):
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob.md5_hash = MD5_HASH
blob.patch()
self.assertEqual(blob.md5_hash, MD5_HASH)
kw = connection._requested
self.assertEqual(len(kw), 1)
Expand Down Expand Up @@ -893,6 +900,7 @@ def test_metadata_setter(self):
bucket = _Bucket(connection)
blob = self._makeOne(BLOB_NAME, bucket=bucket)
blob.metadata = METADATA
blob.patch()
self.assertEqual(blob.metadata, METADATA)
kw = connection._requested
self.assertEqual(len(kw), 1)
Expand Down