Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ 7912c553

History | View | Annotate | Download (31.3 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
# 
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
# 
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
# 
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
# 
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from httplib import HTTPConnection, HTTP
35
from sys import stdin
36
from xml.dom import minidom
37

    
38
import json
39
import types
40
import socket
41
import pithos.api.faults
42

    
43
ERROR_CODES = {304:'Not Modified',
44
               400:'Bad Request',
45
               401:'Unauthorized',
46
               404:'Not Found',
47
               409:'Conflict',
48
               411:'Length Required',
49
               412:'Precondition Failed',
50
               416:'Range Not Satisfiable',
51
               422:'Unprocessable Entity',
52
               503:'Service Unavailable'}
53

    
54
class Fault(Exception):
55
    def __init__(self, data='', status=None):
56
        if data == '' and status in ERROR_CODES.keys():
57
            data = ERROR_CODES[status]
58
        Exception.__init__(self, data)
59
        self.data = data
60
        self.status = status
61

    
62
class Client(object):
63
    def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
64
        """`host` can also include a port, e.g '127.0.0.1:8000'."""
65
        
66
        self.host = host
67
        self.account = account
68
        self.api = api
69
        self.verbose = verbose or debug
70
        self.debug = debug
71
        self.token = token
72
    
73
    def _req(self, method, path, body=None, headers={}, format='text',
74
            params={}):
75
        full_path = '/%s/%s%s?format=%s' % (self.api, self.account, path,
76
                                            format)
77
        for k,v in params.items():
78
            if v:
79
                full_path = '%s&%s=%s' %(full_path, k, v)
80
            else:
81
                full_path = '%s&%s' %(full_path, k)
82
        conn = HTTPConnection(self.host)
83
        
84
        #encode whitespace
85
        full_path = full_path.replace(' ', '%20')
86
        
87
        kwargs = {}
88
        for k,v in headers.items():
89
            headers.pop(k)
90
            k = k.replace('_', '-')
91
            headers[k] = v
92
        
93
        kwargs['headers'] = headers
94
        kwargs['headers']['X-Auth-Token'] = self.token
95
        if body:
96
            kwargs['body'] = body
97
            kwargs['headers'].setdefault('content-type',
98
                                         'application/octet-stream')
99
        kwargs['headers'].setdefault('content-length', len(body) if body else 0)
100
        try:
101
            #print '*', method, full_path, kwargs
102
            conn.request(method, full_path, **kwargs)
103
        except socket.error, e:
104
            raise Fault(status=503)
105
            
106
        resp = conn.getresponse()
107
        headers = dict(resp.getheaders())
108
        
109
        if self.verbose:
110
            print '%d %s' % (resp.status, resp.reason)
111
            for key, val in headers.items():
112
                print '%s: %s' % (key.capitalize(), val)
113
            print
114
        
115
        length = resp.getheader('content-length', None)
116
        data = resp.read(length)
117
        if self.debug:
118
            print data
119
            print
120
        
121
        if int(resp.status) in ERROR_CODES.keys():
122
            #print '**', resp.status
123
            raise Fault(data, int(resp.status))
124
        
125
        #print '**',  resp.status, headers, data
126
        return resp.status, headers, data
127
    
128
    def delete(self, path, format='text', params={}):
129
        return self._req('DELETE', path, format=format, params=params)
130
    
131
    def get(self, path, format='text', headers=None, params={}):
132
        return self._req('GET', path, headers=headers, format=format,
133
                        params=params)
134
    
135
    def head(self, path, format='text', params={}):
136
        return self._req('HEAD', path, format=format, params=params)
137
    
138
    def post(self, path, body=None, format='text', headers=None, params={}):
139
        return self._req('POST', path, body, headers=headers, format=format,
140
                        params=params)
141
    
142
    def put(self, path, body=None, format='text', headers=None):
143
        return self._req('PUT', path, body, headers=headers, format=format)
144
    
145
    def _list(self, path, format='text', params={}, **headers):
146
        status, headers, data = self.get(path, format=format, headers=headers,
147
                                         params=params)
148
        if format == 'json':
149
            data = json.loads(data) if data else ''
150
        elif format == 'xml':
151
            data = minidom.parseString(data)
152
        else:
153
            data = data.strip().split('\n') if data else ''
154
        return data
155
    
156
    def _get_metadata(self, path, prefix=None, params={}):
157
        status, headers, data = self.head(path, params=params)
158
        prefixlen = len(prefix) if prefix else 0
159
        meta = {}
160
        for key, val in headers.items():
161
            if prefix and not key.startswith(prefix):
162
                continue
163
            elif prefix and key.startswith(prefix):
164
                key = key[prefixlen:]
165
            meta[key] = val
166
        return meta
167
    
168
    def _filter(self, l, d):
169
        """
170
        filter out from l elements having the metadata values provided
171
        """
172
        ll = l
173
        for elem in l:
174
            if type(elem) == types.DictionaryType:
175
                for key in d.keys():
176
                    k = 'x_object_meta_%s' % key
177
                    if k in elem.keys() and elem[k] == d[key]:
178
                        ll.remove(elem)
179
                        break
180
        return ll
181
    
182
class OOS_Client(Client):
183
    """Openstack Object Storage Client"""
184
    
185
    def _update_metadata(self, path, entity, **meta):
186
        """adds new and updates the values of previously set metadata"""
187
        ex_meta = self.retrieve_account_metadata(restricted=True)
188
        ex_meta.update(meta)
189
        headers = {}
190
        prefix = 'x-%s-meta-' % entity
191
        for k,v in ex_meta.items():
192
            k = '%s%s' % (prefix, k)
193
            headers[k] = v
194
        return self.post(path, headers=headers)
195
    
196
    def _reset_metadata(self, path, entity, **meta):
197
        """
198
        overwrites all user defined metadata
199
        """
200
        headers = {}
201
        prefix = 'x-%s-meta-' % entity
202
        for k,v in meta.items():
203
            k = '%s%s' % (prefix, k)
204
            headers[k] = v
205
        return self.post(path, headers=headers)
206
    
207
    def _delete_metadata(self, path, entity, meta=[]):
208
        """delete previously set metadata"""
209
        ex_meta = self.retrieve_account_metadata(restricted=True)
210
        headers = {}
211
        prefix = 'x-%s-meta-' % entity
212
        for k in ex_meta.keys():
213
            if k in meta:
214
                headers['%s%s' % (prefix, k)] = ex_meta[k]
215
        return self.post(path, headers=headers)
216
    
217
    # Storage Account Services
218
    
219
    def list_containers(self, format='text', limit=10000, marker=None, params={},
220
                        **headers):
221
        """lists containers"""
222
        if not params:
223
            params = {}
224
        params.update({'limit':limit, 'marker':marker})
225
        return self._list('', format, params, **headers)
226
    
227
    def retrieve_account_metadata(self, restricted=False, **params):
228
        """returns the account metadata"""
229
        prefix = 'x-account-meta-' if restricted else None
230
        return self._get_metadata('', prefix, params)
231
    
232
    def update_account_metadata(self, **meta):
233
        """updates the account metadata"""
234
        return self._update_metadata('', 'account', **meta)
235
        
236
    def delete_account_metadata(self, meta=[]):
237
        """deletes the account metadata"""
238
        return self._delete_metadata('', 'account', meta)
239
    
240
    def reset_account_metadata(self, **meta):
241
        """resets account metadata"""
242
        return self._reset_metadata('', 'account', **meta)
243
    
244
    # Storage Container Services
245
    
246
    def _filter_trashed(self, l):
247
        return self._filter(l, {'trash':'true'})
248
    
249
    def list_objects(self, container, format='text', limit=10000, marker=None,
250
                     prefix=None, delimiter=None, path=None,
251
                     include_trashed=False, params={}, **headers):
252
        """returns a list with the container objects"""
253
        params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
254
                       'delimiter':delimiter, 'path':path})
