Skip to content

Commit

Permalink
Preparing to use futures in storage.
Browse files Browse the repository at this point in the history
Wraps setting/getting of object _properties in custom methods.
This will allow centralized detection of a future in a response
and will also allow replacing with the value on access if it
is ready.

Towards #775
  • Loading branch information
dhermes committed Apr 9, 2015
1 parent f08e71b commit fafb25c
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 45 deletions.
40 changes: 31 additions & 9 deletions gcloud/storage/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(self, name=None):
:param name: The name of the object.
"""
self.name = name
self._is_future = False
self._properties = {}
self._changes = set()

Expand All @@ -54,10 +55,9 @@ def reload(self):
# Pass only '?projection=noAcl' here because 'acl' and related
# are handled via custom endpoints.
query_params = {'projection': 'noAcl'}
self._properties = self.connection.api_request(
api_response = self.connection.api_request(
method='GET', path=self.path, query_params=query_params)
# If the api_request succeeded, we reset changes.
self._changes = set()
self._set_properties(api_response)

def _patch_property(self, name, value):
"""Update field of this object's properties.
Expand All @@ -74,8 +74,31 @@ def _patch_property(self, name, value):
:type value: object
:param value: The value being updated.
"""
self._get_properties()[name] = value
self._changes.add(name)
self._properties[name] = value

def _set_properties(self, value):
"""Set the properties for the current object.
:type value: dict
:param value: The properties to be set.
"""
self._properties = value
# If the values are reset, the changes must as well.
self._changes = set()

def _get_properties(self):
"""Get the properties for the current object.
:rtype: dict
:returns: The properties of the current object.
:raises: :class:`ValueError` if the object is designated as a
future.
"""
if self._is_future:
raise ValueError(self, ('is a future. It cannot be used'
'until the request has completed'))
return self._properties

def patch(self):
"""Sends all changed properties in a PATCH request.
Expand All @@ -84,21 +107,20 @@ def patch(self):
"""
# Pass '?projection=full' here because 'PATCH' documented not
# to work properly w/ 'noAcl'.
update_properties = dict((key, self._properties[key])
update_properties = dict((key, self._get_properties()[key])
for key in self._changes)
self._properties = self.connection.api_request(
api_response = self.connection.api_request(
method='PATCH', path=self.path, data=update_properties,
query_params={'projection': 'full'})
# If the api_request succeeded, we reset changes.
self._changes = set()
self._set_properties(api_response)


def _scalar_property(fieldname):
"""Create a property descriptor around the :class:`_PropertyMixin` helpers.
"""
def _getter(self):
"""Scalar property getter."""
return self._properties.get(fieldname)
return self._get_properties().get(fieldname)

def _setter(self, value):
"""Scalar property setter."""
Expand Down
2 changes: 1 addition & 1 deletion gcloud/storage/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def get_items_from_response(self, response):
for item in response.get('items', []):
name = item.get('name')
bucket = Bucket(name, connection=self.connection)
bucket._properties = item
bucket._set_properties(item)
yield bucket


Expand Down
34 changes: 18 additions & 16 deletions gcloud/storage/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,8 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
:type num_retries: integer
:param num_retries: Number of upload retries. Defaults to 6.
"""
content_type = (content_type or self._properties.get('contentType') or
content_type = (content_type or
self._get_properties().get('contentType') or
'application/octet-stream')

# Rewind the file if desired.
Expand Down Expand Up @@ -355,7 +356,7 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
if not isinstance(response_content,
six.string_types): # pragma: NO COVER Python3
response_content = response_content.decode('utf-8')
self._properties = json.loads(response_content)
self._set_properties(json.loads(response_content))

def upload_from_filename(self, filename, content_type=None):
"""Upload this blob's contents from the content of a named file.
Expand All @@ -382,7 +383,8 @@ def upload_from_filename(self, filename, content_type=None):
:type content_type: string or ``NoneType``
:param content_type: Optional type of content being uploaded.
"""
content_type = content_type or self._properties.get('contentType')
content_type = (content_type or
self._get_properties().get('contentType'))
if content_type is None:
content_type, _ = mimetypes.guess_type(filename)

Expand Down Expand Up @@ -497,7 +499,7 @@ def component_count(self):
``None`` if the property is not set locally. This property
will not be set on objects not created via ``compose``.
"""
component_count = self._properties.get('componentCount')
component_count = self._get_properties().get('componentCount')
if component_count is not None:
return int(component_count)

Expand All @@ -511,7 +513,7 @@ def etag(self):
:rtype: string or ``NoneType``
:returns: The blob etag or ``None`` if the property is not set locally.
"""
return self._properties.get('etag')
return self._get_properties().get('etag')

@property
def generation(self):
Expand All @@ -523,7 +525,7 @@ def generation(self):
:returns: The generation of the blob or ``None`` if the property
is not set locally.
"""
generation = self._properties.get('generation')
generation = self._get_properties().get('generation')
if generation is not None:
return int(generation)

Expand All @@ -537,7 +539,7 @@ def id(self):
:returns: The ID of the blob or ``None`` if the property is not
set locally.
"""
return self._properties.get('id')
return self._get_properties().get('id')

md5_hash = _scalar_property('md5Hash')
"""MD5 hash for this object.
Expand All @@ -560,7 +562,7 @@ def media_link(self):
:returns: The media link for the blob or ``None`` if the property is
not set locally.
"""
return self._properties.get('mediaLink')
return self._get_properties().get('mediaLink')

@property
def metadata(self):
Expand All @@ -572,7 +574,7 @@ def metadata(self):
:returns: The metadata associated with the blob or ``None`` if the
property is not set locally.
"""
return copy.deepcopy(self._properties.get('metadata'))
return copy.deepcopy(self._get_properties().get('metadata'))

@metadata.setter
def metadata(self, value):
Expand All @@ -595,7 +597,7 @@ def metageneration(self):
:returns: The metageneration of the blob or ``None`` if the property
is not set locally.
"""
metageneration = self._properties.get('metageneration')
metageneration = self._get_properties().get('metageneration')
if metageneration is not None:
return int(metageneration)

Expand All @@ -609,7 +611,7 @@ def owner(self):
:returns: Mapping of owner's role/ID. If the property is not set
locally, returns ``None``.
"""
return copy.deepcopy(self._properties.get('owner'))
return copy.deepcopy(self._get_properties().get('owner'))

@property
def self_link(self):
Expand All @@ -621,7 +623,7 @@ def self_link(self):
:returns: The self link for the blob or ``None`` if the property is
not set locally.
"""
return self._properties.get('selfLink')
return self._get_properties().get('selfLink')

@property
def size(self):
Expand All @@ -633,7 +635,7 @@ def size(self):
:returns: The size of the blob or ``None`` if the property
is not set locally.
"""
size = self._properties.get('size')
size = self._get_properties().get('size')
if size is not None:
return int(size)

Expand All @@ -649,7 +651,7 @@ def storage_class(self):
:returns: If set, one of "STANDARD", "NEARLINE", or
"DURABLE_REDUCED_AVAILABILITY", else ``None``.
"""
return self._properties.get('storageClass')
return self._get_properties().get('storageClass')

@property
def time_deleted(self):
Expand All @@ -662,7 +664,7 @@ def time_deleted(self):
``None`` if the property is not set locally. If the blob has
not been deleted, this will never be set.
"""
value = self._properties.get('timeDeleted')
value = self._get_properties().get('timeDeleted')
if value is not None:
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)

Expand All @@ -676,7 +678,7 @@ def updated(self):
:returns: Datetime object parsed from RFC3339 valid timestamp, or
``None`` if the property is not set locally.
"""
value = self._properties.get('updated')
value = self._get_properties().get('updated')
if value is not None:
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)

