Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos_rest_api.py @ 4067cdaf

History | View | Annotate | Download (30.9 kB)

1
# Copyright 2011-2012 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 kamaki.clients.storage import StorageClient
35
from kamaki.clients.utils import path4url, list2str
36

    
37

    
38
class PithosRestAPI(StorageClient):
39

    
40
    def account_head(self,
41
        until=None,
42
        if_modified_since=None,
43
        if_unmodified_since=None,
44
        *args,
45
        **kwargs):
46
        """ Full Pithos+ HEAD at account level
47
        --- request parameters ---
48
        @param until (string): optional timestamp
49
        --- --- optional request headers ---
50
        @param if_modified_since (string): Retrieve if account has changed
51
        since provided timestamp
52
        @param if_unmodified_since (string): Retrieve if account has not
53
        change since provided timestamp
54
        """
55

    
56
        self.assert_account()
57
        path = path4url(self.account)
58

    
59
        self.set_param('until', until, iff=until is not None)
60
        self.set_header('If-Modified-Since', if_modified_since)
61
        self.set_header('If-Unmodified-Since', if_unmodified_since)
62

    
63
        success = kwargs.pop('success', 204)
64
        return self.head(path, *args, success=success, **kwargs)
65

    
66
    def account_get(self,
67
        limit=None,
68
        marker=None,
69
        format='json',
70
        show_only_shared=False,
71
        until=None,
72
        if_modified_since=None,
73
        if_unmodified_since=None,
74
        *args,
75
        **kwargs):
76
        """  Full Pithos+ GET at account level
77
        --- request parameters ---
78
        @param limit (integer): The amount of results requested
79
        (server will use default value if None)
80
        @param marker (string): Return containers with name
81
        lexicographically after marker
82
        @param format (string): reply format can be json or xml
83
        (default: json)
84
        @param shared (bool): If true, only shared containers will be
85
        included in results
86
        @param until (string): optional timestamp
87
        --- --- optional request headers ---
88
        @param if_modified_since (string): Retrieve if account has changed
89
        since provided timestamp
90
        @param if_unmodified_since (string): Retrieve if account has not
91
        changed since provided timestamp
92
        """
93

    
94
        self.assert_account()
95

    
96
        self.set_param('format', format, iff=format is not None)
97
        self.set_param('limit', limit, iff=limit is not None)
98
        self.set_param('marker', marker, iff=marker is not None)
99
        self.set_param('shared', iff=show_only_shared)
100
        self.set_param('until', until, iff=until is not None)
101

    
102
        self.set_header('If-Modified-Since', if_modified_since)
103
        self.set_header('If-Unmodified-Since', if_unmodified_since)
104

    
105
        path = path4url(self.account)
106
        success = kwargs.pop('success', (200, 204))
107
        return self.get(path, *args, success=success, **kwargs)
108

    
109
    def account_post(self,
110
        update=True,
111
        groups={},
112
        metadata=None,
113
        quota=None,
114
        versioning=None,
115
        *args,
116
        **kwargs):
117
        """ Full Pithos+ POST at account level
118
        --- request parameters ---
119
        @param update (bool): if True, Do not replace metadata/groups
120
        --- request headers ---
121
        @groups (dict): Optional user defined groups in the form
122
        { 'group1':['user1', 'user2', ...],
123
        'group2':['userA', 'userB', ...], }
124
        @metadata (dict): Optional user defined metadata in the form
125
        { 'name1': 'value1', 'name2': 'value2', ... }
126
        @param quota(integer): If supported, sets the Account quota
127
        @param versioning(string): If supported, sets the Account versioning
128
        to 'auto' or some other supported versioning string
129
        """
130

    
131
        self.assert_account()
132

    
133
        self.set_param('update', iff=update)
134

    
135
        for group, usernames in groups.items():
136
            userstr = ''
137
            dlm = ''
138
            for user in usernames:
139
                userstr = userstr + dlm + user
140
                dlm = ','
141
            self.set_header('X-Account-Group-' + group, userstr)
142
        if metadata is not None:
