Skip to content

Commit

Permalink
Merge pull request #347 from tseaver/314-omnibus
Browse files Browse the repository at this point in the history
Pull in / reconcile all PRs for #314
  • Loading branch information
tseaver committed Nov 6, 2014
2 parents 01a1eea + 3eaf39d commit 4a8f6b4
Show file tree
Hide file tree
Showing 7 changed files with 796 additions and 519 deletions.
137 changes: 63 additions & 74 deletions gcloud/storage/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand All @@ -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.
"""
Expand Down
Loading

0 comments on commit 4a8f6b4

Please sign in to comment.