Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos_rest_api.py @ 2005b18e

History | View | Annotate | Download (32 kB)

1
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from kamaki.clients.storage import StorageClient
35
from kamaki.clients.utils import path4url, list2str
36

    
37

    
38
class PithosRestAPI(StorageClient):
39

    
40
    def account_head(
41
            self,
42
            until=None,
43
            if_modified_since=None,
44
            if_unmodified_since=None,
45
            *args, **kwargs):
46
        """ Full Pithos+ HEAD at account level
47

48
        --- request parameters ---
49

50
        :param until: (string) optional timestamp
51

52
        --- request headers ---
53

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

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

60
        :returns: ConnectionResponse
61
        """
62

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

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

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

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

85
        --- request parameters ---
86

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

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

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

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

99
        :param until: (string) optional timestamp
100

101
        --- request headers ---
102

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

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

109
        :returns: ConnectionResponse
110
        """
111

    
112
        self._assert_account()
113

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

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

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

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

137
        --- request parameters ---
138

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

141
        --- request headers ---
142

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

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

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

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

155
        :returns: ConnectionResponse
156
        """
157

    
158
        self._assert_account()
159

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

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

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

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

187
        --- request params ---
188

189
        :param until: (string) optional timestamp
190

191
        --- request headers ---
192

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

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

199
        :returns: ConnectionResponse
200
        """
201

    
202
        self._assert_container()
203

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

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

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

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

229
        --- request parameters ---
230

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

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

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

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

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

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

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

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

254
        :param until: (string) optional timestamp
255

256
        --- request headers ---
257

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

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

264
        :returns: ConnectionResponse
265
        """
266

    
267
        self._assert_container()
268

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

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

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

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

294
        --- request headers ---
295

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

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

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

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

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

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

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

330
        --- request params ---
331

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

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

336
        --- request headers ---
337

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

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

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

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

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

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

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

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

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

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

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

374
        --- request parameters ---
375

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

379
        :returns: ConnectionResponse
380
        """
381

    
382
        self._assert_container()
383

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

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

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

401
        --- request parameters ---
402

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

405
        --- request headers ---
406

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

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

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

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

419
        :returns: ConnectionResponse
420
        """
421

    
422
        self._assert_container()
423

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

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

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

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

449
        --- request parameters ---
450

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

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

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

457
        --- request headers ---
458

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

461
        :param if_range: (bool)
462

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

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

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

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

475
        :returns: ConnectionResponse
476
        """
477

    
478
        self._assert_container()
479

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

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

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

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

519
        --- request parameters ---
520

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

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

525
        --- request headers ---
526

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

571
        :returns: ConnectionResponse
572
        """
573

    
574
        self._assert_container()
575

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

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

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

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

632
        --- request parameters ---
633

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

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

638
        --- request headers ---
639

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

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

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

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

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

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

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

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

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

664
        :param permissions: update permissions
665

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

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

673
        :returns: ConnectionResponse
674
        """
675

    
676
        self._assert_container()
677

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

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

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

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

728
        --- request parameters ---
729

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

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

734
        --- request headers ---
735

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

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

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

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

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

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

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

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

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

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

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

765
        :returns: ConnectionResponse
766
        """
767

    
768
        self._assert_container()
769

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

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

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

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

823
        --- request parameters ---
824

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

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

829
        --- request headers ---
830

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

871
        :returns: ConnectionResponse
872
        """
873

    
874
        self._assert_container()
875

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

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

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

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

921
        --- request parameters ---
922

923
        :param until: (string) Optional timestamp
924

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

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

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