cloudfiles.container

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
    148 -    def disable_object_versioning(self):
    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