255
        l = self._list('/' + container, format, params, **headers)
256
        #TODO support filter trashed with xml also
257
        if format != 'xml' and not include_trashed:
258
            l = self._filter_trashed(l)
259
        return l
260
    
261
    def create_container(self, container, **meta):
262
        """creates a container"""
263
        headers = {}
264
        for k,v in meta.items():
265
            headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
266
        status, header, data = self.put('/' + container, headers=headers)
267
        if status == 202:
268
            return False
269
        elif status != 201:
270
            raise Fault(data, int(status))
271
        return True
272
    
273
    def delete_container(self, container, params={}):
274
        """deletes a container"""
275
        return self.delete('/' + container, params=params)
276
    
277
    def retrieve_container_metadata(self, container, restricted=False, **params):
278
        """returns the container metadata"""
279
        prefix = 'x-container-meta-' if restricted else None
280
        return self._get_metadata('/%s' % container, prefix, params)
281
    
282
    def update_container_metadata(self, container, **meta):
283
        """unpdates the container metadata"""
284
        return self._update_metadata('/' + container, 'container', **meta)
285
        
286
    def delete_container_metadata(self, container, meta=[]):
287
        """deletes the container metadata"""
288
        path = '/%s' % (container)
289
        return self._delete_metadata(path, 'container', meta)
