diff --git a/gcloud/storage/acl.py b/gcloud/storage/acl.py index 75e0577e9d8f..448ad1119c57 100644 --- a/gcloud/storage/acl.py +++ b/gcloud/storage/acl.py @@ -167,9 +167,20 @@ def revoke_owner(self): class ACL(object): """Container class representing a list of access controls.""" + loaded = False + def __init__(self): self.entities = {} + def clear(self): + """Remove all entities from the ACL.""" + self.entities.clear() + + def reset(self): + """Remove all entities from the ACL, and clear the ``loaded`` flag.""" + self.entities.clear() + self.loaded = False + def __iter__(self): for entity in self.entities.itervalues(): for role in entity.get_roles(): @@ -242,6 +253,7 @@ def add_entity(self, entity): :param entity: The entity to add to this ACL. """ self.entities[str(entity)] = entity + self.loaded = True def entity(self, entity_type, identifier=None): """Factory method for creating an Entity. diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py index 4d4ce3ac1962..7e7bd9640049 100644 --- a/gcloud/storage/bucket.py +++ b/gcloud/storage/bucket.py @@ -19,15 +19,27 @@ class Bucket(object): :type name: string :param name: The name of the bucket. """ + # ACL rules are lazily retrieved. + _acl = _default_object_acl = None def __init__(self, connection=None, name=None, metadata=None): self.connection = connection self.name = name self.metadata = metadata - # ACL rules are lazily retrieved. - self.acl = None - self.default_object_acl = None + @property + def acl(self): + """Create our ACL on demand.""" + if self._acl is None: + self._acl = BucketACL(self) + return self._acl + + @property + def default_object_acl(self): + """Create our defaultObjectACL on demand.""" + if self._default_object_acl is None: + self._default_object_acl = DefaultObjectACL(self) + return self._default_object_acl @classmethod def from_dict(cls, bucket_dict, connection=None): @@ -313,17 +325,15 @@ def has_metadata(self, field=None): else: return True - def reload_metadata(self, full=False): + def reload_metadata(self): """Reload metadata from Cloud Storage. - :type full: bool - :param full: If True, loads all data (include ACL data). - :rtype: :class:`Bucket` :returns: The bucket you just reloaded data for. """ - projection = 'full' if full else 'noAcl' - query_params = {'projection': projection} + # Pass only '?projection=noAcl' here because 'acl'/'defaultObjectAcl' + # are handled via 'get_acl()'/'get_default_object_acl()' + query_params = {'projection': 'noAcl'} self.metadata = self.connection.api_request( method='GET', path=self.path, query_params=query_params) return self @@ -344,9 +354,14 @@ def get_metadata(self, field=None, default=None): :rtype: dict or anything :returns: All metadata or the value of the specific field. """ + if field == 'acl': + raise KeyError("Use 'get_acl()'") + + if field == 'defaultObjectAcl': + raise KeyError("Use 'get_default_object_acl()'") + if not self.has_metadata(field=field): - full = (field and field in ('acl', 'defaultObjectAcl')) - self.reload_metadata(full=full) + self.reload_metadata() if field: return self.metadata.get(field, default) @@ -431,11 +446,15 @@ def reload_acl(self): :rtype: :class:`Bucket` :returns: The current bucket. """ - self.acl = BucketACL(bucket=self) + self.acl.clear() + + url_path = '%s/acl' % self.path + found = self.connection.api_request(method='GET', path=url_path) + for entry in found['items']: + self.acl.add_entity(self.acl.entity_from_dict(entry)) - for entry in self.get_metadata('acl', []): - entity = self.acl.entity_from_dict(entry) - self.acl.add_entity(entity) + # Even if we fetch no entries, the ACL is still loaded. + self.acl.loaded = True return self @@ -445,7 +464,7 @@ def get_acl(self): :rtype: :class:`gcloud.storage.acl.BucketACL` :returns: An ACL object for the current bucket. """ - if not self.acl: + if not self.acl.loaded: self.reload_acl() return self.acl @@ -487,12 +506,19 @@ def save_acl(self, acl=None): # both evaluate to False, but mean very different things. if acl is None: acl = self.acl + dirty = acl.loaded + else: + dirty = True - if acl is None: - return self + if dirty: + result = self.connection.api_request( + method='PATCH', path=self.path, data={'acl': list(acl)}, + query_params={'projection': 'full'}) + self.acl.clear() + for entry in result['acl']: + self.acl.entity(self.acl.entity_from_dict(entry)) + self.acl.loaded = True - self.patch_metadata({'acl': list(acl)}) - self.reload_acl() return self def clear_acl(self): @@ -522,7 +548,9 @@ def clear_acl(self): At this point all the custom rules you created have been removed. """ - return self.save_acl(acl=[]) + # NOTE: back-end makes some ACL entries sticky (they remain even + # after the PATCH succeeds. + return self.save_acl([]) def reload_default_object_acl(self): """Reload the Default Object ACL rules for this bucket. @@ -530,11 +558,16 @@ def reload_default_object_acl(self): :rtype: :class:`Bucket` :returns: The current bucket. """ - self.default_object_acl = DefaultObjectACL(bucket=self) + doa = self.default_object_acl + doa.clear() - for entry in self.get_metadata('defaultObjectAcl', []): - entity = self.default_object_acl.entity_from_dict(entry) - self.default_object_acl.add_entity(entity) + url_path = '%s/defaultObjectAcl' % self.path + found = self.connection.api_request(method='GET', path=url_path) + for entry in found['items']: + doa.add_entity(doa.entity_from_dict(entry)) + + # Even if we fetch no entries, the ACL is still loaded. + doa.loaded = True return self @@ -547,7 +580,7 @@ def get_default_object_acl(self): :rtype: :class:`gcloud.storage.acl.DefaultObjectACL` :returns: A DefaultObjectACL object for this bucket. """ - if not self.default_object_acl: + if not self.default_object_acl.loaded: self.reload_default_object_acl() return self.default_object_acl @@ -562,18 +595,26 @@ def save_default_object_acl(self, acl=None): """ if acl is None: acl = self.default_object_acl + dirty = acl.loaded + else: + dirty = True + + if dirty: + result = self.connection.api_request( + method='PATCH', path=self.path, + data={'defaultObjectAcl': list(acl)}, + query_params={'projection': 'full'}) + doa = self.default_object_acl + doa.clear() + for entry in result['defaultObjectAcl']: + doa.entity(doa.entity_from_dict(entry)) + doa.loaded = True - if acl is None: - return self - - self.patch_metadata({'defaultObjectAcl': list(acl)}) - self.reload_default_object_acl() return self def clear_default_object_acl(self): """Remove the Default Object ACL from this bucket.""" - - return self.save_default_object_acl(acl=[]) + return self.save_default_object_acl([]) def make_public(self, recursive=False, future=False): """Make a bucket public. diff --git a/gcloud/storage/key.py b/gcloud/storage/key.py index 175206227e3c..217f98ee29e3 100644 --- a/gcloud/storage/key.py +++ b/gcloud/storage/key.py @@ -17,6 +17,8 @@ class Key(object): This must be a multiple of 256 KB per the API specification. """ + # ACL rules are lazily retrieved. + _acl = None def __init__(self, bucket=None, name=None, metadata=None): """Key constructor. @@ -35,8 +37,12 @@ def __init__(self, bucket=None, name=None, metadata=None): self.name = name self.metadata = metadata or {} - # Lazily get the ACL information. - self.acl = None + @property + def acl(self): + """Create our ACL on demand.""" + if self._acl is None: + self._acl = ObjectACL(self) + return self._acl @classmethod def from_dict(cls, key_dict, bucket=None): @@ -316,17 +322,15 @@ def has_metadata(self, field=None): else: return True - def reload_metadata(self, full=False): + def reload_metadata(self): """Reload metadata from Cloud Storage. - :type full: bool - :param full: If True, loads all data (include ACL data). - :rtype: :class:`Key` :returns: The key you just reloaded data for. """ - projection = 'full' if full else 'noAcl' - query_params = {'projection': projection} + # Pass only '?projection=noAcl' here because 'acl' is handled via + # 'get_acl(). + query_params = {'projection': 'noAcl'} self.metadata = self.connection.api_request( method='GET', path=self.path, query_params=query_params) return self @@ -347,9 +351,12 @@ def get_metadata(self, field=None, default=None): :rtype: dict or anything :returns: All metadata or the value of the specific field. """ + # We ignore 'acl' because it is meant to be handled via 'get_acl()'. + if field == 'acl': + raise KeyError("Use 'get_acl()'") + if not self.has_metadata(field=field): - full = (field and field == 'acl') - self.reload_metadata(full=full) + self.reload_metadata() if field: return self.metadata.get(field, default) @@ -382,11 +389,15 @@ def reload_acl(self): :rtype: :class:`Key` :returns: The current key. """ - self.acl = ObjectACL(key=self) + self.acl.clear() - for entry in self.get_metadata('acl', []): - entity = self.acl.entity_from_dict(entry) - self.acl.add_entity(entity) + url_path = '%s/acl' % self.path + found = self.connection.api_request(method='GET', path=url_path) + for entry in found['items']: + self.acl.add_entity(self.acl.entity_from_dict(entry)) + + # Even if we fetch no entries, the ACL is still loaded. + self.acl.loaded = True return self @@ -396,7 +407,7 @@ def get_acl(self): :rtype: :class:`gcloud.storage.acl.ObjectACL` :returns: An ACL object for the current key. """ - if not self.acl: + if not self.acl.loaded: self.reload_acl() return self.acl @@ -407,16 +418,21 @@ def save_acl(self, acl=None): :param acl: The ACL object to save. If left blank, this will save the ACL set locally on the key. """ - # We do things in this weird way because [] and None - # both evaluate to False, but mean very different things. if acl is None: acl = self.acl + dirty = acl.loaded + else: + dirty = True - if acl is None: - return self + if dirty: + result = self.connection.api_request( + method='PATCH', path=self.path, data={'acl': list(acl)}, + query_params={'projection': 'full'}) + self.acl.clear() + for entry in result['acl']: + self.acl.entity(self.acl.entity_from_dict(entry)) + self.acl.loaded = True - self.patch_metadata({'acl': list(acl)}) - self.reload_acl() return self def clear_acl(self): @@ -427,7 +443,7 @@ def clear_acl(self): have access to a key that you created even after you clear ACL rules with this method. """ - return self.save_acl(acl=[]) + return self.save_acl([]) def make_public(self): """Make this key public giving all users read access. diff --git a/gcloud/storage/test_acl.py b/gcloud/storage/test_acl.py index 8a4f6e8f6da0..74f9cccd4ffb 100644 --- a/gcloud/storage/test_acl.py +++ b/gcloud/storage/test_acl.py @@ -125,6 +125,27 @@ def test_ctor(self): acl = self._makeOne() self.assertEqual(acl.entities, {}) self.assertEqual(list(acl.get_entities()), []) + self.assertFalse(acl.loaded) + + def test_clear(self): + TYPE = 'type' + ID = 'id' + acl = self._makeOne() + acl.entity(TYPE, ID) + acl.clear() + self.assertTrue(acl.loaded) + self.assertEqual(acl.entities, {}) + self.assertEqual(list(acl.get_entities()), []) + + def test_reset(self): + TYPE = 'type' + ID = 'id' + acl = self._makeOne() + acl.entity(TYPE, ID) + acl.reset() + self.assertFalse(acl.loaded) + self.assertEqual(acl.entities, {}) + self.assertEqual(list(acl.get_entities()), []) def test___iter___empty(self): acl = self._makeOne() @@ -272,6 +293,7 @@ def test_add_entity_miss(self): entity.grant(ROLE) acl = self._makeOne() acl.add_entity(entity) + self.assertTrue(acl.loaded) self.assertEqual(list(acl), [{'entity': 'type-id', 'role': ROLE}]) self.assertEqual(list(acl.get_entities()), [entity]) @@ -287,6 +309,7 @@ def test_add_entity_hit(self): acl = self._makeOne() before = acl.entity(TYPE, ID) acl.add_entity(entity) + self.assertTrue(acl.loaded) self.assertFalse(acl.get_entity(KEY) is before) self.assertTrue(acl.get_entity(KEY) is entity) self.assertEqual(list(acl), @@ -299,6 +322,7 @@ def test_entity_miss(self): ROLE = 'role' acl = self._makeOne() entity = acl.entity(TYPE, ID) + self.assertTrue(acl.loaded) entity.grant(ROLE) self.assertEqual(list(acl), [{'entity': 'type-id', 'role': ROLE}]) diff --git a/gcloud/storage/test_bucket.py b/gcloud/storage/test_bucket.py index e7869ce0cab3..6177de01b8ef 100644 --- a/gcloud/storage/test_bucket.py +++ b/gcloud/storage/test_bucket.py @@ -17,8 +17,8 @@ def test_ctor_defaults(self): self.assertEqual(bucket.connection, None) self.assertEqual(bucket.name, None) self.assertEqual(bucket.metadata, None) - self.assertEqual(bucket.acl, None) - self.assertEqual(bucket.default_object_acl, None) + self.assertTrue(bucket._acl is None) + self.assertTrue(bucket._default_object_acl is None) def test_ctor_explicit(self): NAME = 'name' @@ -28,8 +28,8 @@ def test_ctor_explicit(self): self.assertTrue(bucket.connection is connection) self.assertEqual(bucket.name, NAME) self.assertEqual(bucket.metadata, metadata) - self.assertEqual(bucket.acl, None) - self.assertEqual(bucket.default_object_acl, None) + self.assertTrue(bucket._acl is None) + self.assertTrue(bucket._default_object_acl is None) def test_from_dict_defaults(self): NAME = 'name' @@ -39,8 +39,8 @@ def test_from_dict_defaults(self): self.assertEqual(bucket.connection, None) self.assertEqual(bucket.name, NAME) self.assertEqual(bucket.metadata, metadata) - self.assertEqual(bucket.acl, None) - self.assertEqual(bucket.default_object_acl, None) + self.assertTrue(bucket._acl is None) + self.assertTrue(bucket._default_object_acl is None) def test_from_dict_explicit(self): NAME = 'name' @@ -51,8 +51,22 @@ def test_from_dict_explicit(self): self.assertTrue(bucket.connection is connection) self.assertEqual(bucket.name, NAME) self.assertEqual(bucket.metadata, metadata) - self.assertEqual(bucket.acl, None) - self.assertEqual(bucket.default_object_acl, None) + self.assertTrue(bucket._acl is None) + self.assertTrue(bucket._default_object_acl is None) + + def test_acl_property(self): + from gcloud.storage.acl import BucketACL + bucket = self._makeOne() + acl = bucket.acl + self.assertTrue(isinstance(acl, BucketACL)) + self.assertTrue(acl is bucket._acl) + + def test_default_object_acl_property(self): + from gcloud.storage.acl import DefaultObjectACL + bucket = self._makeOne() + acl = bucket.default_object_acl + self.assertTrue(isinstance(acl, DefaultObjectACL)) + self.assertTrue(acl is bucket._default_object_acl) def test___iter___empty(self): NAME = 'name' @@ -395,7 +409,7 @@ def test_has_metdata_hit(self): bucket = self._makeOne(metadata=metadata) self.assertTrue(bucket.has_metadata(KEY)) - def test_reload_metadata_default(self): + def test_reload_metadata(self): NAME = 'name' before = {'foo': 'Foo'} after = {'bar': 'Bar'} @@ -410,21 +424,6 @@ def test_reload_metadata_default(self): self.assertEqual(kw[0]['path'], '/b/%s' % NAME) self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) - def test_reload_metadata_explicit(self): - NAME = 'name' - before = {'foo': 'Foo'} - after = {'bar': 'Bar'} - connection = _Connection(after) - bucket = self._makeOne(connection, NAME, before) - found = bucket.reload_metadata(True) - self.assertTrue(found is bucket) - self.assertEqual(found.metadata, after) - kw = connection._requested - self.assertEqual(len(kw), 1) - self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - def test_get_metadata_none_set_none_passed(self): NAME = 'name' after = {'bar': 'Bar'} @@ -439,34 +438,40 @@ def test_get_metadata_none_set_none_passed(self): self.assertEqual(kw[0]['path'], '/b/%s' % NAME) self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) - def test_get_metadata_none_set_acl_hit(self): + def test_get_metadata_acl_no_default(self): NAME = 'name' - after = {'bar': 'Bar', 'acl': []} - connection = _Connection(after) + connection = _Connection() bucket = self._makeOne(connection, NAME) - found = bucket.get_metadata('acl') - self.assertEqual(found, []) - self.assertEqual(bucket.metadata, after) + self.assertRaises(KeyError, bucket.get_metadata, 'acl') kw = connection._requested - self.assertEqual(len(kw), 1) - self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(len(kw), 0) + + def test_get_metadata_acl_w_default(self): + NAME = 'name' + connection = _Connection() + bucket = self._makeOne(connection, NAME) + default = object() + self.assertRaises(KeyError, bucket.get_metadata, 'acl', default) + kw = connection._requested + self.assertEqual(len(kw), 0) + + def test_get_metadata_defaultObjectAcl_no_default(self): + NAME = 'name' + connection = _Connection() + bucket = self._makeOne(connection, NAME) + self.assertRaises(KeyError, bucket.get_metadata, 'defaultObjectAcl') + kw = connection._requested + self.assertEqual(len(kw), 0) def test_get_metadata_none_set_defaultObjectAcl_miss_clear_default(self): NAME = 'name' - after = {'bar': 'Bar'} - connection = _Connection(after) + connection = _Connection() bucket = self._makeOne(connection, NAME) default = object() - found = bucket.get_metadata('defaultObjectAcl', default) - self.assertTrue(found is default) - self.assertEqual(bucket.metadata, after) + self.assertRaises(KeyError, bucket.get_metadata, 'defaultObjectAcl', + default) kw = connection._requested - self.assertEqual(len(kw), 1) - self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(len(kw), 0) def test_get_metadata_miss(self): NAME = 'name' @@ -551,28 +556,42 @@ def test_disable_website(self): def test_reload_acl_eager_empty(self): from gcloud.storage.acl import BucketACL - metadata = {'acl': []} - bucket = self._makeOne(metadata=metadata) + NAME = 'name' + ROLE = 'role' + connection = _Connection({'items': []}) + bucket = self._makeOne(connection, NAME) + bucket.acl.entity('allUsers', ROLE) self.assertTrue(bucket.reload_acl() is bucket) self.assertTrue(isinstance(bucket.acl, BucketACL)) self.assertEqual(list(bucket.acl), []) + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/b/%s/acl' % NAME) def test_reload_acl_eager_nonempty(self): from gcloud.storage.acl import BucketACL + NAME = 'name' ROLE = 'role' - metadata = {'acl': [{'entity': 'allUsers', 'role': ROLE}]} - bucket = self._makeOne(metadata=metadata) + connection = _Connection( + {'items': [{'entity': 'allUsers', 'role': ROLE}]}) + bucket = self._makeOne(connection, NAME) + bucket.acl.loaded = True self.assertTrue(bucket.reload_acl() is bucket) self.assertTrue(isinstance(bucket.acl, BucketACL)) self.assertEqual(list(bucket.acl), [{'entity': 'allUsers', 'role': ROLE}]) + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/b/%s/acl' % NAME) def test_reload_acl_lazy(self): from gcloud.storage.acl import BucketACL NAME = 'name' ROLE = 'role' - after = {'acl': [{'entity': 'allUsers', 'role': ROLE}]} - connection = _Connection(after) + connection = _Connection( + {'items': [{'entity': 'allUsers', 'role': ROLE}]}) bucket = self._makeOne(connection, NAME) self.assertTrue(bucket.reload_acl() is bucket) self.assertTrue(isinstance(bucket.acl, BucketACL)) @@ -581,26 +600,27 @@ def test_reload_acl_lazy(self): kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(kw[0]['path'], '/b/%s/acl' % NAME) def test_get_acl_lazy(self): from gcloud.storage.acl import BucketACL - metadata = {'acl': []} - connection = _Connection() - bucket = self._makeOne(metadata=metadata) + NAME = 'name' + connection = _Connection({'items': []}) + bucket = self._makeOne(connection, NAME) acl = bucket.get_acl() self.assertTrue(acl is bucket.acl) self.assertTrue(isinstance(acl, BucketACL)) self.assertEqual(list(bucket.acl), []) kw = connection._requested - self.assertEqual(len(kw), 0) + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/b/%s/acl' % NAME) def test_get_acl_eager(self): - from gcloud.storage.acl import BucketACL connection = _Connection() bucket = self._makeOne() - preset = bucket.acl = BucketACL(bucket) + preset = bucket.acl # Ensure it is assigned + preset.loaded = True acl = bucket.get_acl() self.assertTrue(acl is preset) kw = connection._requested @@ -616,15 +636,14 @@ def test_save_acl_none_set_none_passed(self): def test_save_acl_existing_set_none_passed(self): NAME = 'name' connection = _Connection({'foo': 'Foo', 'acl': []}) - metadata = {'acl': []} - bucket = self._makeOne(connection, NAME, metadata) - bucket.reload_acl() + bucket = self._makeOne(connection, NAME) + bucket.acl.loaded = True self.assertTrue(bucket.save_acl() is bucket) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['data'], metadata) + self.assertEqual(kw[0]['data'], {'acl': []}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) def test_save_acl_existing_set_new_passed(self): @@ -632,9 +651,8 @@ def test_save_acl_existing_set_new_passed(self): ROLE = 'role' new_acl = [{'entity': 'allUsers', 'role': ROLE}] connection = _Connection({'foo': 'Foo', 'acl': new_acl}) - metadata = {'acl': []} - bucket = self._makeOne(connection, NAME, metadata) - bucket.reload_acl() + bucket = self._makeOne(connection, NAME) + bucket.acl.loaded = True self.assertTrue(bucket.save_acl(new_acl) is bucket) self.assertEqual(list(bucket.acl), new_acl) kw = connection._requested @@ -646,14 +664,17 @@ def test_save_acl_existing_set_new_passed(self): def test_clear_acl(self): NAME = 'name' - ROLE = 'role' - old_acl = [{'entity': 'allUsers', 'role': ROLE}] - connection = _Connection({'foo': 'Foo', 'acl': []}) - metadata = {'acl': old_acl} - bucket = self._makeOne(connection, NAME, metadata) - bucket.reload_acl() + ROLE1 = 'role1' + ROLE2 = 'role2' + STICKY = {'entity': 'allUsers', 'role': ROLE2} + connection = _Connection( + # Emulate back-end, which makes some entries "sticky". + {'foo': 'Foo', 'acl': [STICKY]}, + ) + bucket = self._makeOne(connection, NAME) + bucket.acl.entity('allUsers', ROLE1) self.assertTrue(bucket.clear_acl() is bucket) - self.assertEqual(list(bucket.acl), []) + self.assertEqual(list(bucket.acl), [STICKY]) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') @@ -662,57 +683,67 @@ def test_clear_acl(self): self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) def test_reload_default_object_acl_eager_empty(self): - from gcloud.storage.acl import BucketACL - metadata = {'defaultObjectAcl': []} - bucket = self._makeOne(metadata=metadata) + from gcloud.storage.acl import DefaultObjectACL + NAME = 'name' + after = {'items': []} + connection = _Connection(after) + bucket = self._makeOne(connection, NAME) + bucket.default_object_acl.loaded = True self.assertTrue(bucket.reload_default_object_acl() is bucket) - self.assertTrue(isinstance(bucket.default_object_acl, BucketACL)) + self.assertTrue( + isinstance(bucket.default_object_acl, DefaultObjectACL)) self.assertEqual(list(bucket.default_object_acl), []) def test_reload_default_object_acl_eager_nonempty(self): - from gcloud.storage.acl import BucketACL + from gcloud.storage.acl import DefaultObjectACL + NAME = 'name' ROLE = 'role' - metadata = {'defaultObjectAcl': [{'entity': 'allUsers', 'role': ROLE}]} - bucket = self._makeOne(metadata=metadata) + after = {'items': [{'entity': 'allUsers', 'role': ROLE}]} + connection = _Connection(after) + bucket = self._makeOne(connection, NAME) + bucket.default_object_acl.entity('allUsers', 'OTHERROLE') self.assertTrue(bucket.reload_default_object_acl() is bucket) - self.assertTrue(isinstance(bucket.default_object_acl, BucketACL)) + self.assertTrue( + isinstance(bucket.default_object_acl, DefaultObjectACL)) self.assertEqual(list(bucket.default_object_acl), [{'entity': 'allUsers', 'role': ROLE}]) def test_reload_default_object_acl_lazy(self): - from gcloud.storage.acl import BucketACL + from gcloud.storage.acl import DefaultObjectACL NAME = 'name' ROLE = 'role' - after = {'defaultObjectAcl': [{'entity': 'allUsers', 'role': ROLE}]} + after = {'items': [{'entity': 'allUsers', 'role': ROLE}]} connection = _Connection(after) bucket = self._makeOne(connection, NAME) self.assertTrue(bucket.reload_default_object_acl() is bucket) - self.assertTrue(isinstance(bucket.default_object_acl, BucketACL)) + self.assertTrue( + isinstance(bucket.default_object_acl, DefaultObjectACL)) self.assertEqual(list(bucket.default_object_acl), [{'entity': 'allUsers', 'role': ROLE}]) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(kw[0]['path'], '/b/%s/defaultObjectAcl' % NAME) def test_get_default_object_acl_lazy(self): from gcloud.storage.acl import BucketACL - metadata = {'defaultObjectAcl': []} - connection = _Connection() - bucket = self._makeOne(metadata=metadata) + NAME = 'name' + connection = _Connection({'items': []}) + bucket = self._makeOne(connection, NAME) acl = bucket.get_default_object_acl() self.assertTrue(acl is bucket.default_object_acl) self.assertTrue(isinstance(acl, BucketACL)) self.assertEqual(list(bucket.default_object_acl), []) kw = connection._requested - self.assertEqual(len(kw), 0) + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/b/%s/defaultObjectAcl' % NAME) def test_get_default_object_acl_eager(self): - from gcloud.storage.acl import BucketACL connection = _Connection() bucket = self._makeOne() - preset = bucket.default_object_acl = BucketACL(bucket) + preset = bucket.default_object_acl # ensure it is assigned + preset.loaded = True acl = bucket.get_default_object_acl() self.assertTrue(acl is preset) kw = connection._requested @@ -727,83 +758,66 @@ def test_save_default_object_acl_none_set_none_passed(self): def test_save_default_object_acl_existing_set_none_passed(self): NAME = 'name' - connection = _Connection({'foo': 'Foo', 'acl': []}) connection = _Connection( - {'foo': 'Foo', 'acl': []}, {'foo': 'Foo', 'acl': [], 'defaultObjectAcl': []}, ) - metadata = {'defaultObjectAcl': []} - bucket = self._makeOne(connection, NAME, metadata) - bucket.reload_default_object_acl() + bucket = self._makeOne(connection, NAME) + bucket.default_object_acl.loaded = True self.assertTrue(bucket.save_default_object_acl() is bucket) kw = connection._requested - self.assertEqual(len(kw), 2) + self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['data'], metadata) + self.assertEqual(kw[0]['data'], {'defaultObjectAcl': []}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - self.assertEqual(kw[1]['method'], 'GET') - self.assertEqual(kw[1]['path'], '/b/%s' % NAME) - self.assertEqual(kw[1]['query_params'], {'projection': 'full'}) def test_save_default_object_acl_existing_set_new_passed(self): NAME = 'name' ROLE = 'role' new_acl = [{'entity': 'allUsers', 'role': ROLE}] connection = _Connection( - {'foo': 'Foo', 'acl': new_acl}, {'foo': 'Foo', 'acl': new_acl, 'defaultObjectAcl': new_acl}, ) metadata = {'defaultObjectAcl': []} bucket = self._makeOne(connection, NAME, metadata) - bucket.reload_default_object_acl() + bucket.default_object_acl.loaded = True self.assertTrue(bucket.save_default_object_acl(new_acl) is bucket) self.assertEqual(list(bucket.default_object_acl), new_acl) kw = connection._requested - self.assertEqual(len(kw), 2) + self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) self.assertEqual(kw[0]['data'], {'defaultObjectAcl': new_acl}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - self.assertEqual(kw[1]['method'], 'GET') - self.assertEqual(kw[1]['path'], '/b/%s' % NAME) - self.assertEqual(kw[1]['query_params'], {'projection': 'full'}) def test_clear_default_object_acl(self): NAME = 'name' ROLE = 'role' - old_acl = [{'entity': 'allUsers', 'role': ROLE}] connection = _Connection( - {'foo': 'Foo', 'acl': []}, {'foo': 'Foo', 'acl': [], 'defaultObjectAcl': []}, ) - metadata = {'defaultObjectAcl': old_acl} - bucket = self._makeOne(connection, NAME, metadata) - bucket.reload_default_object_acl() + bucket = self._makeOne(connection, NAME) + bucket.default_object_acl.entity('allUsers', ROLE) self.assertTrue(bucket.clear_default_object_acl() is bucket) self.assertEqual(list(bucket.default_object_acl), []) kw = connection._requested - self.assertEqual(len(kw), 2) + self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) self.assertEqual(kw[0]['data'], {'defaultObjectAcl': []}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - self.assertEqual(kw[1]['method'], 'GET') - self.assertEqual(kw[1]['path'], '/b/%s' % NAME) - self.assertEqual(kw[1]['query_params'], {'projection': 'full'}) def test_make_public_defaults(self): from gcloud.storage.acl import _ACLEntity NAME = 'name' - before = {'acl': [], 'defaultObjectAcl': []} permissive = [{'entity': 'allUsers', 'role': _ACLEntity.READER_ROLE}] after = {'acl': permissive, 'defaultObjectAcl': []} connection = _Connection(after) - bucket = self._makeOne(connection, NAME, before) + bucket = self._makeOne(connection, NAME) + bucket.acl.loaded = True bucket.make_public() - self.assertEqual(bucket.metadata, after) - self.assertEqual(list(bucket.acl), after['acl']) - self.assertEqual(bucket.default_object_acl, None) + self.assertEqual(list(bucket.acl), permissive) + self.assertEqual(list(bucket.default_object_acl), []) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') @@ -814,27 +828,25 @@ def test_make_public_defaults(self): def test_make_public_w_future(self): from gcloud.storage.acl import _ACLEntity NAME = 'name' - before = {'acl': [], 'defaultObjectAcl': []} permissive = [{'entity': 'allUsers', 'role': _ACLEntity.READER_ROLE}] after1 = {'acl': permissive, 'defaultObjectAcl': []} after2 = {'acl': permissive, 'defaultObjectAcl': permissive} connection = _Connection(after1, after2) - bucket = self._makeOne(connection, NAME, before) + bucket = self._makeOne(connection, NAME) + bucket.acl.loaded = True + bucket.default_object_acl.loaded = True bucket.make_public(future=True) - self.assertEqual(bucket.metadata, after2) - self.assertEqual(list(bucket.acl), after2['acl']) - self.assertEqual(list(bucket.default_object_acl), - after2['defaultObjectAcl']) + self.assertEqual(list(bucket.acl), permissive) + self.assertEqual(list(bucket.default_object_acl), permissive) kw = connection._requested self.assertEqual(len(kw), 2) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['data'], {'acl': after1['acl']}) + self.assertEqual(kw[0]['data'], {'acl': permissive}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) self.assertEqual(kw[1]['method'], 'PATCH') self.assertEqual(kw[1]['path'], '/b/%s' % NAME) - self.assertEqual(kw[1]['data'], {'defaultObjectAcl': - after2['defaultObjectAcl']}) + self.assertEqual(kw[1]['data'], {'defaultObjectAcl': permissive}) self.assertEqual(kw[1]['query_params'], {'projection': 'full'}) def test_make_public_recursive(self): @@ -870,22 +882,21 @@ def get_items_from_response(self, response): NAME = 'name' KEY = 'key' - before = {'acl': [], 'defaultObjectAcl': []} permissive = [{'entity': 'allUsers', 'role': _ACLEntity.READER_ROLE}] after = {'acl': permissive, 'defaultObjectAcl': []} connection = _Connection(after, {'items': [{'name': KEY}]}) - bucket = self._makeOne(connection, NAME, before) + bucket = self._makeOne(connection, NAME) + bucket.acl.loaded = True with _Monkey(MUT, _KeyIterator=_KeyIterator): bucket.make_public(recursive=True) - self.assertEqual(bucket.metadata, after) - self.assertEqual(list(bucket.acl), after['acl']) - self.assertEqual(bucket.default_object_acl, None) + self.assertEqual(list(bucket.acl), permissive) + self.assertEqual(list(bucket.default_object_acl), []) self.assertEqual(_saved, [(bucket, KEY, True)]) kw = connection._requested self.assertEqual(len(kw), 2) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['data'], {'acl': after['acl']}) + self.assertEqual(kw[0]['data'], {'acl': permissive}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) self.assertEqual(kw[1]['method'], 'GET') self.assertEqual(kw[1]['path'], '/b/%s/o' % NAME) diff --git a/gcloud/storage/test_key.py b/gcloud/storage/test_key.py index 3a2ac3891ae0..80ec615c3c5a 100644 --- a/gcloud/storage/test_key.py +++ b/gcloud/storage/test_key.py @@ -16,7 +16,7 @@ def test_ctor_defaults(self): self.assertEqual(key.connection, None) self.assertEqual(key.name, None) self.assertEqual(key.metadata, {}) - self.assertEqual(key.acl, None) + self.assertTrue(key._acl is None) def test_ctor_explicit(self): KEY = 'key' @@ -28,7 +28,7 @@ def test_ctor_explicit(self): self.assertTrue(key.connection is connection) self.assertEqual(key.name, KEY) self.assertEqual(key.metadata, metadata) - self.assertEqual(key.acl, None) + self.assertTrue(key._acl is None) def test_from_dict_defaults(self): KEY = 'key' @@ -39,7 +39,7 @@ def test_from_dict_defaults(self): self.assertEqual(key.connection, None) self.assertEqual(key.name, KEY) self.assertEqual(key.metadata, metadata) - self.assertEqual(key.acl, None) + self.assertTrue(key._acl is None) def test_from_dict_explicit(self): KEY = 'key' @@ -52,7 +52,14 @@ def test_from_dict_explicit(self): self.assertTrue(key.connection is connection) self.assertEqual(key.name, KEY) self.assertEqual(key.metadata, metadata) - self.assertEqual(key.acl, None) + self.assertTrue(key._acl is None) + + def test_acl_property(self): + from gcloud.storage.acl import ObjectACL + key = self._makeOne() + acl = key.acl + self.assertTrue(isinstance(acl, ObjectACL)) + self.assertTrue(acl is key._acl) def test_path_no_bucket(self): key = self._makeOne() @@ -342,7 +349,7 @@ def test_has_metdata_hit(self): key = self._makeOne(metadata=metadata) self.assertTrue(key.has_metadata(KEY)) - def test_reload_metadata_default(self): + def test_reload_metadata(self): KEY = 'key' before = {'foo': 'Foo'} after = {'bar': 'Bar'} @@ -358,22 +365,6 @@ def test_reload_metadata_default(self): self.assertEqual(kw[0]['path'], '/b/name/o/%s' % KEY) self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) - def test_reload_metadata_explicit(self): - KEY = 'key' - before = {'foo': 'Foo'} - after = {'bar': 'Bar'} - connection = _Connection(after) - bucket = _Bucket(connection) - key = self._makeOne(bucket, KEY, before) - found = key.reload_metadata(True) - self.assertTrue(found is key) - self.assertEqual(found.metadata, after) - kw = connection._requested - self.assertEqual(len(kw), 1) - self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/name/o/%s' % KEY) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - def test_get_metadata_none_set_none_passed(self): KEY = 'key' after = {'bar': 'Bar'} @@ -389,36 +380,25 @@ def test_get_metadata_none_set_none_passed(self): self.assertEqual(kw[0]['path'], '/b/name/o/%s' % KEY) self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) - def test_get_metadata_none_set_acl_hit(self): + def test_get_metadata_acl_no_default(self): KEY = 'key' - after = {'bar': 'Bar', 'acl': []} - connection = _Connection(after) + connection = _Connection() bucket = _Bucket(connection) key = self._makeOne(bucket, KEY) - found = key.get_metadata('acl') - self.assertEqual(found, []) - self.assertEqual(key.metadata, after) + self.assertRaises(KeyError, key.get_metadata, 'acl') kw = connection._requested - self.assertEqual(len(kw), 1) - self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/name/o/%s' % KEY) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(len(kw), 0) - def test_get_metadata_none_set_acl_miss_explicit_default(self): + def test_get_metadata_acl_w_default(self): KEY = 'key' after = {'bar': 'Bar'} connection = _Connection(after) bucket = _Bucket(connection) key = self._makeOne(bucket, KEY) default = object() - found = key.get_metadata('acl', default) - self.assertTrue(found is default) - self.assertEqual(key.metadata, after) + self.assertRaises(KeyError, key.get_metadata, 'acl', default) kw = connection._requested - self.assertEqual(len(kw), 1) - self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/name/o/%s' % KEY) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(len(kw), 0) def test_get_metadata_miss(self): KEY = 'key' @@ -461,27 +441,39 @@ def test_patch_metadata(self): def test_reload_acl_eager_empty(self): from gcloud.storage.acl import ObjectACL - metadata = {'acl': []} - key = self._makeOne(metadata=metadata) + KEY = 'key' + ROLE = 'role' + after = {'items': [{'entity': 'allUsers', 'role': ROLE}]} + connection = _Connection(after) + bucket = _Bucket(connection) + key = self._makeOne(bucket, KEY) + key.acl.loaded = True self.assertTrue(key.reload_acl() is key) self.assertTrue(isinstance(key.acl, ObjectACL)) - self.assertEqual(list(key.acl), []) + self.assertEqual(list(key.acl), after['items']) + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/b/name/o/%s/acl' % KEY) def test_reload_acl_eager_nonempty(self): from gcloud.storage.acl import ObjectACL + KEY = 'key' ROLE = 'role' - metadata = {'acl': [{'entity': 'allUsers', 'role': ROLE}]} - key = self._makeOne(metadata=metadata) + after = {'items': []} + connection = _Connection(after) + bucket = _Bucket(connection) + key = self._makeOne(bucket, KEY) + key.acl.entity('allUsers', ROLE) self.assertTrue(key.reload_acl() is key) self.assertTrue(isinstance(key.acl, ObjectACL)) - self.assertEqual(list(key.acl), - [{'entity': 'allUsers', 'role': ROLE}]) + self.assertEqual(list(key.acl), []) def test_reload_acl_lazy(self): from gcloud.storage.acl import ObjectACL KEY = 'key' ROLE = 'role' - after = {'acl': [{'entity': 'allUsers', 'role': ROLE}]} + after = {'items': [{'entity': 'allUsers', 'role': ROLE}]} connection = _Connection(after) bucket = _Bucket(connection) key = self._makeOne(bucket, KEY) @@ -492,22 +484,23 @@ def test_reload_acl_lazy(self): kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/name/o/%s' % KEY) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + self.assertEqual(kw[0]['path'], '/b/name/o/%s/acl' % KEY) def test_get_acl_lazy(self): from gcloud.storage.acl import ObjectACL - metadata = {'acl': []} - key = self._makeOne(metadata=metadata) + KEY = 'key' + connection = _Connection({'items': []}) + bucket = _Bucket(connection) + key = self._makeOne(bucket, KEY) acl = key.get_acl() self.assertTrue(acl is key.acl) self.assertTrue(isinstance(acl, ObjectACL)) self.assertEqual(list(key.acl), []) def test_get_acl_eager(self): - from gcloud.storage.acl import ObjectACL key = self._makeOne() - preset = key.acl = ObjectACL(key) + preset = key.acl + preset.loaded = True acl = key.get_acl() self.assertTrue(acl is preset) @@ -524,15 +517,14 @@ def test_save_acl_existing_set_none_passed(self): KEY = 'key' connection = _Connection({'foo': 'Foo', 'acl': []}) bucket = _Bucket(connection) - metadata = {'acl': []} - key = self._makeOne(bucket, KEY, metadata) - key.reload_acl() + key = self._makeOne(bucket, KEY) + key.acl.loaded = True self.assertTrue(key.save_acl() is key) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/name/o/%s' % KEY) - self.assertEqual(kw[0]['data'], metadata) + self.assertEqual(kw[0]['data'], {'acl': []}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) def test_save_acl_existing_set_new_passed(self): @@ -541,9 +533,8 @@ def test_save_acl_existing_set_new_passed(self): new_acl = [{'entity': 'allUsers', 'role': ROLE}] connection = _Connection({'foo': 'Foo', 'acl': new_acl}) bucket = _Bucket(connection) - metadata = {'acl': []} - key = self._makeOne(bucket, KEY, metadata) - key.reload_acl() + key = self._makeOne(bucket, KEY) + key.acl.entity('allUsers', 'other-role') self.assertTrue(key.save_acl(new_acl) is key) self.assertEqual(list(key.acl), new_acl) kw = connection._requested @@ -556,12 +547,10 @@ def test_save_acl_existing_set_new_passed(self): def test_clear_acl(self): KEY = 'key' ROLE = 'role' - old_acl = [{'entity': 'allUsers', 'role': ROLE}] connection = _Connection({'foo': 'Foo', 'acl': []}) bucket = _Bucket(connection) - metadata = {'acl': old_acl} - key = self._makeOne(bucket, KEY, metadata) - key.reload_acl() + key = self._makeOne(bucket, KEY) + key.acl.entity('allUsers', ROLE) self.assertTrue(key.clear_acl() is key) self.assertEqual(list(key.acl), []) kw = connection._requested @@ -574,20 +563,19 @@ def test_clear_acl(self): def test_make_public(self): from gcloud.storage.acl import _ACLEntity KEY = 'key' - before = {'acl': []} permissive = [{'entity': 'allUsers', 'role': _ACLEntity.READER_ROLE}] after = {'acl': permissive} connection = _Connection(after) bucket = _Bucket(connection) - key = self._makeOne(bucket, KEY, before) + key = self._makeOne(bucket, KEY) + key.acl.loaded = True key.make_public() - self.assertEqual(key.metadata, after) - self.assertEqual(list(key.acl), after['acl']) + self.assertEqual(list(key.acl), permissive) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/name/o/%s' % KEY) - self.assertEqual(kw[0]['data'], {'acl': after['acl']}) + self.assertEqual(kw[0]['data'], {'acl': permissive}) self.assertEqual(kw[0]['query_params'], {'projection': 'full'})