Skip to content

Commit fafb25c

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 #775
1 parent f08e71b commit fafb25c

File tree

7 files changed

+78
-45
lines changed

7 files changed

+78
-45
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: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,8 @@ def upload_from_file(self, file_obj, rewind=False, size=None,
301301
:type num_retries: integer
302302
:param num_retries: Number of upload retries. Defaults to 6.
303303
"""
304-
content_type = (content_type or self._properties.get('contentType') or
304+
content_type = (content_type or
305+
self._get_properties().get('contentType') or
305306
'application/octet-stream')
306307

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

360361
def upload_from_filename(self, filename, content_type=None):
361362
"""Upload this blob's contents from the content of a named file.
@@ -382,7 +383,8 @@ def upload_from_filename(self, filename, content_type=None):
382383
:type content_type: string or ``NoneType``
383384
:param content_type: Optional type of content being uploaded.
384385
"""
385-
content_type = content_type or self._properties.get('contentType')
386+
content_type = (content_type or
387+
self._get_properties().get('contentType'))
386388
if content_type is None:
387389
content_type, _ = mimetypes.guess_type(filename)
388390

@@ -497,7 +499,7 @@ def component_count(self):
497499
``None`` if the property is not set locally. This property
498500
will not be set on objects not created via ``compose``.
499501
"""
500-
component_count = self._properties.get('componentCount')
502+
component_count = self._get_properties().get('componentCount')
501503
if component_count is not None:
502504
return int(component_count)
503505

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

516518
@property
517519
def generation(self):
@@ -523,7 +525,7 @@ def generation(self):
523525
:returns: The generation of the blob or ``None`` if the property
524526
is not set locally.
525527
"""
526-
generation = self._properties.get('generation')
528+
generation = self._get_properties().get('generation')
527529
if generation is not None:
528530
return int(generation)
529531

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

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

565567
@property
566568
def metadata(self):
@@ -572,7 +574,7 @@ def metadata(self):
572574
:returns: The metadata associated with the blob or ``None`` if the
573575
property is not set locally.
574576
"""
575-
return copy.deepcopy(self._properties.get('metadata'))
577+
return copy.deepcopy(self._get_properties().get('metadata'))
576578

577579
@metadata.setter
578580
def metadata(self, value):
@@ -595,7 +597,7 @@ def metageneration(self):
595597
:returns: The metageneration of the blob or ``None`` if the property
596598
is not set locally.
597599
"""
598-
metageneration = self._properties.get('metageneration')
600+
metageneration = self._get_properties().get('metageneration')
599601
if metageneration is not None:
600602
return int(metageneration)
601603

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

614616
@property
615617
def self_link(self):
@@ -621,7 +623,7 @@ def self_link(self):
621623
:returns: The self link for the blob or ``None`` if the property is
622624
not set locally.
623625
"""
624-
return self._properties.get('selfLink')
626+
return self._get_properties().get('selfLink')
625627

626628
@property
627629
def size(self):
@@ -633,7 +635,7 @@ def size(self):
633635
:returns: The size of the blob or ``None`` if the property
634636
is not set locally.
635637
"""
636-
size = self._properties.get('size')
638+
size = self._get_properties().get('size')
637639
if size is not None:
638640
return int(size)
639641

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

654656
@property
655657
def time_deleted(self):
@@ -662,7 +664,7 @@ def time_deleted(self):
662664
``None`` if the property is not set locally. If the blob has
663665
not been deleted, this will never be set.
664666
"""
665-
value = self._properties.get('timeDeleted')
667+
value = self._get_properties().get('timeDeleted')
666668
if value is not None:
667669
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)
668670

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

gcloud/storage/bucket.py

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

8181

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

