1 """
2 container operations
3
4 Containers are storage compartments where you put your data (objects).
5 A container is similar to a directory or folder on a conventional filesystem
6 with the exception that they exist in a flat namespace, you can not create
7 containers inside of containers.
8
9 See COPYING for license information.
10 """
11
12 from storage_object import Object, ObjectResults
13 from errors import ResponseError, InvalidContainerName, InvalidObjectName, \
14 ContainerNotPublic, CDNNotEnabled
15 from utils import requires_name
16 import consts
17 from fjson import json_loads
24 """
25 Container object and Object instance factory.
26
27 If your account has the feature enabled, containers can be publically
28 shared over a global content delivery network.
29
30 @ivar name: the container's name (generally treated as read-only)
31 @type name: str
32 @ivar object_count: the number of objects in this container (cached)
33 @type object_count: number
34 @ivar size_used: the sum of the sizes of all objects in this container
35 (cached)
36 @type size_used: number
37 @ivar cdn_ttl: the time-to-live of the CDN's public cache of this container
38 (cached, use make_public to alter)
39 @type cdn_ttl: number
40 @ivar cdn_log_retention: retention of the logs in the container.
41 @type cdn_log_retention: bool
42
43 @undocumented: _fetch_cdn_data
44 @undocumented: _list_objects_raw
45 """
52
53 name = property(fget=lambda self: self._name, fset=__set_name,
54 doc="the name of the container (read-only)")
55
56 - def __init__(self, connection=None, name=None, count=None, size=None):
57 """
58 Containers will rarely if ever need to be instantiated directly by the
59 user.
60
61 Instead, use the L{create_container<Connection.create_container>},
62 L{get_container<Connection.get_container>},
63 L{list_containers<Connection.list_containers>} and
64 other methods on a valid Connection object.
65 """
66 self._name = None
67 self.name = name
68 self.conn = connection
69 self.object_count = count
70 self.size_used = size
71 self.cdn_uri = None
72 self.cdn_ttl = None
73 self.cdn_log_retention = None
74 if connection.cdn_enabled:
75 self._fetch_cdn_data()
76
77 @requires_name(InvalidContainerName)
79 """
80 Fetch the object's CDN data from the CDN service
81 """
82 response = self.conn.cdn_request('HEAD', [self.name])
83 if (response.status >= 200) and (response.status < 300):
84 for hdr in response.getheaders():
85 if hdr[0].lower() == 'x-cdn-uri':
86 self.cdn_uri = hdr[1]
87 if hdr[0].lower() == 'x-ttl':
88 self.cdn_ttl = int(hdr[1])
89 if hdr[0].lower() == 'x-log-retention':
90 self.cdn_log_retention = hdr[1] == "True" and True or False
91
92 @requires_name(InvalidContainerName)
94 """
95 Either publishes the current container to the CDN or updates its
96 CDN attributes. Requires CDN be enabled on the account.
97
98 >>> container.make_public(ttl=604800) # expire in 1 week
99
100 @param ttl: cache duration in seconds of the CDN server
101 @type ttl: number
102 """
103 if not self.conn.cdn_enabled:
104 raise CDNNotEnabled()
105 if self.cdn_uri:
106 request_method = 'POST'
107 else:
108 request_method = 'PUT'
109 hdrs = {'X-TTL': str(ttl), 'X-CDN-Enabled': 'True'}
110 response = self.conn.cdn_request(request_method, [self.name], hdrs=hdrs)
111 if (response.status < 200) or (response.status >= 300):
112 raise ResponseError(response.status, response.reason)
113 self.cdn_ttl = ttl
114 for hdr in response.getheaders():
115 if hdr[0].lower() == 'x-cdn-uri':
116 self.cdn_uri = hdr[1]
117
118 @requires_name(InvalidContainerName)
120 """
121 Disables CDN access to this container.
122 It may continue to be available until its TTL expires.
123
124 >>> container.make_private()
125 """
126 if not self.conn.cdn_enabled:
127 raise CDNNotEnabled()
128 hdrs = {'X-CDN-Enabled': 'False'}
129 self.cdn_uri = None
130 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
131 if (response.status < 200) or (response.status >= 300):
132 raise ResponseError(response.status, response.reason)
133
134 @requires_name(InvalidContainerName)
135 - def log_retention(self, log_retention=consts.cdn_log_retention):
136 """
137 Enable CDN log retention on the container. If enabled logs will be
138 periodically (at unpredictable intervals) compressed and uploaded to
139 a ".CDN_ACCESS_LOGS" container in the form of
140 "container_name.YYYYMMDDHH-XXXX.gz". Requires CDN be enabled on the
141 account.
142
143 >>> container.log_retention(True)
144
145 @param log_retention: Enable or disable logs retention.
146 @type log_retention: bool
147 """
148 if not self.conn.cdn_enabled:
149 raise CDNNotEnabled()
150
151 hdrs = {'X-Log-Retention': log_retention}
152 response = self.conn.cdn_request('POST', [self.name], hdrs=hdrs)
153 if (response.status < 200) or (response.status >= 300):
154 raise ResponseError(response.status, response.reason)
155
156 self.cdn_log_retention = log_retention
157
159 """
160 Returns a boolean indicating whether or not this container is
161 publically accessible via the CDN.
162
163 >>> container.is_public()
164 False
165 >>> container.make_public()
166 >>> container.is_public()
167 True
168
169 @rtype: bool
170 @return: whether or not this container is published to the CDN
171 """
172 if not self.conn.cdn_enabled:
173 raise CDNNotEnabled()
174 return self.cdn_uri is not None
175
176 @requires_name(InvalidContainerName)
178 """
179 Return the URI for this container, if it is publically
180 accessible via the CDN.
181
182 >>> connection['container1'].public_uri()
183 'http://c00061.cdn.cloudfiles.rackspacecloud.com'
184
185 @rtype: str
186 @return: the public URI for this container
187 """
188 if not self.is_public():
189 raise ContainerNotPublic()
190 return self.cdn_uri
191
192 @requires_name(InvalidContainerName)
194 """
195 Return an L{Object} instance, creating it if necessary.
196
197 When passed the name of an existing object, this method will
198 return an instance of that object, otherwise it will create a
199 new one.
200
201 >>> container.create_object('new_object')
202 <cloudfiles.storage_object.Object object at 0xb778366c>
203 >>> obj = container.create_object('new_object')
204 >>> obj.name
205 'new_object'
206
207 @type object_name: str
208 @param object_name: the name of the object to create
209 @rtype: L{Object}
210 @return: an object representing the newly created storage object
211 """
212 return Object(self, object_name)
213
214 @requires_name(InvalidContainerName)
215 - def get_objects(self, prefix=None, limit=None, marker=None,
216 path=None, **parms):
217 """
218 Return a result set of all Objects in the Container.
219
220 Keyword arguments are treated as HTTP query parameters and can
221 be used to limit the result set (see the API documentation).
222
223 >>> container.get_objects(limit=2)
224 ObjectResults: 2 objects
225 >>> for obj in container.get_objects():
226 ... print obj.name
227 new_object
228 old_object
229
230 @param prefix: filter the results using this prefix
231 @type prefix: str
232 @param limit: return the first "limit" objects found
233 @type limit: int
234 @param marker: return objects whose names are greater than "marker"
235 @type marker: str
236 @param path: return all objects in "path"
237 @type path: str
238
239 @rtype: L{ObjectResults}
240 @return: an iterable collection of all storage objects in the container
241 """
242 return ObjectResults(self, self.list_objects_info(
243 prefix, limit, marker, path, **parms))
244
245 @requires_name(InvalidContainerName)
247 """
248 Return an L{Object} instance for an existing storage object.
249
250 If an object with a name matching object_name does not exist
251 then a L{NoSuchObject} exception is raised.
252
253 >>> obj = container.get_object('old_object')
254 >>> obj.name
255 'old_object'
256
257 @param object_name: the name of the object to retrieve
258 @type object_name: str
259 @rtype: L{Object}
260 @return: an Object representing the storage object requested
261 """
262 return Object(self, object_name, force_exists=True)
263
264 @requires_name(InvalidContainerName)
265 - def list_objects_info(self, prefix=None, limit=None, marker=None,
266 path=None, **parms):
267 """
268 Return information about all objects in the Container.
269
270 Keyword arguments are treated as HTTP query parameters and can
271 be used limit the result set (see the API documentation).
272
273 >>> conn['container1'].list_objects_info(limit=2)
274 [{u'bytes': 4820,
275 u'content_type': u'application/octet-stream',
276 u'hash': u'db8b55400b91ce34d800e126e37886f8',
277 u'last_modified': u'2008-11-05T00:56:00.406565',
278 u'name': u'new_object'},
279 {u'bytes': 1896,
280 u'content_type': u'application/octet-stream',
281 u'hash': u'1b49df63db7bc97cd2a10e391e102d4b',
282 u'last_modified': u'2008-11-05T00:56:27.508729',
283 u'name': u'old_object'}]
284
285 @param prefix: filter the results using this prefix
286 @type prefix: str
287 @param limit: return the first "limit" objects found
288 @type limit: int
289 @param marker: return objects with names greater than "marker"
290 @type marker: str
291 @param path: return all objects in "path"
292 @type path: str
293
294 @rtype: list({"name":"...", "hash":..., "size":..., "type":...})
295 @return: a list of all container info as dictionaries with the
296 keys "name", "hash", "size", and "type"
297 """
298 parms['format'] = 'json'
299 resp = self._list_objects_raw(
300 prefix, limit, marker, path, **parms)
301 return json_loads(resp)
302
303 @requires_name(InvalidContainerName)
304 - def list_objects(self, prefix=None, limit=None, marker=None,
305 path=None, **parms):
306 """
307 Return names of all L{Object}s in the L{Container}.
308
309 Keyword arguments are treated as HTTP query parameters and can
310 be used to limit the result set (see the API documentation).
311
312 >>> container.list_objects()
313 ['new_object', 'old_object']
314
315 @param prefix: filter the results using this prefix
316 @type prefix: str
317 @param limit: return the first "limit" objects found
318 @type limit: int
319 @param marker: return objects with names greater than "marker"
320 @type marker: str
321 @param path: return all objects in "path"
322 @type path: str
323
324 @rtype: list(str)
325 @return: a list of all container names
326 """
327 resp = self._list_objects_raw(prefix=prefix, limit=limit,
328 marker=marker, path=path, **parms)
329 return resp.splitlines()
330
331 @requires_name(InvalidContainerName)
332 - def _list_objects_raw(self, prefix=None, limit=None, marker=None,
333 path=None, **parms):
334 """
335 Returns a chunk list of storage object info.
336 """
337 if prefix: parms['prefix'] = prefix
338 if limit: parms['limit'] = limit
339 if marker: parms['marker'] = marker
340 if not path is None: parms['path'] = path
341 response = self.conn.make_request('GET', [self.name], parms=parms)
342 if (response.status < 200) or (response.status > 299):
343 buff = response.read()
344 raise ResponseError(response.status, response.reason)
345 return response.read()
346
349
352
353 @requires_name(InvalidContainerName)
355 """
356 Permanently remove a storage object.
357
358 >>> container.list_objects()
359 ['new_object', 'old_object']
360 >>> container.delete_object('old_object')
361 >>> container.list_objects()
362 ['new_object']
363
364 @param object_name: the name of the object to retrieve
365 @type object_name: str
366 """
367 if isinstance(object_name, Object):
368 object_name = object_name.name
369 if not object_name:
370 raise InvalidObjectName(object_name)
371 response = self.conn.make_request('DELETE', [self.name, object_name])
372 if (response.status < 200) or (response.status > 299):
373 buff = response.read()
374 raise ResponseError(response.status, response.reason)
375 buff = response.read()
376
378 """
379 An iterable results set object for Containers.
380
381 This class implements dictionary- and list-like interfaces.
382 """
383 - def __init__(self, conn, containers=list()):
384 self._containers = containers
385 self._names = [k['name'] for k in containers]
386 self.conn = conn
387
389 return Container(self.conn,
390 self._containers[key]['name'],
391 self._containers[key]['count'],
392 self._containers[key]['bytes'])
393
395 return [Container(self.conn, k['name'], k['count'], k['size']) for k in self._containers[i:j] ]
396
398 return item in self._names
399
401 return 'ContainerResults: %s containers' % len(self._containers)
402 __str__ = __repr__
403
405 return len(self._containers)
406
407 - def index(self, value, *args):
408 """
409 returns an integer for the first index of value
410 """
411 return self._names.index(value, *args)
412
414 """
415 returns the number of occurrences of value
416 """
417 return self._names.count(value)
418
419
420