"""
Client module. Contains the primary interface for the client.
See COPYING for license information.
"""
try:
import simplejson as json
except ImportError:
import json
from object_storage.container import Container
from object_storage.storage_object import StorageObject
from object_storage.utils import get_path
from object_storage import errors
import logging
import UserDict
logger = logging.getLogger(__name__)
class AccountModel(UserDict.UserDict):
def __init__(self, controller, headers={}):
_headers = {}
# Lowercase headers
for key, value in headers.iteritems():
_key = key.lower()
_headers[_key] = value
self.headers = _headers
self._meta = None
_properties = {}
_properties['container_count'] = int(self.headers.get('x-account-container-count') or\
self.headers.get('count') or 0)
_properties['object_count'] = int(self.headers.get('x-account-object-count') or\
self.headers.get('object_count') or 0)
_properties['size'] = int(self.headers.get('x-account-bytes-used') or\
self.headers.get('size') or 0)
_properties['path'] = controller.path
_properties['url'] = controller.url
meta = {}
for key, value in self.headers.iteritems():
if key.startswith('meta_'):
meta[key[5:]] = value
elif key.startswith('x-account-meta-'):
meta[key[15:]] = value
self.meta = meta
_properties['meta'] = self.meta
self.properties = _properties
self.data = self.properties
[docs]class Client(object):
"""
Client class. Primary interface for the client.
"""
def __init__(self, username=None,
api_key=None,
connection=None,
delimiter='/',
**kwargs):
""" constructor for Client object
@param username: the username
@param api_key: api_key for Object Storage
@param connection: `object_storage.transport.AuthenticatedConnection`
instance.
@param delimiter: the symbol to use to divid up hiearchical divisions
for objects.
@param container_class: factory or class for Container constructing
@param object_class: factory or class for StorageObject constructing
"""
self.username = username
self.api_key = api_key
self.delimiter = delimiter
self.container_class = kwargs.get('container_class', Container)
self.object_class = kwargs.get('object_class', StorageObject)
self.storage_url = None
self.conn = connection
self.model = None
[docs] def load(self):
""" load data for the account
@return: object_storage.client, self
"""
def _formatter(res):
self.model = AccountModel(self, res.headers)
return self
return self.make_request('HEAD', formatter=_formatter)
[docs] def get_info(self):
""" loads data if not already available and returns the properties """
if not self.model:
self.load()
return self.model.properties
@property
[docs] def properties(self):
""" loads data if not already available and returns the properties """
return self.get_info()
props = properties
@property
@property
@property
[docs] def path(self):
""" Returns the file-path. Always returns an empty string. """
return ''
@property
[docs] def url(self):
""" Returns the url of the resource. """
return self.get_url()
[docs] def is_dir(self):
""" Returns whether or not this is a directory. Always True. """
return True
[docs] def search(self, q, options=None, **kwargs):
""" Access the search interface.
@param q: the search query. This can be None.
@param options: options for the search API. Valid options:
q.[fieldname] -> define search query for a specific field.
field -> field name (when using q)
type -> 'object' or 'container'; default shows both.
recursive -> whether to search recursively or to limit to
one level; default=true
@param **kwargs: to be merged into the options param.
Provides a nicer interface for the same thing.
More information on options:
http://sldn.softlayer.com/article/API-Operations-Search-Services
"""
default_params = {
'format': 'json',
'q': q
}
params = {}
options = options or {}
options.update(kwargs)
for key, val in options.iteritems():
if key.startswith('q_'):
params["q.%s" % key[2:]] = val
else:
params[key] = val
params = dict(default_params.items() + params.items())
headers = {'X-Context': 'search'}
_path = None
if options.has_key('container'):
_path = [options['container']]
if 'path' in options and type(options['path']) is not dict:
_path = [options['path']]
def _formatter(response):
""" Formats search results. """
headers = response.headers
content = response.content
items = json.loads(content)
objs = []
for item in items:
if 'type' not in item or item['type'] == 'container':
objs.append(self.container(item['name'], headers=item))
elif item['type'] == 'object':
obj = self.storage_object(item['container'],
item['name'],
headers=item)
objs.append(obj)
count = int(headers.get('x-search-items-count', 0))
total = int(headers.get('x-search-items-total', 0))
return {'count': count, 'total': total, 'results': objs}
return self.make_request('GET', _path, headers=headers,
params=params,
formatter=_formatter)
[docs] def set_delimiter(self, delimiter):
""" Sets the delimiter for pseudo hierarchical directory structure.
@param delimiter: delimiter to use
"""
self.delimiter = delimiter
[docs] def set_storage_url(self, url):
""" Sets the storage URL. After authentication, the URL is automatically
populated, but the default value can be overwritten.
@param url: url to use to call the Object Storage API.
"""
self.storage_url = url
[docs] def container(self, name, headers=None):
""" Initializes container object.
@param name: name of the container
@param headers: initial headers to use to initialize the object
"""
return self.container_class(name, headers=headers, client=self)
[docs] def get_container(self, name):
""" Makes a container object and calls load() on it.
@param name: container name
@raises ResponseError
"""
return self.container(name).load()
[docs] def create_container(self, name):
""" Creates a new container
@param name: container name
@raises ResponseError
"""
return self.container(name).create()
[docs] def delete_container(self, name, recursive=False):
""" Deletes a container.
@param name: container name
@raises ResponseError
@raises ContainerNotEmpty if container is not empty
"""
params = {}
if recursive:
params['recursive'] = True
def _formatter(res):
if res.status_code is 409:
raise errors.ContainerNotEmpty(name)
return True
return self.make_request('DELETE', [name], params=params, formatter=_formatter)
[docs] def containers(self, marker=None, headers=None):
""" Lists containers
@param marker: start listing after this container name
@param headers: extra headers to use when making the listing call
@raises ResponseError
"""
params = {'format': 'json'}
if marker:
params['marker'] = marker
def _formatter(res):
containers = []
if res.content:
items = json.loads(res.content)
for item in items:
name = item.get('name', None)
containers.append(self.container(name, item))
return containers
return self.make_request('GET', params=params, headers=headers, formatter=_formatter)
[docs] def public_containers(self, *args, **kwargs):
""" Lists public containers. Same interface as self.containers()
@raises ResponseError
"""
kwargs['headers'] = {'X-Context': 'cdn'}
return self.containers(*args, **kwargs)
[docs] def storage_object(self, container, name, headers=None):
""" Initialize a StorageObject instance
@param container: container name
@param name: object name
@param headers: initial headers to use to initialize the object
"""
return self.object_class(container, name,
headers=headers, client=self)
[docs] def get_object(self, container, name):
""" Load an object from swift
@param container: container name
@param name: object name
@raises ResponseError
"""
return self.storage_object(container, name).load()
[docs] def delete_object(self, container, name):
""" Delete an object from swift
@param container: container name
@param name: object name
@raises ResponseError
"""
return self.make_request('DELETE', [container, name], formatter=lambda r: True)
[docs] def get_url(self, path=None):
""" Returns the url of the resource
@param path: path to append to the end of the URL
"""
url = self.storage_url
if not url:
self.storage_url = self.conn.storage_url
url = self.storage_url
if path:
url = "%s/%s" % (url, get_path(path))
return url
[docs] def make_request(self, method, path=None, *args, **kwargs):
""" Make an HTTP request
@param method: HTTP method (GET, HEAD, POST, PUT, ...)
@param path: path
@raises ResponseError
"""
url = self.get_url(path)
result = self.conn.make_request(method, url, *args, **kwargs)
return result
[docs] def chunk_download(self, path, chunk_size=10*1024, headers=None):
""" Returns a chunk download generator
@param path: path
@param chunk_size: the max size in bytes to return on each yield
@param headers: extra headers to use with this request
@raises ResponseError
"""
url = self.get_url(path)
return self.conn.chunk_download(url, chunk_size=chunk_size)
[docs] def chunk_upload(self, path, headers=None):
""" Returns a chunkable connection object at the given path
@param path: path
@param headers: extra headers to use with this request
@raises ResponseError
"""
url = self.get_url(path)
return self.conn.chunk_upload('PUT', url, headers)
def __getitem__(self, name):
""" Returns a container object with the given name """
return self.container(name)
def __iter__(self):
""" Returns an interator based on results of self.containers() """
listing = self.containers()
for obj in listing:
yield obj