Statistics
| Branch: | Tag: | Revision:

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

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

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

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

188
        --- request params ---
189

190
        :param until: (string) optional timestamp
191

192
        --- request headers ---
193

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

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

200
        :returns: ConnectionResponse
201
        """
202

    
203
        self._assert_container()
204

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

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

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

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

230
        --- request parameters ---
231

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

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

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

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

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

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

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

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

255
        :param until: (string) optional timestamp
256

257
        --- request headers ---
258

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

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

265
        :returns: ConnectionResponse
266
        """
267

    
268
        self._assert_container()
269

    
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('format', format, iff=format)
278
        self.set_param('shared', iff=show_only_shared)
279
        if meta:
280
            self.set_param('meta',  ','.join(meta))
281
        self.set_param('until', until, iff=until)
282

    
283
        self.set_header('If-Modified-Since', if_modified_since)
284
        self.set_header('If-Unmodified-Since', if_unmodified_since)
285

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

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

296
        --- request headers ---
297

298
        :param quota: (integer) Size limit in KB
299

300
        :param versioning: (string) 'auto' or other string supported by server
301

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

305
        :returns: ConnectionResponse
306
        """
307
        self._assert_container()
308

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

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

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

332
        --- request params ---
333

334
        :param update: (bool)  if True, Do not replace metadata/groups
335

336
        :param format: (string) json (default) or xml
337

338
        --- request headers ---
339

340
        :param quota: (integer) Size limit in KB
341

342
        :param versioning: (string) 'auto' or other string supported by server
343

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

347
        :param content_type: (string) set a custom content type
348

349
        :param content_length: (string) set a custrom content length
350

351
        :param transfer_encoding: (string) set a custrom transfer encoding
352

353
        :returns: ConnectionResponse
354
        """
355
        self._assert_container()
356

    
357
        self.set_param('format', format, iff=format)
358
        self.set_param('update', iff=update)
359

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

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

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

376
        --- request parameters ---
377

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

381
        :returns: ConnectionResponse
382
        """
383

    
384
        self._assert_container()
385

    
386
        self.set_param('until', until, iff=until)
387
        self.set_param('delimiter', delimiter, iff=delimiter)
388

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

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

403
        --- request parameters ---
404

405
        :param version: (string) optional version identified
406

407
        --- request headers ---
408

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

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

415
        :param if_modified_since: (string) Retrieve if account has changed
416
            since provided timestamp
417

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

421
        :returns: ConnectionResponse
422
        """
423

    
424
        self._assert_container()
425

    
426
        self.set_param('version', version, iff=version)
427

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

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

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

451
        --- request parameters ---
452

453
        :param format: (string) json (default) or xml
454

455
        :param hashmap: (bool) Optional request for hashmap
456

457
        :param version: (string) optional version identified
458

459
        --- request headers ---
460

461
        :param data_range: (string) Optional range of data to retrieve
462

463
        :param if_range: (bool)
464

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

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

471
        :param if_modified_since: (string) Retrieve if account has changed
472
            since provided timestamp
473

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

477
        :returns: ConnectionResponse
478
        """
479

    
480
        self._assert_container()
481

    
482
        self.set_param('format', format, iff=format)
483
        self.set_param('version', version, iff=version)
484
        self.set_param('hashmap', hashmap, iff=hashmap)
485

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

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

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

521
        --- request parameters ---
522

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

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

527
        --- request headers ---
528

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

573
        :returns: ConnectionResponse
574
        """
575

    
576
        self._assert_container()
577

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

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

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

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

631
        --- request parameters ---
632

633
        :param format: (string) json (default) or xml
634

635
        :param ignore_content_type: (bool) Ignore the supplied Content-Type
636

637
        --- request headers ---
638

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

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

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

648
        :param destination_account: (string) The destination account to copy to
649

650
        :param content_type: (string) The MIME content type of the object
651

652
        :param content_encoding: (string) The encoding of the object
653

654
        :param content_disposition: (string) Object resentation style
655

656
        :param source_version: (string) The source version to copy from
657

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

663
        :param permissions: update permissions
664

665
        :param public: (bool) If true, Object is publicly accessible
666

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

672
        :returns: ConnectionResponse