290
    
291
    # Storage Object Services
292
    
293
    def request_object(self, container, object, format='text', params={},
294
                        **headers):
295
        """returns tuple containing the status, headers and data response for an object request"""
296
        path = '/%s/%s' % (container, object)
297
        status, headers, data = self.get(path, format, headers, params)
298
        return status, headers, data
299
    
300
    def retrieve_object(self, container, object, format='text', params={},
301
                             **headers):
302
        """returns an object's data"""
303
        t = self.request_object(container, object, format, params, **headers)
304
        return t[2]
305
    
306
    def create_directory_marker(self, container, object):
307
        """creates a dierectory marker"""
308
        if not object:
309
            raise Fault('Directory markers have to be nested in a container')
310
        h = {'content_type':'application/directory'}
311
        return self.create_zero_length_object(container, object, **h)
312
    
313
    def create_object(self, container, object, f=stdin, format='text', meta={},
314
                      etag=None, content_type=None, content_encoding=None,
315
                      content_disposition=None, **headers):
316
        """creates a zero-length object"""
317
        path = '/%s/%s' % (container, object)
318
        for k, v  in headers.items():
319
            if not v:
320
                headers.pop(k)
321
        
322
        l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
323
        l = [elem for elem in l if eval(elem)]
324
        for elem in l:
325
            headers.update({elem:eval(elem)})
326
            
327
        for k,v in meta.items():
328
            headers['x-object-meta-%s' %k.strip()] = v.strip()
329
        data = f.read() if f else None
330
        return self.put(path, data, format, headers=headers)
331
    
332
    def create_zero_length_object(self, container, object, meta={}, etag=None,
333
                                  content_type=None, content_encoding=None,
334
                                  content_disposition=None, **headers):
335
        args = locals()
336
        for elem in ['self', 'container', 'headers']:
337
            args.pop(elem)
338
        args.update(headers)
339
        return self.create_object(container, f=None, **args)
340
    
341
    def update_object(self, container, object, f=stdin, offset=None, meta={},
342
                      content_length=None, content_type=None,
343
                      content_encoding=None, content_disposition=None,
344
                      **headers):
345
        path = '/%s/%s' % (container, object)
346
        for k, v  in headers.items():
347
            if not v:
348
                headers.pop(k)
349
        
350
        l = ['content_encoding', 'content_disposition', 'content_type',
351
             'content_length']
352
        l = [elem for elem in l if eval(elem)]
353
        for elem in l:
354
            headers.update({elem:eval(elem)})
355
            
356
        if 'content_range' not in headers.keys():
357
            if offset != None:
358
                headers['content_range'] = 'bytes %s-/*' % offset
359
            else:
360
                headers['content_range'] = 'bytes */*'
361
            
362
        for k,v in meta.items():