143
            for metaname, metaval in metadata.items():
144
                self.set_header('X-Account-Meta-' + metaname, metaval)
145
        self.set_header('X-Account-Policy-Quota', quota)
146
        self.set_header('X-Account-Policy-Versioning', versioning)
147

    
148
        path = path4url(self.account)
149
        success = kwargs.pop('success', 202)
150
        return self.post(path, *args, success=success, **kwargs)
151

    
152
    def container_head(self, until=None,
153
        if_modified_since=None, if_unmodified_since=None, *args, **kwargs):
154
        """ Full Pithos+ HEAD at container level
155
        --- request params ---
156
        @param until (string): optional timestamp
157
        --- optional request headers ---
158
        @param if_modified_since (string): Retrieve if account has changed
159
        since provided timestamp
160
        @param if_unmodified_since (string): Retrieve if account has not
161
        changed since provided timestamp
162
        """
163

    
164
        self.assert_container()
165

    
166
        self.set_param('until', until, iff=until is not None)
167

    
168
        self.set_header('If-Modified-Since', if_modified_since)
169
        self.set_header('If-Unmodified-Since', if_unmodified_since)
170

    
171
        path = path4url(self.account, self.container)
172
        success = kwargs.pop('success', 204)
173
        return self.head(path, *args, success=success, **kwargs)
174

    
175
    def container_get(self,
176
        limit=None,
177
        marker=None,
178
        prefix=None,
179
        delimiter=None,
180
        path=None,
181
        format='json',
182
        meta=[],
183
        show_only_shared=False,
184
        until=None,
185
        if_modified_since=None,
186
        if_unmodified_since=None,
187
        *args,
188
        **kwargs):
189
        """ Full Pithos+ GET at container level
190
        --- request parameters ---
191
        @param limit (integer): The amount of results requested
192
        (server qill use default value if None)
193
        @param marker (string): Return containers with name lexicographically
194
        after marker
195
        @param prefix (string): Return objects starting with prefix
196
        @param delimiter (string): Return objects up to the delimiter
197
        @param path (string): assume prefix = path and delimiter = /
198
        (overwrites prefix and delimiter)
199
        @param format (string): reply format can be json or xml (default:json)
200
        @param meta (list): Return objects that satisfy the key queries in
201
        the specified comma separated list (use <key>, !<key> for
202
        existence queries, <key><op><value> for value queries, where <op>
203
        can be one of =, !=, <=, >=, <, >)
204
        @param shared (bool): If true, only shared containers will be included
205
        in results
206
        @param until (string): optional timestamp
207
        --- --- optional request headers ---
208
        @param if_modified_since (string): Retrieve if account has changed
209
        since provided timestamp
210
        @param if_unmodified_since (string): Retrieve if account has not
211
        changed since provided timestamp
212
        """
213

    
214
        self.assert_container()
215

    
216
        self.set_param('format', format, iff=format is not None)
217
        self.set_param('limit', limit, iff=limit is not None)
218
        self.set_param('marker', marker, iff=marker is not None)
219
        if path is None:
220
            self.set_param('prefix', prefix, iff=prefix is not None)
221
            self.set_param('delimiter', delimiter, iff=delimiter is not None)
222
        else:
223
            self.set_param('path', path)
224
        self.set_param('shared', iff=show_only_shared)
225
        self.set_param('meta',
226
            list2str(meta),
227
            iff=meta is not None and len(meta) > 0)
228
        self.set_param('until', until, iff=until is not None)
229

    
230
        self.set_header('If-Modified-Since', if_modified_since)
231
        self.set_header('If-Unmodified-Since', if_unmodified_since)
232

    
233
        path = path4url(self.account, self.container)
234
        success = kwargs.pop('success', 200)
235
        return self.get(path, *args, success=success, **kwargs)
236

    
237
    def container_put(self,
238
        quota=None,
239
        versioning=None,
240
        metadata=None,
241
        *args,
242
        **kwargs):
