diff --git a/gcloud/storage/_helpers.py b/gcloud/storage/_helpers.py index 9f724045a82b..d4a4125b1bd3 100644 --- a/gcloud/storage/_helpers.py +++ b/gcloud/storage/_helpers.py @@ -4,35 +4,23 @@ """ -class _MetadataMixin(object): - """Abstract mixin for cloud storage classes with associated metadata. +class _PropertyMixin(object): + """Abstract mixin for cloud storage classes with associated propertties. Non-abstract subclasses should implement: - - CUSTOM_METADATA_FIELDS + - CUSTOM_PROPERTY_ACCESSORS - connection - path """ - CUSTOM_METADATA_FIELDS = None + CUSTOM_PROPERTY_ACCESSORS = None """Mapping of field name -> accessor for fields w/ custom accessors. Expected to be set by subclasses. Fields in this mapping will cause - `get_metadata()` to raise a KeyError with a message to use the relevant - accessor methods. + :meth:`_get_property()` to raise a KeyError with a message to use the + relevant accessor methods. """ - def __init__(self, name=None, metadata=None): - """_MetadataMixin constructor. - - :type name: string - :param name: The name of the object. - - :type metadata: dict - :param metadata: All the other data provided by Cloud Storage. - """ - self.name = name - self.metadata = metadata - @property def connection(self): """Abstract getter for the connection to use.""" @@ -43,90 +31,91 @@ def path(self): """Abstract getter for the object path.""" raise NotImplementedError - def has_metadata(self, field=None): - """Check if metadata is available. + def __init__(self, name=None, properties=None): + """_PropertyMixin constructor. - :type field: string - :param field: (optional) the particular field to check for. + :type name: string + :param name: The name of the object. - :rtype: bool - :returns: Whether metadata is available locally. + :type properties: dict + :param properties: All the other data provided by Cloud Storage. """ - if not self.metadata: - return False - elif field and field not in self.metadata: - return False - else: - return True + self.name = name + self._properties = {} + if properties is not None: + self._properties.update(properties) - def reload_metadata(self): - """Reload metadata from Cloud Storage. + @property + def properties(self): + """Ensure properties are loaded, and return a copy. + + :rtype: dict + """ + if not self._properties: + self._reload_properties() + return self._properties.copy() - :rtype: :class:`_MetadataMixin` + def _reload_properties(self): + """Reload properties from Cloud Storage. + + :rtype: :class:`_PropertyMixin` :returns: The object you just reloaded data for. """ # Pass only '?projection=noAcl' here because 'acl' and related # are handled via 'get_acl()' etc. query_params = {'projection': 'noAcl'} - self.metadata = self.connection.api_request( + self._properties = self.connection.api_request( method='GET', path=self.path, query_params=query_params) return self - def get_metadata(self, field=None, default=None): - """Get all metadata or a specific field. + 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 also reload the properties locally based on the server's + response. + + :type properties: dict + :param properties: The dictionary of values to update. + + :rtype: :class:`_PropertyMixin` + :returns: The current object. + """ + # Pass '?projection=full' here because 'PATCH' documented not + # to work properly w/ 'noAcl'. + self._properties = self.connection.api_request( + method='PATCH', path=self.path, data=properties, + query_params={'projection': 'full'}) + return self + + def _get_property(self, field, default=None): + """Return the value of a field from the server-side representation. If you request a field that isn't available, and that field can be retrieved by refreshing data from Cloud Storage, this method - will reload the data using :func:`_MetadataMixin.reload_metadata`. + will reload the data using :func:`_PropertyMixin._reload_properties`. :type field: string - :param field: (optional) A particular field to retrieve from metadata. + :param field: A particular field to retrieve from properties. :type default: anything :param default: The value to return if the field provided wasn't found. - :rtype: dict or anything - :returns: All metadata or the value of the specific field. - - :raises: :class:`KeyError` if the field is in CUSTOM_METADATA_FIELDS. + :rtype: anything + :returns: value of the specific field, or the default if not found. """ - # We ignore 'acl' and related fields because they are meant to be - # handled via 'get_acl()' and related methods. - custom = self.CUSTOM_METADATA_FIELDS.get(field) + # Raise for fields which have custom accessors. + custom = self.CUSTOM_PROPERTY_ACCESSORS.get(field) if custom is not None: - message = 'Use %s or related methods instead.' % custom + message = "Use '%s' or related methods instead." % custom raise KeyError((field, message)) - if not self.has_metadata(field=field): - self.reload_metadata() - - if field: - return self.metadata.get(field, default) - else: - return self.metadata - - def patch_metadata(self, metadata): - """Update particular fields of this object's metadata. - - This method will only update the fields provided and will not - touch the other fields. - - It will also reload the metadata locally based on the server's - response. - - :type metadata: dict - :param metadata: The dictionary of values to update. - - :rtype: :class:`_MetadataMixin` - :returns: The current object. - """ - self.metadata = self.connection.api_request( - method='PATCH', path=self.path, data=metadata, - query_params={'projection': 'full'}) - return self + return self.properties.get(field, default) def get_acl(self): - """Get ACL metadata as an object. + """Get ACL as an object. :returns: An ACL object for the current object. """ diff --git a/gcloud/storage/bucket.py b/gcloud/storage/bucket.py index e99680e257ec..7d60a8c1cd4d 100644 --- a/gcloud/storage/bucket.py +++ b/gcloud/storage/bucket.py @@ -2,7 +2,7 @@ import os -from gcloud.storage._helpers import _MetadataMixin +from gcloud.storage._helpers import _PropertyMixin from gcloud.storage import exceptions from gcloud.storage.acl import BucketACL from gcloud.storage.acl import DefaultObjectACL @@ -11,7 +11,7 @@ from gcloud.storage.key import _KeyIterator -class Bucket(_MetadataMixin): +class Bucket(_PropertyMixin): """A class representing a Bucket on Cloud Storage. :type connection: :class:`gcloud.storage.connection.Connection` @@ -21,34 +21,33 @@ class Bucket(_MetadataMixin): :param name: The name of the bucket. """ - CUSTOM_METADATA_FIELDS = { - 'acl': 'get_acl', - 'defaultObjectAcl': 'get_default_object_acl', - 'lifecycle': 'get_lifecycle', + CUSTOM_PROPERTY_ACCESSORS = { + 'acl': 'get_acl()', + 'cors': 'get_cors()', + 'defaultObjectAcl': 'get_default_object_acl()', + 'etag': 'etag', + 'id': 'id', + 'lifecycle': 'get_lifecycle()', + 'location': 'get_location()', + 'logging': 'get_logging()', + 'metageneration': 'metageneration', + 'name': 'name', + 'owner': 'owner', + 'projectNumber': 'project_number', + 'selfLink': 'self_link', + 'storageClass': 'storage_class', + 'timeCreated': 'time_created', + 'versioning': 'get_versioning()', } - """Mapping of field name -> accessor for fields w/ custom accessors.""" + """Map field name -> accessor for fields w/ custom accessors.""" # ACL rules are lazily retrieved. _acl = _default_object_acl = None - def __init__(self, connection=None, name=None, metadata=None): - super(Bucket, self).__init__(name=name, metadata=metadata) + def __init__(self, connection=None, name=None, properties=None): + super(Bucket, self).__init__(name=name, properties=properties) self._connection = connection - @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): """Construct a new bucket from a dictionary of data from Cloud Storage. @@ -60,7 +59,7 @@ def from_dict(cls, bucket_dict, connection=None): :returns: A bucket constructed from the data provided. """ return cls(connection=connection, name=bucket_dict['name'], - metadata=bucket_dict) + properties=bucket_dict) def __repr__(self): return '' % self.name @@ -71,6 +70,20 @@ def __iter__(self): def __contains__(self, key): return self.get_key(key) is not 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 + @property def connection(self): """Getter property for the connection to use with this Bucket. @@ -120,7 +133,7 @@ def get_all_keys(self): """List all the keys in this bucket. This will **not** retrieve all the data for all the keys, it - will only retrieve metadata about the keys. + will only retrieve the keys. This is equivalent to:: @@ -343,17 +356,250 @@ def upload_file_object(self, file_obj, key=None): key = self.new_key(os.path.basename(file_obj.name)) return key.upload_from_file(file_obj) + def get_cors(self): + """Retrieve CORS policies configured for this bucket. + + See: http://www.w3.org/TR/cors/ and + https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: list(dict) + :returns: A sequence of mappings describing each CORS policy. + """ + return [policy.copy() for policy in self.properties.get('cors', ())] + + def update_cors(self, entries): + """Update CORS policies configured for this bucket. + + See: http://www.w3.org/TR/cors/ and + https://cloud.google.com/storage/docs/json_api/v1/buckets + + :type entries: list(dict) + :param entries: A sequence of mappings describing each CORS policy. + """ + self._patch_properties({'cors': entries}) + + def get_default_object_acl(self): + """Get the current Default Object ACL rules. + + If the acl isn't available locally, this method will reload it from + Cloud Storage. + + :rtype: :class:`gcloud.storage.acl.DefaultObjectACL` + :returns: A DefaultObjectACL object for this bucket. + """ + if not self.default_object_acl.loaded: + self.default_object_acl.reload() + return self.default_object_acl + + @property + def etag(self): + """Retrieve the ETag for the bucket. + + See: http://tools.ietf.org/html/rfc2616#section-3.11 and + https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: string + :returns: a unique identifier for the bucket and current metadata. + """ + return self.properties['etag'] + + @property + def id(self): + """Retrieve the ID for the bucket. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: string + :returns: a unique identifier for the bucket. + """ + return self.properties['id'] + + def get_lifecycle(self): + """Retrieve lifecycle rules configured for this bucket. + + See: https://cloud.google.com/storage/docs/lifecycle and + https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: list(dict) + :returns: A sequence of mappings describing each lifecycle rule. + """ + info = self.properties.get('lifecycle', {}) + return [rule.copy() for rule in info.get('rule', ())] + + def update_lifecycle(self, rules): + """Update CORS policies configured for this bucket. + + See: https://cloud.google.com/storage/docs/lifecycle and + https://cloud.google.com/storage/docs/json_api/v1/buckets + + :type rules: list(dict) + :param rules: A sequence of mappings describing each lifecycle rule. + """ + self._patch_properties({'lifecycle': {'rule': rules}}) + + def get_location(self): + """Retrieve location configured for this bucket. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets and + https://cloud.google.com/storage/docs/concepts-techniques#specifyinglocations + + :rtype: string + :returns: The configured location. + """ + return self.properties.get('location') + + def set_location(self, location): + """Update location configured for this bucket. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets and + https://cloud.google.com/storage/docs/concepts-techniques#specifyinglocations + + :type location: string + :param location: The new configured location. + """ + self._patch_properties({'location': location}) + + def get_logging(self): + """Return info about access logging for this bucket. + + See: https://cloud.google.com/storage/docs/accesslogs#status + + :rtype: dict or None + :returns: a dict w/ keys, ``logBucket`` and ``logObjectPrefix`` + (if logging is enabled), or None (if not). + """ + info = self.properties.get('logging') + if info is not None: + return info.copy() + + def enable_logging(self, bucket_name, object_prefix=''): + """Enable access logging for this bucket. + + See: https://cloud.google.com/storage/docs/accesslogs#delivery + + :type bucket_name: string + :param bucket_name: name of bucket in which to store access logs + + :type object_prefix: string + :param object_prefix: prefix for access log filenames + """ + info = {'logBucket': bucket_name, 'logObjectPrefix': object_prefix} + self._patch_properties({'logging': info}) + + def disable_logging(self): + """Disable access logging for this bucket. + + See: https://cloud.google.com/storage/docs/accesslogs#disabling + """ + self._patch_properties({'logging': None}) + + @property + def metageneration(self): + """Retrieve the metageneration for the bucket. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: integer + :returns: count of times since creation the bucket's metadata has + been updated. + """ + return self.properties['metageneration'] + + @property + def owner(self): + """Retrieve info about the owner of the bucket. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: dict + :returns: mapping of owner's role/ID. + """ + return self.properties['owner'].copy() + + @property + def project_number(self): + """Retrieve the number of the project to which the bucket is assigned. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: integer + :returns: a unique identifier for the bucket. + """ + return self.properties['projectNumber'] + + @property + def self_link(self): + """Retrieve the URI for the bucket. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: string + :returns: URI of the bucket. + """ + return self.properties['selfLink'] + + @property + def storage_class(self): + """Retrieve the storage class for the bucket. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets and + https://cloud.google.com/storage/docs/durable-reduced-availability#_DRA_Bucket + + :rtype: string + :returns: the storage class for the bucket (currently one of + ``STANDARD``, ``DURABLE_REDUCED_AVAILABILITY``) + """ + return self.properties['storageClass'] + + @property + def time_created(self): + """Retrieve the timestamp at which the bucket was created. + + See: https://cloud.google.com/storage/docs/json_api/v1/buckets + + :rtype: string + :returns: timestamp for the bucket's creation, in RFC 3339 format. + """ + return self.properties['timeCreated'] + + def get_versioning(self): + """Is versioning enabled for this bucket? + + See: https://cloud.google.com/storage/docs/object-versioning for + details. + + :rtype: boolean + :returns: True if enabled, else False. + """ + versioning = self.properties.get('versioning', {}) + return versioning.get('enabled', False) + + def enable_versioning(self): + """Enable versioning for this bucket. + + See: https://cloud.google.com/storage/docs/object-versioning for + details. + """ + self._patch_properties({'versioning': {'enabled': True}}) + + def disable_versioning(self): + """Disable versioning for this bucket. + + See: https://cloud.google.com/storage/docs/object-versioning for + details. + """ + self._patch_properties({'versioning': {'enabled': False}}) + def configure_website(self, main_page_suffix=None, not_found_page=None): - """Configure website-related metadata. + """Configure website-related properties. + + See: https://developers.google.com/storage/docs/website-configuration .. note:: This (apparently) only works if your bucket name is a domain name (and to do that, you need to get approved somehow...). - Check out the official documentation here: - https://developers.google.com/storage/docs/website-configuration - If you want this bucket to host a website, just provide the name of an index page and a page to use when a key isn't found:: @@ -385,7 +631,7 @@ def configure_website(self, main_page_suffix=None, not_found_page=None): 'notFoundPage': not_found_page, }, } - return self.patch_metadata(data) + return self._patch_properties(data) def disable_website(self): """Disable the website configuration for this bucket. @@ -395,29 +641,6 @@ def disable_website(self): """ return self.configure_website(None, None) - def get_acl(self): - """Get ACL metadata as a :class:`gcloud.storage.acl.BucketACL` object. - - :rtype: :class:`gcloud.storage.acl.BucketACL` - :returns: An ACL object for the current bucket. - """ - if not self.acl.loaded: - self.acl.reload() - return self.acl - - def get_default_object_acl(self): - """Get the current Default Object ACL rules. - - If the appropriate metadata isn't available locally, this method - will reload it from Cloud Storage. - - :rtype: :class:`gcloud.storage.acl.DefaultObjectACL` - :returns: A DefaultObjectACL object for this bucket. - """ - if not self.default_object_acl.loaded: - self.default_object_acl.reload() - return self.default_object_acl - def make_public(self, recursive=False, future=False): """Make a bucket public. @@ -442,35 +665,6 @@ def make_public(self, recursive=False, future=False): key.get_acl().all().grant_read() key.save_acl() - def get_lifecycle(self): - """Retrieve CORS policies configured for this bucket. - - See: https://cloud.google.com/storage/docs/lifecycle and - https://cloud.google.com/storage/docs/json_api/v1/buckets - - :rtype: list(dict) - :returns: A sequence of mappings describing each CORS policy. - """ - if not self.has_metadata('lifecycle'): - self.reload_metadata() - result = [] - info = self.metadata.get('lifecycle', {}) - for rule in info.get('rule', ()): - rule = rule.copy() - result.append(rule) - return result - - def update_lifecycle(self, rules): - """Update CORS policies configured for this bucket. - - See: https://cloud.google.com/storage/docs/lifecycle and - https://cloud.google.com/storage/docs/json_api/v1/buckets - - :type rules: list(dict) - :param rules: A sequence of mappings describing each lifecycle policy. - """ - self.patch_metadata({'lifecycle': {'rule': rules}}) - class BucketIterator(Iterator): """An iterator listing all buckets. diff --git a/gcloud/storage/key.py b/gcloud/storage/key.py index cac96b2471f9..935d71c98c7a 100644 --- a/gcloud/storage/key.py +++ b/gcloud/storage/key.py @@ -4,19 +4,19 @@ import os from StringIO import StringIO -from gcloud.storage._helpers import _MetadataMixin +from gcloud.storage._helpers import _PropertyMixin from gcloud.storage.acl import ObjectACL from gcloud.storage.exceptions import StorageError from gcloud.storage.iterator import Iterator -class Key(_MetadataMixin): +class Key(_PropertyMixin): """A wrapper around Cloud Storage's concept of an ``Object``.""" - CUSTOM_METADATA_FIELDS = { - 'acl': 'get_acl', + CUSTOM_PROPERTY_ACCESSORS = { + 'acl': 'get_acl()', } - """Mapping of field name -> accessor for fields w/ custom accessors.""" + """Map field name -> accessor for fields w/ custom accessors.""" CHUNK_SIZE = 1024 * 1024 # 1 MB. """The size of a chunk of data whenever iterating (1 MB). @@ -26,7 +26,7 @@ class Key(_MetadataMixin): # ACL rules are lazily retrieved. _acl = None - def __init__(self, bucket=None, name=None, metadata=None): + def __init__(self, bucket=None, name=None, properties=None): """Key constructor. :type bucket: :class:`gcloud.storage.bucket.Bucket` @@ -36,10 +36,10 @@ def __init__(self, bucket=None, name=None, metadata=None): :param name: The name of the key. This corresponds to the unique path of the object in the bucket. - :type metadata: dict - :param metadata: All the other data provided by Cloud Storage. + :type properties: dict + :param properties: All the other data provided by Cloud Storage. """ - super(Key, self).__init__(name=name, metadata=metadata or {}) + super(Key, self).__init__(name=name, properties=properties) self.bucket = bucket @property @@ -65,7 +65,7 @@ def from_dict(cls, key_dict, bucket=None): :returns: A key based on the data provided. """ - return cls(bucket=bucket, name=key_dict['name'], metadata=key_dict) + return cls(bucket=bucket, name=key_dict['name'], properties=key_dict) def __repr__(self): if self.bucket: diff --git a/gcloud/storage/test__helpers.py b/gcloud/storage/test__helpers.py index 313b6b452e9e..db8a75944a2b 100644 --- a/gcloud/storage/test__helpers.py +++ b/gcloud/storage/test__helpers.py @@ -1,38 +1,135 @@ import unittest2 -class Test_MetadataMixin(unittest2.TestCase): +class Test_PropertyMixin(unittest2.TestCase): def _getTargetClass(self): - from gcloud.storage._helpers import _MetadataMixin - return _MetadataMixin + from gcloud.storage._helpers import _PropertyMixin + return _PropertyMixin def _makeOne(self, *args, **kw): return self._getTargetClass()(*args, **kw) - def test_abstract_properties(self): - metadata_object = self._makeOne() - self.assertRaises(NotImplementedError, - lambda: metadata_object.connection) - self.assertRaises(NotImplementedError, - lambda: metadata_object.path) + def _derivedClass(self, connection=None, path=None, **custom_fields): - def test_get_metadata_w_custom_field(self): class Derived(self._getTargetClass()): - CUSTOM_METADATA_FIELDS = {'foo': 'get_foo'} + CUSTOM_PROPERTY_ACCESSORS = custom_fields @property - def connection(self): # pragma: NO COVER - return None + def connection(self): + return connection @property - def path(self): # pragma: NO COVER - return None + def path(self): + return path - derived = Derived() + return Derived + + def test_connetction_is_abstract(self): + mixin = self._makeOne() + self.assertRaises(NotImplementedError, lambda: mixin.connection) + + def test_path_is_abstract(self): + mixin = self._makeOne() + self.assertRaises(NotImplementedError, lambda: mixin.path) + + def test__reload_properties(self): + connection = _Connection({'foo': 'Foo'}) + derived = self._derivedClass(connection, '/path')() + derived._reload_properties() + self.assertEqual(derived._properties, {'foo': 'Foo'}) + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/path') + self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) + + def test__get_property_eager_hit(self): + derived = self._derivedClass()(properties={'foo': 'Foo'}) + self.assertEqual(derived._get_property('foo'), 'Foo') + + def test__get_property_eager_miss_w_default(self): + connection = _Connection({'foo': 'Foo'}) + derived = self._derivedClass(connection, '/path')() + default = object() + self.assertTrue(derived._get_property('nonesuch', default) is default) + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/path') + self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) + + def test__get_property_lazy_hit(self): + connection = _Connection({'foo': 'Foo'}) + derived = self._derivedClass(connection, '/path')() + self.assertTrue(derived._get_property('nonesuch') is None) + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/path') + self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) + + def test__get_property_w_custom_field(self): + derived = self._derivedClass(foo='get_foo')() try: - derived.get_metadata('foo') + derived._get_property('foo') except KeyError as e: self.assertTrue('get_foo' in str(e)) else: # pragma: NO COVER self.assert_('KeyError not raised') + + def test__patch_properties(self): + connection = _Connection({'foo': 'Foo'}) + derived = self._derivedClass(connection, '/path')() + self.assertTrue(derived._patch_properties({'foo': 'Foo'}) is derived) + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'PATCH') + self.assertEqual(kw[0]['path'], '/path') + self.assertEqual(kw[0]['data'], {'foo': 'Foo'}) + self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + + def test_properties_eager(self): + derived = self._derivedClass()(properties={'extant': False}) + self.assertEqual(derived.properties, {'extant': False}) + + def test_properties_lazy(self): + connection = _Connection({'foo': 'Foo'}) + derived = self._derivedClass(connection, '/path')() + self.assertEqual(derived.properties, {'foo': 'Foo'}) + kw = connection._requested + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/path') + self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) + + def test_get_acl_not_yet_loaded(self): + class ACL(object): + loaded = False + + def reload(self): + self.loaded = True + + mixin = self._makeOne() + acl = mixin.acl = ACL() + self.assertTrue(mixin.get_acl() is acl) + self.assertTrue(acl.loaded) + + def test_get_acl_already_loaded(self): + class ACL(object): + loaded = True + mixin = self._makeOne() + acl = mixin.acl = ACL() + self.assertTrue(mixin.get_acl() is acl) # no 'reload' + + +class _Connection(object): + + def __init__(self, *responses): + self._responses = responses + self._requested = [] + + def api_request(self, **kw): + self._requested.append(kw) + response, self._responses = self._responses[0], self._responses[1:] + return response diff --git a/gcloud/storage/test_bucket.py b/gcloud/storage/test_bucket.py index 1466ecdde320..dc46016d033b 100644 --- a/gcloud/storage/test_bucket.py +++ b/gcloud/storage/test_bucket.py @@ -16,58 +16,44 @@ def test_ctor_defaults(self): bucket = self._makeOne() self.assertEqual(bucket.connection, None) self.assertEqual(bucket.name, None) - self.assertEqual(bucket.metadata, None) + self.assertEqual(bucket._properties, {}) self.assertTrue(bucket._acl is None) self.assertTrue(bucket._default_object_acl is None) def test_ctor_explicit(self): NAME = 'name' connection = _Connection() - metadata = {'key': 'value'} - bucket = self._makeOne(connection, NAME, metadata) + properties = {'key': 'value'} + bucket = self._makeOne(connection, NAME, properties) self.assertTrue(bucket.connection is connection) self.assertEqual(bucket.name, NAME) - self.assertEqual(bucket.metadata, metadata) + self.assertEqual(bucket._properties, properties) self.assertTrue(bucket._acl is None) self.assertTrue(bucket._default_object_acl is None) def test_from_dict_defaults(self): NAME = 'name' - metadata = {'key': 'value', 'name': NAME} + properties = {'key': 'value', 'name': NAME} klass = self._getTargetClass() - bucket = klass.from_dict(metadata) + bucket = klass.from_dict(properties) self.assertEqual(bucket.connection, None) self.assertEqual(bucket.name, NAME) - self.assertEqual(bucket.metadata, metadata) + self.assertEqual(bucket.properties, properties) self.assertTrue(bucket._acl is None) self.assertTrue(bucket._default_object_acl is None) def test_from_dict_explicit(self): NAME = 'name' connection = _Connection() - metadata = {'key': 'value', 'name': NAME} + properties = {'key': 'value', 'name': NAME} klass = self._getTargetClass() - bucket = klass.from_dict(metadata, connection) + bucket = klass.from_dict(properties, connection) self.assertTrue(bucket.connection is connection) self.assertEqual(bucket.name, NAME) - self.assertEqual(bucket.metadata, metadata) + self.assertEqual(bucket.properties, properties) 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' connection = _Connection({'items': []}) @@ -113,6 +99,20 @@ def test___contains___hit(self): self.assertEqual(kw['method'], 'GET') self.assertEqual(kw['path'], '/b/%s/o/%s' % (NAME, KEY)) + 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_path_no_name(self): bucket = self._makeOne() self.assertRaises(ValueError, getattr, bucket, 'path') @@ -406,143 +406,366 @@ def upload_from_file(self, fh): bucket.upload_file_object(FILEOBJECT, KEY) self.assertEqual(_uploaded, [(bucket, KEY, FILEOBJECT)]) - def test_has_metdata_none_set(self): - NONESUCH = 'nonesuch' + def test_get_cors_eager(self): + NAME = 'name' + CORS_ENTRY = { + 'maxAgeSeconds': 1234, + 'method': ['OPTIONS', 'GET'], + 'origin': ['127.0.0.1'], + 'responseHeader': ['Content-Type'], + } + before = {'cors': [CORS_ENTRY, {}]} + connection = _Connection() + bucket = self._makeOne(connection, NAME, before) + entries = bucket.get_cors() + self.assertEqual(len(entries), 2) + self.assertEqual(entries[0]['maxAgeSeconds'], + CORS_ENTRY['maxAgeSeconds']) + self.assertEqual(entries[0]['method'], + CORS_ENTRY['method']) + self.assertEqual(entries[0]['origin'], + CORS_ENTRY['origin']) + self.assertEqual(entries[0]['responseHeader'], + CORS_ENTRY['responseHeader']) + self.assertEqual(entries[1], {}) + kw = connection._requested + self.assertEqual(len(kw), 0) + + def test_get_cors_lazy(self): + NAME = 'name' + CORS_ENTRY = { + 'maxAgeSeconds': 1234, + 'method': ['OPTIONS', 'GET'], + 'origin': ['127.0.0.1'], + 'responseHeader': ['Content-Type'], + } + after = {'cors': [CORS_ENTRY]} + connection = _Connection(after) + bucket = self._makeOne(connection, NAME) + entries = bucket.get_cors() + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0]['maxAgeSeconds'], + CORS_ENTRY['maxAgeSeconds']) + self.assertEqual(entries[0]['method'], + CORS_ENTRY['method']) + self.assertEqual(entries[0]['origin'], + CORS_ENTRY['origin']) + self.assertEqual(entries[0]['responseHeader'], + CORS_ENTRY['responseHeader']) + 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': 'noAcl'}) + + def test_update_cors(self): + NAME = 'name' + CORS_ENTRY = { + 'maxAgeSeconds': 1234, + 'method': ['OPTIONS', 'GET'], + 'origin': ['127.0.0.1'], + 'responseHeader': ['Content-Type'], + } + after = {'cors': [CORS_ENTRY, {}]} + connection = _Connection(after) + bucket = self._makeOne(connection, NAME) + bucket.update_cors([CORS_ENTRY, {}]) + 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'], after) + self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + entries = bucket.get_cors() + self.assertEqual(entries, [CORS_ENTRY, {}]) + + def test_get_default_object_acl_lazy(self): + from gcloud.storage.acl import BucketACL + 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), 1) + self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['path'], '/b/%s/defaultObjectAcl' % NAME) + + def test_get_default_object_acl_eager(self): + connection = _Connection() bucket = self._makeOne() - self.assertFalse(bucket.has_metadata(NONESUCH)) + 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 + self.assertEqual(len(kw), 0) - def test_has_metdata_miss(self): - NONESUCH = 'nonesuch' - metadata = {'key': 'value'} - bucket = self._makeOne(metadata=metadata) - self.assertFalse(bucket.has_metadata(NONESUCH)) + def test_etag(self): + ETAG = 'ETAG' + properties = {'etag': ETAG} + bucket = self._makeOne(properties=properties) + self.assertEqual(bucket.etag, ETAG) - def test_has_metdata_none_passed(self): - KEY = 'key' - metadata = {KEY: 'value'} - bucket = self._makeOne(metadata=metadata) - self.assertTrue(bucket.has_metadata()) + def test_id(self): + ID = 'ID' + properties = {'id': ID} + bucket = self._makeOne(properties=properties) + self.assertEqual(bucket.id, ID) - def test_has_metdata_hit(self): - KEY = 'key' - metadata = {KEY: 'value'} - bucket = self._makeOne(metadata=metadata) - self.assertTrue(bucket.has_metadata(KEY)) + def test_get_lifecycle_eager(self): + NAME = 'name' + LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}} + before = {'lifecycle': {'rule': [LC_RULE]}} + connection = _Connection() + bucket = self._makeOne(connection, NAME, before) + entries = bucket.get_lifecycle() + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0]['action']['type'], 'Delete') + self.assertEqual(entries[0]['condition']['age'], 42) + kw = connection._requested + self.assertEqual(len(kw), 0) - def test_reload_metadata(self): + def test_get_lifecycle_lazy(self): NAME = 'name' - before = {'foo': 'Foo'} - after = {'bar': 'Bar'} + LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}} + after = {'lifecycle': {'rule': [LC_RULE]}} connection = _Connection(after) - bucket = self._makeOne(connection, NAME, before) - found = bucket.reload_metadata() - self.assertTrue(found is bucket) - self.assertEqual(found.metadata, after) + bucket = self._makeOne(connection, NAME) + entries = bucket.get_lifecycle() + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0]['action']['type'], 'Delete') + self.assertEqual(entries[0]['condition']['age'], 42) 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': 'noAcl'}) - def test_get_metadata_none_set_none_passed(self): + def test_update_lifecycle(self): NAME = 'name' - after = {'bar': 'Bar'} + LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}} + after = {'lifecycle': {'rule': [LC_RULE]}} connection = _Connection(after) bucket = self._makeOne(connection, NAME) - found = bucket.get_metadata() - self.assertEqual(found, after) - self.assertEqual(bucket.metadata, after) + bucket.update_lifecycle([LC_RULE]) kw = connection._requested self.assertEqual(len(kw), 1) - self.assertEqual(kw[0]['method'], 'GET') + self.assertEqual(kw[0]['method'], 'PATCH') self.assertEqual(kw[0]['path'], '/b/%s' % NAME) - self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) + self.assertEqual(kw[0]['data'], after) + self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + entries = bucket.get_lifecycle() + self.assertEqual(len(entries), 1) + self.assertEqual(entries[0]['action']['type'], 'Delete') + self.assertEqual(entries[0]['condition']['age'], 42) - def test_get_metadata_acl_no_default(self): + def test_get_location_eager(self): NAME = 'name' connection = _Connection() - bucket = self._makeOne(connection, NAME) - self.assertRaises(KeyError, bucket.get_metadata, 'acl') + before = {'location': 'AS'} + bucket = self._makeOne(connection, NAME, before) + self.assertEqual(bucket.get_location(), 'AS') kw = connection._requested self.assertEqual(len(kw), 0) - def test_get_metadata_acl_w_default(self): + def test_get_location_lazy(self): NAME = 'name' - connection = _Connection() + connection = _Connection({'location': 'AS'}) bucket = self._makeOne(connection, NAME) - default = object() - self.assertRaises(KeyError, bucket.get_metadata, 'acl', default) + self.assertEqual(bucket.get_location(), 'AS') 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' % NAME) - def test_get_metadata_defaultObjectAcl_no_default(self): + def test_update_location(self): NAME = 'name' - connection = _Connection() + connection = _Connection({'location': 'AS'}) bucket = self._makeOne(connection, NAME) - self.assertRaises(KeyError, bucket.get_metadata, 'defaultObjectAcl') + bucket.set_location('AS') + self.assertEqual(bucket.get_location(), 'AS') kw = connection._requested - self.assertEqual(len(kw), 0) + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'PATCH') + self.assertEqual(kw[0]['path'], '/b/%s' % NAME) + self.assertEqual(kw[0]['data'], {'location': 'AS'}) + self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - def test_get_metadata_none_set_defaultObjectAcl_miss_clear_default(self): + def test_get_logging_eager_w_prefix(self): NAME = 'name' + LOG_BUCKET = 'logs' + LOG_PREFIX = 'pfx' + before = { + 'logging': {'logBucket': LOG_BUCKET, + 'logObjectPrefix': LOG_PREFIX}} connection = _Connection() - bucket = self._makeOne(connection, NAME) - default = object() - self.assertRaises(KeyError, bucket.get_metadata, 'defaultObjectAcl', - default) + bucket = self._makeOne(connection, NAME, before) + info = bucket.get_logging() + self.assertEqual(info['logBucket'], LOG_BUCKET) + self.assertEqual(info['logObjectPrefix'], LOG_PREFIX) kw = connection._requested self.assertEqual(len(kw), 0) - def test_get_metadata_lifecycle_no_default(self): + def test_get_logging_lazy_wo_prefix(self): NAME = 'name' - connection = _Connection() + LOG_BUCKET = 'logs' + after = {'logging': {'logBucket': LOG_BUCKET}} + connection = _Connection(after) bucket = self._makeOne(connection, NAME) - self.assertRaises(KeyError, bucket.get_metadata, 'lifecycle') + info = bucket.get_logging() + self.assertEqual(info['logBucket'], LOG_BUCKET) + self.assertEqual(info.get('logObjectPrefix'), None) 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' % NAME) + self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'}) - def test_get_metadata_lifecycle_w_default(self): + def test_enable_logging_defaults(self): NAME = 'name' + LOG_BUCKET = 'logs' + before = {'logging': None} + after = {'logging': {'logBucket': LOG_BUCKET, 'logObjectPrefix': ''}} + connection = _Connection(after) + bucket = self._makeOne(connection, NAME, before) + self.assertTrue(bucket.get_logging() is None) + bucket.enable_logging(LOG_BUCKET) + info = bucket.get_logging() + self.assertEqual(info['logBucket'], LOG_BUCKET) + self.assertEqual(info['logObjectPrefix'], '') + 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'], after) + self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + + def test_enable_logging_explicit(self): + NAME = 'name' + LOG_BUCKET = 'logs' + LOG_PFX = 'pfx' + before = {'logging': None} + after = { + 'logging': {'logBucket': LOG_BUCKET, 'logObjectPrefix': LOG_PFX}} + connection = _Connection(after) + bucket = self._makeOne(connection, NAME, before) + self.assertTrue(bucket.get_logging() is None) + bucket.enable_logging(LOG_BUCKET, LOG_PFX) + info = bucket.get_logging() + self.assertEqual(info['logBucket'], LOG_BUCKET) + self.assertEqual(info['logObjectPrefix'], LOG_PFX) + 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'], after) + self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + + def test_disable_logging(self): + NAME = 'name' + before = {'logging': {'logBucket': 'logs', 'logObjectPrefix': 'pfx'}} + after = {'logging': None} + connection = _Connection(after) + bucket = self._makeOne(connection, NAME, before) + self.assertTrue(bucket.get_logging() is not None) + bucket.disable_logging() + self.assertTrue(bucket.get_logging() is None) + 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'], {'logging': None}) + self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) + + def test_metageneration(self): + METAGENERATION = 42 + properties = {'metageneration': METAGENERATION} + bucket = self._makeOne(properties=properties) + self.assertEqual(bucket.metageneration, METAGENERATION) + + def test_owner(self): + OWNER = {'entity': 'project-owner-12345', 'entityId': '23456'} + properties = {'owner': OWNER} + bucket = self._makeOne(properties=properties) + owner = bucket.owner + self.assertEqual(owner['entity'], 'project-owner-12345') + self.assertEqual(owner['entityId'], '23456') + + def test_project_number(self): + PROJECT_NUMBER = 12345 + properties = {'projectNumber': PROJECT_NUMBER} + bucket = self._makeOne(properties=properties) + self.assertEqual(bucket.project_number, PROJECT_NUMBER) + + def test_self_link(self): + SELF_LINK = 'http://example.com/self/' + properties = {'selfLink': SELF_LINK} + bucket = self._makeOne(properties=properties) + self.assertEqual(bucket.self_link, SELF_LINK) + + def test_storage_class(self): + STORAGE_CLASS = 'http://example.com/self/' + properties = {'storageClass': STORAGE_CLASS} + bucket = self._makeOne(properties=properties) + self.assertEqual(bucket.storage_class, STORAGE_CLASS) + + def test_time_created(self): + TIME_CREATED = '2014-11-05T20:34:37Z' + properties = {'timeCreated': TIME_CREATED} + bucket = self._makeOne(properties=properties) + self.assertEqual(bucket.time_created, TIME_CREATED) + + def test_get_versioning_eager(self): + NAME = 'name' + before = {'bar': 'Bar', 'versioning': {'enabled': True}} connection = _Connection() - bucket = self._makeOne(connection, NAME) - default = object() - self.assertRaises(KeyError, bucket.get_metadata, 'lifecycle', default) + bucket = self._makeOne(connection, NAME, before) + self.assertEqual(bucket.get_versioning(), True) kw = connection._requested self.assertEqual(len(kw), 0) - def test_get_metadata_miss(self): + def test_get_versioning_lazy(self): NAME = 'name' - before = {'bar': 'Bar'} - after = {'bar': 'Bar'} + after = {'bar': 'Bar', 'versioning': {'enabled': True}} connection = _Connection(after) - bucket = self._makeOne(connection, NAME, before) - self.assertEqual(bucket.get_metadata('foo'), None) + bucket = self._makeOne(connection, NAME) + self.assertEqual(bucket.get_versioning(), True) 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': 'noAcl'}) - def test_get_metadata_hit(self): + def test_enable_versioning(self): NAME = 'name' - before = {'bar': 'Bar'} - connection = _Connection() + before = {'versioning': {'enabled': False}} + after = {'versioning': {'enabled': True}} + connection = _Connection(after) bucket = self._makeOne(connection, NAME, before) - self.assertEqual(bucket.get_metadata('bar'), 'Bar') + self.assertFalse(bucket.get_versioning()) + bucket.enable_versioning() + self.assertTrue(bucket.get_versioning()) kw = connection._requested - self.assertEqual(len(kw), 0) + self.assertEqual(len(kw), 1) + self.assertEqual(kw[0]['method'], 'PATCH') + self.assertEqual(kw[0]['data'], {'versioning': {'enabled': True}}) + self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - def test_patch_metadata(self): + def test_disable_versioning(self): NAME = 'name' - before = {'foo': 'Foo'} - after = {'bar': 'Bar'} + before = {'versioning': {'enabled': True}} + after = {'versioning': {'enabled': False}} connection = _Connection(after) bucket = self._makeOne(connection, NAME, before) - self.assertTrue(bucket.patch_metadata(after) is bucket) + self.assertTrue(bucket.get_versioning()) + bucket.disable_versioning() + self.assertFalse(bucket.get_versioning()) 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'], after) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) @@ -553,7 +776,7 @@ def test_configure_website_defaults(self): connection = _Connection(patched) bucket = self._makeOne(connection, NAME) self.assertTrue(bucket.configure_website() is bucket) - self.assertEqual(bucket.metadata, patched) + self.assertEqual(bucket.properties, patched) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') @@ -568,7 +791,7 @@ def test_configure_website_explicit(self): connection = _Connection(patched) bucket = self._makeOne(connection, NAME) self.assertTrue(bucket.configure_website('html', '404.html') is bucket) - self.assertEqual(bucket.metadata, patched) + self.assertEqual(bucket.properties, patched) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') @@ -583,7 +806,7 @@ def test_disable_website(self): connection = _Connection(patched) bucket = self._makeOne(connection, NAME) self.assertTrue(bucket.disable_website() is bucket) - self.assertEqual(bucket.metadata, patched) + self.assertEqual(bucket.properties, patched) kw = connection._requested self.assertEqual(len(kw), 1) self.assertEqual(kw[0]['method'], 'PATCH') @@ -591,54 +814,6 @@ def test_disable_website(self): self.assertEqual(kw[0]['data'], patched) self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - def test_get_acl_lazy(self): - from gcloud.storage.acl import BucketACL - 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), 1) - self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/%s/acl' % NAME) - - def test_get_acl_eager(self): - connection = _Connection() - bucket = self._makeOne() - preset = bucket.acl # Ensure it is assigned - preset.loaded = True - acl = bucket.get_acl() - self.assertTrue(acl is preset) - kw = connection._requested - self.assertEqual(len(kw), 0) - - def test_get_default_object_acl_lazy(self): - from gcloud.storage.acl import BucketACL - 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), 1) - self.assertEqual(kw[0]['method'], 'GET') - self.assertEqual(kw[0]['path'], '/b/%s/defaultObjectAcl' % NAME) - - def test_get_default_object_acl_eager(self): - connection = _Connection() - bucket = self._makeOne() - 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 - self.assertEqual(len(kw), 0) - def test_make_public_defaults(self): from gcloud.storage.acl import _ACLEntity NAME = 'name' @@ -734,53 +909,6 @@ def get_items_from_response(self, response): self.assertEqual(kw[1]['path'], '/b/%s/o' % NAME) self.assertEqual(kw[1]['query_params'], {}) - def test_get_lifecycle_eager(self): - NAME = 'name' - LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}} - before = {'lifecycle': {'rule': [LC_RULE]}} - connection = _Connection() - bucket = self._makeOne(connection, NAME, before) - entries = bucket.get_lifecycle() - self.assertEqual(len(entries), 1) - self.assertEqual(entries[0]['action']['type'], 'Delete') - self.assertEqual(entries[0]['condition']['age'], 42) - kw = connection._requested - self.assertEqual(len(kw), 0) - - def test_get_lifecycle_lazy(self): - NAME = 'name' - LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}} - after = {'lifecycle': {'rule': [LC_RULE]}} - connection = _Connection(after) - bucket = self._makeOne(connection, NAME) - entries = bucket.get_lifecycle() - self.assertEqual(len(entries), 1) - self.assertEqual(entries[0]['action']['type'], 'Delete') - self.assertEqual(entries[0]['condition']['age'], 42) - 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': 'noAcl'}) - - def test_update_lifecycle(self): - NAME = 'name' - LC_RULE = {'action': {'type': 'Delete'}, 'condition': {'age': 42}} - after = {'lifecycle': {'rule': [LC_RULE]}} - connection = _Connection(after) - bucket = self._makeOne(connection, NAME) - bucket.update_lifecycle([LC_RULE]) - 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'], after) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - entries = bucket.get_lifecycle() - self.assertEqual(len(entries), 1) - self.assertEqual(entries[0]['action']['type'], 'Delete') - self.assertEqual(entries[0]['condition']['age'], 42) - class TestBucketIterator(unittest2.TestCase): diff --git a/gcloud/storage/test_key.py b/gcloud/storage/test_key.py index 7ddd77f49cbc..7fdeb2733efe 100644 --- a/gcloud/storage/test_key.py +++ b/gcloud/storage/test_key.py @@ -15,43 +15,43 @@ def test_ctor_defaults(self): self.assertEqual(key.bucket, None) self.assertEqual(key.connection, None) self.assertEqual(key.name, None) - self.assertEqual(key.metadata, {}) + self.assertEqual(key._properties, {}) self.assertTrue(key._acl is None) def test_ctor_explicit(self): KEY = 'key' connection = _Connection() bucket = _Bucket(connection) - metadata = {'key': 'value'} - key = self._makeOne(bucket, KEY, metadata) + properties = {'key': 'value'} + key = self._makeOne(bucket, KEY, properties) self.assertTrue(key.bucket is bucket) self.assertTrue(key.connection is connection) self.assertEqual(key.name, KEY) - self.assertEqual(key.metadata, metadata) + self.assertEqual(key.properties, properties) self.assertTrue(key._acl is None) def test_from_dict_defaults(self): KEY = 'key' - metadata = {'key': 'value', 'name': KEY} + properties = {'key': 'value', 'name': KEY} klass = self._getTargetClass() - key = klass.from_dict(metadata) + key = klass.from_dict(properties) self.assertEqual(key.bucket, None) self.assertEqual(key.connection, None) self.assertEqual(key.name, KEY) - self.assertEqual(key.metadata, metadata) + self.assertEqual(key.properties, properties) self.assertTrue(key._acl is None) def test_from_dict_explicit(self): KEY = 'key' connection = _Connection() bucket = _Bucket(connection) - metadata = {'key': 'value', 'name': KEY} + properties = {'key': 'value', 'name': KEY} klass = self._getTargetClass() - key = klass.from_dict(metadata, bucket) + key = klass.from_dict(properties, bucket) self.assertTrue(key.bucket is bucket) self.assertTrue(key.connection is connection) self.assertEqual(key.name, KEY) - self.assertEqual(key.metadata, metadata) + self.assertEqual(key.properties, properties) self.assertTrue(key._acl is None) def test_acl_property(self): @@ -326,137 +326,6 @@ def test_upload_from_string(self): self.assertEqual(rq[2]['data'], DATA[5:]) self.assertEqual(rq[2]['headers'], {'Content-Range': 'bytes 5-5/6'}) - def test_has_metdata_none_set(self): - NONESUCH = 'nonesuch' - key = self._makeOne() - self.assertFalse(key.has_metadata(NONESUCH)) - - def test_has_metdata_miss(self): - NONESUCH = 'nonesuch' - metadata = {'key': 'value'} - key = self._makeOne(metadata=metadata) - self.assertFalse(key.has_metadata(NONESUCH)) - - def test_has_metdata_none_passed(self): - KEY = 'key' - metadata = {KEY: 'value'} - key = self._makeOne(metadata=metadata) - self.assertTrue(key.has_metadata()) - - def test_has_metdata_hit(self): - KEY = 'key' - metadata = {KEY: 'value'} - key = self._makeOne(metadata=metadata) - self.assertTrue(key.has_metadata(KEY)) - - def test_reload_metadata(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() - 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': 'noAcl'}) - - def test_get_metadata_none_set_none_passed(self): - KEY = 'key' - after = {'bar': 'Bar'} - connection = _Connection(after) - bucket = _Bucket(connection) - key = self._makeOne(bucket, KEY) - found = key.get_metadata() - self.assertEqual(found, after) - self.assertEqual(key.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': 'noAcl'}) - - def test_get_metadata_acl_no_default(self): - KEY = 'key' - connection = _Connection() - bucket = _Bucket(connection) - key = self._makeOne(bucket, KEY) - self.assertRaises(KeyError, key.get_metadata, 'acl') - kw = connection._requested - self.assertEqual(len(kw), 0) - - 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() - self.assertRaises(KeyError, key.get_metadata, 'acl', default) - kw = connection._requested - self.assertEqual(len(kw), 0) - - def test_get_metadata_miss(self): - KEY = 'key' - before = {'bar': 'Bar'} - after = {'bar': 'Bar'} - connection = _Connection(after) - bucket = _Bucket(connection) - key = self._makeOne(bucket, KEY, before) - self.assertEqual(key.get_metadata('foo'), None) - 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': 'noAcl'}) - - def test_get_metadata_hit(self): - KEY = 'key' - before = {'bar': 'Bar'} - connection = _Connection() - bucket = _Bucket(connection) - key = self._makeOne(bucket, KEY, before) - self.assertEqual(key.get_metadata('bar'), 'Bar') - kw = connection._requested - self.assertEqual(len(kw), 0) - - def test_patch_metadata(self): - KEY = 'key' - before = {'foo': 'Foo'} - after = {'bar': 'Bar'} - connection = _Connection(after) - bucket = _Bucket(connection) - key = self._makeOne(bucket, KEY, before) - self.assertTrue(key.patch_metadata(after) 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'], after) - self.assertEqual(kw[0]['query_params'], {'projection': 'full'}) - - def test_get_acl_lazy(self): - from gcloud.storage.acl import ObjectACL - 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): - key = self._makeOne() - preset = key.acl - preset.loaded = True - acl = key.get_acl() - self.assertTrue(acl is preset) - def test_make_public(self): from gcloud.storage.acl import _ACLEntity KEY = 'key' diff --git a/pylintrc_default b/pylintrc_default index 84293654d3aa..89370c69e4b4 100644 --- a/pylintrc_default +++ b/pylintrc_default @@ -7,7 +7,7 @@ good-names = i, j, k, ex, Run, _, pb, id, [DESIGN] max-args = 10 -max-public-methods = 30 +max-public-methods = 40 [FORMAT] # NOTE: By default pylint ignores whitespace checks around the