Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos_rest_api.py @ 24ff0a35

History | View | Annotate | Download (32 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(
41
            self,
42
            until=None,
43
            if_modified_since=None,
44
            if_unmodified_since=None,
45
            *args, **kwargs):
46
        """ Full Pithos+ HEAD at account level
47

48
        --- request parameters ---
49

50
        :param until: (string) optional timestamp
51

52
        --- request headers ---
53

54
        :param if_modified_since: (string) Retrieve if account has changed
55
            since provided timestamp
56

57
        :param if_unmodified_since: (string) Retrieve if account has not
58
            change since provided timestamp
59

60
        :returns: ConnectionResponse
61
        """
62

    
63
        self._assert_account()
64
        path = path4url(self.account)
65

    
66
        self.set_param('until', until, iff=until)
67
        self.set_header('If-Modified-Since', if_modified_since)
68
        self.set_header('If-Unmodified-Since', if_unmodified_since)
69

    
70
        success = kwargs.pop('success', 204)
71
        return self.head(path, *args, success=success, **kwargs)
72

    
73
    def account_get(
74
            self,
75
            limit=None,
76
            marker=None,
77
            format='json',
78
            show_only_shared=False,
79
            until=None,
80
            if_modified_since=None,
81
            if_unmodified_since=None,
82
            *args, **kwargs):
83
        """  Full Pithos+ GET at account level
84

85
        --- request parameters ---
86

87
        :param limit: (integer) The amount of results requested
88
            (server will use default value if None)
89

90
        :param marker: string Return containers with name
91
            lexicographically after marker
92

93
        :param format: (string) reply format can be json or xml
94
            (default: json)
95

96
        :param shared: (bool) If true, only shared containers will be
97
            included in results
98

99
        :param until: (string) optional timestamp
100

101
        --- request headers ---
102

103
        :param if_modified_since: (string) Retrieve if account has changed
104
            since provided timestamp
105

106
        :param if_unmodified_since: (string) Retrieve if account has not
107
            changed since provided timestamp
108

109
        :returns: ConnectionResponse
110
        """
111

    
112
        self._assert_account()
113

    
114
        self.set_param('format', format, iff=format)
115
        self.set_param('limit', limit, iff=limit)
116
        self.set_param('marker', marker, iff=marker)
117
        self.set_param('shared', iff=show_only_shared)
118
        self.set_param('until', until, iff=until)
119

    
120
        self.set_header('If-Modified-Since', if_modified_since)
121
        self.set_header('If-Unmodified-Since', if_unmodified_since)
122

    
123
        path = path4url(self.account)
124
        success = kwargs.pop('success', (200, 204))
125
        return self.get(path, *args, success=success, **kwargs)
126

    
127
    def account_post(
128
            self,
129
            update=True,
130
            groups={},
131
            metadata=None,
132
            quota=None,
133
            versioning=None,
134
            *args, **kwargs):
135
        """ Full Pithos+ POST at account level
136

137
        --- request parameters ---
138

139
        :param update: (bool) if True, Do not replace metadata/groups
140

141
        --- request headers ---
142

143
        :param groups: (dict) Optional user defined groups in the form
144
            { 'group1':['user1', 'user2', ...],
145
            'group2':['userA', 'userB', ...], }
146

147
        :param metadata: (dict) Optional user defined metadata in the form
148
            { 'name1': 'value1', 'name2': 'value2', ... }
149

150
        :param quota: (integer) If supported, sets the Account quota
151

152
        :param versioning: (string) If supported, sets the Account versioning
153
            to 'auto' or some other supported versioning string
154

155
        :returns: ConnectionResponse
156
        """
157

    
158
        self._assert_account()
159

    
160
        self.set_param('update', iff=update)
161

    
162
        for group, usernames in groups.items():
163
            userstr = ''
164
            dlm = ''
165
            for user in usernames:
166
                userstr = userstr + dlm + user
167
                dlm = ','
168
            self.set_header('X-Account-Group-' + group, userstr)
169
        if metadata:
170
            for metaname, metaval in metadata.items():
171
                self.set_header('X-Account-Meta-' + metaname, metaval)
172
        self.set_header('X-Account-Policy-Quota', quota)
173
        self.set_header('X-Account-Policy-Versioning', versioning)
174

    
175
        path = path4url(self.account)
176
        success = kwargs.pop('success', 202)
177
        return self.post(path, *args, success=success, **kwargs)
178

    
179
    def container_head(
180
            self,
181
            until=None,
182
            if_modified_since=None,
183
            if_unmodified_since=None,
184
            *args, **kwargs):
185
        """ Full Pithos+ HEAD at container level
186

187
        --- request params ---
188

189
        :param until: (string) optional timestamp
190

191
        --- request headers ---
192

193
        :param if_modified_since: (string) Retrieve if account has changed
194
            since provided timestamp
195

196
        :param if_unmodified_since: (string) Retrieve if account has not
197
            changed since provided timestamp
198

199
        :returns: ConnectionResponse
200
        """
201

    
202
        self._assert_container()
203

    
204
        self.set_param('until', until, iff=until)
205

    
206
        self.set_header('If-Modified-Since', if_modified_since)
207
        self.set_header('If-Unmodified-Since', if_unmodified_since)
208

    
209
        path = path4url(self.account, self.container)
210
        success = kwargs.pop('success', 204)
211
        return self.head(path, *args, success=success, **kwargs)
212

    
213
    def container_get(
214
            self,
215
            limit=None,
216
            marker=None,
217
            prefix=None,
218
            delimiter=None,
219
            path=None,
220
            format='json',
221
            meta=[],
222
            show_only_shared=False,
223
            until=None,
224
            if_modified_since=None,
225
            if_unmodified_since=None,
226
            *args, **kwargs):
227
        """ Full Pithos+ GET at container level
228

229
        --- request parameters ---
230

231
        :param limit: (integer) The amount of results requested
232
            (server will use default value if None)
233

234
        :param marker: (string) Return containers with name lexicographically
235
            after marker
236

237
        :param prefix: (string) Return objects starting with prefix
238

239
        :param delimiter: (string) Return objects up to the delimiter
240

241
        :param path: (string) assume prefix = path and delimiter = /
242
            (overwrites prefix and delimiter)
243

244
        :param format: (string) reply format can be json or xml (default:json)
245

246
        :param meta: (list) Return objects that satisfy the key queries in
247
            the specified comma separated list (use <key>, !<key> for
248
            existence queries, <key><op><value> for value queries, where <op>
249
            can be one of =, !=, <=, >=, <, >)
250

251
        :param shared: (bool) If true, only shared containers will be included
252
            in results
253

254
        :param until: (string) optional timestamp
255

256
        --- request headers ---
257

258
        :param if_modified_since: (string) Retrieve if account has changed
259
            since provided timestamp
260

261
        :param if_unmodified_since: (string) Retrieve if account has not
262
            changed since provided timestamp
263

264
        :returns: ConnectionResponse
265
        """
266

    
267
        self._assert_container()
268

    
269
        self.set_param('format', format, iff=format)
270
        self.set_param('limit', limit, iff=limit)
271
        self.set_param('marker', marker, iff=marker)
272
        if not path:
273
            self.set_param('prefix', prefix, iff=prefix)
274
            self.set_param('delimiter', delimiter, iff=delimiter)
275
        else:
276
            self.set_param('path', path)
277
        self.set_param('shared', iff=show_only_shared)
278
        self.set_param('meta', list2str(meta), iff=meta)
279
        self.set_param('until', until, iff=until)
280

    
281
        self.set_header('If-Modified-Since', if_modified_since)
282
        self.set_header('If-Unmodified-Since', if_unmodified_since)
283

    
284
        path = path4url(self.account, self.container)
285
        success = kwargs.pop('success', 200)
286
        return self.get(path, *args, success=success, **kwargs)
287

    
288
    def container_put(
289
            self,
290
            quota=None, versioning=None, metadata=None,
291
            *args, **kwargs):
292
        """ Full Pithos+ PUT at container level
293

294
        --- request headers ---
295

296
        :param quota: (integer) Size limit in KB
297

298
        :param versioning: (string) 'auto' or other string supported by server
299

300
        :param metadata: (dict) Optional user defined metadata in the form
301
            { 'name1': 'value1', 'name2': 'value2', ... }
302

303
        :returns: ConnectionResponse
304
        """
305
        self._assert_container()
306

    
307
        if metadata:
308
            for metaname, metaval in metadata.items():
309
                self.set_header('X-Container-Meta-' + metaname, metaval)
310
        self.set_header('X-Container-Policy-Quota', quota)
311
        self.set_header('X-Container-Policy-Versioning', versioning)
312

    
313
        path = path4url(self.account, self.container)
314
        success = kwargs.pop('success', (201, 202))
315
        return self.put(path, *args, success=success, **kwargs)
316

    
317
    def container_post(
318
            self,
319
            update=True,
320
            format='json',
321
            quota=None,
322
            versioning=None,
323
            metadata=None,
324
            content_type=None,
325
            content_length=None,
326
            transfer_encoding=None,
327
            *args, **kwargs):
328
        """ Full Pithos+ POST at container level
329

330
        --- request params ---
331

332
        :param update: (bool)  if True, Do not replace metadata/groups
333

334
        :param format: (string) json (default) or xml
335

336
        --- request headers ---
337

338
        :param quota: (integer) Size limit in KB
339

340
        :param versioning: (string) 'auto' or other string supported by server
341

342
        :param metadata: (dict) Optional user defined metadata in the form
343
            { 'name1': 'value1', 'name2': 'value2', ... }
344

345
        :param content_type: (string) set a custom content type
346

347
        :param content_length: (string) set a custrom content length
348

349
        :param transfer_encoding: (string) set a custrom transfer encoding
350

351
        :returns: ConnectionResponse
352
        """
353
        self._assert_container()
354

    
355
        self.set_param('format', format, iff=format)
356
        self.set_param('update', iff=update)
357

    
358
        if metadata:
359
            for metaname, metaval in metadata.items():
360
                self.set_header('X-Container-Meta-' + metaname, metaval)
361
        self.set_header('X-Container-Policy-Quota', quota)
362
        self.set_header('X-Container-Policy-Versioning', versioning)
363
        self.set_header('Content-Type', content_type)
364
        self.set_header('Content-Length', content_length)
365
        self.set_header('Transfer-Encoding', transfer_encoding)
366

    
367
        path = path4url(self.account, self.container)
368
        success = kwargs.pop('success', 202)
369
        return self.post(path, *args, success=success, **kwargs)
370

    
371
    def container_delete(self, until=None, delimiter=None, *args, **kwargs):
372
        """ Full Pithos+ DELETE at container level
373

374
        --- request parameters ---
375

376
        :param until: (timestamp string) if defined, container is purged up to
377
            that time
378

379
        :returns: ConnectionResponse
380
        """
381

    
382
        self._assert_container()
383

    
384
        self.set_param('until', until, iff=until)
385
        self.set_param('delimiter', delimiter, iff=delimiter)
386

    
387
        path = path4url(self.account, self.container)
388
        success = kwargs.pop('success', 204)
389
        return self.delete(path, success=success)
390

    
391
    def object_head(
392
            self, object,
393
            version=None,
394
            if_etag_match=None,
395
            if_etag_not_match=None,
396
            if_modified_since=None,
397
            if_unmodified_since=None,
398
            *args, **kwargs):
399
        """ Full Pithos+ HEAD at object level
400

401
        --- request parameters ---
402

403
        :param version: (string) optional version identified
404

405
        --- request headers ---
406

407
        :param if_etag_match: (string) if provided, return only results
408
            with etag matching with this
409

410
        :param if_etag_not_match: (string) if provided, return only results
411
            with etag not matching with this
412

413
        :param if_modified_since: (string) Retrieve if account has changed
414
            since provided timestamp
415

416
        :param if_unmodified_since: (string) Retrieve if account has not
417
            changed since provided timestamp
418

419
        :returns: ConnectionResponse
420
        """
421

    
422
        self._assert_container()
423

    
424
        self.set_param('version', version, iff=version)
425

    
426
        self.set_header('If-Match', if_etag_match)
427
        self.set_header('If-None-Match', if_etag_not_match)
428
        self.set_header('If-Modified-Since', if_modified_since)
429
        self.set_header('If-Unmodified-Since', if_unmodified_since)
430

    
431
        path = path4url(self.account, self.container, object)
432
        success = kwargs.pop('success', 200)
433
        return self.head(path, *args, success=success, **kwargs)
434

    
435
    def object_get(
436
            self, object,
437
            format='json',
438
            hashmap=False,
439
            version=None,
440
            data_range=None,
441
            if_range=False,
442
            if_etag_match=None,
443
            if_etag_not_match=None,
444
            if_modified_since=None,
445
            if_unmodified_since=None,
446
            *args, **kwargs):
447
        """ Full Pithos+ GET at object level
448

449
        --- request parameters ---
450

451
        :param format: (string) json (default) or xml
452

453
        :param hashmap: (bool) Optional request for hashmap
454

455
        :param version: (string) optional version identified
456

457
        --- request headers ---
458

459
        :param data_range: (string) Optional range of data to retrieve
460

461
        :param if_range: (bool)
462

463
        :param if_etag_match: (string) if provided, return only results
464
            with etag matching with this
465

466
        :param if_etag_not_match: (string) if provided, return only results
467
            with etag not matching with this
468

469
        :param if_modified_since: (string) Retrieve if account has changed
470
            since provided timestamp
471

472
        :param if_unmodified_since: (string) Retrieve if account has not
473
            changed since provided timestamp
474

475
        :returns: ConnectionResponse
476
        """
477

    
478
        self._assert_container()
479

    
480
        self.set_param('format', format, iff=format)
481
        self.set_param('version', version, iff=version)
482
        self.set_param('hashmap', hashmap, iff=hashmap)
483

    
484
        self.set_header('Range', data_range)
485
        self.set_header('If-Range', '',
486
            if_range is True and data_range)
487
        self.set_header('If-Match', if_etag_match, )
488
        self.set_header('If-None-Match', if_etag_not_match)
489
        self.set_header('If-Modified-Since', if_modified_since)
490
        self.set_header('If-Unmodified-Since', if_unmodified_since)
491

    
492
        path = path4url(self.account, self.container, object)
493
        success = kwargs.pop('success', 200)
494
        return self.get(path, *args, success=success, **kwargs)
495

    
496
    def object_put(
497
            self, object,
498
            format='json',
499
            hashmap=False,
500
            delimiter=None,
501
            if_etag_match=None,
502
            if_etag_not_match=None,
503
            etag=None,
504
            content_length=None,
505
            content_type=None,
506
            transfer_encoding=None,
507
            copy_from=None,
508
            move_from=None,
509
            source_account=None,
510
            source_version=None,
511
            content_encoding=None,
512
            content_disposition=None,
513
            manifest=None,
514
            permissions=None,
515
            public=None,
516
            metadata=None,
517
            *args, **kwargs):
518
        """ Full Pithos+ PUT at object level
519

520
        --- request parameters ---
521

522
        :param format: (string) json (default) or xml
523

524
        :param hashmap: (bool) Optional hashmap provided instead of data
525

526
        --- request headers ---
527

528
        :param if_etag_match: (string) if provided, return only results
529
            with etag matching with this
530

531
        :param if_etag_not_match: (string) if provided, return only results
532
            with etag not matching with this
533

534
        :param etag: (string) The MD5 hash of the object (optional to check
535
            written data)
536

537
        :param content_length: (integer) The size of the data written
538

539
        :param content_type: (string) The MIME content type of the object
540

541
        :param transfer_encoding: (string) Set to chunked to specify
542
            incremental uploading (if used, Content-Length is ignored)
543

544
        :param copy_from: (string) The source path in the form
545
            /<container>/<object>
546

547
        :param move_from: (string) The source path in the form
548
            /<container>/<object>
549

550
        :param source_account: (string) The source account to copy/move from
551

552
        :param source_version: (string) The source version to copy from
553

554
        :param conent_encoding: (string) The encoding of the object
555

556
        :param content_disposition: (string) Presentation style of the object
557

558
        :param manifest: (string) Object parts prefix in
559
            /<container>/<object> form
560

561
        :param permissions: (dict) Object permissions in the form (all fields
562
            are optional)
563
            { 'read':[user1, group1, user2, ...],
564
            'write':['user3, group2, group3, ...] }
565

566
        :param public: (bool) If true, Object is publicly accessible,
567
            if false, not
568

569
        :param metadata: (dict) Optional user defined metadata in the form
570
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
571

572
        :returns: ConnectionResponse
573
        """
574

    
575
        self._assert_container()
576

    
577
        self.set_param('format', format, iff=format)
578
        self.set_param('hashmap', hashmap, iff=hashmap)
579
        self.set_param('delimiter', delimiter, iff=delimiter)
580

    
581
        self.set_header('If-Match', if_etag_match)
582
        self.set_header('If-None-Match', if_etag_not_match)
583
        self.set_header('ETag', etag)
584
        self.set_header('Content-Length', content_length)
585
        self.set_header('Content-Type', content_type)
586
        self.set_header('Transfer-Encoding', transfer_encoding)
587
        self.set_header('X-Copy-From', copy_from)
588
        self.set_header('X-Move-From', move_from)
589
        self.set_header('X-Source-Account', source_account)
590
        self.set_header('X-Source-Version', source_version)
591
        self.set_header('Content-Encoding', content_encoding)
592
        self.set_header('Content-Disposition', content_disposition)
593
        self.set_header('X-Object-Manifest', manifest)
594
        perms = None
595
        if permissions:
596
            for permission_type, permission_list in permissions.items():
597
                if not perms:
598
                    perms = ''  # Remove permissions
599
                if len(permission_list) == 0:
600
                    continue
601
                if len(perms):
602
                    perms += ';'
603
                perms += '%s=%s' % (
604
                    permission_type,
605
                    list2str(permission_list, separator=','))
606
        self.set_header('X-Object-Sharing', perms)
607
        self.set_header('X-Object-Public', public)
608
        if metadata:
609
            for key, val in metadata.items():
610
                self.set_header('X-Object-Meta-' + key, val)
611

    
612
        path = path4url(self.account, self.container, object)
613
        success = kwargs.pop('success', 201)
614
        return self.put(path, *args, success=success, **kwargs)
615

    
616
    def object_copy(
617
            self, object, destination,
618
            format='json',
619
            ignore_content_type=False,
620
            if_etag_match=None,
621
            if_etag_not_match=None,
622
            destination_account=None,
623
            content_type=None,
624
            content_encoding=None,
625
            content_disposition=None,
626
            source_version=None,
627
            permissions=None,
628
            public=False,
629
            metadata=None,
630
            *args, **kwargs):
631
        """ Full Pithos+ COPY at object level
632

633
        --- request parameters ---
634

635
        :param format: (string) json (default) or xml
636

637
        :param ignore_content_type: (bool) Ignore the supplied Content-Type
638

639
        --- request headers ---
640

641
        :param if_etag_match: (string) if provided, copy only results
642
            with etag matching with this
643

644
        :param if_etag_not_match: (string) if provided, copy only results
645
            with etag not matching with this
646

647
        :param destination: (string) The destination path in the form
648
            /<container>/<object>
649

650
        :param destination_account: (string) The destination account to copy to
651

652
        :param content_type: (string) The MIME content type of the object
653

654
        :param content_encoding: (string) The encoding of the object
655

656
        :param content_disposition: (string) Object resentation style
657

658
        :param source_version: (string) The source version to copy from
659

660
        :param permissions: (dict) Object permissions in the form
661
            (all fields are optional)
662
            { 'read':[user1, group1, user2, ...],
663
            'write':['user3, group2, group3, ...] }
664

665
        :param permissions: update permissions
666

667
        :param public: (bool) If true, Object is publicly accessible
668

669
        :param metadata: (dict) Optional user defined metadata in the form
670
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
671
            Metadata are appended to the source metadata. In case of same
672
            keys, they replace the old metadata
673

674
        :returns: ConnectionResponse
675
        """
676

    
677
        self._assert_container()
678

    
679
        self.set_param('format', format, iff=format)
680
        self.set_param('ignore_content_type', iff=ignore_content_type)
681

    
682
        self.set_header('If-Match', if_etag_match)
683
        self.set_header('If-None-Match', if_etag_not_match)
684
        self.set_header('Destination', destination)
685
        self.set_header('Destination-Account', destination_account)
686
        self.set_header('Content-Type', content_type)
687
        self.set_header('Content-Encoding', content_encoding)
688
        self.set_header('Content-Disposition', content_disposition)
689
        self.set_header('X-Source-Version', source_version)
690
        perms = None
691
        if permissions:
692
            for permission_type, permission_list in permissions.items():
693
                if not perms:
694
                    perms = ''  # Remove permissions
695
                if len(permission_list) == 0:
696
                    continue
697
                if len(perms):
698
                    perms += ';'
699
                perms += '%s=%s' % (
700
                    permission_type,
701
                    list2str(permission_list, separator=','))
702
        self.set_header('X-Object-Sharing', perms)
703
        self.set_header('X-Object-Public', public)
704
        if metadata:
705
            for key, val in metadata.items():
706
                self.set_header('X-Object-Meta-' + key, val)
707

    
708
        path = path4url(self.account, self.container, object)
709
        success = kwargs.pop('success', 201)
710
        return self.copy(path, *args, success=success, **kwargs)
711

    
712
    def object_move(
713
            self, object,
714
            format='json',
715
            ignore_content_type=False,
716
            if_etag_match=None,
717
            if_etag_not_match=None,
718
            destination=None,
719
            destination_account=None,
720
            content_type=None,
721
            content_encoding=None,
722
            content_disposition=None,
723
            permissions={},
724
            public=False,
725
            metadata={},
726
            *args, **kwargs):
727
        """ Full Pithos+ COPY at object level
728

729
        --- request parameters ---
730

731
        :param format: (string) json (default) or xml
732

733
        :param ignore_content_type: (bool) Ignore the supplied Content-Type
734

735
        --- request headers ---
736

737
        :param if_etag_match: (string) if provided, return only results
738
            with etag matching with this
739

740
        :param if_etag_not_match: (string) if provided, return only results
741
            with etag not matching with this
742

743
        :param destination: (string) The destination path in the form
744
            /<container>/<object>
745

746
        :param destination_account: (string) The destination account to copy to
747

748
        :param content_type: (string) The MIME content type of the object
749

750
        :param content_encoding: (string) The encoding of the object
751

752
        :param content_disposition: (string) Object presentation style
753

754
        :param source_version: (string) The source version to copy from
755

756
        :param permissions: (dict) Object permissions in the form
757
            (all fields are optional)
758
            { 'read':[user1, group1, user2, ...],
759
            'write':['user3, group2, group3, ...] }
760

761
        :param public: (bool) If true, Object is publicly accessible
762

763
        :param metadata: (dict) Optional user defined metadata in the form
764
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
765

766
        :returns: ConnectionResponse
767
        """
768

    
769
        self._assert_container()
770

    
771
        self.set_param('format', format, iff=format)
772
        self.set_param('ignore_content_type', iff=ignore_content_type)
773

    
774
        self.set_header('If-Match', if_etag_match)
775
        self.set_header('If-None-Match', if_etag_not_match)
776
        self.set_header('Destination', destination)
777
        self.set_header('Destination-Account', destination_account)
778
        self.set_header('Content-Type', content_type)
779
        self.set_header('Content-Encoding', content_encoding)
780
        self.set_header('Content-Disposition', content_disposition)
781
        perms = None
782
        for permission_type, permission_list in permissions.items():
783
            if not perms:
784
                perms = ''  # Remove permissions
785
            if len(permission_list) == 0:
786
                continue
787
            if len(perms):
788
                perms += ';'
789
            perms += '%s=%s' % (
790
                permission_type,
791
                list2str(permission_list, separator=','))
792
        self.set_header('X-Object-Sharing', perms)
793
        self.set_header('X-Object-Public', public)
794
        for key, val in metadata.items():
795
            self.set_header('X-Object-Meta-' + key, val)
796

    
797
        path = path4url(self.account, self.container, object)
798
        success = kwargs.pop('success', 201)
799
        return self.move(path, *args, success=success, **kwargs)
800

    
801
    def object_post(
802
            self, object,
803
            format='json',
804
            update=True,
805
            if_etag_match=None,
806
            if_etag_not_match=None,
807
            content_length=None,
808
            content_type=None,
809
            content_range=None,
810
            transfer_encoding=None,
811
            content_encoding=None,
812
            content_disposition=None,
813
            source_object=None,
814
            source_account=None,
815
            source_version=None,
816
            object_bytes=None,
817
            manifest=None,
818
            permissions={},
819
            public=False,
820
            metadata={},
821
            *args, **kwargs):
822
        """ Full Pithos+ POST at object level
823

824
        --- request parameters ---
825

826
        :param format: (string) json (default) or xml
827

828
        :param update: (bool) Do not replace metadata
829

830
        --- request headers ---
831

832
        :param if_etag_match: (string) if provided, return only results
833
            with etag matching with this
834

835
        :param if_etag_not_match: (string) if provided, return only results
836
            with etag not matching with this
837

838
        :param content_length: (string) The size of the data written
839

840
        :param content_type: (string) The MIME content type of the object
841

842
        :param content_range: (string) The range of data supplied
843

844
        :param transfer_encoding: (string) Set to chunked to specify
845
            incremental uploading (if used, Content-Length is ignored)
846

847
        :param content_encoding: (string) The encoding of the object
848

849
        :param content_disposition: (string) Object presentation style
850

851
        :param source_object: (string) Update with data from the object at
852
            path /<container>/<object>
853

854
        :param source_account: (string) The source account to update from
855

856
        :param source_version: (string) The source version to copy from
857

858
        :param object_bytes: (integer) The updated objects final size
859

860
        :param manifest: (string) Object parts prefix as /<container>/<object>
861

862
        :param permissions: (dict) Object permissions in the form (all fields
863
            are optional)
864
            { 'read':[user1, group1, user2, ...],
865
            'write':['user3, group2, group3, ...] }
866

867
        :param public: (bool) If true, Object is publicly accessible
868

869
        :param metadata: (dict) Optional user defined metadata in the form
870
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
871

872
        :returns: ConnectionResponse
873
        """
874

    
875
        self._assert_container()
876

    
877
        self.set_param('format', format, iff=format)
878
        self.set_param('update', iff=update)
879

    
880
        self.set_header('If-Match', if_etag_match)
881
        self.set_header('If-None-Match', if_etag_not_match)
882
        self.set_header(
883
            'Content-Length',
884
            content_length,
885
            iff=not transfer_encoding)
886
        self.set_header('Content-Type', content_type)
887
        self.set_header('Content-Range', content_range)
888
        self.set_header('Transfer-Encoding', transfer_encoding)
889
        self.set_header('Content-Encoding', content_encoding)
890
        self.set_header('Content-Disposition', content_disposition)
891
        self.set_header('X-Source-Object', source_object)
892
        self.set_header('X-Source-Account', source_account)
893
        self.set_header('X-Source-Version', source_version)
894
        self.set_header('X-Object-Bytes', object_bytes)
895
        self.set_header('X-Object-Manifest', manifest)
896
        perms = None
897
        for permission_type, permission_list in permissions.items():
898
            if not perms:
899
                perms = ''  # Remove permissions
900
            if len(permission_list) == 0:
901
                continue
902
            if len(perms):
903
                perms += ';'
904
            perms += '%s=%s' % (
905
                permission_type,
906
                list2str(permission_list, separator=','))
907
        self.set_header('X-Object-Sharing', perms)
908
        self.set_header('X-Object-Public', public)
909
        for key, val in metadata.items():
910
            self.set_header('X-Object-Meta-' + key, val)
911

    
912
        path = path4url(self.account, self.container, object)
913
        success = kwargs.pop('success', (202, 204))
914
        return self.post(path, *args, success=success, **kwargs)
915

    
916
    def object_delete(
917
            self, object,
918
            until=None, delimiter=None,
919
            *args, **kwargs):
920
        """ Full Pithos+ DELETE at object level
921

922
        --- request parameters ---
923

924
        :param until: (string) Optional timestamp
925

926
        :returns: ConnectionResponse
927
        """
928
        self._assert_container()
929

    
930
        self.set_param('until', until, iff=until)
931
        self.set_param('delimiter', delimiter, iff=delimiter)
932

    
933
        path = path4url(self.account, self.container, object)
934
        success = kwargs.pop('success', 204)
935
        return self.delete(path, *args, success=success, **kwargs)