Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (31.4 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
        self.set_header('X-Container-Policy-Quota', quota)
310
        self.set_header('X-Container-Policy-Versioning', versioning)
311
        if metadata:
312
            for metaname, metaval in metadata.items():
313
                self.set_header('X-Container-Meta-' + metaname, metaval)
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 custom transfer encoding
352

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

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

    
360
        self.set_header('X-Container-Policy-Quota', quota)
361
        self.set_header('X-Container-Policy-Versioning', versioning)
362
        if metadata:
363
            for metaname, metaval in metadata.items():
364
                self.set_header('X-Container-Meta-' + metaname, metaval)
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, *args, success=success, **kwargs)
392

    
393
    def object_head(
394
            self, obj,
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, obj)
434
        success = kwargs.pop('success', 200)
435
        return self.head(path, *args, success=success, **kwargs)
436

    
437
    def object_get(
438
            self, obj,
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('hashmap', hashmap, iff=hashmap)
484
        self.set_param('version', version, iff=version)
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, obj)
494
        success = kwargs.pop('success', 200)
495
        return self.get(path, *args, success=success, **kwargs)
496

    
497
    def object_put(
498
            self, obj,
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 published, False, unpublished
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
        if permissions:
595
            perms = None
596
            if permissions:
597
                for perm_type, perm_list in permissions.items():
598
                    if not perms:
599
                        perms = ''  # Remove permissions
600
                    if perm_list:
601
                        perms += ';' if perms else ''
602
                        perms += '%s=%s' % (perm_type, ','.join(perm_list))
603
            self.set_header('X-Object-Sharing', perms)
604
        self.set_header('X-Object-Public', public, public is not None)
605
        if metadata:
606
            for key, val in metadata.items():
607
                self.set_header('X-Object-Meta-' + key, val)
608

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

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

630
        --- request parameters ---
631

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

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

636
        --- request headers ---
637

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

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

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

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

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

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

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

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

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

662
        :param public: (bool) If true, Object is published, False, unpublished
663

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

669
        :returns: ConnectionResponse
670
        """
671

    
672
        self._assert_container()
673

    
674
        self.set_param('format', format, iff=format)
675
        self.set_param('ignore_content_type', iff=ignore_content_type)
676

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

    
699
        path = path4url(self.account, self.container, obj)
700
        success = kwargs.pop('success', 201)
701
        return self.copy(path, *args, success=success, **kwargs)
702

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

720
        --- request parameters ---
721

722
        :param format: (string) json (default) or xml
723

724
        :param ignore_content_type: (bool) Ignore the supplied Content-Type
725

726
        --- request headers ---
727

728
        :param if_etag_match: (string) if provided, return only results
729
            with etag matching with this
730

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

734
        :param destination: (string) The destination path in the form
735
            /<container>/<object>
736

737
        :param destination_account: (string) The destination account to copy to
738

739
        :param content_type: (string) The MIME content type of the object
740

741
        :param content_encoding: (string) The encoding of the object
742

743
        :param content_disposition: (string) Object presentation style
744

745
        :param source_version: (string) The source version to copy from
746

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

752
        :param public: (bool) If true, Object is published, False, unpublished
753

754
        :param metadata: (dict) Optional user defined metadata in the form
755
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
756

757
        :returns: ConnectionResponse
758
        """
759

    
760
        self._assert_container()
761

    
762
        self.set_param('format', format, iff=format)
763
        self.set_param('ignore_content_type', iff=ignore_content_type)
764

    
765
        self.set_header('If-Match', if_etag_match)
766
        self.set_header('If-None-Match', if_etag_not_match)
767
        self.set_header('Destination', destination)
768
        self.set_header('Destination-Account', destination_account)
769
        self.set_header('Content-Type', content_type)
770
        self.set_header('Content-Encoding', content_encoding)
771
        self.set_header('Content-Disposition', content_disposition)
772
        perms = ';'.join(
773
            ['%s=%s' % (k, ','.join(v)) for k, v in permissions.items() if (
774
                v)]) if (permissions) else ''
775
        self.set_header('X-Object-Sharing', perms, iff=permissions)
776
        self.set_header('X-Object-Public', public, public is not None)
777
        if metadata:
778
            for key, val in metadata.items():
779
                self.set_header('X-Object-Meta-' + key, val)
780

    
781
        path = path4url(self.account, self.container, object)
782
        success = kwargs.pop('success', 201)
783
        return self.move(path, *args, success=success, **kwargs)
784

    
785
    def object_post(
786
            self, obj,
787
            format='json',
788
            update=True,
789
            if_etag_match=None,
790
            if_etag_not_match=None,
791
            content_length=None,
792
            content_type=None,
793
            content_range=None,
794
            transfer_encoding=None,
795
            content_encoding=None,
796
            content_disposition=None,
797
            source_object=None,
798
            source_account=None,
799
            source_version=None,
800
            object_bytes=None,
801
            manifest=None,
802
            permissions={},
803
            public=None,
804
            metadata={},
805
            *args, **kwargs):
806
        """ Full Pithos+ POST at object level
807

808
        --- request parameters ---
809

810
        :param format: (string) json (default) or xml
811

812
        :param update: (bool) Do not replace metadata
813

814
        --- request headers ---
815

816
        :param if_etag_match: (string) if provided, return only results
817
            with etag matching with this
818

819
        :param if_etag_not_match: (string) if provided, return only results
820
            with etag not matching with this
821

822
        :param content_length: (string) The size of the data written
823

824
        :param content_type: (string) The MIME content type of the object
825

826
        :param content_range: (string) The range of data supplied
827

828
        :param transfer_encoding: (string) Set to chunked to specify
829
            incremental uploading (if used, Content-Length is ignored)
830

831
        :param content_encoding: (string) The encoding of the object
832

833
        :param content_disposition: (string) Object presentation style
834

835
        :param source_object: (string) Update with data from the object at
836
            path /<container>/<object>
837

838
        :param source_account: (string) The source account to update from
839

840
        :param source_version: (string) The source version to copy from
841

842
        :param object_bytes: (integer) The updated objects final size
843

844
        :param manifest: (string) Object parts prefix as /<container>/<object>
845

846
        :param permissions: (dict) Object permissions in the form (all fields
847
            are optional)
848
            { 'read':[user1, group1, user2, ...],
849
            'write':['user3, group2, group3, ...] }
850

851
        :param public: (bool) If true, Object is published, False, unpublished
852

853
        :param metadata: (dict) Optional user defined metadata in the form
854
            {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
855

856
        :returns: ConnectionResponse
857
        """
858

    
859
        self._assert_container()
860

    
861
        self.set_param('format', format, iff=format)
862
        self.set_param('update', '', iff=update)
863

    
864
        self.set_header('If-Match', if_etag_match)
865
        self.set_header('If-None-Match', if_etag_not_match)
866
        self.set_header(
867
            'Content-Length', content_length, iff=not transfer_encoding)
868
        self.set_header('Content-Type', content_type)
869
        self.set_header('Content-Range', content_range)
870
        self.set_header('Transfer-Encoding', transfer_encoding)
871
        self.set_header('Content-Encoding', content_encoding)
872
        self.set_header('Content-Disposition', content_disposition)
873
        self.set_header('X-Source-Object', source_object)
874
        self.set_header('X-Source-Account', source_account)
875
        self.set_header('X-Source-Version', source_version)
876
        self.set_header('X-Object-Bytes', object_bytes)
877
        self.set_header('X-Object-Manifest', manifest)
878
        perms = ';'.join(
879
            ['%s=%s' % (k, ','.join(v)) for k, v in permissions.items() if (
880
                v)]) if (permissions) else ''
881
        self.set_header('X-Object-Sharing', perms, iff=permissions)
882
        self.set_header('X-Object-Public', public, public is not None)
883
        for key, val in metadata.items():
884
            self.set_header('X-Object-Meta-' + key, val)
885

    
886
        path = path4url(self.account, self.container, obj)
887
        success = kwargs.pop('success', (202, 204))
888
        return self.post(path, *args, success=success, **kwargs)
889

    
890
    def object_delete(
891
            self, object,
892
            until=None, delimiter=None,
893
            *args, **kwargs):
894
        """ Full Pithos+ DELETE at object level
895

896
        --- request parameters ---
897

898
        :param until: (string) Optional timestamp
899

900
        :returns: ConnectionResponse
901
        """
902
        self._assert_container()
903

    
904
        self.set_param('until', until, iff=until)
905
        self.set_param('delimiter', delimiter, iff=delimiter)
906

    
907
        path = path4url(self.account, self.container, object)
908
        success = kwargs.pop('success', 204)
909
        return self.delete(path, *args, success=success, **kwargs)