Skip to content

Commit 36966e8

Browse files
committed
Stop using 'metadata' to refer to all properties in bucket/key represetnation.
Make helper methods which get / set those properties private. Split some dual-mode methods.
1 parent 2c3725d commit 36966e8

File tree

6 files changed

+183
-186
lines changed

6 files changed

+183
-186
lines changed

gcloud/storage/_helpers.py

Lines changed: 82 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,23 @@
44
"""
55

66

7-
class _MetadataMixin(object):
8-
"""Abstract mixin for cloud storage classes with associated metadata.
7+
class _PropertyMixin(object):
8+
"""Abstract mixin for cloud storage classes with associated propertties.
99
1010
Non-abstract subclasses should implement:
11-
- CUSTOM_METADATA_FIELDS
11+
- CUSTOM_PROPERTY_ACCESSORS
1212
- connection
1313
- path
1414
"""
1515

16-
CUSTOM_METADATA_FIELDS = None
16+
CUSTOM_PROPERTY_ACCESSORS = None
1717
"""Mapping of field name -> accessor for fields w/ custom accessors.
1818
1919
Expected to be set by subclasses. Fields in this mapping will cause
20-
`get_metadata()` to raise a KeyError with a message to use the relevant
21-
accessor methods.
20+
:meth:`_get_property()` to raise a KeyError with a message to use the
21+
relevant accessor methods.
2222
"""
2323

24-
def __init__(self, name=None, metadata=None):
25-
"""_MetadataMixin constructor.
26-
27-
:type name: string
28-
:param name: The name of the object.
29-
30-
:type metadata: dict
31-
:param metadata: All the other data provided by Cloud Storage.
32-
"""
33-
self.name = name
34-
self.metadata = metadata
35-
3624
@property
3725
def connection(self):
3826
"""Abstract getter for the connection to use."""
@@ -43,90 +31,112 @@ def path(self):
4331
"""Abstract getter for the object path."""
4432
raise NotImplementedError
4533

46-
def has_metadata(self, field=None):
47-
"""Check if metadata is available.
34+
def __init__(self, name=None, properties=None):
35+
"""_PropertyMixin constructor.
4836
49-
:type field: string
50-
:param field: (optional) the particular field to check for.
37+
:type name: string
38+
:param name: The name of the object.
39+
40+
:type metadata: dict
41+
:param metadata: All the other data provided by Cloud Storage.
42+
"""
43+
self.name = name
44+
self._properties = {}
45+
if properties is not None:
46+
self._properties.update(properties)
5147

52-
:rtype: bool
53-
:returns: Whether metadata is available locally.
48+
@property
49+
def properties(self):
50+
"""Ensure properties are loaded, and return a copy.
5451
"""
55-
if not self.metadata:
56-
return False
57-
elif field and field not in self.metadata:
58-
return False
59-
else:
60-
return True
52+
if not self._properties:
53+
self._reload_properties()
54+
return self._properties.copy()
6155

62-
def reload_metadata(self):
63-
"""Reload metadata from Cloud Storage.
56+
metadata = properties # Backward-compatibiltiy alias
6457

