1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
34 from httplib import HTTPConnection, HTTP
36 from xml.dom import minidom
44 ERROR_CODES = {304:'Not Modified',
49 411:'Length Required',
50 412:'Precondition Failed',
51 416:'Range Not Satisfiable',
52 422:'Unprocessable Entity',
53 503:'Service Unavailable'}
55 class Fault(Exception):
56 def __init__(self, data='', status=None):
57 if data == '' and status in ERROR_CODES.keys():
58 data = ERROR_CODES[status]
59 Exception.__init__(self, data)
64 def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
65 """`host` can also include a port, e.g '127.0.0.1:8000'."""
68 self.account = account
70 self.verbose = verbose or debug
74 def _req(self, method, path, body=None, headers={}, format='text', params={}):
75 full_path = '/%s%s?format=%s' % (self.api, path, format)
77 for k,v in params.items():
79 full_path = '%s&%s=%s' %(full_path, k, v)
81 full_path = '%s&%s=' %(full_path, k)
82 conn = HTTPConnection(self.host)
85 full_path = full_path.replace(' ', '%20')
88 for k,v in headers.items():
90 k = k.replace('_', '-')
93 kwargs['headers'] = headers
94 kwargs['headers']['X-Auth-Token'] = self.token
97 kwargs['headers'].setdefault('content-type', 'application/octet-stream')
98 kwargs['headers'].setdefault('content-length', len(body) if body else 0)
100 #print '#', method, full_path, kwargs
101 t1 = datetime.datetime.utcnow()
102 conn.request(method, full_path, **kwargs)
104 resp = conn.getresponse()
105 t2 = datetime.datetime.utcnow()
106 #print 'response time:', str(t2-t1)
107 headers = dict(resp.getheaders())
110 print '%d %s' % (resp.status, resp.reason)
111 for key, val in headers.items():
112 print '%s: %s' % (key.capitalize(), val)
115 length = resp.getheader('content-length', None)
116 data = resp.read(length)
121 if int(resp.status) in ERROR_CODES.keys():
122 raise Fault(data, int(resp.status))
124 #print '**', resp.status, headers, data, '\n'
125 return resp.status, headers, data
127 def delete(self, path, format='text', params={}):
128 return self._req('DELETE', path, format=format, params=params)
130 def get(self, path, format='text', headers={}, params={}):
131 return self._req('GET', path, headers=headers, format=format,
134 def head(self, path, format='text', params={}):
135 return self._req('HEAD', path, format=format, params=params)
137 def post(self, path, body=None, format='text', headers=None, params={}):
138 return self._req('POST', path, body, headers=headers, format=format,
141 def put(self, path, body=None, format='text', headers=None):
142 return self._req('PUT', path, body, headers=headers, format=format)
144 def _list(self, path, format='text', params={}, **headers):
145 status, headers, data = self.get(path, format=format, headers=headers,
148 data = json.loads(data) if data else ''
149 elif format == 'xml':
150 data = minidom.parseString(data)
152 data = data.strip().split('\n') if data else ''
155 def _get_metadata(self, path, prefix=None, params={}):
156 status, headers, data = self.head(path, params=params)
157 prefixlen = len(prefix) if prefix else 0
159 for key, val in headers.items():
160 if prefix and not key.startswith(prefix):
162 elif prefix and key.startswith(prefix):
163 key = key[prefixlen:]
167 def _filter(self, l, d):
169 filter out from l elements having the metadata values provided
173 if type(elem) == types.DictionaryType:
175 k = 'x_object_meta_%s' % key
176 if k in elem.keys() and elem[k] == d[key]:
181 class OOS_Client(Client):
182 """Openstack Object Storage Client"""
184 def _update_metadata(self, path, entity, **meta):
185 """adds new and updates the values of previously set metadata"""
186 ex_meta = self.retrieve_account_metadata(restricted=True)
189 prefix = 'x-%s-meta-' % entity
190 for k,v in ex_meta.items():
191 k = '%s%s' % (prefix, k)
193 return self.post(path, headers=headers)
195 def _reset_metadata(self, path, entity, **meta):
197 overwrites all user defined metadata
200 prefix = 'x-%s-meta-' % entity
201 for k,v in meta.items():
202 k = '%s%s' % (prefix, k)
204 return self.post(path, headers=headers)
206 def _delete_metadata(self, path, entity, meta=[]):
207 """delete previously set metadata"""
208 ex_meta = self.retrieve_account_metadata(restricted=True)
210 prefix = 'x-%s-meta-' % entity
211 for k in ex_meta.keys():
213 headers['%s%s' % (prefix, k)] = ex_meta[k]
214 return self.post(path, headers=headers)
216 # Storage Account Services
218 def list_containers(self, format='text', limit=None,
219 marker=None, params={}, account=None, **headers):
220 """lists containers"""
221 account = account or self.account
222 path = '/%s' % account
223 params.update({'limit':limit, 'marker':marker})
224 return self._list(path, format, params, **headers)
226 def retrieve_account_metadata(self, restricted=False, account=None, **params):
227 """returns the account metadata"""
228 account = account or self.account
229 path = '/%s' % account
230 prefix = 'x-account-meta-' if restricted else None
231 return self._get_metadata(path, prefix, params)
233 def update_account_metadata(self, account=None, **meta):
234 """updates the account metadata"""
235 account = account or self.account
236 path = '/%s' % account
237 return self._update_metadata(path, 'account', **meta)
239 def delete_account_metadata(self, meta=[], account=None):
240 """deletes the account metadata"""
241 account = account or self.account
242 path = '/%s' % account
243 return self._delete_metadata(path, 'account', meta)
245 def reset_account_metadata(self, account=None, **meta):
246 """resets account metadata"""
247 account = account or self.account
248 path = '/%s' % account
249 return self._reset_metadata(path, 'account', **meta)
251 # Storage Container Services
253 def _filter_trashed(self, l):
254 return self._filter(l, {'trash':'true'})
256 def list_objects(self, container, format='text',
257 limit=None, marker=None, prefix=None, delimiter=None,
258 path=None, include_trashed=False, params={}, account=None,
260 """returns a list with the container objects"""
261 account = account or self.account
262 params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
263 'delimiter':delimiter, 'path':path})
264 l = self._list('/%s/%s' % (account, container), format, params,
266 #TODO support filter trashed with xml also
267 if format != 'xml' and not include_trashed:
268 l = self._filter_trashed(l)
271 def create_container(self, container, account=None, **meta):
272 """creates a container"""
273 account = account or self.account
275 for k,v in meta.items():
276 headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
277 status, header, data = self.put('/%s/%s' % (account, container),
282 raise Fault(data, int(status))
285 def delete_container(self, container, params={}, account=None):
286 """deletes a container"""
287 account = account or self.account
288 return self.delete('/%s/%s' % (account, container), params=params)
290 def retrieve_container_metadata(self, container, restricted=False,
291 account=None, **params):
292 """returns the container metadata"""
293 account = account or self.account
294 prefix = 'x-container-meta-' if restricted else None
295 return self._get_metadata('/%s/%s' % (account, container), prefix,
298 def update_container_metadata(self, container, account=None, **meta):
299 """unpdates the container metadata"""
300 account = account or self.account
301 return self._update_metadata('/%s/%s' % (account, container),
304 def delete_container_metadata(self, container, meta=[], account=None):
305 """deletes the container metadata"""
306 account = account or self.account
307 path = '/%s/%s' % (account, container)
308 return self._delete_metadata(path, 'container', meta)
310 # Storage Object Services
312 def request_object(self, container, object, format='text', params={},
313 account=None, **headers):
314 """returns tuple containing the status, headers and data response for an object request"""
315 account = account or self.account
316 path = '/%s/%s/%s' % (account, container, object)
317 status, headers, data = self.get(path, format, headers, params)
318 return status, headers, data
320 def retrieve_object(self, container, object, format='text', params={},
321 account=None, **headers):
322 """returns an object's data"""
323 account = account or self.account
324 t = self.request_object(container, object, format, params, account,
328 data = json.loads(data) if data else ''
329 elif format == 'xml':
330 data = minidom.parseString(data)
333 def retrieve_object_hashmap(self, container, object, params={},
334 account=None, **headers):
335 """returns the hashmap representing object's data"""
337 for elem in ['self', 'container', 'object']:
339 data = self.retrieve_object(container, object, format='json', **args)
340 return data['hashes']
342 def create_directory_marker(self, container, object, account=None):
343 """creates a dierectory marker"""
344 account = account or self.account
346 raise Fault('Directory markers have to be nested in a container')
347 h = {'content_type':'application/directory'}
348 return self.create_zero_length_object(container, object, account=account,
351 def create_object(self, container, object, f=stdin, format='text', meta={},
352 etag=None, content_type=None, content_encoding=None,
353 content_disposition=None, account=None, **headers):
354 """creates a zero-length object"""
355 account = account or self.account
356 path = '/%s/%s/%s' % (account, container, object)
357 for k, v in headers.items():
361 l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
362 l = [elem for elem in l if eval(elem)]
364 headers.update({elem:eval(elem)})
365 headers.setdefault('content-type', 'application/octet-stream')
367 for k,v in meta.items():
368 headers['x-object-meta-%s' %k.strip()] = v.strip()
369 data = f.read() if f else None
370 return self.put(path, data, format, headers=headers)
372 def create_zero_length_object(self, container, object, meta={}, etag=None,
373 content_type=None, content_encoding=None,
374 content_disposition=None, account=None,
376 account = account or self.account
378 for elem in ['self', 'container', 'headers', 'account']:
381 return self.create_object(container, account=account, f=None, **args)
383 def update_object(self, container, object, f=stdin,
384 offset=None, meta={}, content_length=None,
385 content_type=None, content_encoding=None,
386 content_disposition=None, account=None, **headers):
387 account = account or self.account
388 path = '/%s/%s/%s' % (account, container, object)
389 for k, v in headers.items():
393 l = ['content_encoding', 'content_disposition', 'content_type',
395 l = [elem for elem in l if eval(elem)]
397 headers.update({elem:eval(elem)})
399 if 'content_range' not in headers.keys():
401 headers['content_range'] = 'bytes %s-/*' % offset
403 headers['content_range'] = 'bytes */*'
405 for k,v in meta.items():
406 headers['x-object-meta-%s' %k.strip()] = v.strip()
407 data = f.read() if f else None
408 return self.post(path, data, headers=headers)
410 def _change_obj_location(self, src_container, src_object, dst_container,
411 dst_object, remove=False, meta={}, account=None,
413 account = account or self.account
414 path = '/%s/%s/%s' % (account, dst_container, dst_object)
415 headers = {} if not headers else headers
416 for k, v in meta.items():
417 headers['x-object-meta-%s' % k] = v
419 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
421 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
422 headers['content_length'] = 0
423 return self.put(path, headers=headers)
425 def copy_object(self, src_container, src_object, dst_container, dst_object,
426 meta={}, account=None, **headers):
427 """copies an object"""
428 account = account or self.account
429 return self._change_obj_location(src_container, src_object,
430 dst_container, dst_object, account=account,
431 remove=False, meta=meta, **headers)
433 def move_object(self, src_container, src_object, dst_container,
434 dst_object, meta={}, account=None,
436 """moves an object"""
437 account = account or self.account
438 return self._change_obj_location(src_container, src_object,
439 dst_container, dst_object,
440 account=account, remove=True,
441 meta=meta, **headers)
443 def delete_object(self, container, object, params={}, account=None):
444 """deletes an object"""
445 account = account or self.account
446 return self.delete('/%s/%s/%s' % (account, container, object),
449 def retrieve_object_metadata(self, container, object, restricted=False,
450 version=None, account=None):
452 set restricted to True to get only user defined metadata
454 account = account or self.account
455 path = '/%s/%s/%s' % (account, container, object)
456 prefix = 'x-object-meta-' if restricted else None
457 params = {'version':version} if version else {}
458 return self._get_metadata(path, prefix, params=params)
460 def update_object_metadata(self, container, object, account=None,
463 updates object's metadata
465 account = account or self.account
466 path = '/%s/%s/%s' % (account, container, object)
467 return self._update_metadata(path, 'object', **meta)
469 def delete_object_metadata(self, container, object, meta=[], account=None):
471 deletes object's metadata
473 account = account or self.account
474 path = '/%s/%s' % (account, container, object)
475 return self._delete_metadata(path, 'object', meta)
477 class Pithos_Client(OOS_Client):
478 """Pithos Storage Client. Extends OOS_Client"""
480 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
482 """perfomrs a chunked request"""
483 http = HTTPConnection(self.host)
486 path = '/%s%s' % (self.api, path)
487 http.putrequest(method, path)
488 http.putheader('x-auth-token', self.token)
489 http.putheader('content-type', 'application/octet-stream')
490 http.putheader('transfer-encoding', 'chunked')
492 for header,value in headers.items():
493 http.putheader(header, value)
501 block = f.read(blocksize)
504 data = '%x\r\n%s\r\n' % (len(block), block)
518 resp = http.getresponse()
520 headers = dict(resp.getheaders())
523 print '%d %s' % (resp.status, resp.reason)
524 for key, val in headers.items():
525 print '%s: %s' % (key.capitalize(), val)
528 length = resp.getheader('Content-length', None)
529 data = resp.read(length)
534 if int(resp.status) in ERROR_CODES.keys():
535 raise Fault(data, int(resp.status))
537 #print '*', resp.status, headers, data
538 return resp.status, headers, data
540 def _update_metadata(self, path, entity, **meta):
542 adds new and updates the values of previously set metadata
544 params = {'update':None}
546 prefix = 'x-%s-meta-' % entity
547 for k,v in meta.items():
548 k = '%s%s' % (prefix, k)
550 return self.post(path, headers=headers, params=params)
552 def _delete_metadata(self, path, entity, meta=[]):
554 delete previously set metadata
556 params = {'update':None}
558 prefix = 'x-%s-meta-' % entity
560 headers['%s%s' % (prefix, m)] = ''
561 return self.post(path, headers=headers, params=params)
563 # Storage Account Services
565 def list_containers(self, format='text', if_modified_since=None,
566 if_unmodified_since=None, limit=None, marker=None,
567 shared=False, until=None, account=None):
568 """returns a list with the account containers"""
569 account = account or self.account
570 params = {'until':until} if until else {}
572 params['shared'] = None
573 headers = {'if-modified-since':if_modified_since,
574 'if-unmodified-since':if_unmodified_since}
575 return OOS_Client.list_containers(self, account=account, format=format,
576 limit=limit, marker=marker,
577 params=params, **headers)
579 def retrieve_account_metadata(self, restricted=False, until=None,
581 """returns the account metadata"""
582 account = account or self.account
583 params = {'until':until} if until else {}
584 return OOS_Client.retrieve_account_metadata(self, account=account,
585 restricted=restricted,
588 def set_account_groups(self, account=None, **groups):
589 """create account groups"""
590 account = account or self.account
591 path = '/%s' % account
593 for k, v in groups.items():
594 headers['x-account-group-%s' % k] = v
595 params = {'update':None}
596 return self.post(path, headers=headers, params=params)
598 def retrieve_account_groups(self, account=None):
599 """returns the account groups"""
600 account = account or self.account
601 meta = self.retrieve_account_metadata(account=account)
602 prefix = 'x-account-group-'
603 prefixlen = len(prefix)
605 for key, val in meta.items():
606 if prefix and not key.startswith(prefix):
608 elif prefix and key.startswith(prefix):
609 key = key[prefixlen:]
613 def unset_account_groups(self, groups=[], account=None):
614 """delete account groups"""
615 account = account or self.account
616 path = '/%s' % account
619 headers['x-account-group-%s' % elem] = ''
620 params = {'update':None}
621 return self.post(path, headers=headers, params=params)
623 def reset_account_groups(self, account=None, **groups):
624 """overrides account groups"""
625 account = account or self.account
626 path = '/%s' % account
628 for k, v in groups.items():
630 headers['x-account-group-%s' % k] = v
631 meta = self.retrieve_account_metadata(restricted=True)
632 prefix = 'x-account-meta-'
633 for k,v in meta.items():
634 k = '%s%s' % (prefix, k)
636 return self.post(path, headers=headers)
638 # Storage Container Services
640 def list_objects(self, container, format='text',
641 limit=None, marker=None, prefix=None, delimiter=None,
642 path=None, shared=False, include_trashed=False, params={},
643 if_modified_since=None, if_unmodified_since=None, meta='',
644 until=None, account=None):
645 """returns a list with the container objects"""
646 account = account or self.account
647 params = {'until':until, 'meta':meta}
649 params['shared'] = None
651 for elem in ['self', 'container', 'params', 'until', 'meta']:
653 return OOS_Client.list_objects(self, container, params=params, **args)
655 def retrieve_container_metadata(self, container, restricted=False,
656 until=None, account=None):
657 """returns container's metadata"""
658 account = account or self.account
659 params = {'until':until} if until else {}
660 return OOS_Client.retrieve_container_metadata(self, container,
662 restricted=restricted,
665 def set_container_policies(self, container, account=None,
667 """sets containers policies"""
668 account = account or self.account
669 path = '/%s/%s' % (account, container)
671 for key, val in policies.items():
672 headers['x-container-policy-%s' % key] = val
673 return self.post(path, headers=headers)
675 def delete_container(self, container, until=None, account=None):
676 """deletes a container or the container history until the date provided"""
677 account = account or self.account
678 params = {'until':until} if until else {}
679 return OOS_Client.delete_container(self, container, account=account,
682 # Storage Object Services
684 def retrieve_object(self, container, object, params={}, format='text',
685 range=None, if_range=None,
686 if_match=None, if_none_match=None,
687 if_modified_since=None, if_unmodified_since=None,
688 account=None, **headers):
689 """returns an object"""
690 account = account or self.account
692 l = ['range', 'if_range', 'if_match', 'if_none_match',
693 'if_modified_since', 'if_unmodified_since']
694 l = [elem for elem in l if eval(elem)]
696 headers.update({elem:eval(elem)})
697 return OOS_Client.retrieve_object(self, container, object,
698 account=account, format=format,
699 params=params, **headers)
701 def retrieve_object_version(self, container, object, version,
702 format='text', range=None, if_range=None,
703 if_match=None, if_none_match=None,
704 if_modified_since=None, if_unmodified_since=None,
706 """returns a specific object version"""
707 account = account or self.account
709 l = ['self', 'container', 'object']
712 params = {'version':version}
713 return self.retrieve_object(container, object, params=params, **args)
715 def retrieve_object_versionlist(self, container, object, range=None,
716 if_range=None, if_match=None,
717 if_none_match=None, if_modified_since=None,
718 if_unmodified_since=None, account=None):
719 """returns the object version list"""
720 account = account or self.account
722 l = ['self', 'container', 'object']
726 return self.retrieve_object_version(container, object, version='list',
727 format='json', **args)
729 def create_zero_length_object(self, container, object,
730 meta={}, etag=None, content_type=None,
731 content_encoding=None,
732 content_disposition=None,
733 x_object_manifest=None, x_object_sharing=None,
734 x_object_public=None, account=None):
735 """createas a zero length object"""
736 account = account or self.account
738 for elem in ['self', 'container', 'object']:
740 return OOS_Client.create_zero_length_object(self, container, object,
743 def create_object(self, container, object, f=stdin,
744 meta={}, etag=None, content_type=None,
745 content_encoding=None, content_disposition=None,
746 x_object_manifest=None, x_object_sharing=None,
747 x_object_public=None, account=None):
748 """creates an object"""
749 account = account or self.account
751 for elem in ['self', 'container', 'object']:
753 return OOS_Client.create_object(self, container, object, **args)
755 def create_object_using_chunks(self, container, object,
756 f=stdin, blocksize=1024, meta={}, etag=None,
757 content_type=None, content_encoding=None,
758 content_disposition=None,
759 x_object_sharing=None, x_object_manifest=None,
760 x_object_public=None, account=None):
761 """creates an object (incremental upload)"""
762 account = account or self.account
763 path = '/%s/%s/%s' % (account, container, object)
765 l = ['etag', 'content_type', 'content_encoding', 'content_disposition',
766 'x_object_sharing', 'x_object_manifest', 'x_object_public']
767 l = [elem for elem in l if eval(elem)]
769 headers.update({elem:eval(elem)})
770 headers.setdefault('content-type', 'application/octet-stream')
772 for k,v in meta.items():
774 headers['x-object-meta-%s' %k.strip()] = v
776 return self._chunked_transfer(path, 'PUT', f, headers=headers,
779 def create_object_by_hashmap(container, object, f=stdin, format='json',
780 meta={}, etag=None, content_encoding=None,
781 content_disposition=None, content_type=None,
782 x_object_sharing=None, x_object_manifest=None,
783 x_object_public = None, account=None):
784 """creates an object by uploading hashes representing data instead of data"""
785 account = account or self.account
787 for elem in ['self', 'container', 'object']:
790 data = f.read() if f else None
791 if data and format == 'json':
794 data = json.dumps(data)
796 raise Fault('Invalid formatting')
799 return self.create_object(container, object, **args)
801 def create_manifestation(self, container, object, manifest, account=None):
802 """creates a manifestation"""
803 account = account or self.account
804 headers={'x_object_manifest':manifest}
805 return self.create_object(container, object, f=None, account=account,
808 def update_object(self, container, object, f=stdin,
809 offset=None, meta={}, content_length=None,
810 content_type=None, content_range=None,
811 content_encoding=None, content_disposition=None,
812 x_object_bytes=None, x_object_manifest=None,
813 x_object_sharing=None, x_object_public=None,
814 x_source_object=None, account=None):
815 """updates an object"""
816 account = account or self.account
818 for elem in ['self', 'container', 'object']:
820 return OOS_Client.update_object(self, container, object, **args)
822 def update_object_using_chunks(self, container, object, f=stdin,
823 blocksize=1024, offset=None, meta={},
824 content_type=None, content_encoding=None,
825 content_disposition=None, x_object_bytes=None,
826 x_object_manifest=None, x_object_sharing=None,
827 x_object_public=None, account=None):
828 """updates an object (incremental upload)"""
829 account = account or self.account
830 path = '/%s/%s/%s' % (account, container, object)
832 l = ['content_type', 'content_encoding', 'content_disposition',
833 'x_object_bytes', 'x_object_manifest', 'x_object_sharing',
835 l = [elem for elem in l if eval(elem)]
837 headers.update({elem:eval(elem)})
840 headers['content_range'] = 'bytes %s-/*' % offset
842 headers['content_range'] = 'bytes */*'
844 for k,v in meta.items():
846 headers['x-object-meta-%s' %k.strip()] = v
847 return self._chunked_transfer(path, 'POST', f, headers=headers,
850 def update_from_other_source(self, container, object, source,
851 offset=None, meta={}, content_range=None,
852 content_encoding=None, content_disposition=None,
853 x_object_bytes=None, x_object_manifest=None,
854 x_object_sharing=None, x_object_public=None, account=None):
855 """updates an object"""
856 account = account or self.account
858 for elem in ['self', 'container', 'object', 'source']:
861 args['x_source_object'] = source
862 return self.update_object(container, object, f=None, **args)
864 def delete_object(self, container, object, until=None, account=None):
865 """deletes an object or the object history until the date provided"""
866 account = account or self.account
867 params = {'until':until} if until else {}
868 return OOS_Client.delete_object(self, container, object, params, account)
870 def trash_object(self, container, object):
871 """trashes an object"""
872 account = account or self.account
873 path = '/%s/%s' % (container, object)
874 meta = {'trash':'true'}
875 return self._update_metadata(path, 'object', **meta)
877 def restore_object(self, container, object, account=None):
878 """restores a trashed object"""
879 account = account or self.account
880 return self.delete_object_metadata(container, object, account, ['trash'])
882 def publish_object(self, container, object, account=None):
883 """sets a previously created object publicly accessible"""
884 account = account or self.account
885 path = '/%s/%s/%s' % (account, container, object)
887 headers['x_object_public'] = True
888 params = {'update':None}
889 return self.post(path, headers=headers, params=params)
891 def unpublish_object(self, container, object, account=None):
892 """unpublish an object"""
893 account = account or self.account
894 path = '/%s/%s/%s' % (account, container, object)
896 headers['x_object_public'] = False
897 params = {'update':None}
898 return self.post(path, headers=headers, params=params)
900 def _change_obj_location(self, src_container, src_object, dst_container,
901 dst_object, remove=False,
902 meta={}, account=None, **headers):
903 account = account or self.account
904 path = '/%s/%s/%s' % (account, dst_container, dst_object)
905 headers = {} if not headers else headers
906 for k, v in meta.items():
907 headers['x-object-meta-%s' % k] = v
909 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
911 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
912 headers['content_length'] = 0
913 return self.put(path, headers=headers)
915 def copy_object(self, src_container, src_object, dst_container, dst_object,
916 meta={}, public=False, version=None, account=None):
917 """copies an object"""
918 account = account or self.account
920 headers['x_object_public'] = public
922 headers['x_object_version'] = version
923 return OOS_Client.copy_object(self, src_container, src_object,
924 dst_container, dst_object, meta=meta,
925 account=account,**headers)
927 def move_object(self, src_container, src_object, dst_container,
928 dst_object, meta={}, public=False, version=None,
930 """moves an object"""
932 headers['x_object_public'] = public
934 headers['x_object_version'] = version
935 return OOS_Client.move_object(self, src_container, src_object,
936 dst_container, dst_object, meta=meta,
937 account=account, **headers)
939 def list_shared_by_others(self, limit=None, marker=None, format='text'):
940 """lists other accounts that share objects to the user"""
941 l = ['limit', 'marker']
943 for elem in [elem for elem in l if eval(elem)]:
944 params[elem] = eval(elem)
945 return self._list('', format, params)
947 def share_object(self, container, object, l, read=True):
948 """gives access(read by default) to an object to a user/group list"""
949 action = 'read' if read else 'write'
950 sharing = '%s=%s' % (action, ','.join(l))
951 self.update_object(container, object, f=None, x_object_sharing=sharing)
953 def _encode_headers(headers):
955 for k, v in headers.items():
957 if v and type(v) == types.StringType:
958 v = urllib.quote(v, '/=,-* :"')