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, 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)
84 - def update_metadata(self, metadata):
85 """ 86 Update Container Metadata 87 88 >>> metadata = {'x-container-meta-foo' : 'bar'} 89 >>> container.update_metadata(metadata) 90 91 @param metadata: A dictionary containing metadata. 92 @type metadata: dict 93 """ 94 response = self.conn.make_request('POST', [self.name]) 95 response.read() 96 if (response.status < 200) or (response.status > 299): 97 raise ResponseError(response.status, response.reason)
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
129 - def disable_static_web(self):
130 """ 131 Disable static web for this Container 132 133 >>> container.disable_static_web() 134 """ 135 self.enable_static_web()
136
137 - def enable_object_versioning(self, container_name):
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)
157 - def _fetch_cdn_data(self):
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)
176 - def make_public(self, ttl=consts.default_cdn_ttl):
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)
205 - def make_private(self):
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)
221 - def purge_from_cdn(self, email=None):
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
276 - def is_public(self):
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)
295 - def public_uri(self):
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)
311 - def public_ssl_uri(self):
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)
327 - def public_streaming_uri(self):
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)
343 - def create_object(self, object_name):
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)
398 - def get_object(self, object_name):
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 # empty strings are valid 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
510 - def __getitem__(self, key):
511 return self.get_object(key)
512
513 - def __str__(self):
514 return self.name
515 516 @requires_name(InvalidContainerName)
517 - def delete_object(self, object_name):
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
540 541 -class ContainerResults(object):
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
552 - def __getitem__(self, key):
553 return Container(self.conn, 554 self._containers[key]['name'], 555 self._containers[key]['count'], 556 self._containers[key]['bytes'])
557
558 - def __getslice__(self, i, j):
559 return [Container(self.conn, k['name'], k['count'], \ 560 k['size']) for k in self._containers[i:j]]
561
562 - def __contains__(self, item):
563 return item in self._names
564
565 - def __repr__(self):
566 return 'ContainerResults: %s containers' % len(self._containers)
567 __str__ = __repr__ 568
569 - def __len__(self):
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
578 - def count(self, value):
579 """ 580 returns the number of occurrences of value 581 """ 582 return self._names.count(value)
583 584 # vim:set ai sw=4 ts=4 tw=0 expandtab: 585