Package cloudfiles :: Module container
[frames] | no frames]

Source Code for Module cloudfiles.container

  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 
18 19 # Because HTTPResponse objects *have* to have read() called on them 20 # before they can be used again ... 21 # pylint: disable-msg=W0612 22 23 24 -class Container(object):
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 """
47 - def __set_name(self, name):
48 # slashes make for invalid names 49 if isinstance(name, (str, unicode)) and \ 50 ('/' in name or len(name) > consts.container_name_limit): 51 raise InvalidContainerName(name) 52 self._name = name
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)
82 - def _fetch_cdn_data(self):
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)
101 - def make_public(self, ttl=consts.default_cdn_ttl):
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)
128 - def make_private(self):
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)
144 - def purge_from_cdn(self, email=None):
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
199 - def is_public(self):
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)
218 - def public_uri(self):
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)
234 - def public_ssl_uri(self):
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)
250 - def public_streaming_uri(self):
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)
266 - def create_object(self, object_name):
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)
321 - def get_object(self, object_name):
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 # empty strings are valid 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
433 - def __getitem__(self, key):
434 return self.get_object(key)
435
436 - def __str__(self):
437 return self.name
438 439 @requires_name(InvalidContainerName)
440 - def delete_object(self, object_name):
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
463 464 -class ContainerResults(object):
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
475 - def __getitem__(self, key):
476 return Container(self.conn, 477 self._containers[key]['name'], 478 self._containers[key]['count'], 479 self._containers[key]['bytes'])
480
481 - def __getslice__(self, i, j):
482 return [Container(self.conn, k['name'], k['count'], \ 483 k['size']) for k in self._containers[i:j]]
484
485 - def __contains__(self, item):
486 return item in self._names
487
488 - def __repr__(self):
489 return 'ContainerResults: %s containers' % len(self._containers)
490 __str__ = __repr__ 491
492 - def __len__(self):
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
501 - def count(self, value):
502 """ 503 returns the number of occurrences of value 504 """ 505 return self._names.count(value)
506 507 # vim:set ai sw=4 ts=4 tw=0 expandtab: 508