65-
:rtype: :class:`_MetadataMixin`
58+
def _reload_properties(self):
59+
"""Reload properties from Cloud Storage.
60+
61+
:rtype: :class:`_PropertyMixin`
6662
:returns: The object you just reloaded data for.
6763
"""
6864
# Pass only '?projection=noAcl' here because 'acl' and related
6965
# are handled via 'get_acl()' etc.
7066
query_params = {'projection': 'noAcl'}
71-
self.metadata = self.connection.api_request(
67+
self._properties = self.connection.api_request(
7268
method='GET', path=self.path, query_params=query_params)
7369
return self
70+
reload_metadata = _reload_properties # backward-compat alias
71+
72+
def _patch_properties(self, properties):
73+
"""Update particular fields of this object's properties.
74+
75+
This method will only update the fields provided and will not
76+
touch the other fields.
77+
78+
It will also reload the properties locally based on the server's
79+
response.
7480
75-
def get_metadata(self, field=None, default=None):
76-
"""Get all metadata or a specific field.
81+
:type properties: dict
82+
:param properties: The dictionary of values to update.
83+
84+
:rtype: :class:`_PropertyMixin`
85+
:returns: The current object.
86+
"""
87+
# Pass '?projection=full' here because 'PATCH' documented not
88+
# to work properly w/ 'noAcl'.
89+
self._properties = self.connection.api_request(
90+
method='PATCH', path=self.path, data=properties,
91+
query_params={'projection': 'full'})
92+
return self
93+
patch_metadata = _patch_properties # backward-compat alias
94+
95+
def _has_property(self, field=None):
96+
"""Check if property is available.
97+
98+
:type field: string
99+
:param field: (optional) the particular field to check for.
100+
101+
:rtype: boolean
102+
:returns: Whether property is available locally. If no ``field``
103+
passed, return whether *any* properties are available.
104+
"""
105+
if field and field not in self._properties:
106+
return False
107+
return len(self._properties) > 0
108+
has_metadata = _has_property # backward-compat alias
109+
110+
def _get_property(self, field, default=None):
111+
"""Return the value of a field from the server-side representation.
77112
78113
If you request a field that isn't available, and that field can
79114
be retrieved by refreshing data from Cloud Storage, this method
80-
will reload the data using :func:`_MetadataMixin.reload_metadata`.
115+
will reload the data using :func:`_PropertyMixin._reload_properties`.
81116
82117
:type field: string
83-
:param field: (optional) A particular field to retrieve from metadata.
118+
:param field: A particular field to retrieve from properties.
84119
85120
:type default: anything
86121
:param default: The value to return if the field provided wasn't found.
87122
88-
:rtype: dict or anything
89-
:returns: All metadata or the value of the specific field.
90-
91-
:raises: :class:`KeyError` if the field is in CUSTOM_METADATA_FIELDS.
123+
:rtype: anything
124+
:returns: value of the specific field, or the default if not found.
92125
"""
93-
# We ignore 'acl' and related fields because they are meant to be
94-
# handled via 'get_acl()' and related methods.
95-
custom = self.CUSTOM_METADATA_FIELDS.get(field)
126+
# Raise for fields which have custom accessors.
127+
custom = self.CUSTOM_PROPERTY_ACCESSORS.get(field)
96128
if custom is not None:
97129
message = 'Use %s or related methods instead.' % custom
98130
raise KeyError((field, message))
99131

100-
if not self.has_metadata(field=field):
101-
self.reload_metadata()
132+
if not self._properties or field not in self._properties:
133+
self._reload_properties()
102134

103-
if field:
104-
return self.metadata.get(field, default)
105-
else:
106-
return self.metadata
107-
108-
def patch_metadata(self, metadata):
109-
"""Update particular fields of this object's metadata.
110-
111-
This method will only update the fields provided and will not
112-
touch the other fields.
113-
114-
It will also reload the metadata locally based on the server's
115-
response.
116-
117-
:type metadata: dict
118-
:param metadata: The dictionary of values to update.
119-
120-
:rtype: :class:`_MetadataMixin`
121-
:returns: The current object.
122-
"""
123-
self.metadata = self.connection.api_request(
124-
method='PATCH', path=self.path, data=metadata,
125-
query_params={'projection': 'full'})
126-
return self
135+
return self._properties.get(field, default)
136+
get_metadata = _get_property # Backward-compat alias
127137

128138
def get_acl(self):
129-
"""Get ACL metadata as an object.
139+
"""Get ACL as an object.
130140
131141
:returns: An ACL object for the current object.
132142
"""

gcloud/storage/bucket.py

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import os
44

5-
from gcloud.storage._helpers import _MetadataMixin
5+
from gcloud.storage._helpers import _PropertyMixin
66
from gcloud.storage import exceptions
77
from gcloud.storage.acl import BucketACL
88
from gcloud.storage.acl import DefaultObjectACL
@@ -11,7 +11,7 @@
1111
from gcloud.storage.key import _KeyIterator
1212

1313

14-
class Bucket(_MetadataMixin):
14+
class Bucket(_PropertyMixin):
1515
"""A class representing a Bucket on Cloud Storage.
1616
1717
:type connection: :class:`gcloud.storage.connection.Connection`
@@ -21,18 +21,18 @@ class Bucket(_MetadataMixin):
2121
:param name: The name of the bucket.
2222
"""
2323

24-
CUSTOM_METADATA_FIELDS = {
24+
CUSTOM_PROPERTY_ACCESSORS = {
2525
'acl': 'get_acl',
2626
'defaultObjectAcl': 'get_default_object_acl',
2727
'lifecycle': 'get_lifecycle',
2828
}
29-
"""Mapping of field name -> accessor for fields w/ custom accessors."""
29+
"""Map field name -> accessor for fields w/ custom accessors."""
3030

3131
# ACL rules are lazily retrieved.
3232
_acl = _default_object_acl = None
3333

34-
def __init__(self, connection=None, name=None, metadata=None):
35-
super(Bucket, self).__init__(name=name, metadata=metadata)
34+
def __init__(self, connection=None, name=None, properties=None):
35+
super(Bucket, self).__init__(name=name, properties=properties)
3636
self._connection = connection
3737

3838
@property
@@ -60,7 +60,7 @@ def from_dict(cls, bucket_dict, connection=None):
6060
:returns: A bucket constructed from the data provided.
6161
"""
6262
return cls(connection=connection, name=bucket_dict['name'],
63-
metadata=bucket_dict)
63+
properties=bucket_dict)
6464

6565
def __repr__(self):
6666
return '<Bucket: %s>' % self.name
@@ -120,7 +120,7 @@ def get_all_keys(self):
120120
"""List all the keys in this bucket.
121121
122122
This will **not** retrieve all the data for all the keys, it
123-
will only retrieve metadata about the keys.
123+
will only retrieve the keys.
124124
125125
This is equivalent to::
126126
@@ -344,7 +344,7 @@ def upload_file_object(self, file_obj, key=None):
344344
return key.upload_from_file(file_obj)
345345

346346
def configure_website(self, main_page_suffix=None, not_found_page=None):
347-
"""Configure website-related metadata.
347+
"""Configure website-related properties.
348348
349349
.. note::
350350
This (apparently) only works
@@ -385,7 +385,7 @@ def configure_website(self, main_page_suffix=None, not_found_page=None):
385385
'notFoundPage': not_found_page,
386386
},
387387
}
388-
return self.patch_metadata(data)
388+
return self._patch_properties(data)
389389