243
        """ Full Pithos+ PUT at container level
244
        --- request headers ---
245
        @param quota (integer): Size limit in KB
246
        @param versioning (string): 'auto' or other string supported by server
247
        @metadata (dict): Optional user defined metadata in the form
248
        {   'name1': 'value1',
249
        'name2': 'value2', ...
250
        }
251
        """
252
        self.assert_container()
253

    
254
        if metadata is not None:
255
            for metaname, metaval in metadata.items():
256
                self.set_header('X-Container-Meta-' + metaname, metaval)
257
        self.set_header('X-Container-Policy-Quota', quota)
258
        self.set_header('X-Container-Policy-Versioning', versioning)
259

    
260
        path = path4url(self.account, self.container)
261
        success = kwargs.pop('success', (201, 202))
262
        return self.put(path, *args, success=success, **kwargs)
263

    
264
    def container_post(self,
265
        update=True,
266
        format='json',
267
        quota=None,
268
        versioning=None,
269
        metadata=None,
270
        content_type=None,
271
        content_length=None,
272
        transfer_encoding=None,
273
        *args,
274
        **kwargs):
275
        """ Full Pithos+ POST at container level
276
        --- request params ---
277
        @param update (bool):  if True, Do not replace metadata/groups
278
        @param format(string): json (default) or xml
279
        --- request headers ---
280
        @param quota (integer): Size limit in KB
281
        @param versioning (string): 'auto' or other string supported by server
282
        @metadata (dict): Optional user defined metadata in the form
283
        {   'name1': 'value1',
284
        'name2': 'value2', ...
285
        }
286
        @param content_type (string): set a custom content type
287
        @param content_length (string): set a custrom content length
288
        @param transfer_encoding (string): set a custrom transfer encoding
289
        """
290
        self.assert_container()
291

    
292
        self.set_param('format', format, iff=format is not None)
293
        self.set_param('update', iff=update)
294

    
295
        if metadata is not None:
296
            for metaname, metaval in metadata.items():
297
                self.set_header('X-Container-Meta-' + metaname, metaval)
298
        self.set_header('X-Container-Policy-Quota', quota)
299
        self.set_header('X-Container-Policy-Versioning', versioning)
300
        self.set_header('Content-Type', content_type)
301
        self.set_header('Content-Length', content_length)
302
        self.set_header('Transfer-Encoding', transfer_encoding)
303

    
304
        path = path4url(self.account, self.container)
305
        success = kwargs.pop('success', 202)
306
        return self.post(path, *args, success=success, **kwargs)
307

    
308
    def container_delete(self, until=None, delimiter=None, *args, **kwargs):
309
        """ Full Pithos+ DELETE at container level
310
        --- request parameters ---
311
        @param until (timestamp string): if defined, container is purged up to
312
        that time
313
        """
314

    
315
        self.assert_container()
316

    
317
        self.set_param('until', until, iff=until is not None)
318
        self.set_param('delimiter', delimiter, iff=delimiter is not None)
319

    
320
        path = path4url(self.account, self.container)
321
        success = kwargs.pop('success', 204)
322
        return self.delete(path, success=success)
323

    
324
    def object_head(self, object,
325
        version=None,
326
        if_etag_match=None,
327
        if_etag_not_match=None,
328
        if_modified_since=None,
329
        if_unmodified_since=None,
330
        *args,
331
        **kwargs):
332
        """ Full Pithos+ HEAD at object level
333
        --- request parameters ---
334
        @param version (string): optional version identified
335
        --- request headers ---
336
        @param if_etag_match (string): if provided, return only results
337
        with etag matching with this
338
        @param if_etag_not_match (string): if provided, return only results
339
        with etag not matching with this
340
        @param if_modified_since (string): Retrieve if account has changed
341
        since provided timestamp
342
        @param if_unmodified_since (string): Retrieve if account has not
343
        changed since provided timestamp
344
        """
345

    
346
        self.assert_container()
347

    
348
        self.set_param('version', version, iff=version is not None)
349

    
350
        self.set_header('If-Match', if_etag_match)
351
        self.set_header('If-None-Match', if_etag_not_match)
