Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / pithos_rest_api.py @ 43ee6ae1

History | View | Annotate | Download (31.8 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(self,
41
        until=None,
42
        if_modified_since=None,
43
        if_unmodified_since=None,
44
        *args,
45
        **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 is not None)
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(self,
74
        limit=None,
75
        marker=None,
76
        format='json',
77
        show_only_shared=False,
78
        until=None,
79
        if_modified_since=None,
80
        if_unmodified_since=None,
81
        *args,
82
        **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 is not None)
115
        self.set_param('limit', limit, iff=limit is not None)
116
        self.set_param('marker', marker, iff=marker is not None)
117
        self.set_param('shared', iff=show_only_shared)
118
        self.set_param('until', until, iff=until is not None)
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(self,
128
        update=True,
129
        groups={},
130
        metadata=None,
131
        quota=None,
132
        versioning=None,
133
        *args,
134
        **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 is not None:
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(self, until=None,
180
        if_modified_since=None, if_unmodified_since=None, *args, **kwargs):
181
        """ Full Pithos+ HEAD at container level
182

183
        --- request params ---
184

185
        :param until: (string) optional timestamp
186

187
        --- request headers ---
188

189
        :param if_modified_since: (string) Retrieve if account has changed
190
            since provided timestamp
191

192
        :param if_unmodified_since: (string) Retrieve if account has not
193
            changed since provided timestamp
194

195
        :returns: ConnectionResponse
196
        """
197

    
198
        self._assert_container()
199

    
200
        self.set_param('until', until, iff=until is not None)
201

    
202
        self.set_header('If-Modified-Since', if_modified_since)
203
        self.set_header('If-Unmodified-Since', if_unmodified_since)
204

    
205
        path = path4url(self.account, self.container)
206
        success = kwargs.pop('success', 204)
207
        return self.head(path, *args, success=success, **kwargs)
208

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

225
        --- request parameters ---
226

227
        :param limit: (integer) The amount of results requested
228
            (server will use default value if None)
229

230
        :param marker: (string) Return containers with name lexicographically
231
            after marker
232

233
        :param prefix: (string) Return objects starting with prefix
234

235
        :param delimiter: (string) Return objects up to the delimiter
236

237
        :param path: (string) assume prefix = path and delimiter = /
238
            (overwrites prefix and delimiter)
239

240
        :param format: (string) reply format can be json or xml (default:json)
241

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

247
        :param shared: (bool) If true, only shared containers will be included
248
            in results
249

250
        :param until: (string) optional timestamp
251

252
        --- request headers ---
253

254
        :param if_modified_since: (string) Retrieve if account has changed
255
            since provided timestamp
256

257
        :param if_unmodified_since: (string) Retrieve if account has not
258
            changed since provided timestamp
259

260
        :returns: ConnectionResponse
261
        """
262

    
263
        self._assert_container()
264

    
265
        self.set_param('format', format, iff=format is not None)
266
        self.set_param('limit', limit, iff=limit is not None)
267
        self.set_param('marker', marker, iff=marker is not None)
268
        if path is None:
269
            self.set_param('prefix', prefix, iff=prefix is not None)
270
            self.set_param('delimiter', delimiter, iff=delimiter is not None)
271
        else:
272
            self.set_param('path', path)
273
        self.set_param('shared', iff=show_only_shared)
274
        self.set_param('meta',
275
            list2str(meta),
276
            iff=meta is not None and len(meta) > 0)
277
        self.set_param('until', until, iff=until is not None)
278

    
279
        self.set_header('If-Modified-Since', if_modified_since)
280
        self.set_header('If-Unmodified-Since', if_unmodified_since)
281

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

    
286
    def container_put(self,
287
        quota=None,
288
        versioning=None,
289
        metadata=None,
290
        *args,
291
        **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 is not None:
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(self,
318
        update=True,
319
        format='json',
320
        quota=None,
321
        versioning=None,
322
        metadata=None,
323
        content_type=None,
324
        content_length=None,
325
        transfer_encoding=None,
326
        *args,
327
        **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 is not None)
356
        self.set_param('update', iff=update)
357

    
358
        if metadata is not None:
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 is not None)
385
        self.set_param('delimiter', delimiter, iff=delimiter is not None)
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(self, object,
392
        version=None,
393
        if_etag_match=None,
394
        if_etag_not_match=None,
395
        if_modified_since=None,
396
        if_unmodified_since=None,
397
        *args,
398
        **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 is not None)
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(self, object,
436
        format='json',
437
        hashmap=False,
438
        version=None,
439
        data_range=None,
440
        if_range=False,
441
        if_etag_match=None,
442
        if_etag_not_match=None,
443
        if_modified_since=None,
444
        if_unmodified_since=None,
445
        *args,
446
        **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 is not None)
481
        self.set_param('version', version, iff=version is not None)
482
        self.set_param('hashmap', hashmap, iff=hashmap)
483

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

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

    
496
    def object_put(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,
517
        **kwargs):
518
        """ Full Pithos+ PUT at object level
519

520
        --- request parameters ---
521

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

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

526
        --- request headers ---
527

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

572
        :returns: ConnectionResponse
573
        """
574

    
575
        self._assert_container()
576

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

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

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

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

727
        --- request parameters ---
728

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

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

733
        --- request headers ---
734

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

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

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

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

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

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

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

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

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

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

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

764
        :returns: ConnectionResponse
765
        """
766

    
767
        self._assert_container()
768

    
769
        self.set_param('format', format, iff=format is not None)
770
        self.set_param('ignore_content_type', iff=ignore_content_type)
771

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

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

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

821
        --- request parameters ---
822

823
        :param format: (string) json (default) or xml
824

825
        :param update: (bool) Do not replace metadata
826

827
        --- request headers ---
828

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

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

835
        :param content_length: (string) The size of the data written
836

837
        :param content_type: (string) The MIME content type of the object
838

839
        :param content_range: (string) The range of data supplied
840

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

844
        :param content_encoding: (string) The encoding of the object
845

846
        :param content_disposition: (string) Object presentation style
847

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

851
        :param source_account: (string) The source account to update from
852

853
        :param source_version: (string) The source version to copy from
854

855
        :param object_bytes: (integer) The updated objects final size
856

857
        :param manifest: (string) Object parts prefix as /<container>/<object>
858

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

864
        :param public: (bool) If true, Object is publicly accessible
865

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

869
        :returns: ConnectionResponse
870
        """
871

    
872
        self._assert_container()
873

    
874
        self.set_param('format', format, iff=format is not None)
875
        self.set_param('update', iff=update)
876

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

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

    
911
    def object_delete(self, object,
912
        until=None,
913
        delimiter=None,
914
        *args,
915
        **kwargs):
916
        """ Full Pithos+ DELETE at object level
917

918
        --- request parameters ---
919

920
        :param until: (string) Optional timestamp
921

922
        :returns: ConnectionResponse
923
        """
924
        self._assert_container()
925

    
926
        self.set_param('until', until, iff=until is not None)
927
        self.set_param('delimiter', delimiter, iff=delimiter is not None)
928

    
929
        path = path4url(self.account, self.container, object)
930
        success = kwargs.pop('success', 204)
931
        return self.delete(path, *args, success=success, **kwargs)