363
            headers['x-object-meta-%s' %k.strip()] = v.strip()
364
        data = f.read() if f else None
365
        return self.post(path, data, headers=headers)
366
    
367
    def _change_obj_location(self, src_container, src_object, dst_container,
368
                             dst_object, remove=False, public=False, **meta):
369
        path = '/%s/%s' % (dst_container, dst_object)
370
        headers = {}
371
        for k, v in meta.items():
372
            headers['x-object-meta-%s' % k] = v 
373
        if remove:
374
            headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
375
        else:
376
            headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
377
        self._set_public_header(headers, public)
378
        self.headers = headers if headers else None
379
        headers['content-length'] = 0
380
        return self.put(path, headers=headers)
381
    
382
    def copy_object(self, src_container, src_object, dst_container,
383
                             dst_object, public=False, **meta):
384
        return self._change_obj_location(src_container, src_object,
385
                                   dst_container, dst_object, False,
386
                                   public, **meta)
387
    
388
    def move_object(self, src_container, src_object, dst_container,
389
                             dst_object, public=False, **meta):
390
        return self._change_obj_location(src_container, src_object,
391
                                   dst_container, dst_object, True,
392
                                   public, **meta)
393
    
394
    def delete_object(self, container, object, params={}):
395
        return self.delete('/%s/%s' % (container, object), params=params)
396
    
397
    def retrieve_object_metadata(self, container, object, restricted=False,
398
                                 version=None):
399
        """
400
        set restricted to True to get only user defined metadata
401
        """
402
        path = '/%s/%s' % (container, object)
403
        prefix = 'x-object-meta-' if restricted else None
404
        params = {'version':version} if version else {}
405
        return self._get_metadata(path, prefix, params=params)
406
    
407
    def update_object_metadata(self, container, object, **meta):
408
        path = '/%s/%s' % (container, object)
409
        return self._update_metadata(path, 'object', **meta)
410
    
411
    def delete_object_metadata(self, container, object, meta=[]):
412
        path = '/%s/%s' % (container, object)
413
        return self._delete_metadata(path, 'object', meta)
414
    
415
class Pithos_Client(OOS_Client):
416
    """Pithos Storage Client. Extends OOS_Client"""
417
    
418
    def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
419
                          blocksize=1024):
420
        """perfomrs a chunked request"""
421
        http = HTTPConnection(self.host)
422
        
423
        # write header
424
        path = '/%s/%s%s' % (self.api, self.account, path)
425
        http.putrequest(method, path)
426
        http.putheader('x-auth-token', self.token)
427
        http.putheader('content-type', 'application/octet-stream')
428
        http.putheader('transfer-encoding', 'chunked')
429
        if headers:
430
            for header,value in headers.items():
431
                http.putheader(header, value)
432
        http.endheaders()
433
        
434
        # write body
435
        data = ''
436
        while True:
437
            if f.closed:
438
                break
439
            block = f.read(blocksize)
440
            if block == '':
441
                break
442
            data = '%s\r\n%s\r\n' % (hex(len(block)), block)
443
            try:
444
                http.send(data)
445
            except:
446
                #retry
447
                http.send(data)
448
        data = '0x0\r\n'
449
        try:
450
            http.send(data)
451
        except:
452
            #retry
453
            http.send(data)
454
        
455
        # get response
456
        resp = http.getresponse()
457
        
458
        headers = dict(resp.getheaders())
459
        
460
        if self.verbose:
461
            print '%d %s' % (resp.status, resp.reason)
462
            for key, val in headers.items():
463
                print '%s: %s' % (key.capitalize(), val)
464
            print
465
        
466
        length = resp.getheader('Content-length', None)
467
        data = resp.read(length)
468
        if self.debug:
469
            print data
470
            print
471
        
472
        if int(resp.status) in ERROR_CODES.keys():
473
            raise Fault(data, int(resp.status))
474
        
475
        #print '*',  resp.status, headers, data
476
        return resp.status, headers, data
477
    
478
    def _update_metadata(self, path, entity, **meta):
