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