Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos / rest_api.py @ b86c37f9

History | View | Annotate | Download (31.8 kB)

1
# Copyright 2012-2013 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
36

    
37

    
38
class PithosRestClient(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
            public=False,
80
            until=None,
81
            if_modified_since=None,
82
            if_unmodified_since=None,
83
            *args, **kwargs):
84
        """  Full Pithos+ GET at account level
85

86
        --- request parameters ---
87

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

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

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

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

100
        :param until: (string) optional timestamp
101

102
        --- request headers ---
103

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

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

110
        :returns: ConnectionResponse
111
        """
112
        self._assert_account()
113

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

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

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

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

138
        --- request parameters ---
139

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

142
        --- request headers ---
143

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

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

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

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

156
        :returns: ConnectionResponse
157
        """
158

    
159
        self._assert_account()
160

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

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

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

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

189
        --- request params ---
190

191
        :param until: (string) optional timestamp
192

193
        --- request headers ---
194

195
        :param if_modified_since: (string) Retrieve if account has changed
196
            since provided timestamp
197

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

201
        :returns: ConnectionResponse
202
        """
203

    
204
        self._assert_container()
205

    
206
        self.set_param('until', until, iff=until)
207

    
208
        self.set_header('If-Modified-Since', if_modified_since)
209
        self.set_header('If-Unmodified-Since', if_unmodified_since)
210

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

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

232
        --- request parameters ---
233

234
        :param limit: (integer) The amount of results requested
235
            (server will use default value if None)
236

237
        :param marker: (string) Return containers with name lexicographically
238
            after marker
239

240
        :param prefix: (string) Return objects starting with prefix
241

242
        :param delimiter: (string) Return objects up to the delimiter
243

244
        :param path: (string) assume prefix = path and delimiter = /
245
            (overwrites prefix and delimiter)
246

247
        :param format: (string) reply format can be json or xml (default:json)
248

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

254
        :param show_only_shared: (bool) If true, only shared containers will
255
            be included in results
256

257
        :param until: (string) optional timestamp
258

259
        --- request headers ---
260

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

264
        :param if_unmodified_since: (string) Retrieve if account has not
265
            changed since provided timestamp
266

267
        :returns: ConnectionResponse
268
        """
269

    
270
        self._assert_container()
271

    
272
        self.set_param('limit', limit, iff=limit)
273
        self.set_param('marker', marker, iff=marker)
274
        if not path:
275
            self.set_param('prefix', prefix, iff=prefix)
276
            self.set_param('delimiter', delimiter, iff=delimiter)
277
        else:
278
            self.set_param('path', path)
279
        self.set_param('format', format, iff=format)
280
        self.set_param('shared', iff=show_only_shared)
281
        self.set_param('public', iff=public)
282
        if meta:
283
            self.set_param('meta',  ','.join(meta))
284
        self.set_param('until', until, iff=until)
285

    
286
        self.set_header('If-Modified-Since', if_modified_since)
287
        self.set_header('If-Unmodified-Since', if_unmodified_since)
288

    
289
        path = path4url(self.account, self.container)
290
        success = kwargs.pop('success', 200)
291
        return self.get(path, *args, success=success, **kwargs)
292

    
293
    def container_put(
294
            self,
295
            quota=None, versioning=None, project=None, metadata=None,
296
            *args, **kwargs):
297
        """ Full Pithos+ PUT at container level
298

299
        --- request headers ---
300

301
        :param quota: (integer) Size limit in KB
302

303
        :param versioning: (string) 'auto' or other string supported by server
304

305
        :param metadata: (dict) Optional user defined metadata in the form
306
            { 'name1': 'value1', 'name2': 'value2', ... }
307

308
        :returns: ConnectionResponse
309
        """
310
        self._assert_container()
311

    
312
        self.set_header('X-Container-Policy-Quota', quota)
313
        self.set_header('X-Container-Policy-Versioning', versioning)
314
        if project:
315
            self.set_header('X-Container-Policy-project', project)
316

    
317
        if metadata:
318
            for metaname, metaval in metadata.items():
319
                self.set_header('X-Container-Meta-' + metaname, metaval)
320

    
321
        path = path4url(self.account, self.container)
322
        success = kwargs.pop('success', (201, 202))
323
        return self.put(path, *args, success=success, **kwargs)
324

    
325
    def container_post(
326
            self,
327
            update=True,
328
            format='json',
329
            quota=None,
330
            versioning=None,
331
            project=None,
332
            metadata=None,
333
            content_type=None,
334
            content_length=None,
335
            transfer_encoding=None,
336
            *args, **kwargs):
337
        """ Full Pithos+ POST at container level