673
        """
674

    
675
        self._assert_container()
676

    
677
        self.set_param('format', format, iff=format)
678
        self.set_param('ignore_content_type', iff=ignore_content_type)
679

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

    
702
        path = path4url(self.account, self.container, object)
703
        success = kwargs.pop('success', 201)
704
        return self.copy(path, *args, success=success, **kwargs)
705

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

723
        --- request parameters ---
724

725
        :param format: (string) json (default) or xml
726

727
        :param ignore_content_type: (bool) Ignore the supplied Content-Type
728

729
        --- request headers ---
730

731
        :param if_etag_match: (string) if provided, return only results
732
            with etag matching with this
733

734
        :param if_etag_not_match: (string) if provided, return only results
735
            with etag not matching with this
736

737
        :param destination: (string) The destination path in the form
738
            /<container>/<object>
739

740
        :param destination_account: (string) The destination account to copy to
741

742
        :param content_type: (string) The MIME content type of the object
743

744
        :param content_encoding: (string) The encoding of the object
745

746
        :param content_disposition: (string) Object presentation style
747

748
        :param source_version: (string) The source version to copy from
749

750
        :param permissions: (dict) Object permissions in the form
751
            (all fields are optional)
752
            { 'read':[user1, group1, user2, ...],
753
            'write':['user3, group2, group3, ...] }
754

755
        :param public: (bool) If true, Object is publicly accessible
756

757
        :param metadata: (dict) Optional user defined metadata in the form
758
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
759

760
        :returns: ConnectionResponse
761
        """
762

    
763
        self._assert_container()
764

    
765
        self.set_param('format', format, iff=format)
766
        self.set_param('ignore_content_type', iff=ignore_content_type)
767

    
768
        self.set_header('If-Match', if_etag_match)
769
        self.set_header('If-None-Match', if_etag_not_match)
770
        self.set_header('Destination', destination)
771
        self.set_header('Destination-Account', destination_account)
772
        self.set_header('Content-Type', content_type)
773
        self.set_header('Content-Encoding', content_encoding)
774
        self.set_header('Content-Disposition', content_disposition)
775
        if permissions:
776
            perms = ''
777
            for perm_type, perm_list in permissions.items():
778
                if not perms:
779
                    perms = ''  # Remove permissions
780
                if perm_list:
781
                    perms += ';' if perms else ''
782
                    perms += '%s=%s' % (perm_type, ','.join(perm_list))
783
            self.set_header('X-Object-Sharing', perms)
784
        self.set_header('X-Object-Public', public)
785
        if metadata:
786
            for key, val in metadata.items():
787
                self.set_header('X-Object-Meta-' + key, val)
788

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

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

816
        --- request parameters ---
817

818
        :param format: (string) json (default) or xml
819

820
        :param update: (bool) Do not replace metadata
821

822
        --- request headers ---
823

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

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

830
        :param content_length: (string) The size of the data written
831

832
        :param content_type: (string) The MIME content type of the object
833

834
        :param content_range: (string) The range of data supplied
835

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

839
        :param content_encoding: (string) The encoding of the object
840

841
        :param content_disposition: (string) Object presentation style
842

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

846
        :param source_account: (string) The source account to update from
847

848
        :param source_version: (string) The source version to copy from
849

850
        :param object_bytes: (integer) The updated objects final size
851

852
        :param manifest: (string) Object parts prefix as /<container>/<object>
853

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

859
        :param public: (bool) If true, Object is publicly accessible
860

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

864
        :returns: ConnectionResponse
865
        """
866

    
867
        self._assert_container()
868

    
869
        self.set_param('format', format, iff=format)
870
        self.set_param('update', iff=update)
871

    
872
        self.set_header('If-Match', if_etag_match)
873
        self.set_header('If-None-Match', if_etag_not_match)
874
        self.set_header(
875
            'Content-Length',
876
            content_length,
877
            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
        if permissions:
889
            perms = ''
890
            for perm_type, perm_list in permissions.items():
891
                if not perms:
892
                    perms = ''  # Remove permissions
893
                if perm_list:
894
                    perms += ';' if perms else ''
895
                    perms += '%s=%s' % (perm_type, ','.join(perm_list))
896
                self.set_header('X-Object-Sharing', perms)
897
        self.set_header('X-Object-Public', public)
898
        for key, val in metadata.items():
899
            self.set_header('X-Object-Meta-' + key, val)
900

    
901
        path = path4url(self.account, self.container, object)
902
        success = kwargs.pop('success', (202, 204))
903
        return self.post(path, *args, success=success, **kwargs)
904

    
905
    def object_delete(
906
            self, object,
907
            until=None, delimiter=None,
908
            *args, **kwargs):
909
        """ Full Pithos+ DELETE at object level
910

911
        --- request parameters ---
912

913
        :param until: (string) Optional timestamp
914

915
        :returns: ConnectionResponse
916
        """
917
        self._assert_container()
918

    
919
        self.set_param('until', until, iff=until)
920
        self.set_param('delimiter', delimiter, iff=delimiter)
921

    
922
        path = path4url(self.account, self.container, object)
923
        success = kwargs.pop('success', 204)
924
        return self.delete(path, *args, success=success, **kwargs)