352
        self.set_header('If-Modified-Since', if_modified_since)
353
        self.set_header('If-Unmodified-Since', if_unmodified_since)
354

    
355
        path = path4url(self.account, self.container, object)
356
        success = kwargs.pop('success', 200)
357
        return self.head(path, *args, success=success, **kwargs)
358

    
359
    def object_get(self, object,
360
        format='json',
361
        hashmap=False,
362
        version=None,
363
        data_range=None,
364
        if_range=False,
365
        if_etag_match=None,
366
        if_etag_not_match=None,
367
        if_modified_since=None,
368
        if_unmodified_since=None,
369
        *args,
370
        **kwargs):
371
        """ Full Pithos+ GET at object level
372
        --- request parameters ---
373
        @param format (string): json (default) or xml
374
        @param hashmap (bool): Optional request for hashmap
375
        @param version (string): optional version identified
376
        --- request headers ---
377
        @param data_range (string): Optional range of data to retrieve
378
        @param if_range (bool):
379
        @param if_etag_match (string): if provided, return only results
380
        with etag matching with this
381
        @param if_etag_not_match (string): if provided, return only results
382
        with etag not matching with this
383
        @param if_modified_since (string): Retrieve if account has changed
384
        since provided timestamp
385
        @param if_unmodified_since (string): Retrieve if account has not
386
        changed since provided timestamp
387
        """
388

    
389
        self.assert_container()
390

    
391
        self.set_param('format', format, iff=format is not None)
392
        self.set_param('version', version, iff=version is not None)
393
        self.set_param('hashmap', hashmap, iff=hashmap)
394

    
395
        self.set_header('Range', data_range)
396
        self.set_header('If-Range', '',
397
            if_range is True and data_range is not None)
398
        self.set_header('If-Match', if_etag_match, )
399
        self.set_header('If-None-Match', if_etag_not_match)
400
        self.set_header('If-Modified-Since', if_modified_since)
401
        self.set_header('If-Unmodified-Since', if_unmodified_since)
402

    
403
        path = path4url(self.account, self.container, object)
404
        success = kwargs.pop('success', 200)
405
        return self.get(path, *args, success=success, **kwargs)
406

    
407
    def object_put(self, object,
408
        format='json',
409
        hashmap=False,
410
        delimiter=None,
411
        if_etag_match=None,
412
        if_etag_not_match=None,
413
        etag=None,
414
        content_length=None,
415
        content_type=None,
416
        transfer_encoding=None,
417
        copy_from=None,
418
        move_from=None,
419
        source_account=None,
420
        source_version=None,
421
        content_encoding=None,
422
        content_disposition=None,
423
        manifest=None,
424
        permissions=None,
425
        public=None,
426
        metadata=None,
427
        *args,
428
        **kwargs):
429
        """ Full Pithos+ PUT at object level
430
        --- request parameters ---
431
        @param format (string): json (default) or xml
432
        @param hashmap (bool): Optional hashmap provided instead of data
433
        --- request headers ---
434
        @param if_etag_match (string): if provided, return only results
435
        with etag matching with this
436
        @param if_etag_not_match (string): if provided, return only results
437
        with etag not matching with this
438
        @param etag (string): The MD5 hash of the object (optional to check
439
        written data)
440
        @param content_length (integer): The size of the data written
441
        @param content_type (string): The MIME content type of the object
442
        @param transfer_encoding (string): Set to chunked to specify
443
        incremental uploading (if used, Content-Length is ignored)
444
        @param copy_from (string): The source path in the form
445
        /<container>/<object>
446
        @param move_from (string): The source path in the form
447
        /<container>/<object>
448
        @param source_account (string): The source account to copy/move from
449
        @param source_version (string): The source version to copy from
450
        @param conent_encoding (string): The encoding of the object
451
        @param content_disposition (string): Presentation style of the object
452
        @param manifest (string): Object parts prefix in
453
        /<container>/<object> form
454
        @param permissions (dict): Object permissions in the form (all fields
455
        are optional)
456
        { 'read':[user1, group1, user2, ...],
457
        'write':['user3, group2, group3, ...] }
458
        @param public (bool): If true, Object is publicly accessible,
459
        if false, not
460
        @param metadata (dict): Optional user defined metadata in the form
461
        {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
462
        """