338

339
        --- request params ---
340

341
        :param update: (bool)  if True, Do not replace metadata/groups
342

343
        :param format: (string) json (default) or xml
344

345
        --- request headers ---
346

347
        :param quota: (integer) Size limit in KB
348

349
        :param versioning: (string) 'auto' or other string supported by server
350

351
        :param metadata: (dict) Optional user defined metadata in the form
352
            { 'name1': 'value1', 'name2': 'value2', ... }
353

354
        :param content_type: (string) set a custom content type
355

356
        :param content_length: (string) set a custrom content length
357

358
        :param transfer_encoding: (string) set a custom transfer encoding
359

360
        :returns: ConnectionResponse
361
        """
362
        self._assert_container()
363

    
364
        self.set_param('update', '', iff=update)
365
        self.set_param('format', format, iff=format)
366

    
367
        self.set_header('X-Container-Policy-Quota', quota)
368
        self.set_header('X-Container-Policy-Versioning', versioning)
369
        if project:
370
            self.set_header('X-Container-Policy-project', project)
371

    
372
        if metadata:
373
            for metaname, metaval in metadata.items():
374
                self.set_header('X-Container-Meta-' + metaname, metaval)
375
        self.set_header('Content-Type', content_type)
376
        self.set_header('Content-Length', content_length)
377
        self.set_header('Transfer-Encoding', transfer_encoding)
378

    
379
        path = path4url(self.account, self.container)
380
        success = kwargs.pop('success', 202)
381
        return self.post(path, *args, success=success, **kwargs)
382

    
383
    def container_delete(self, until=None, delimiter=None, *args, **kwargs):
384
        """ Full Pithos+ DELETE at container level
385

386
        --- request parameters ---
387

388
        :param until: (timestamp string) if defined, container is purged up to
389
            that time
390

391
        :returns: ConnectionResponse
392
        """
393

    
394
        self._assert_container()
395

    
396
        self.set_param('until', until, iff=until)
397
        self.set_param('delimiter', delimiter, iff=delimiter)
398

    
399
        path = path4url(self.account, self.container)
400
        success = kwargs.pop('success', 204)
401
        return self.delete(path, *args, success=success, **kwargs)
402

    
403
    def object_head(
404
            self, obj,
405
            version=None,
406
            if_etag_match=None,
407
            if_etag_not_match=None,
408
            if_modified_since=None,
409
            if_unmodified_since=None,
410
            *args, **kwargs):
411
        """ Full Pithos+ HEAD at object level
412

413
        --- request parameters ---
414

415
        :param version: (string) optional version identified
416

417
        --- request headers ---
418

419
        :param if_etag_match: (string) if provided, return only results
420
            with etag matching with this
421

422
        :param if_etag_not_match: (string) if provided, return only results
423
            with etag not matching with this
424

425
        :param if_modified_since: (string) Retrieve if account has changed
426
            since provided timestamp
427

428
        :param if_unmodified_since: (string) Retrieve if account has not
429
            changed since provided timestamp
430

431
        :returns: ConnectionResponse
432
        """
433

    
434
        self._assert_container()
435

    
436
        self.set_param('version', version, iff=version)
437

    
438
        self.set_header('If-Match', if_etag_match)
439
        self.set_header('If-None-Match', if_etag_not_match)
440
        self.set_header('If-Modified-Since', if_modified_since)
441
        self.set_header('If-Unmodified-Since', if_unmodified_since)
442

    
443
        path = path4url(self.account, self.container, obj)
444
        success = kwargs.pop('success', 200)
445
        return self.head(path, *args, success=success, **kwargs)
446

    
447
    def object_get(
448
            self, obj,
449
            format='json',
450
            hashmap=False,
451
            version=None,
452
            data_range=None,
453
            if_range=False,
454
            if_etag_match=None,
455
            if_etag_not_match=None,
456
            if_modified_since=None,
457
            if_unmodified_since=None,
458
            *args, **kwargs):
459
        """ Full Pithos+ GET at object level
