Skip to content

Commit b0af13c

Browse files
committed
Preparing to use futures in storage.
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 googleapis#775
1 parent 1cdcc6d commit b0af13c

File tree

7 files changed

+79
-46
lines changed

7 files changed

+79
-46
lines changed

gcloud/storage/_helpers.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def __init__(self, name=None):
4646
:param name: The name of the object.
4747
"""
4848
self.name = name
49+
self._is_future = False
4950
self._properties = {}
5051
self._changes = set()
5152

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

6262
def _patch_property(self, name, value):
6363
"""Update field of this object's properties.
@@ -74,8 +74,31 @@ def _patch_property(self, name, value):
7474
:type value: object
7575
:param value: The value being updated.
7676
"""
77+
self._get_properties()[name] = value
7778
self._changes.add(name)
78-
self._properties[name] = value
79+
80+
def _set_properties(self, value):
81+
"""Set the properties for the current object.
82+
83+
:type value: dict
84+
:param value: The properties to be set.
85+
"""
86+
self._properties = value
87+
# If the values are reset, the changes must as well.
88+
self._changes = set()
89+
90+
def _get_properties(self):
91+
"""Get the properties for the current object.
92+
93+
:rtype: dict
94+
:returns: The properties of the current object.
95+
:raises: :class:`ValueError` if the object is designated as a
96+
future.
97+
"""
98+
if self._is_future:
99+
raise ValueError(self, ('is a future. It cannot be used'
100+
'until the request has completed'))
101+
return self._properties
79102

80103
def patch(self):
81104
"""Sends all changed properties in a PATCH request.
@@ -84,21 +107,20 @@ def patch(self):
84107
"""
85108
# Pass '?projection=full' here because 'PATCH' documented not
86109
# to work properly w/ 'noAcl'.
87-
update_properties = dict((key, self._properties[key])
110+
update_properties = dict((key, self._get_properties()[key])
88111
for key in self._changes)
89-
self._properties = self.connection.api_request(
112+
api_response = self.connection.api_request(
90113
method='PATCH', path=self.path, data=update_properties,
91114
query_params={'projection': 'full'})
92-
# If the api_request succeeded, we reset changes.
93-
self._changes = set()
115+
self._set_properties(api_response)
94116

95117

96118
def _scalar_property(fieldname):
97119
"""Create a property descriptor around the :class:`_PropertyMixin` helpers.
98120
"""
99121
def _getter(self):
100122
"""Scalar property getter."""
101-
return self._properties.get(fieldname)
123+
return self._get_properties().get(fieldname)
102124

103125
def _setter(self, value):
104126
"""Scalar property setter."""

gcloud/storage/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ def get_items_from_response(self, response):
227227
for item in response.get('items', []):
228228
name = item.get('name')
229229
bucket = Bucket(name, connection=self.connection)
230-
bucket._properties = item
230+
bucket._set_properties(item)
231231
yield bucket
232232

233233

gcloud/storage/blob.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ def download_to_filename(self, filename):
250250

251251
mtime = time.mktime(
252252
datetime.datetime.strptime(
253-
self._properties['updated'],
253+
self._get_properties()['updated'],
254254
'%Y-%m-%dT%H:%M:%S.%fz').timetuple()
255255
)
256256
os.utime(file_obj.name, (mtime, mtime))
@@ -304,7 +304,8 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
304304
:type num_retries: integer
305305
:param num_retries: Number of upload retries. Defaults to 6.
306306
"""
307-
content_type = (content_type or self._properties.get('contentType') or
307+
content_type = (content_type or
308+
self._get_properties().get('contentType') or
308309
'application/octet-stream')
309310

310311
# Rewind the file if desired.
@@ -358,7 +359,7 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
358359
if not isinstance(response_content,
359360
six.string_types): # pragma: NO COVER Python3
360361
response_content = response_content.decode('utf-8')
361-
self._properties = json.loads(response_content)
362+
self._set_properties(json.loads(response_content))
362363

363364
def upload_from_filename(self, filename, content_type=None):
364365
"""Upload this blob's contents from the content of a named file.
@@ -385,7 +386,8 @@ def upload_from_filename(self, filename, content_type=None):
385386
:type content_type: string or ``NoneType``
386387
:param content_type: Optional type of content being uploaded.
387388
"""
388-
content_type = content_type or self._properties.get('contentType')
389+
content_type = (content_type or
390+
self._get_properties().get('contentType'))
389391
if content_type is None:
390392
content_type, _ = mimetypes.guess_type(filename)
391393

@@ -500,7 +502,7 @@ def component_count(self):
500502
``None`` if the property is not set locally. This property
501503
will not be set on objects not created via ``compose``.
502504
"""
503-
component_count = self._properties.get('componentCount')
505+
component_count = self._get_properties().get('componentCount')
504506
if component_count is not None:
505507
return int(component_count)
506508

@@ -514,7 +516,7 @@ def etag(self):
514516
:rtype: string or ``NoneType``
515517
:returns: The blob etag or ``None`` if the property is not set locally.
516518
"""
517-
return self._properties.get('etag')
519+
return self._get_properties().get('etag')
518520

519521
@property
520522
def generation(self):
@@ -526,7 +528,7 @@ def generation(self):
526528
:returns: The generation of the blob or ``None`` if the property
527529
is not set locally.
528530
"""
529-
generation = self._properties.get('generation')
531+
generation = self._get_properties().get('generation')
530532
if generation is not None:
531533
return int(generation)
532534

@@ -540,7 +542,7 @@ def id(self):
540542
:returns: The ID of the blob or ``None`` if the property is not
541543
set locally.
542544
"""
543-
return self._properties.get('id')
545+
return self._get_properties().get('id')
544546

545547
md5_hash = _scalar_property('md5Hash')
546548
"""MD5 hash for this object.
@@ -563,7 +565,7 @@ def media_link(self):
563565
:returns: The media link for the blob or ``None`` if the property is
564566
not set locally.
565567
"""
566-
return self._properties.get('mediaLink')
568+
return self._get_properties().get('mediaLink')
567569

568570
@property
569571
def metadata(self):
@@ -575,7 +577,7 @@ def metadata(self):
575577
:returns: The metadata associated with the blob or ``None`` if the
576578
property is not set locally.
577579
"""
578-
return copy.deepcopy(self._properties.get('metadata'))
580+
return copy.deepcopy(self._get_properties().get('metadata'))
579581

580582
@metadata.setter
581583
def metadata(self, value):
@@ -598,7 +600,7 @@ def metageneration(self):
598600
:returns: The metageneration of the blob or ``None`` if the property
599601
is not set locally.
600602
"""
601-
metageneration = self._properties.get('metageneration')
603+
metageneration = self._get_properties().get('metageneration')
602604
if metageneration is not None:
603605
return int(metageneration)
604606

@@ -612,7 +614,7 @@ def owner(self):
612614
:returns: Mapping of owner's role/ID. If the property is not set
613615
locally, returns ``None``.
614616
"""
615-
return copy.deepcopy(self._properties.get('owner'))
617+
return copy.deepcopy(self._get_properties().get('owner'))
616618

617619
@property
618620
def self_link(self):
@@ -624,7 +626,7 @@ def self_link(self):
624626
:returns: The self link for the blob or ``None`` if the property is
625627
not set locally.
626628
"""
627-
return self._properties.get('selfLink')
629+
return self._get_properties().get('selfLink')
628630

629631
@property
630632
def size(self):
@@ -636,7 +638,7 @@ def size(self):
636638
:returns: The size of the blob or ``None`` if the property
637639
is not set locally.
638640
"""
639-
size = self._properties.get('size')
641+
size = self._get_properties().get('size')
640642
if size is not None:
641643
return int(size)
642644

@@ -652,7 +654,7 @@ def storage_class(self):
652654
:returns: If set, one of "STANDARD", "NEARLINE", or
653655
"DURABLE_REDUCED_AVAILABILITY", else ``None``.
654656
"""
655-
return self._properties.get('storageClass')
657+
return self._get_properties().get('storageClass')
656658

657659
@property
658660
def time_deleted(self):
@@ -665,7 +667,7 @@ def time_deleted(self):
665667
set locally. If the blob has not been deleted, this will
666668
never be set.
667669
"""
668-
return self._properties.get('timeDeleted')
670+
return self._get_properties().get('timeDeleted')
669671

670672
@property
671673
def updated(self):
@@ -677,7 +679,7 @@ def updated(self):
677679
:returns: RFC3339 valid timestamp, or ``None`` if the property is not
678680
set locally.
679681
"""
680-
return self._properties.get('updated')
682+
return self._get_properties().get('updated')
681683

682684

683685
class _UploadConfig(object):

gcloud/storage/bucket.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def get_items_from_response(self, response):
7373
for item in response.get('items', []):
7474
name = item.get('name')
7575
blob = Blob(name, bucket=self.bucket)
76-
blob._properties = item
76+
blob._set_properties(item)
7777
yield blob
7878

7979

@@ -150,9 +150,10 @@ def create(self, project=None):
150150
'from environment.')
151151