154154
query_params = {'project': project}
155-
self._properties = self.connection.api_request(
155+
api_response = self.connection.api_request(
156156
method='POST', path='/b', query_params=query_params,
157157
data={'name': self.name})
158+
self._set_properties(api_response)
158159

159160
@property
160161
def acl(self):
@@ -220,7 +221,7 @@ def get_blob(self, blob_name):
220221
path=blob.path)
221222
name = response.get('name') # Expect this to be blob_name
222223
blob = Blob(name, bucket=self)
223-
blob._properties = response
224+
blob._set_properties(response)
224225
return blob
225226
except NotFound:
226227
return None
@@ -408,7 +409,7 @@ def copy_blob(self, blob, destination_bucket, new_name=None):
408409
new_blob = Blob(bucket=destination_bucket, name=new_name)
409410
api_path = blob.path + '/copyTo' + new_blob.path
410411
copy_result = self.connection.api_request(method='POST', path=api_path)
411-
new_blob._properties = copy_result
412+
new_blob._set_properties(copy_result)
412413
return new_blob
413414

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

511512
@cors.setter
512513
def cors(self, entries):
@@ -531,7 +532,7 @@ def etag(self):
531532
:returns: The bucket etag or ``None`` if the property is not
532533
set locally.
533534
"""
534-
return self._properties.get('etag')
535+
return self._get_properties().get('etag')
535536

536537
@property
537538
def id(self):
@@ -543,7 +544,7 @@ def id(self):
543544
:returns: The ID of the bucket or ``None`` if the property is not
544545
set locally.
545546
"""
546-
return self._properties.get('id')
547+
return self._get_properties().get('id')
547548

548549
@property
549550
def lifecycle_rules(self):
@@ -555,7 +556,7 @@ def lifecycle_rules(self):
555556
:rtype: list(dict)
556557
:returns: A sequence of mappings describing each lifecycle rule.
557558
"""
558-
info = self._properties.get('lifecycle', {})
559+
info = self._get_properties().get('lifecycle', {})
559560
return [copy.deepcopy(rule) for rule in info.get('rule', ())]
560561

561562
@lifecycle_rules.setter
@@ -590,7 +591,7 @@ def get_logging(self):
590591
:returns: a dict w/ keys, ``logBucket`` and ``logObjectPrefix``
591592
(if logging is enabled), or None (if not).
592593
"""
593-
info = self._properties.get('logging')
594+
info = self._get_properties().get('logging')
594595
return copy.deepcopy(info)
595596

596597
def enable_logging(self, bucket_name, object_prefix=''):
@@ -624,7 +625,7 @@ def metageneration(self):
624625
:returns: The metageneration of the bucket or ``None`` if the property
625626
is not set locally.
626627
"""
627-
metageneration = self._properties.get('metageneration')
628+
metageneration = self._get_properties().get('metageneration')
628629
if metageneration is not None:
629630
return int(metageneration)
630631

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

643644
@property
644645
def project_number(self):
@@ -650,7 +651,7 @@ def project_number(self):
650651
:returns: The project number that owns the bucket or ``None`` if the
651652
property is not set locally.
652653
"""
653-
project_number = self._properties.get('projectNumber')
654+
project_number = self._get_properties().get('projectNumber')
654655
if project_number is not None:
655656
return int(project_number)
656657

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

669670
@property
670671
def storage_class(self):
@@ -678,7 +679,7 @@ def storage_class(self):
678679
:returns: If set, one of "STANDARD", "NEARLINE", or
679680
"DURABLE_REDUCED_AVAILABILITY", else ``None``.
680681
"""
681-
return self._properties.get('storageClass')
682+
return self._get_properties().get('storageClass')
682683

683684
@property
684685
def time_created(self):
@@ -690,7 +691,7 @@ def time_created(self):
690691
:returns: Datetime object parsed from RFC3339 valid timestamp, or
691692
``None`` if the property is not set locally.
692693
"""
693-
value = self._properties.get('timeCreated')
694+
value = self._get_properties().get('timeCreated')
694695
if value is not None:
695696
return datetime.datetime.strptime(value, _GOOGLE_TIMESTAMP_FORMAT)
696697

@@ -704,7 +705,7 @@ def versioning_enabled(self):
704705
:rtype: boolean
705706
:returns: True if enabled, else False.
706707
"""
707-
versioning = self._properties.get('versioning', {})
708+
versioning = self._get_properties().get('versioning', {})
708709
return versioning.get('enabled', False)
709710

710711
@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)