479
        """
480
        adds new and updates the values of previously set metadata
481
        """
482
        params = {'update':None}
483
        headers = {}
484
        prefix = 'x-%s-meta-' % entity
485
        for k,v in meta.items():
486
            k = '%s%s' % (prefix, k)
487
            headers[k] = v
488
        return self.post(path, headers=headers, params=params)
489
    
490
    def _delete_metadata(self, path, entity, meta=[]):
491
        """
492
        delete previously set metadata
493
        """
494
        params = {'update':None}
495
        headers = {}
496
        prefix = 'x-%s-meta-' % entity
497
        for m in meta:
498
            headers['%s%s' % (prefix, m)] = ''
499
        return self.post(path, headers=headers, params=params)
500
    
501
    # Storage Account Services
502
    
503
    def list_containers(self, format='text', if_modified_since=None,
504
                        if_unmodified_since=None, limit=1000, marker=None,
505
                        until=None):
506
        """returns a list with the account containers"""
507
        params = {'until':until} if until else None
508
        headers = {'if-modified-since':if_modified_since,
509
                   'if-unmodified-since':if_unmodified_since}
510
        return OOS_Client.list_containers(self, format=format, limit=limit,
511
                                          marker=marker, params=params,
512
                                          **headers)
513
    
514
    def retrieve_account_metadata(self, restricted=False, until=None):
515
        """returns the account metadata"""
516
        params = {'until':until} if until else {}
517
        return OOS_Client.retrieve_account_metadata(self, restricted=restricted,
518
                                                   **params)
519
    
520
    def set_account_groups(self, **groups):
521
        """create account groups"""
522
        headers = {}
523
        for key, val in groups.items():
524
            headers['x-account-group-%s' % key] = val
525
        params = {'update':None}
526
        return self.post('', headers=headers, params=params)
527
    
528
    def retrieve_account_groups(self):
529
        """returns the account groups"""
530
        meta = self.retrieve_account_metadata()
531
        prefix = 'x-account-group-'
532
        prefixlen = len(prefix)
533
        groups = {}
534
        for key, val in meta.items():
535
            if prefix and not key.startswith(prefix):
536
                continue
537
            elif prefix and key.startswith(prefix):
538
                key = key[prefixlen:]
539
            groups[key] = val
540
        return groups
541
    
542
    def unset_account_groups(self, groups=[]):
543
        """delete account groups"""
544
        headers = {}
545
        for elem in groups:
546
            headers['x-account-group-%s' % elem] = ''
547
        params = {'update':None}
548
        return self.post('', headers=headers, params=params)
549
    
550
    def reset_account_groups(self, **groups):
551
        """overrides account groups"""
552
        headers = {}
553
        for key, val in groups.items():
554
            headers['x-account-group-%s' % key] = val
555
        meta = self.retrieve_account_metadata()
556
        headers.update(meta)
557
        return self.post('', headers=headers)
558
    
559
    # Storage Container Services
560
    
561
    def list_objects(self, container, format='text', limit=10000, marker=None,
562
                     prefix=None, delimiter=None, path=None,
563
                     include_trashed=False, params={}, if_modified_since=None,
564
                     if_unmodified_since=None, meta={}, until=None):
565
        """returns a list with the container objects"""
566
        params = {'until':until, 'meta':meta}
567
        args = locals()
568
        for elem in ['self', 'container', 'params', 'until', 'meta']:
569
            args.pop(elem)
570
        return OOS_Client.list_objects(self, container, params=params, 
571
                                       **args)
572
    
573
    def retrieve_container_metadata(self, container, restricted=False,
574
                                    until=None):
575
        """returns container's metadata"""
576
        params = {'until':until} if until else {}
577
        return OOS_Client.retrieve_container_metadata(self, container,
578
                                                      restricted=restricted,
579
                                                      **params)
580
    
581
    def set_container_policies(self, container, **policies):
582
        """sets containers policies"""
583
        path = '/%s' % (container)
584
        headers = {}
585
        print ''
586
        for key, val in policies.items():