463

    
464
        self.assert_container()
465

    
466
        self.set_param('format', format, iff=format is not None)
467
        self.set_param('hashmap', hashmap, iff=hashmap)
468
        self.set_param('delimiter', delimiter, iff=delimiter is not None)
469

    
470
        self.set_header('If-Match', if_etag_match)
471
        self.set_header('If-None-Match', if_etag_not_match)
472
        self.set_header('ETag', etag)
473
        self.set_header('Content-Length', content_length)
474
        self.set_header('Content-Type', content_type)
475
        self.set_header('Transfer-Encoding', transfer_encoding)
476
        self.set_header('X-Copy-From', copy_from)
477
        self.set_header('X-Move-From', move_from)
478
        self.set_header('X-Source-Account', source_account)
479
        self.set_header('X-Source-Version', source_version)
480
        self.set_header('Content-Encoding', content_encoding)
481
        self.set_header('Content-Disposition', content_disposition)
482
        self.set_header('X-Object-Manifest', manifest)
483
        perms = None
484
        if permissions:
485
            for permission_type, permission_list in permissions.items():
486
                if perms is None:
487
                    perms = ''  # Remove permissions
488
                if len(permission_list) == 0:
489
                    continue
490
                if len(perms):
491
                    perms += ';'
492
                perms += '%s=%s'\
493
                % (permission_type, list2str(permission_list, seperator=','))
494
        self.set_header('X-Object-Sharing', perms)
495
        self.set_header('X-Object-Public', public)
496
        if metadata is not None:
497
            for key, val in metadata.items():
498
                self.set_header('X-Object-Meta-' + key, val)
499

    
500
        path = path4url(self.account, self.container, object)
501
        success = kwargs.pop('success', 201)
502
        return self.put(path, *args, success=success, **kwargs)
503

    
504
    def object_copy(self, object, destination,
505
        format='json',
506
        ignore_content_type=False,
507
        if_etag_match=None,
508
        if_etag_not_match=None,
509
        destination_account=None,
510
        content_type=None,
511
        content_encoding=None,
512
        content_disposition=None,
513
        source_version=None,
514
        permissions=None,
515
        public=False,
516
        metadata=None,
517
        *args,
518
        **kwargs):
519
        """ Full Pithos+ COPY at object level
520
        --- request parameters ---
521
        @param format (string): json (default) or xml
522
        @param ignore_content_type (bool): Ignore the supplied Content-Type
523
        --- request headers ---
524
        @param if_etag_match (string): if provided, copy only results
525
        with etag matching with this
526
        @param if_etag_not_match (string): if provided, copy only results
527
        with etag not matching with this
528
        @param destination (string): The destination path in the form
529
        /<container>/<object>
530
        @param destination_account (string): The destination account to copy to
531
        @param content_type (string): The MIME content type of the object
532
        @param content_encoding (string): The encoding of the object
533
        @param content_disposition (string): Object resentation style
534
        @param source_version (string): The source version to copy from
535
        @param permissions (dict): Object permissions in the form
536
        (all fields are optional)
537
        { 'read':[user1, group1, user2, ...],
538
        'write':['user3, group2, group3, ...] }
539
        @permissions override source permissions, removing any old permissions
540
        @param public (bool): If true, Object is publicly accessible
541
        @param metadata (dict): Optional user defined metadata in the form
542
        {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
543
        Metadata are appended to the source metadata. In case of same keys,
544
        they replace the old metadata
545
        """
546

    
547
        self.assert_container()
548

    
549
        self.set_param('format', format, iff=format is not None)
550
        self.set_param('ignore_content_type', iff=ignore_content_type)
551

    
552
        self.set_header('If-Match', if_etag_match)
553
        self.set_header('If-None-Match', if_etag_not_match)
554
        self.set_header('Destination', destination)
555
        self.set_header('Destination-Account', destination_account)
