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