587
            headers['x-container-policy-%s' % key] = val
588
        return self.post(path, headers=headers)
589
    
590
    def delete_container(self, container, until=None):
591
        """deletes a container or the container history until the date provided"""
592
        params = {'until':until} if until else {}
593
        return OOS_Client.delete_container(self, container, params)
594
    
595
    # Storage Object Services
596
    
597
    def retrieve_object(self, container, object, params={}, format='text', range=None,
598
                        if_range=None, if_match=None, if_none_match=None,
599
                        if_modified_since=None, if_unmodified_since=None,
600
                        **headers):
601
        """returns an object"""
602
        headers={}
603
        l = ['range', 'if_range', 'if_match', 'if_none_match',
604
             'if_modified_since', 'if_unmodified_since']
605
        l = [elem for elem in l if eval(elem)]
606
        for elem in l:
607
            headers.update({elem:eval(elem)})
608
        return OOS_Client.retrieve_object(self, container, object, format=format,
609
                                          params=params, **headers)
610
    
611
    def retrieve_object_version(self, container, object, version, detail=False,
612
                                range=None, if_range=None, if_match=None,
613
                                if_none_match=None, if_modified_since=None,
614
                                if_unmodified_since=None):
615
        """returns a specific object version"""
616
        args = locals()
617
        l = ['self', 'container', 'object']
618
        for elem in l:
619
            args.pop(elem)
620
        params = {'version':version}
621
        return self.retrieve_object(container, object, params, **args)
622
    
623
    def retrieve_object_versionlist(self, container, object, range=None,
624
                                    if_range=None, if_match=None,
625
                                    if_none_match=None, if_modified_since=None,
626
                                    if_unmodified_since=None):
627
        """returns the object version list"""
628
        args = locals()
629
        l = ['self', 'container', 'object']
630
        for elem in l:
631
            args.pop(elem)
632
            
633
        return self.retrieve_object_version(container, object, version='list',
634
                                            detail=True, **args)
635
    
636
    def create_zero_length_object(self, container, object, meta={},
637
                      etag=None, content_type=None, content_encoding=None,
638
                      content_disposition=None, x_object_manifest=None,
639
                      x_object_sharing=None, x_object_public=None):
640
        """createas a zero length object"""
641
        args = locals()
642
        for elem in ['self', 'container', 'object']:
643
            args.pop(elem)
644
        return OOS_Client.create_zero_length_object(self, container, object,
645
                                                    **args)
646
    
647
    def create_object(self, container, object, f=stdin, meta={},
648
                      etag=None, content_type=None, content_encoding=None,
649
                      content_disposition=None, x_object_manifest=None,
650
                      x_object_sharing=None, x_object_public=None):
651
        """creates an object"""
652
        args = locals()
653
        for elem in ['self', 'container', 'object']:
654
            args.pop(elem)
655
        return OOS_Client.create_object(self, container, object, **args)
656
        
657
    def create_object_using_chunks(self, container, object, f=stdin,
658
                                    blocksize=1024, meta={}, etag=None,
659
                                    content_type=None, content_encoding=None,
660
                                    content_disposition=None, 
661
                                    x_object_sharing=None,
662
                                    x_object_manifest=None, 
663
                                    x_object_public=None):
664
        """creates an object (incremental upload)"""
665
        path = '/%s/%s' % (container, object)
666
        headers = {}
667
        l = ['etag', 'content_type', 'content_encoding', 'content_disposition', 
668
             'x_object_sharing', 'x_object_manifest', 'x_object_public']
669
        l = [elem for elem in l if eval(elem)]
670
        for elem in l:
671
            headers.update({elem:eval(elem)})
672
        
673
        for k,v in meta.items():
674
            headers['x-object-meta-%s' %k.strip()] = v.strip()
675
        
676
        return self._chunked_transfer(path, 'PUT', f, headers=headers,
677
                                      blocksize=blocksize)
678
    
679
    def create_object_by_hashmap(container, object, f=stdin, format='json',
680
                                 meta={}, etag=None, content_encoding=None,
681
                                 content_disposition=None, content_type=None,
682
                                 x_object_sharing=None, x_object_manifest=None,
683
                                 x_object_public = None):
