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