556
        self.set_header('Content-Type', content_type)
557
        self.set_header('Content-Encoding', content_encoding)
558
        self.set_header('Content-Disposition', content_disposition)
559
        self.set_header('X-Source-Version', source_version)
560
        perms = None
561
        if permissions:
562
            for permission_type, permission_list in permissions.items():
563
                if perms is None:
564
                    perms = ''  # Remove permissions
565
                if len(permission_list) == 0:
566
                    continue
567
                if len(perms):
568
                    perms += ';'
569
                perms += '%s=%s'\
570
                % (permission_type, list2str(permission_list, seperator=','))
571
        self.set_header('X-Object-Sharing', perms)
572
        self.set_header('X-Object-Public', public)
573
        if metadata is not None:
574
            for key, val in metadata.items():
575
                self.set_header('X-Object-Meta-' + key, val)
576

    
577
        path = path4url(self.account, self.container, object)
578
        success = kwargs.pop('success', 201)
579
        return self.copy(path, *args, success=success, **kwargs)
580

    
581
    def object_move(self, object,
582
        format='json',
583
        ignore_content_type=False,
584
        if_etag_match=None,
585
        if_etag_not_match=None,
586
        destination=None,
587
        destination_account=None,
588
        content_type=None,
589
        content_encoding=None,
590
        content_disposition=None,
591
        permissions={},
592
        public=False,
593
        metadata={},
594
        *args,
595
        **kwargs):
596
        """ Full Pithos+ COPY at object level
597
        --- request parameters ---
598
        @param format (string): json (default) or xml
599
        @param ignore_content_type (bool): Ignore the supplied Content-Type
600
        --- request headers ---
601
        @param if_etag_match (string): if provided, return only results
602
        with etag matching with this
603
        @param if_etag_not_match (string): if provided, return only results
604
        with etag not matching with this
605
        @param destination (string): The destination path in the form
606
        /<container>/<object>
607
        @param destination_account (string): The destination account to copy to
608
        @param content_type (string): The MIME content type of the object
609
        @param content_encoding (string): The encoding of the object
610
        @param content_disposition (string): Object presentation style
611
        @param source_version (string): The source version to copy from
612
        @param permissions (dict): Object permissions in the form
613
        (all fields are optional)
614
        { 'read':[user1, group1, user2, ...],
615
        'write':['user3, group2, group3, ...] }
616
        @param public (bool): If true, Object is publicly accessible
617
        @param metadata (dict): Optional user defined metadata in the form
618
        {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
619
        """
620

    
621
        self.assert_container()
622

    
623
        self.set_param('format', format, iff=format is not None)
624
        self.set_param('ignore_content_type', iff=ignore_content_type)
625

    
626
        self.set_header('If-Match', if_etag_match)
627
        self.set_header('If-None-Match', if_etag_not_match)
628
        self.set_header('Destination', destination)
629
        self.set_header('Destination-Account', destination_account)
630
        self.set_header('Content-Type', content_type)
631
        self.set_header('Content-Encoding', content_encoding)
632
        self.set_header('Content-Disposition', content_disposition)
633
        perms = None
634
        for permission_type, permission_list in permissions.items():
635
            if perms is None:
636
                perms = ''  # Remove permissions
637
            if len(permission_list) == 0:
638
                continue
639
            if len(perms):
640
                perms += ';'
641
            perms += '%s=%s'\
642
            % (permission_type, list2str(permission_list, seperator=','))
643
        self.set_header('X-Object-Sharing', perms)
644
        self.set_header('X-Object-Public', public)
645
        for key, val in metadata.items():
646
            self.set_header('X-Object-Meta-' + key, val)
647

    
648
        path = path4url(self.account, self.container, object)
649
        success = kwargs.pop('success', 201)
650
        return self.move(path, *args, success=success, **kwargs)