460

461
        --- request parameters ---
462

463
        :param format: (string) json (default) or xml
464

465
        :param hashmap: (bool) Optional request for hashmap
466

467
        :param version: (string) optional version identified
468

469
        --- request headers ---
470

471
        :param data_range: (string) Optional range of data to retrieve
472

473
        :param if_range: (bool)
474

475
        :param if_etag_match: (string) if provided, return only results
476
            with etag matching with this
477

478
        :param if_etag_not_match: (string) if provided, return only results
479
            with etag not matching with this
480

481
        :param if_modified_since: (string) Retrieve if account has changed
482
            since provided timestamp
483

484
        :param if_unmodified_since: (string) Retrieve if account has not
485
            changed since provided timestamp
486

487
        :returns: ConnectionResponse
488
        """
489

    
490
        self._assert_container()
491

    
492
        self.set_param('format', format, iff=format)
493
        self.set_param('hashmap', hashmap, iff=hashmap)
494
        self.set_param('version', version, iff=version)
495

    
496
        self.set_header('Range', data_range)
497
        self.set_header('If-Range', '', if_range and data_range)
498
        self.set_header('If-Match', if_etag_match, )
499
        self.set_header('If-None-Match', if_etag_not_match)
500
        self.set_header('If-Modified-Since', if_modified_since)
501
        self.set_header('If-Unmodified-Since', if_unmodified_since)
502

    
503
        path = path4url(self.account, self.container, obj)
504
        success = kwargs.pop('success', 200)
505
        return self.get(path, *args, success=success, **kwargs)
506

    
507
    def object_put(
508
            self, obj,
509
            format='json',
510
            hashmap=False,
511
            delimiter=None,
512
            if_etag_match=None,
513
            if_etag_not_match=None,
514
            etag=None,
515
            content_length=None,
516
            content_type=None,
517
            transfer_encoding=None,
518
            copy_from=None,
519
            move_from=None,
520
            source_account=None,
521
            source_version=None,
522
            content_encoding=None,
523
            content_disposition=None,
524
            manifest=None,
525
            permissions=None,
526
            public=None,
527
            metadata=None,
528
            *args, **kwargs):
529
        """ Full Pithos+ PUT at object level
530

531
        --- request parameters ---
532

533
        :param format: (string) json (default) or xml
534

535
        :param hashmap: (bool) Optional hashmap provided instead of data
536

537
        --- request headers ---
538

539
        :param if_etag_match: (string) if provided, return only results
540
            with etag matching with this
541

542
        :param if_etag_not_match: (string) if provided, return only results
543
            with etag not matching with this
544

545
        :param etag: (string) The MD5 hash of the object (optional to check
546
            written data)
547

548
        :param content_length: (integer) The size of the data written
549

550
        :param content_type: (string) The MIME content type of the object
551

552
        :param transfer_encoding: (string) Set to chunked to specify
553
            incremental uploading (if used, Content-Length is ignored)
554

555
        :param copy_from: (string) The source path in the form
556
            /<container>/<object>
557

558
        :param move_from: (string) The source path in the form
559
            /<container>/<object>
560

561
        :param source_account: (string) The source account to copy/move from
562

563
        :param source_version: (string) The source version to copy from
564

565
        :param conent_encoding: (string) The encoding of the object
566

567
        :param content_disposition: (string) Presentation style of the object
568

569
        :param manifest: (string) Object parts prefix in
570
            /<container>/<object> form
571

572
        :param permissions: (dict) Object permissions in the form (all fields
573
            are optional)
