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