Expand Down
33 changes: 17 additions & 16 deletions gcloud/storage/bucket.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def get_items_from_response(self, response):
for item in response.get('items', []):
name = item.get('name')
blob = Blob(name, bucket=self.bucket)
blob._properties = item
blob._set_properties(item)
yield blob


Expand Down Expand Up @@ -152,9 +152,10 @@ def create(self, project=None):
'from environment.')

query_params = {'project': project}
self._properties = self.connection.api_request(
api_response = self.connection.api_request(
method='POST', path='/b', query_params=query_params,
data={'name': self.name})
self._set_properties(api_response)

@property
def acl(self):
Expand Down Expand Up @@ -220,7 +221,7 @@ def get_blob(self, blob_name):
path=blob.path)
name = response.get('name') # Expect this to be blob_name
blob = Blob(name, bucket=self)
blob._properties = response
blob._set_properties(response)
return blob
except NotFound:
return None
Expand Down Expand Up @@ -408,7 +409,7 @@ def copy_blob(self, blob, destination_bucket, new_name=None):
new_blob = Blob(bucket=destination_bucket, name=new_name)
api_path = blob.path + '/copyTo' + new_blob.path
copy_result = self.connection.api_request(method='POST', path=api_path)
new_blob._properties = copy_result
new_blob._set_properties(copy_result)
return new_blob