152152
query_params = {'project': project}
153-
self._properties = self.connection.api_request(
153+
api_response = self.connection.api_request(
154154
method='POST', path='/b', query_params=query_params,
155155
data={'name': self.name})
156+
self._set_properties(api_response)
156157

157158
@property
158159
def acl(self):
@@ -218,7 +219,7 @@ def get_blob(self, blob_name):
218219
path=blob.path)
219220
name = response.get('name') # Expect this to be blob_name
220221
blob = Blob(name, bucket=self)
221-
blob._properties = response
222+
blob._set_properties(response)
222223
return blob
223224
except NotFound:
224225
return None
@@ -406,7 +407,7 @@ def copy_blob(self, blob, destination_bucket, new_name=None):
406407
new_blob = Blob(bucket=destination_bucket, name=new_name)
407408
api_path = blob.path + '/copyTo' + new_blob.path
408409
copy_result = self.connection.api_request(method='POST', path=api_path)
409-
new_blob._properties = copy_result
410+
new_blob._set_properties(copy_result)
410411
return new_blob
411412

412413
def upload_file(self, filename, blob_name=None):
@@ -504,7 +505,7 @@ def cors(self):
504505
:returns: A sequence of mappings describing each CORS policy.
505506
"""
506507
return [copy.deepcopy(policy)
507-
for policy in self._properties.get('cors', ())]
508+
for policy in self._get_properties().get('cors', ())]
508509

509510
@cors.setter
510511
def cors(self, entries):
@@ -529,7 +530,7 @@ def etag(self):
529530
:returns: The bucket etag or ``None`` if the property is not
530531
set locally.
531532
"""
532-
return self._properties.get('etag')
533+
return self._get_properties().get('etag')
533534

