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 """
0 commit comments