def upload_file(self, filename, blob_name=None):
Expand Down Expand Up @@ -506,7 +507,7 @@ def cors(self):
:returns: A sequence of mappings describing each CORS policy.
"""
return [copy.deepcopy(policy)
for policy in self._properties.get('cors', ())]
for policy in self._get_properties().get('cors', ())]

@cors.setter
def cors(self, entries):
Expand All @@ -531,7 +532,7 @@ def etag(self):
:returns: The bucket etag or ``None`` if the property is not
set locally.
"""
return self._properties.get('etag')
return self._get_properties().get('etag')

@property
def id(self):
Expand All @@ -543,7 +544,7 @@ def id(self):
:returns: The ID of the bucket or ``None`` if the property is not
set locally.
"""
return self._properties.get('id')
return self._get_properties().get('id')

@property
def lifecycle_rules(self):
Expand All @@ -555,7 +556,7 @@ def lifecycle_rules(self):
:rtype: list(dict)
:returns: A sequence of mappings describing each lifecycle rule.
"""
info = self._properties.get('lifecycle', {})
info = self._get_properties().get('lifecycle', {})
return [copy.deepcopy(rule) for rule in info.get('rule', ())]

@lifecycle_rules.setter
Expand Down Expand Up @@ -590,7 +591,7 @@ def get_logging(self):
:returns: a dict w/ keys, ``logBucket`` and ``logObjectPrefix``
(if logging is enabled), or None (if not).
"""
info = self._properties.get('logging')
info = self._get_properties().get('logging')
return copy.deepcopy(info)

def enable_logging(self, bucket_name, object_prefix=''):
Expand Down Expand Up @@ -624,7 +625,7 @@ def metageneration(self):
:returns: The metageneration of the bucket or ``None`` if the property
is not set locally.
"""
metageneration = self._properties.get('metageneration')
metageneration = self._get_properties().get('metageneration')
if metageneration is not None:
return int(metageneration)

Expand All @@ -638,7 +639,7 @@ def owner(self):
:returns: Mapping of owner's role/ID. If the property is not set
locally, returns ``None``.
"""
return copy.deepcopy(self._properties.get('owner'))
return copy.deepcopy(self._get_properties().get('owner'))

@property
def project_number(self):
Expand All @@ -650,7 +651,7 @@ def project_number(self):
:returns: The project number that owns the bucket or ``None`` if the
property is not set locally.
"""
project_number = self._properties.get('projectNumber')
project_number = self._get_properties().get('projectNumber')
if project_number is not None:
return int(project_number)

Expand All @@ -664,7 +665,7 @@ def self_link(self):
:returns: The self link for the bucket or ``None`` if the property is
not set locally.
"""
return self._properties.get('selfLink')
return self._get_properties().get('selfLink')

@property
def storage_class(self):
Expand All @@ -678,7 +679,7 @@ def storage_class(self):
:returns: If set, one of "STANDARD", "NEARLINE", or
"DURABLE_REDUCED_AVAILABILITY", else ``None``.
"""
return self._properties.get('storageClass')
return self._get_properties().get('storageClass')

@property
def time_created(self):
Expand All @@ -690,7 +691,7 @@ def time_created(self):
:returns: Datetime object parsed from RFC3339 valid timestamp, or
``None`` if the property is not set locally.
"""
value = self._properties.get('timeCreated')
value = self._get_properties().get('timeCreated')
if value is not None:
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)

Expand All @@ -704,7 +705,7 @@ def versioning_enabled(self):
:rtype: boolean
:returns: True if enabled, else False.
"""
versioning = self._properties.get('versioning', {})
versioning = self._get_properties().get('versioning', {})
return versioning.get('enabled', False)

@versioning_enabled.setter
Expand Down
2 changes: 1 addition & 1 deletion gcloud/storage/iterator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def get_items_from_response(self, response):
items = response.get('items', [])
for item in items:
my_item = MyItemClass(other_arg=True)
my_item._properties = item
my_item._set_properties(item)
yield my_item
You then can use this to get **all** the results from a resource::
Expand Down
Loading

0 comments on commit fafb25c

Please sign in to comment.