Rewrite client library function for updating metadata using update POST parameter
[pithos] / pithos / lib / client.py
index 0762a0b..b13aebc 100644 (file)
@@ -59,7 +59,7 @@ class Fault(Exception):
         self.status = status
 
 class Client(object):
-    def __init__(self, host, account, api='v1', verbose=False, debug=False):
+    def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
         """`host` can also include a port, e.g '127.0.0.1:8000'."""
         
         self.host = host
@@ -67,6 +67,7 @@ class Client(object):
         self.api = api
         self.verbose = verbose or debug
         self.debug = debug
+        self.token = token
     
     def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
                           blocksize=1024):
@@ -75,6 +76,7 @@ class Client(object):
         # write header
         path = '/%s/%s%s' % (self.api, self.account, path)
         http.putrequest(method, path)
+        http.putheader('X-Auth-Token', self.token)
         http.putheader('Content-Type', 'application/octet-stream')
         http.putheader('Transfer-Encoding', 'chunked')
         if headers:
@@ -134,6 +136,8 @@ class Client(object):
             for k,v in params.items():
                 if v:
                     full_path = '%s&%s=%s' %(full_path, k, v)
+                else:
+                    full_path = '%s&%s' %(full_path, k)
         conn = HTTPConnection(self.host)
         
         #encode whitespace
@@ -141,14 +145,16 @@ class Client(object):
         
         kwargs = {}
         kwargs['headers'] = headers or {}
+        kwargs['headers']['X-Auth-Token'] = self.token
         if not headers or \
         'Transfer-Encoding' not in headers \
         or headers['Transfer-Encoding'] != 'chunked':
             kwargs['headers']['Content-Length'] = len(body) if body else 0
         if body:
             kwargs['body'] = body
-            kwargs['headers']['Content-Type'] = 'application/octet-stream'
-        #print '****', method, full_path, kwargs
+        else:
+            kwargs['headers']['Content-Type'] = ''
+        kwargs['headers'].setdefault('Content-Type', 'application/octet-stream')
         try:
             conn.request(method, full_path, **kwargs)
         except socket.error, e:
@@ -185,8 +191,9 @@ class Client(object):
     def head(self, path, format='text', params=None):
         return self.req('HEAD', path, format=format, params=params)
     
-    def post(self, path, body=None, format='text', headers=None):
-        return self.req('POST', path, body, headers=headers, format=format)
+    def post(self, path, body=None, format='text', headers=None, params=None):
+        return self.req('POST', path, body, headers=headers, format=format,
+                        params=params)
     
     def put(self, path, body=None, format='text', headers=None):
         return self.req('PUT', path, body, headers=headers, format=format)
@@ -196,7 +203,7 @@ class Client(object):
         status, headers, data = self.get(path, format=format, headers=headers,
                                          params=params)
         if detail:
-            data = json.loads(data)
+            data = json.loads(data) if data else ''
         else:
             data = data.strip().split('\n')
         return data
@@ -215,29 +222,30 @@ class Client(object):
     
     def _update_metadata(self, path, entity, **meta):
         """
-         adds new and updates the values of previously set metadata
+        adds new and updates the values of previously set metadata
         """
-        for key, val in meta.items():
-            meta.pop(key)
-            meta['X-%s-Meta-%s' %(entity.capitalize(), key.capitalize())] = val
-        prev_meta = self._get_metadata(path)
-        prev_meta.update(meta)
+        params = {'update':None}
         headers = {}
-        for key, val in prev_meta.items():
-            headers[key.capitalize()] = val
-        self.post(path, headers=headers)
+        prefix = 'x-%s-meta-' % entity
+        for k,v in meta.items():
+            k = '%s%s' % (prefix, k)
+            k = '-'.join(elem.capitalize() for elem in k.split('-'))
+            headers[k] = v
+        self.post(path, headers=headers, params=params)
     
     def _delete_metadata(self, path, entity, meta=[]):
         """
         delete previously set metadata
         """
-        prev_meta = self._get_metadata(path)
+        prefix = 'x-%s-meta-' % entity
+        prev_meta = self._get_metadata(path, prefix)
         headers = {}
         for key, val in prev_meta.items():
-            if key.split('-')[-1] in meta:
+            if key in meta:
                 continue
-            http_key = key.capitalize()
-            headers[http_key] = val
+            key = '%s%s' % (prefix, key)
+            key = '-'.join(elem.capitalize() for elem in key.split('-'))
+            headers[key] = val
         self.post(path, headers=headers)
     
     # Storage Account Services
@@ -256,6 +264,12 @@ class Client(object):
     def delete_account_metadata(self, meta=[]):
         self._delete_metadata('', 'account', meta)
     
+    def set_account_groups(self, **groups):
+        headers = {}
+        for key, val in groups.items():
+            headers['X-Account-Group-%s' % key.capitalize()] = val
+        self.post('', headers=headers)
+    
     # Storage Container Services
     
     def _filter(self, l, d):
@@ -275,14 +289,16 @@ class Client(object):
     def _filter_trashed(self, l):
         return self._filter(l, {'trash':'true'})
     
-    def list_objects(self, container, detail=False, params=None, headers=None,
-                     include_trashed=False):
+    def list_objects(self, container, detail=False, headers=None,
+                     include_trashed=False, **params):
         l = self._list('/' + container, detail, params, headers)
         if not include_trashed:
             l = self._filter_trashed(l)
         return l
     