574
            { 'read':[user1, group1, user2, ...],
575
            'write':['user3, group2, group3, ...] }
576

577
        :param public: (bool) If true, Object is published, False, unpublished
578

579
        :param metadata: (dict) Optional user defined metadata in the form
580
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
581

582
        :returns: ConnectionResponse
583
        """
584

    
585
        self._assert_container()
586

    
587
        self.set_param('format', format, iff=format)
588
        self.set_param('hashmap', hashmap, iff=hashmap)
589
        self.set_param('delimiter', delimiter, iff=delimiter)
590

    
591
        self.set_header('If-Match', if_etag_match)
592
        self.set_header('If-None-Match', if_etag_not_match)
593
        self.set_header('ETag', etag)
594
        self.set_header('Content-Length', content_length)
595
        self.set_header('Content-Type', content_type)
596
        self.set_header('Transfer-Encoding', transfer_encoding)
597
        self.set_header('X-Copy-From', copy_from)
598
        self.set_header('X-Move-From', move_from)
599
        self.set_header('X-Source-Account', source_account)
600
        self.set_header('X-Source-Version', source_version)
601
        self.set_header('Content-Encoding', content_encoding)
602
        self.set_header('Content-Disposition', content_disposition)
603
        self.set_header('X-Object-Manifest', manifest)
604
        if permissions:
605
            perms = None
606
            if permissions:
607
                for perm_type, perm_list in permissions.items():
608
                    if not perms:
609
                        perms = ''  # Remove permissions
610
                    if perm_list:
611
                        perms += ';' if perms else ''
612
                        perms += '%s=%s' % (perm_type, ','.join(perm_list))
613
            self.set_header('X-Object-Sharing', perms)
614
        self.set_header('X-Object-Public', public, public is not None)
615
        if metadata:
616
            for key, val in metadata.items():
617
                self.set_header('X-Object-Meta-' + key, val)
618

    
619
        path = path4url(self.account, self.container, obj)
620
        success = kwargs.pop('success', 201)
621
        return self.put(path, *args, success=success, **kwargs)
622

    
623
    def object_copy(
624
            self, obj, destination,
625
            format='json',
626
            ignore_content_type=False,
627
            if_etag_match=None,
628
            if_etag_not_match=None,
629
            destination_account=None,
630
            content_type=None,
631
            content_encoding=None,
632
            content_disposition=None,
633
            source_version=None,
634
            permissions=None,
635
            public=None,
636
            metadata=None,
637
            *args, **kwargs):
638
        """ Full Pithos+ COPY at object level
639

640
        --- request parameters ---
641

642
        :param format: (string) json (default) or xml
643

644
        :param ignore_content_type: (bool) Ignore the supplied Content-Type
645

646
        --- request headers ---
647

648
        :param if_etag_match: (string) if provided, copy only results
649
            with etag matching with this
650

651
        :param if_etag_not_match: (string) if provided, copy only results
652
            with etag not matching with this
653

654
        :param destination: (string) The destination path in the form
655
            /<container>/<object>
656

657
        :param destination_account: (string) The destination account to copy to
658

659
        :param content_type: (string) The MIME content type of the object
660

661
        :param content_encoding: (string) The encoding of the object
662

663
        :param content_disposition: (string) Object resentation style
664

665
        :param source_version: (string) The source version to copy from
666

667
        :param permissions: (dict) Object permissions in the form
668
            (all fields are optional)
669
            { 'read':[user1, group1, user2, ...],
670
            'write':['user3, group2, group3, ...] }
671

672
        :param public: (bool) If true, Object is published, False, unpublished
673

674
        :param metadata: (dict) Optional user defined metadata in the form
675
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
676
            Metadata are appended to the source metadata. In case of same
677
            keys, they replace the old metadata
678

679
        :returns: ConnectionResponse
680
        """
681

    
682
        self._assert_container()
683

    
684
        self.set_param('format', format, iff=format)
685
        self.set_param('ignore_content_type', iff=ignore_content_type)
686

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

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

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

730
        --- request parameters ---
731

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

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

736
        --- request headers ---
737

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

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

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

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

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

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

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

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

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

762
        :param public: (bool) If true, Object is published, False, unpublished
763

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

767
        :returns: ConnectionResponse
768
        """
769

    
770
        self._assert_container()
771

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

    
775
        self.set_header('If-Match', if_etag_match)
776
        self.set_header('If-None-Match', if_etag_not_match)
777
        self.set_header('Destination', destination)
778
        self.set_header('Destination-Account', destination_account)
779
        self.set_header('Content-Type', content_type)
780
        self.set_header('Content-Encoding', content_encoding)
781
        self.set_header('Content-Disposition', content_disposition)
782
        perms = ';'.join(
783
            ['%s=%s' % (k, ','.join(v)) for k, v in permissions.items() if (
784
                v)]) if (permissions) else ''
785
        self.set_header('X-Object-Sharing', perms, iff=permissions)
786
        self.set_header('X-Object-Public', public, public is not None)
787
        if metadata:
788
            for key, val in metadata.items():
789
                self.set_header('X-Object-Meta-' + key, val)
790

    
791
        path = path4url(self.account, self.container, object)
792
        success = kwargs.pop('success', 201)
793
        return self.move(path, *args, success=success, **kwargs)
794

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

818
        --- request parameters ---
819

820
        :param format: (string) json (default) or xml
821

822
        :param update: (bool) Do not replace metadata
823

824
        --- request headers ---
825

826
        :param if_etag_match: (string) if provided, return only results
827
            with etag matching with this
828

829
        :param if_etag_not_match: (string) if provided, return only results
830
            with etag not matching with this
831

832
        :param content_length: (string) The size of the data written
833

834
        :param content_type: (string) The MIME content type of the object
835

836
        :param content_range: (string) The range of data supplied
837

838
        :param transfer_encoding: (string) Set to chunked to specify
839
            incremental uploading (if used, Content-Length is ignored)
840

841
        :param content_encoding: (string) The encoding of the object
842

843
        :param content_disposition: (string) Object presentation style
844

845
        :param source_object: (string) Update with data from the object at
846
            path /<container>/<object>
847

848
        :param source_account: (string) The source account to update from
849

850
        :param source_version: (string) The source version to copy from
851

852
        :param object_bytes: (integer) The updated objects final size
853

854
        :param manifest: (string) Object parts prefix as /<container>/<object>
855

856
        :param permissions: (dict) Object permissions in the form (all fields
857
            are optional)
858
            { 'read':[user1, group1, user2, ...],
859
            'write':['user3, group2, group3, ...] }
860

861
        :param public: (bool) If true, Object is published, False, unpublished
862

863
        :param metadata: (dict) Optional user defined metadata in the form
864
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
865

866
        :returns: ConnectionResponse
867
        """
868

    
869
        self._assert_container()
870

    
871
        self.set_param('format', format, iff=format)
872
        self.set_param('update', '', iff=update)
873

    
874
        self.set_header('If-Match', if_etag_match)
875
        self.set_header('If-None-Match', if_etag_not_match)
876
        self.set_header(
877
            'Content-Length', content_length, iff=not transfer_encoding)
878
        self.set_header('Content-Type', content_type)
879
        self.set_header('Content-Range', content_range)
880
        self.set_header('Transfer-Encoding', transfer_encoding)
881
        self.set_header('Content-Encoding', content_encoding)
882
        self.set_header('Content-Disposition', content_disposition)
883
        self.set_header('X-Source-Object', source_object)
884
        self.set_header('X-Source-Account', source_account)
885
        self.set_header('X-Source-Version', source_version)
886
        self.set_header('X-Object-Bytes', object_bytes)
887
        self.set_header('X-Object-Manifest', manifest)
888
        perms = ';'.join(
889
            ['%s=%s' % (k, ','.join(v)) for k, v in permissions.items() if (
890
                v)]) if (permissions) else ''
891
        self.set_header('X-Object-Sharing', perms, iff=permissions)
892
        self.set_header('X-Object-Public', public, public is not None)
893
        for key, val in metadata.items():
894
            self.set_header('X-Object-Meta-' + key, val)
895

    
896
        path = path4url(self.account, self.container, obj)
897
        success = kwargs.pop('success', (202, 204))
898
        return self.post(path, *args, success=success, **kwargs)
899

    
900
    def object_delete(
901
            self, object,
902
            until=None, delimiter=None,
903
            *args, **kwargs):
904
        """ Full Pithos+ DELETE at object level
905

906
        --- request parameters ---
907

908
        :param until: (string) Optional timestamp
909

910
        :returns: ConnectionResponse
911
        """
912
        self._assert_container()
913

    
914
        self.set_param('until', until, iff=until)
915
        self.set_param('delimiter', delimiter, iff=delimiter)
916

    
917
        path = path4url(self.account, self.container, object)
918
        success = kwargs.pop('success', 204)
919
        return self.delete(path, *args, success=success, **kwargs)