684
        """creates an object by uploading hashes representing data instead of data"""
685
        args = locals()
686
        for elem in ['self', 'container', 'object']:
687
            args.pop(elem)
688
            
689
        data = f.read() if f else None
690
        if data and format == 'json':
691
            try:
692
                data = eval(data)
693
                data = json.dumps(data)
694
            except SyntaxError:
695
                raise Fault('Invalid formatting')
696
        
697
        #TODO check with xml
698
        return self.create_object(container, object, **args)
699
    
700
    def create_manifestation(self, container, object, manifest):
701
        """creates a manifestation"""
702
        headers={'x_object_manifest':manifest}
703
        return self.create_object(container, object, f=None, **headers)
704
    
705
    def update_object(self, container, object, f=stdin, offset=None, meta={},
706
                      content_length=None, content_type=None, content_range=None,
707
                      content_encoding=None, content_disposition=None,
708
                      x_object_bytes=None, x_object_manifest=None,
709
                      x_object_sharing=None, x_object_public=None):
710
        """updates an object"""
711
        spath = '/%s/%s' % (container, object)
712
        args = locals()
713
        for elem in ['self', 'container', 'object']:
714
            args.pop(elem)
715
        
716
        return OOS_Client.update_object(self, container, object, **args)
717
        
718
    def update_object_using_chunks(self, container, object, f=stdin,
719
                                    blocksize=1024, offset=None, meta={},
720
                                    content_type=None, content_encoding=None,
721
                                    content_disposition=None, x_object_bytes=None,
722
                                    x_object_manifest=None, x_object_sharing=None,
723
                                    x_object_public=None):
724
        """updates an object (incremental upload)"""
725
        path = '/%s/%s' % (container, object)
726
        headers = {}
727
        l = ['content_type', 'content_encoding', 'content_disposition',
728
             'x_object_bytes', 'x_object_manifest', 'x_object_sharing',
729
             'x_object_public']
730
        l = [elem for elem in l if eval(elem)]
731
        for elem in l:
732
            headers.update({elem:eval(elem)})
733
        
734
        if offset != None:
735
            headers['content_range'] = 'bytes %s-/*' % offset
736
        else:
737
            headers['content_range'] = 'bytes */*'
738
        
739
        for k,v in meta.items():
740
            headers['x-object-meta-%s' %k.strip()] = v.strip()
741
        
742
        return self._chunked_transfer(path, 'POST', f, headers=headers,
743
                                      blocksize=blocksize)
744
    
745
    def delete_object(self, container, object, until=None):
746
        """deletes an object or the object history until the date provided"""
747
        params = {'until':until} if until else {}
748
        return OOS_Client.delete_object(self, container, object, params)
749
    
750
    def trash_object(self, container, object):
751
        """trashes an object"""
752
        path = '/%s/%s' % (container, object)
753
        meta = {'trash':'true'}
754
        return self._update_metadata(path, 'object', **meta)
755
    
756
    def restore_object(self, container, object):
757
        """restores a trashed object"""
758
        return self.delete_object_metadata(container, object, ['trash'])
759
    
760
    def _set_public_header(self, headers, public=False):
761
        """sets the public header"""
762
        if not headers:
763
            headers = {}
764
        if public == None:
765
            return
766
        else:
767
            headers['x-object-public'] = public if public else ''
768
    
769
    def publish_object(self, container, object):
770
        """sets a previously created object publicly accessible"""
771
        path = '/%s/%s' % (container, object)
772
        headers = {'content_range':'bytes */*'}
773
        self._set_public_header(headers, public=True)
774
        return self.post(path, headers=headers)
775
    
776
    def unpublish_object(self, container, object):
777
        """unpublish an object"""
778
        path = '/%s/%s' % (container, object)
779
        headers = {'content_range':'bytes */*'}
780
        self._set_public_header(headers, public=False)
781
        return self.post(path, headers=headers)