-    def create_container(self, container, headers=None):
+    def create_container(self, container, headers=None, **meta):
+        for k,v in meta.items():
+            headers['X-Container-Meta-%s' %k.strip().upper()] = v.strip()
         status, header, data = self.put('/' + container, headers=headers)
         if status == 202:
             return False
@@ -306,6 +322,14 @@ class Client(object):
         path = '/%s' % (container)
         self._delete_metadata(path, 'container', meta)
     
+    def set_container_policies(self, container, **policies):
+        path = '/%s' % (container)
+        headers = {}
+        print ''
+        for key, val in policies.items():
+            headers['X-Container-Policy-%s' % key.capitalize()] = val
+        self.post(path, headers=headers)
+    
     # Storage Object Services
     
     def retrieve_object(self, container, object, detail=False, headers=None,
@@ -322,35 +346,64 @@ class Client(object):
         h = {'Content-Type':'application/directory'}
         self.create_object(container, object, f=None, headers=h)
     
+    def _set_public(self, headers, public=False):
+        """
+        sets the public header
+        """
+        if public == None:
+            return
+        elif public:
+            headers['X-Object-Public'] = public
+        else:
+            headers['X-Object-Public'] = ''
+    
     def create_object(self, container, object, f=stdin, chunked=False,
-                      blocksize=1024, headers=None):
+                      blocksize=1024, headers={}, use_hashes=False,
+                      public=None, **meta):
         """
         creates an object
         if f is None then creates a zero length object
         if f is stdin or chunked is set then performs chunked transfer 
         """
         path = '/%s/%s' % (container, object)
-        if not chunked and f != stdin:
+        for k,v in meta.items():
+            headers['X-Object-Meta-%s' %k.strip().upper()] = v.strip()
+        self._set_public(headers, public)
+        headers = headers if headers else None
+        if not chunked:
+            format = 'json' if use_hashes else 'text'
             data = f.read() if f else None
-            return self.put(path, data, headers=headers)
+            if data:
+                if format == 'json':
+                    data = eval(data)
+                    data = json.dumps(data)
+            return self.put(path, data, headers=headers, format=format)
         else:
             return self._chunked_transfer(path, 'PUT', f, headers=headers,
                                    blocksize=1024)
     
     def update_object(self, container, object, f=stdin, chunked=False,
-                      blocksize=1024, headers=None):
-        if not f:
-            return
+                      blocksize=1024, headers={}, offset=None, public=None,
+                      **meta):
+        print locals()
         path = '/%s/%s' % (container, object)
+        for k,v in meta.items():
+            headers['X-Object-Meta-%s' %k.strip().upper()] = v.strip()
+        if offset:
+            headers['Content-Range'] = 'bytes %s-/*' % offset
+        else:
+            headers['Content-Range'] = 'bytes */*'
+        self._set_public(headers, public)
+        headers = headers if headers else None
         if not chunked and f != stdin:
-            data = f.read()
+            data = f.read() if f else None
             self.post(path, data, headers=headers)
         else:
             self._chunked_transfer(path, 'POST', f, headers=headers,
                                    blocksize=1024)
     
     def _change_obj_location(self, src_container, src_object, dst_container,
-                             dst_object, remove=False, headers=None):
+                             dst_object, remove=False, public=None, headers={}):
         path = '/%s/%s' % (dst_container, dst_object)
         if not headers:
             headers = {}
@@ -358,19 +411,22 @@ class Client(object):
             headers['X-Move-From'] = '/%s/%s' % (src_container, src_object)
         else:
             headers['X-Copy-From'] = '/%s/%s' % (src_container, src_object)
+        self._set_public(headers, public)
+        self.headers = headers if headers else None
         headers['Content-Length'] = 0
         self.put(path, headers=headers)
     
     def copy_object(self, src_container, src_object, dst_container,
-                             dst_object, headers=None):
+                             dst_object, public=False, headers=None):
         self._change_obj_location(src_container, src_object,
                                    dst_container, dst_object,
-                                   headers=headers)
+                                   public, headers)
     
     def move_object(self, src_container, src_object, dst_container,
                              dst_object, headers=None):
         self._change_obj_location(src_container, src_object,
-                                   dst_container, dst_object, True, headers)
+                                   dst_container, dst_object, True,
+                                   public, headers)
     
     def delete_object(self, container, object):
         self.delete('/%s/%s' % (container, object))
@@ -402,7 +458,26 @@ class Client(object):
     def restore_object(self, container, object):
         """
         restores a trashed object
-        actualy just resets all object metadata except trash
+        actualy removes trash object metadata info
         """
         self.delete_object_metadata(container, object, ['trash'])
-
+    
+    def publish_object(self, container, object):
+        """
+        sets a previously created object publicly accessible
+        """
+        path = '/%s/%s' % (container, object)
+        headers = {}
+        headers['Content-Range'] = 'bytes */*'
+        self._set_public(headers, public=True)
+        self.post(path, headers=headers)
+    
+    def unpublish_object(self, container, object):
+        """
+        unpublish an object
+        """
+        path = '/%s/%s' % (container, object)
+        headers = {}
+        headers['Content-Range'] = 'bytes */*'
+        self._set_public(headers, public=False)
+        self.post(path, headers=headers)