Rename file setquota to file containerlimit set
[kamaki] / kamaki / clients / pithos / rest_api.py
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=False,
517             metadata=None,
518             *args, **kwargs):
519         """ Full Pithos+ PUT at object level
520
521         --- request parameters ---
522
523         :param format: (string) json (default) or xml
524
525         :param hashmap: (bool) Optional hashmap provided instead of data
526
527         --- request headers ---
528
529         :param if_etag_match: (string) if provided, return only results
530             with etag matching with this
531
532         :param if_etag_not_match: (string) if provided, return only results
533             with etag not matching with this
534
535         :param etag: (string) The MD5 hash of the object (optional to check
536             written data)
537
538         :param content_length: (integer) The size of the data written
539
540         :param content_type: (string) The MIME content type of the object
541
542         :param transfer_encoding: (string) Set to chunked to specify
543             incremental uploading (if used, Content-Length is ignored)
544
545         :param copy_from: (string) The source path in the form
546             /<container>/<object>
547
548         :param move_from: (string) The source path in the form
549             /<container>/<object>
550
551         :param source_account: (string) The source account to copy/move from
552
553         :param source_version: (string) The source version to copy from
554
555         :param conent_encoding: (string) The encoding of the object
556
557         :param content_disposition: (string) Presentation style of the object
558
559         :param manifest: (string) Object parts prefix in
560             /<container>/<object> form
561
562         :param permissions: (dict) Object permissions in the form (all fields
563             are optional)
564             { 'read':[user1, group1, user2, ...],
565             'write':['user3, group2, group3, ...] }
566
567         :param public: (bool) If true, Object is publicly accessible,
568             if false, not
569
570         :param metadata: (dict) Optional user defined metadata in the form
571             {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
572
573         :returns: ConnectionResponse
574         """
575
576         self._assert_container()
577
578         self.set_param('format', format, iff=format)
579         self.set_param('hashmap', hashmap, iff=hashmap)
580         self.set_param('delimiter', delimiter, iff=delimiter)
581
582         self.set_header('If-Match', if_etag_match)
583         self.set_header('If-None-Match', if_etag_not_match)
584         self.set_header('ETag', etag)
585         self.set_header('Content-Length', content_length)
586         self.set_header('Content-Type', content_type)
587         self.set_header('Transfer-Encoding', transfer_encoding)
588         self.set_header('X-Copy-From', copy_from)
589         self.set_header('X-Move-From', move_from)
590         self.set_header('X-Source-Account', source_account)
591         self.set_header('X-Source-Version', source_version)
592         self.set_header('Content-Encoding', content_encoding)
593         self.set_header('Content-Disposition', content_disposition)
594         self.set_header('X-Object-Manifest', manifest)
595         if permissions:
596             perms = None
597             if permissions:
598                 for perm_type, perm_list in permissions.items():
599                     if not perms:
600                         perms = ''  # Remove permissions
601                     if perm_list:
602                         perms += ';' if perms else ''
603                         perms += '%s=%s' % (perm_type, ','.join(perm_list))
604             self.set_header('X-Object-Sharing', perms)
605         self.set_header('X-Object-Public', public)
606         if metadata:
607             for key, val in metadata.items():
608                 self.set_header('X-Object-Meta-' + key, val)
609
610         path = path4url(self.account, self.container, obj)
611         success = kwargs.pop('success', 201)
612         return self.put(path, *args, success=success, **kwargs)
613
614     def object_copy(
615             self, obj, destination,
616             format='json',
617             ignore_content_type=False,
618             if_etag_match=None,
619             if_etag_not_match=None,
620             destination_account=None,
621             content_type=None,
622             content_encoding=None,
623             content_disposition=None,
624             source_version=None,
625             permissions=None,
626             public=False,
627             metadata=None,
628             *args, **kwargs):
629         """ Full Pithos+ COPY at object level
630
631         --- request parameters ---
632
633         :param format: (string) json (default) or xml
634
635         :param ignore_content_type: (bool) Ignore the supplied Content-Type
636
637         --- request headers ---
638
639         :param if_etag_match: (string) if provided, copy only results
640             with etag matching with this
641
642         :param if_etag_not_match: (string) if provided, copy only results
643             with etag not matching with this
644
645         :param destination: (string) The destination path in the form
646             /<container>/<object>
647
648         :param destination_account: (string) The destination account to copy to
649
650         :param content_type: (string) The MIME content type of the object
651
652         :param content_encoding: (string) The encoding of the object
653
654         :param content_disposition: (string) Object resentation style
655
656         :param source_version: (string) The source version to copy from
657
658         :param permissions: (dict) Object permissions in the form
659             (all fields are optional)
660             { 'read':[user1, group1, user2, ...],
661             'write':['user3, group2, group3, ...] }
662
663         :param public: (bool) If true, Object is publicly accessible
664
665         :param metadata: (dict) Optional user defined metadata in the form
666             {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
667             Metadata are appended to the source metadata. In case of same
668             keys, they replace the old metadata
669
670         :returns: ConnectionResponse
671         """
672
673         self._assert_container()
674
675         self.set_param('format', format, iff=format)
676         self.set_param('ignore_content_type', iff=ignore_content_type)
677
678         self.set_header('If-Match', if_etag_match)
679         self.set_header('If-None-Match', if_etag_not_match)
680         self.set_header('Destination', destination)
681         self.set_header('Destination-Account', destination_account)
682         self.set_header('Content-Type', content_type)
683         self.set_header('Content-Encoding', content_encoding)
684         self.set_header('Content-Disposition', content_disposition)
685         self.set_header('X-Source-Version', source_version)
686         if permissions:
687             perms = ''
688             for perm_type, perm_list in permissions.items():
689                 if not perms:
690                     perms = ''  # Remove permissions
691                 if perm_list:
692                     perms += ';' if perms else ''
693                     perms += '%s=%s' % (perm_type, ','.join(perm_list))
694             self.set_header('X-Object-Sharing', perms)
695         self.set_header('X-Object-Public', public)
696         if metadata:
697             for key, val in metadata.items():
698                 self.set_header('X-Object-Meta-' + key, val)
699
700         path = path4url(self.account, self.container, obj)
701         success = kwargs.pop('success', 201)
702         return self.copy(path, *args, success=success, **kwargs)
703
704     def object_move(
705             self, object,
706             format='json',
707             ignore_content_type=False,
708             if_etag_match=None,
709             if_etag_not_match=None,
710             destination=None,
711             destination_account=None,
712             content_type=None,
713             content_encoding=None,
714             content_disposition=None,
715             permissions={},
716             public=False,
717             metadata={},
718             *args, **kwargs):
719         """ Full Pithos+ COPY at object level
720
721         --- request parameters ---
722
723         :param format: (string) json (default) or xml
724
725         :param ignore_content_type: (bool) Ignore the supplied Content-Type
726
727         --- request headers ---
728
729         :param if_etag_match: (string) if provided, return only results
730             with etag matching with this
731
732         :param if_etag_not_match: (string) if provided, return only results
733             with etag not matching with this
734
735         :param destination: (string) The destination path in the form
736             /<container>/<object>
737
738         :param destination_account: (string) The destination account to copy to
739
740         :param content_type: (string) The MIME content type of the object
741
742         :param content_encoding: (string) The encoding of the object
743
744         :param content_disposition: (string) Object presentation style
745
746         :param source_version: (string) The source version to copy from
747
748         :param permissions: (dict) Object permissions in the form
749             (all fields are optional)
750             { 'read':[user1, group1, user2, ...],
751             'write':['user3, group2, group3, ...] }
752
753         :param public: (bool) If true, Object is publicly accessible
754
755         :param metadata: (dict) Optional user defined metadata in the form
756             {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
757
758         :returns: ConnectionResponse
759         """
760
761         self._assert_container()
762
763         self.set_param('format', format, iff=format)
764         self.set_param('ignore_content_type', iff=ignore_content_type)
765
766         self.set_header('If-Match', if_etag_match)
767         self.set_header('If-None-Match', if_etag_not_match)
768         self.set_header('Destination', destination)
769         self.set_header('Destination-Account', destination_account)
770         self.set_header('Content-Type', content_type)
771         self.set_header('Content-Encoding', content_encoding)
772         self.set_header('Content-Disposition', content_disposition)
773         if permissions:
774             perms = ''
775             for perm_type, perm_list in permissions.items():
776                 if not perms:
777                     perms = ''  # Remove permissions
778                 if perm_list:
779                     perms += ';' if perms else ''
780                     perms += '%s=%s' % (perm_type, ','.join(perm_list))
781             self.set_header('X-Object-Sharing', perms)
782         self.set_header('X-Object-Public', public)
783         if metadata:
784             for key, val in metadata.items():
785                 self.set_header('X-Object-Meta-' + key, val)
786
787         path = path4url(self.account, self.container, object)
788         success = kwargs.pop('success', 201)
789         return self.move(path, *args, success=success, **kwargs)
790
791     def object_post(
792             self, obj,
793             format='json',
794             update=True,
795             if_etag_match=None,
796             if_etag_not_match=None,
797             content_length=None,
798             content_type=None,
799             content_range=None,
800             transfer_encoding=None,
801             content_encoding=None,
802             content_disposition=None,
803             source_object=None,
804             source_account=None,
805             source_version=None,
806             object_bytes=None,
807             manifest=None,
808             permissions={},
809             public=False,
810             metadata={},
811             *args, **kwargs):
812         """ Full Pithos+ POST at object level
813
814         --- request parameters ---
815
816         :param format: (string) json (default) or xml
817
818         :param update: (bool) Do not replace metadata
819
820         --- request headers ---
821
822         :param if_etag_match: (string) if provided, return only results
823             with etag matching with this
824
825         :param if_etag_not_match: (string) if provided, return only results
826             with etag not matching with this
827
828         :param content_length: (string) The size of the data written
829
830         :param content_type: (string) The MIME content type of the object
831
832         :param content_range: (string) The range of data supplied
833
834         :param transfer_encoding: (string) Set to chunked to specify
835             incremental uploading (if used, Content-Length is ignored)
836
837         :param content_encoding: (string) The encoding of the object
838
839         :param content_disposition: (string) Object presentation style
840
841         :param source_object: (string) Update with data from the object at
842             path /<container>/<object>
843
844         :param source_account: (string) The source account to update from
845
846         :param source_version: (string) The source version to copy from
847
848         :param object_bytes: (integer) The updated objects final size
849
850         :param manifest: (string) Object parts prefix as /<container>/<object>
851
852         :param permissions: (dict) Object permissions in the form (all fields
853             are optional)
854             { 'read':[user1, group1, user2, ...],
855             'write':['user3, group2, group3, ...] }
856
857         :param public: (bool) If true, Object is publicly accessible
858
859         :param metadata: (dict) Optional user defined metadata in the form
860             {'meta-key-1':'meta-value-1', 'meta-key-2':'meta-value-2', ...}
861
862         :returns: ConnectionResponse
863         """
864
865         self._assert_container()
866
867         self.set_param('format', format, iff=format)
868         self.set_param('update', iff=update)
869
870         self.set_header('If-Match', if_etag_match)
871         self.set_header('If-None-Match', if_etag_not_match)
872         self.set_header(
873             'Content-Length',
874             content_length,
875             iff=not transfer_encoding)
876         self.set_header('Content-Type', content_type)
877         self.set_header('Content-Range', content_range)
878         self.set_header('Transfer-Encoding', transfer_encoding)
879         self.set_header('Content-Encoding', content_encoding)
880         self.set_header('Content-Disposition', content_disposition)
881         self.set_header('X-Source-Object', source_object)
882         self.set_header('X-Source-Account', source_account)
883         self.set_header('X-Source-Version', source_version)
884         self.set_header('X-Object-Bytes', object_bytes)
885         self.set_header('X-Object-Manifest', manifest)
886         if permissions:
887             perms = ''
888             for perm_type, perm_list in permissions.items():
889                 if not perms:
890                     perms = ''  # Remove permissions
891                 if perm_list:
892                     perms += ';' if perms else ''
893                     perms += '%s=%s' % (perm_type, ','.join(perm_list))
894             self.set_header('X-Object-Sharing', perms)
895         self.set_header('X-Object-Public', public)
896         for key, val in metadata.items():
897             self.set_header('X-Object-Meta-' + key, val)
898
899         path = path4url(self.account, self.container, obj)
900         success = kwargs.pop('success', (202, 204))
901         return self.post(path, *args, success=success, **kwargs)
902
903     def object_delete(
904             self, object,
905             until=None, delimiter=None,
906             *args, **kwargs):
907         """ Full Pithos+ DELETE at object level
908
909         --- request parameters ---
910
911         :param until: (string) Optional timestamp
912
913         :returns: ConnectionResponse
914         """
915         self._assert_container()
916
917         self.set_param('until', until, iff=until)
918         self.set_param('delimiter', delimiter, iff=delimiter)
919
920         path = path4url(self.account, self.container, object)
921         success = kwargs.pop('success', 204)
922         return self.delete(path, *args, success=success, **kwargs)