651

    
652
    def object_post(self, object,
653
        format='json',
654
        update=True,
655
        if_etag_match=None,
656
        if_etag_not_match=None,
657
        content_length=None,
658
        content_type=None,
659
        content_range=None,
660
        transfer_encoding=None,
661
        content_encoding=None,
662
        content_disposition=None,
663
        source_object=None,
664
        source_account=None,
665
        source_version=None,
666
        object_bytes=None,
667
        manifest=None,
668
        permissions={},
669
        public=False,
670
        metadata={},
671
        *args,
672
        **kwargs):
673
        """ Full Pithos+ POST at object level
674
        --- request parameters ---
675
        @param format (string): json (default) or xml
676
        @param update (bool): Do not replace metadata
677
        --- request headers ---
678
        @param if_etag_match (string): if provided, return only results
679
        with etag matching with this
680
        @param if_etag_not_match (string): if provided, return only results
681
        with etag not matching with this
682
        @param content_length (string): The size of the data written
683
        @param content_type (string): The MIME content type of the object
684
        @param content_range (string): The range of data supplied
685
        @param transfer_encoding (string): Set to chunked to specify
686
        incremental uploading (if used, Content-Length is ignored)
687
        @param content_encoding (string): The encoding of the object
688
        @param content_disposition (string): Object presentation style
689
        @param source_object (string): Update with data from the object at
690
        path /<container>/<object>
691
        @param source_account (string): The source account to update from
692
        @param source_version (string): The source version to copy from
693
        @param object_bytes (integer): The updated objects final size
694
        @param manifest (string): Object parts prefix as /<container>/<object>
695
        @param permissions (dict): Object permissions in the form (all fields
696
        are optional)
697
        { 'read':[user1, group1, user2, ...],
698
        'write':['user3, group2, group3, ...] }
699
        @param public (bool): If true, Object is publicly accessible
700
        @param metadata (dict): Optional user defined metadata in the form
701
        {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
702
        """
703

    
704
        self.assert_container()
705

    
706
        self.set_param('format', format, iff=format is not None)
707
        self.set_param('update', iff=update)
708

    
709
        self.set_header('If-Match', if_etag_match)
710
        self.set_header('If-None-Match', if_etag_not_match)
711
        self.set_header('Content-Length',
712
            content_length,
713
            iff=transfer_encoding is None)
714
        self.set_header('Content-Type', content_type)
715
        self.set_header('Content-Range', content_range)
716
        self.set_header('Transfer-Encoding', transfer_encoding)
717
        self.set_header('Content-Encoding', content_encoding)
718
        self.set_header('Content-Disposition', content_disposition)
719
        self.set_header('X-Source-Object', source_object)
720
        self.set_header('X-Source-Account', source_account)
721
        self.set_header('X-Source-Version', source_version)
722
        self.set_header('X-Object-Bytes', object_bytes)
723
        self.set_header('X-Object-Manifest', manifest)
724
        perms = None
725
        for permission_type, permission_list in permissions.items():
726
            if perms is None:
727
                perms = ''  # Remove permissions
728
            if len(permission_list) == 0:
729
                continue
730
            if len(perms):
731
                perms += ';'
732
            perms += '%s=%s'\
733
            % (permission_type, list2str(permission_list, seperator=','))
734
        self.set_header('X-Object-Sharing', perms)
735
        self.set_header('X-Object-Public', public)
736
        for key, val in metadata.items():
737
            self.set_header('X-Object-Meta-' + key, val)
738

    
739
        path = path4url(self.account, self.container, object)
740
        success = kwargs.pop('success', (202, 204))
741
        return self.post(path, *args, success=success, **kwargs)
742

    
743
    def object_delete(self, object,
744
        until=None,
745
        delimiter=None,
746
        *args,
747
        **kwargs):
748
        """ Full Pithos+ DELETE at object level
749
        --- request parameters ---
750
        @param until (string): Optional timestamp
751
        """
752
        self.assert_container()
753

    
754
        self.set_param('until', until, iff=until is not None)
755
        self.set_param('delimiter', delimiter, iff=delimiter is not None)
756

    
757
        path = path4url(self.account, self.container, object)
758
        success = kwargs.pop('success', 204)
759
        return self.delete(path, *args, success=success, **kwargs)