390390
def disable_website(self):
391391
"""Disable the website configuration for this bucket.
@@ -395,21 +395,11 @@ def disable_website(self):
395395
"""
396396
return self.configure_website(None, None)
397397

398-
def get_acl(self):
399-
"""Get ACL metadata as a :class:`gcloud.storage.acl.BucketACL` object.
400-
401-
:rtype: :class:`gcloud.storage.acl.BucketACL`
402-
:returns: An ACL object for the current bucket.
403-
"""
404-
if not self.acl.loaded:
405-
self.acl.reload()
406-
return self.acl
407-
408398
def get_default_object_acl(self):
409399
"""Get the current Default Object ACL rules.
410400
411-
If the appropriate metadata isn't available locally, this method
412-
will reload it from Cloud Storage.
401+
If the acl isn't available locally, this method will reload it from
402+
Cloud Storage.
413403
414404
:rtype: :class:`gcloud.storage.acl.DefaultObjectACL`
415405
:returns: A DefaultObjectACL object for this bucket.
@@ -451,10 +441,10 @@ def get_lifecycle(self):
451441
:rtype: list(dict)
452442
:returns: A sequence of mappings describing each CORS policy.
453443
"""
454-
if not self.has_metadata('lifecycle'):
455-
self.reload_metadata()
444+
if not self._has_property('lifecycle'):
445+
self._reload_properties()
456446
result = []
457-
info = self.metadata.get('lifecycle', {})
447+
info = self._properties.get('lifecycle', {})
458448
for rule in info.get('rule', ()):
459449
rule = rule.copy()
460450
result.append(rule)
@@ -469,7 +459,7 @@ def update_lifecycle(self, rules):
469459
:type rules: list(dict)
470460
:param rules: A sequence of mappings describing each lifecycle policy.
471461
"""
472-
self.patch_metadata({'lifecycle': {'rule': rules}})
462+
self._patch_properties({'lifecycle': {'rule': rules}})
473463

474464

475465
class BucketIterator(Iterator):

gcloud/storage/key.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
import os
55
from StringIO import StringIO
66

7-
from gcloud.storage._helpers import _MetadataMixin
7+
from gcloud.storage._helpers import _PropertyMixin
88
from gcloud.storage.acl import ObjectACL
99
from gcloud.storage.exceptions import StorageError
1010
from gcloud.storage.iterator import Iterator
1111

1212

13-
class Key(_MetadataMixin):
13+
class Key(_PropertyMixin):
1414
"""A wrapper around Cloud Storage's concept of an ``Object``."""
1515

16-
CUSTOM_METADATA_FIELDS = {
16+
CUSTOM_PROPERTY_ACCESSORS = {
1717
'acl': 'get_acl',
1818
}
19-
"""Mapping of field name -> accessor for fields w/ custom accessors."""
19+
"""Map field name -> accessor for fields w/ custom accessors."""
2020

2121
CHUNK_SIZE = 1024 * 1024 # 1 MB.
2222
"""The size of a chunk of data whenever iterating (1 MB).
@@ -26,7 +26,7 @@ class Key(_MetadataMixin):
2626
# ACL rules are lazily retrieved.
2727
_acl = None
2828

29-
def __init__(self, bucket=None, name=None, metadata=None):
29+
def __init__(self, bucket=None, name=None, properties=None):
3030
"""Key constructor.
3131
3232
:type bucket: :class:`gcloud.storage.bucket.Bucket`
@@ -36,10 +36,10 @@ def __init__(self, bucket=None, name=None, metadata=None):
3636
:param name: The name of the key. This corresponds to the
3737
unique path of the object in the bucket.
3838
39-
:type metadata: dict
40-
:param metadata: All the other data provided by Cloud Storage.
39+
:type properties: dict
40+
:param properties: All the other data provided by Cloud Storage.
4141
"""
42-
super(Key, self).__init__(name=name, metadata=metadata or {})
42+
super(Key, self).__init__(name=name, properties=properties)
4343
self.bucket = bucket
4444

4545
@property
@@ -65,7 +65,7 @@ def from_dict(cls, key_dict, bucket=None):
6565
:returns: A key based on the data provided.
6666
"""
6767

68-
return cls(bucket=bucket, name=key_dict['name'], metadata=key_dict)
68+
return cls(bucket=bucket, name=key_dict['name'], properties=key_dict)
6969

7070
def __repr__(self):
7171
if self.bucket:

0 commit comments

Comments
 (0)