534535
@property
535536
def id(self):
@@ -541,7 +542,7 @@ def id(self):
541542
:returns: The ID of the bucket or ``None`` if the property is not
542543
set locally.
543544
"""
544-
return self._properties.get('id')
545+
return self._get_properties().get('id')
545546

546547
@property
547548
def lifecycle_rules(self):
@@ -553,7 +554,7 @@ def lifecycle_rules(self):
553554
:rtype: list(dict)
554555
:returns: A sequence of mappings describing each lifecycle rule.
555556
"""
556-
info = self._properties.get('lifecycle', {})
557+
info = self._get_properties().get('lifecycle', {})
557558
return [copy.deepcopy(rule) for rule in info.get('rule', ())]
558559

559560
@lifecycle_rules.setter
@@ -588,7 +589,7 @@ def get_logging(self):
588589
:returns: a dict w/ keys, ``logBucket`` and ``logObjectPrefix``
589590
(if logging is enabled), or None (if not).
590591
"""
591-
info = self._properties.get('logging')
592+
info = self._get_properties().get('logging')
592593
return copy.deepcopy(info)
593594

594595
def enable_logging(self, bucket_name, object_prefix=''):
@@ -622,7 +623,7 @@ def metageneration(self):
622623
:returns: The metageneration of the bucket or ``None`` if the property
623624
is not set locally.
624625
"""
625-
metageneration = self._properties.get('metageneration')
626+
metageneration = self._get_properties().get('metageneration')
626627
if metageneration is not None:
627628
return int(metageneration)
628629

@@ -636,7 +637,7 @@ def owner(self):
636637
:returns: Mapping of owner's role/ID. If the property is not set
637638
locally, returns ``None``.
638639
"""
639-
return copy.deepcopy(self._properties.get('owner'))
640+
return copy.deepcopy(self._get_properties().get('owner'))
640641

641642
@property
642643
def project_number(self):
@@ -648,7 +649,7 @@ def project_number(self):
648649
:returns: The project number that owns the bucket or ``None`` if the
649650
property is not set locally.
650651
"""
651-
project_number = self._properties.get('projectNumber')
652+
project_number = self._get_properties().get('projectNumber')
652653
if project_number is not None:
653654
return int(project_number)
654655

@@ -662,7 +663,7 @@ def self_link(self):
662663
:returns: The self link for the bucket or ``None`` if the property is
663664
not set locally.
664665
"""
665-
return self._properties.get('selfLink')
666+
return self._get_properties().get('selfLink')
666667

667668
@property
668669
def storage_class(self):
@@ -676,7 +677,7 @@ def storage_class(self):
676677
:returns: If set, one of "STANDARD", "NEARLINE", or
677678
"DURABLE_REDUCED_AVAILABILITY", else ``None``.
678679
"""
679-
return self._properties.get('storageClass')
680+
return self._get_properties().get('storageClass')
680681

681682
@property
682683
def time_created(self):
@@ -688,7 +689,7 @@ def time_created(self):
688689
:returns: RFC3339 valid timestamp, or ``None`` if the property is not
689690
set locally.
690691
"""
691-
return self._properties.get('timeCreated')
692+
return self._get_properties().get('timeCreated')
692693

693694
@property
694695
def versioning_enabled(self):
@@ -700,7 +701,7 @@ def versioning_enabled(self):
700701
:rtype: boolean
701702
:returns: True if enabled, else False.
702703
"""
703-
versioning = self._properties.get('versioning', {})
704+
versioning = self._get_properties().get('versioning', {})
704705
return versioning.get('enabled', False)
705706

706707
@versioning_enabled.setter

gcloud/storage/iterator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def get_items_from_response(self, response):
2626
items = response.get('items', [])
2727
for item in items:
2828
my_item = MyItemClass(other_arg=True)
29-
my_item._properties = item
29+
my_item._set_properties(item)
3030
yield my_item
3131
3232
You then can use this to get **all** the results from a resource::

0 commit comments

Comments
 (0)