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_ttl = None 75 self.cdn_log_retention = None 76 if connection.cdn_enabled: 77 self._fetch_cdn_data()
78 79 @requires_name(InvalidContainerName)
80 - def _fetch_cdn_data(self):
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)
97 - def make_public(self, ttl=consts.default_cdn_ttl):
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)
124 - def make_private(self):
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)
140 - def purge_from_cdn(self, email=None):
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
195 - def is_public(self):
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)
214 - def public_uri(self):
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)
230 - def public_ssl_uri(self):
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)
246 - def create_object(self, object_name):
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)
301 - def get_object(self, object_name):
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 # empty strings are valid 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
413 - def __getitem__(self, key):
414 return self.get_object(key)
415
416 - def __str__(self):
417 return self.name
418 419 @requires_name(InvalidContainerName)
420 - def delete_object(self, object_name):
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
443 444 -class ContainerResults(object):
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
455 - def __getitem__(self, key):
456 return Container(self.conn, 457 self._containers[key]['name'], 458 self._containers[key]['count'], 459 self._containers[key]['bytes'])
460
461 - def __getslice__(self, i, j):
462 return [Container(self.conn, k['name'], k['count'], \ 463 k['size']) for k in self._containers[i:j]]
464
465 - def __contains__(self, item):
466 return item in self._names
467
468 - def __repr__(self):
469 return 'ContainerResults: %s containers' % len(self._containers)
470 __str__ = __repr__ 471
472 - def __len__(self):
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
481 - def count(self, value):
482 """ 483 returns the number of occurrences of value 484 """ 485 return self._names.count(value)
486 487 # vim:set ai sw=4 ts=4 tw=0 expandtab: 488