Statistics
| Branch: | Tag: | Revision:

root / pithos / lib / client.py @ b9ca81a0

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