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
37 from StringIO import StringIO
45 ERROR_CODES = {304:'Not Modified',
50 411:'Length Required',
51 412:'Precondition Failed',
52 416:'Range Not Satisfiable',
53 422:'Unprocessable Entity',
54 503:'Service Unavailable'}
56 class Fault(Exception):
57 def __init__(self, data='', status=None):
58 if data == '' and status in ERROR_CODES.keys():
59 data = ERROR_CODES[status]
60 Exception.__init__(self, data)
65 def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
66 """`host` can also include a port, e.g '127.0.0.1:8000'."""
69 self.account = account
71 self.verbose = verbose or debug
75 def _req(self, method, path, body=None, headers={}, format='text', params={}):
76 slash = '/' if self.api else ''
77 full_path = '%s%s%s?format=%s' % (slash, self.api, path, format)
79 for k,v in params.items():
81 full_path = '%s&%s=%s' %(full_path, k, v)
83 full_path = '%s&%s=' %(full_path, k)
84 conn = HTTPConnection(self.host)
87 full_path = full_path.replace(' ', '%20')
90 for k,v in headers.items():
92 k = k.replace('_', '-')
95 kwargs['headers'] = headers
96 kwargs['headers']['X-Auth-Token'] = self.token
99 kwargs['headers'].setdefault('content-type', 'application/octet-stream')
100 kwargs['headers'].setdefault('content-length', len(body) if body else 0)
102 #print '#', method, full_path, kwargs
103 t1 = datetime.datetime.utcnow()
104 conn.request(method, full_path, **kwargs)
106 resp = conn.getresponse()
107 t2 = datetime.datetime.utcnow()
108 #print 'response time:', str(t2-t1)
109 headers = dict(resp.getheaders())
112 print '%d %s' % (resp.status, resp.reason)
113 for key, val in headers.items():
114 print '%s: %s' % (key.capitalize(), val)
117 length = resp.getheader('content-length', None)
118 data = resp.read(length)
123 if int(resp.status) in ERROR_CODES.keys():
124 raise Fault(data, int(resp.status))
126 #print '**', resp.status, headers, data, '\n'
127 return resp.status, headers, data
129 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
130 blocksize=1024, params={}):
131 """perfomrs a chunked request"""
132 http = HTTPConnection(self.host)
135 full_path = '/%s%s?' % (self.api, path)
137 for k,v in params.items():
139 full_path = '%s&%s=%s' %(full_path, k, v)
141 full_path = '%s&%s=' %(full_path, k)
142 http.putrequest(method, full_path)
143 http.putheader('x-auth-token', self.token)
144 http.putheader('content-type', 'application/octet-stream')
145 http.putheader('transfer-encoding', 'chunked')
147 for header,value in headers.items():
148 http.putheader(header, value)
156 block = f.read(blocksize)
159 data = '%x\r\n%s\r\n' % (len(block), block)
173 resp = http.getresponse()
175 headers = dict(resp.getheaders())
178 print '%d %s' % (resp.status, resp.reason)
179 for key, val in headers.items():
180 print '%s: %s' % (key.capitalize(), val)
183 length = resp.getheader('Content-length', None)
184 data = resp.read(length)
189 if int(resp.status) in ERROR_CODES.keys():
190 raise Fault(data, int(resp.status))
192 #print '*', resp.status, headers, data
193 return resp.status, headers, data
195 def delete(self, path, format='text', params={}):
196 return self._req('DELETE', path, format=format, params=params)
198 def get(self, path, format='text', headers={}, params={}):
199 return self._req('GET', path, headers=headers, format=format,
202 def head(self, path, format='text', params={}):
203 return self._req('HEAD', path, format=format, params=params)
205 def post(self, path, body=None, format='text', headers=None, params={}):
206 return self._req('POST', path, body, headers=headers, format=format,
209 def put(self, path, body=None, format='text', headers=None, params={}):
210 return self._req('PUT', path, body, headers=headers, format=format,
213 def _list(self, path, format='text', params={}, **headers):
214 status, headers, data = self.get(path, format=format, headers=headers,
217 data = json.loads(data) if data else ''
218 elif format == 'xml':
219 data = minidom.parseString(data)
221 data = data.strip().split('\n') if data else ''
224 def _get_metadata(self, path, prefix=None, params={}):
225 status, headers, data = self.head(path, params=params)
226 prefixlen = len(prefix) if prefix else 0
228 for key, val in headers.items():
229 if prefix and not key.startswith(prefix):
231 elif prefix and key.startswith(prefix):
232 key = key[prefixlen:]
236 def _filter(self, l, d):
238 filter out from l elements having the metadata values provided
242 if type(elem) == types.DictionaryType:
244 k = 'x_object_meta_%s' % key
245 if k in elem.keys() and elem[k] == d[key]:
250 class OOS_Client(Client):
251 """Openstack Object Storage Client"""
253 def _update_metadata(self, path, entity, **meta):
254 """adds new and updates the values of previously set metadata"""
255 ex_meta = self.retrieve_account_metadata(restricted=True)
258 prefix = 'x-%s-meta-' % entity
259 for k,v in ex_meta.items():
260 k = '%s%s' % (prefix, k)
262 return self.post(path, headers=headers)
264 def _reset_metadata(self, path, entity, **meta):
266 overwrites all user defined metadata
269 prefix = 'x-%s-meta-' % entity
270 for k,v in meta.items():
271 k = '%s%s' % (prefix, k)
273 return self.post(path, headers=headers)
275 def _delete_metadata(self, path, entity, meta=[]):
276 """delete previously set metadata"""
277 ex_meta = self.retrieve_account_metadata(restricted=True)
279 prefix = 'x-%s-meta-' % entity
280 for k in ex_meta.keys():
282 headers['%s%s' % (prefix, k)] = ex_meta[k]
283 return self.post(path, headers=headers)
285 # Storage Account Services
287 def list_containers(self, format='text', limit=None,
288 marker=None, params={}, account=None, **headers):
289 """lists containers"""
290 account = account or self.account
291 path = '/%s' % account
292 params.update({'limit':limit, 'marker':marker})
293 return self._list(path, format, params, **headers)
295 def retrieve_account_metadata(self, restricted=False, account=None, **params):
296 """returns the account metadata"""
297 account = account or self.account
298 path = '/%s' % account
299 prefix = 'x-account-meta-' if restricted else None
300 return self._get_metadata(path, prefix, params)
302 def update_account_metadata(self, account=None, **meta):
303 """updates the account metadata"""
304 account = account or self.account
305 path = '/%s' % account
306 return self._update_metadata(path, 'account', **meta)
308 def delete_account_metadata(self, meta=[], account=None):
309 """deletes the account metadata"""
310 account = account or self.account
311 path = '/%s' % account
312 return self._delete_metadata(path, 'account', meta)
314 def reset_account_metadata(self, account=None, **meta):
315 """resets account metadata"""
316 account = account or self.account
317 path = '/%s' % account
318 return self._reset_metadata(path, 'account', **meta)
320 # Storage Container Services
322 def _filter_trashed(self, l):
323 return self._filter(l, {'trash':'true'})
325 def list_objects(self, container, format='text',
326 limit=None, marker=None, prefix=None, delimiter=None,
327 path=None, include_trashed=False, params={}, account=None,
329 """returns a list with the container objects"""
330 account = account or self.account
331 params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
332 'delimiter':delimiter, 'path':path})
333 l = self._list('/%s/%s' % (account, container), format, params,
335 #TODO support filter trashed with xml also
336 if format != 'xml' and not include_trashed:
337 l = self._filter_trashed(l)
340 def create_container(self, container, account=None, **meta):
341 """creates a container"""
342 account = account or self.account
344 for k,v in meta.items():
345 headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
346 status, header, data = self.put('/%s/%s' % (account, container),
351 raise Fault(data, int(status))
354 def delete_container(self, container, params={}, account=None):
355 """deletes a container"""
356 account = account or self.account
357 return self.delete('/%s/%s' % (account, container), params=params)
359 def retrieve_container_metadata(self, container, restricted=False,
360 account=None, **params):
361 """returns the container metadata"""
362 account = account or self.account
363 prefix = 'x-container-meta-' if restricted else None
364 return self._get_metadata('/%s/%s' % (account, container), prefix,
367 def update_container_metadata(self, container, account=None, **meta):
368 """unpdates the container metadata"""
369 account = account or self.account
370 return self._update_metadata('/%s/%s' % (account, container),
373 def delete_container_metadata(self, container, meta=[], account=None):
374 """deletes the container metadata"""
375 account = account or self.account
376 path = '/%s/%s' % (account, container)
377 return self._delete_metadata(path, 'container', meta)
379 # Storage Object Services
381 def request_object(self, container, object, format='text', params={},
382 account=None, **headers):
383 """returns tuple containing the status, headers and data response for an object request"""
384 account = account or self.account
385 path = '/%s/%s/%s' % (account, container, object)
386 status, headers, data = self.get(path, format, headers, params)
387 return status, headers, data
389 def retrieve_object(self, container, object, format='text', params={},
390 account=None, **headers):
391 """returns an object's data"""
392 account = account or self.account
393 t = self.request_object(container, object, format, params, account,
397 data = json.loads(data) if data else ''
398 elif format == 'xml':
399 data = minidom.parseString(data)
402 def retrieve_object_hashmap(self, container, object, params={},
403 account=None, **headers):
404 """returns the hashmap representing object's data"""
405 args = locals().copy()
406 for elem in ['self', 'container', 'object']:
408 data = self.retrieve_object(container, object, format='json', **args)
409 return data['hashes']
411 def create_directory_marker(self, container, object, account=None):
412 """creates a dierectory marker"""
413 account = account or self.account
415 raise Fault('Directory markers have to be nested in a container')
416 h = {'content_type':'application/directory'}
417 return self.create_zero_length_object(container, object, account=account,
420 def create_object(self, container, object, f=stdin, format='text', meta={},
421 params={}, etag=None, content_type=None, content_encoding=None,
422 content_disposition=None, account=None, **headers):
423 """creates a zero-length object"""
424 account = account or self.account
425 path = '/%s/%s/%s' % (account, container, object)
426 for k, v in headers.items():
430 l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
431 l = [elem for elem in l if eval(elem)]
433 headers.update({elem:eval(elem)})
434 headers.setdefault('content-type', 'application/octet-stream')
436 for k,v in meta.items():
437 headers['x-object-meta-%s' %k.strip()] = v.strip()
438 data = f.read() if f else None
439 return self.put(path, data, format, headers=headers, params=params)
441 def create_zero_length_object(self, container, object, meta={}, etag=None,
442 content_type=None, content_encoding=None,
443 content_disposition=None, account=None,
445 account = account or self.account
446 args = locals().copy()
447 for elem in ['self', 'container', 'headers', 'account']:
450 return self.create_object(container, account=account, f=None, **args)
452 def update_object(self, container, object, f=stdin,
453 offset=None, meta={}, params={}, content_length=None,
454 content_type=None, content_encoding=None,
455 content_disposition=None, account=None, **headers):
456 account = account or self.account
457 path = '/%s/%s/%s' % (account, container, object)
458 for k, v in headers.items():
462 l = ['content_encoding', 'content_disposition', 'content_type',
464 l = [elem for elem in l if eval(elem)]
466 headers.update({elem:eval(elem)})
468 if 'content_range' not in headers.keys():
470 headers['content_range'] = 'bytes %s-/*' % offset
472 headers['content_range'] = 'bytes */*'
474 for k,v in meta.items():
475 headers['x-object-meta-%s' %k.strip()] = v.strip()
476 data = f.read() if f else None
477 return self.post(path, data, headers=headers, params=params)
479 def update_object_using_chunks(self, container, object, f=stdin,
480 blocksize=1024, offset=None, meta={},
481 params={}, content_type=None, content_encoding=None,
482 content_disposition=None, account=None, **headers):
483 """updates an object (incremental upload)"""
484 account = account or self.account
485 path = '/%s/%s/%s' % (account, container, object)
486 headers = headers if not headers else {}
487 l = ['content_type', 'content_encoding', 'content_disposition']
488 l = [elem for elem in l if eval(elem)]
490 headers.update({elem:eval(elem)})
493 headers['content_range'] = 'bytes %s-/*' % offset
495 headers['content_range'] = 'bytes */*'
497 for k,v in meta.items():
499 headers['x-object-meta-%s' %k.strip()] = v
500 return self._chunked_transfer(path, 'POST', f, headers=headers,
501 blocksize=blocksize, params=params)
503 def _change_obj_location(self, src_container, src_object, dst_container,
504 dst_object, remove=False, meta={}, account=None,
505 content_type=None, **headers):
506 account = account or self.account
507 path = '/%s/%s/%s' % (account, dst_container, dst_object)
508 headers = {} if not headers else headers
509 for k, v in meta.items():
510 headers['x-object-meta-%s' % k] = v
512 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
514 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
515 headers['content_length'] = 0
517 headers['content_type'] = content_type
518 return self.put(path, headers=headers)
520 def copy_object(self, src_container, src_object, dst_container, dst_object,
521 meta={}, account=None, content_type=None, **headers):
522 """copies an object"""
523 account = account or self.account
524 return self._change_obj_location(src_container, src_object,
525 dst_container, dst_object, account=account,
526 remove=False, meta=meta,
527 content_type=content_type, **headers)
529 def move_object(self, src_container, src_object, dst_container,
530 dst_object, meta={}, account=None,
531 content_type=None, **headers):
532 """moves an object"""
533 account = account or self.account
534 return self._change_obj_location(src_container, src_object,
535 dst_container, dst_object,
536 account=account, remove=True,
537 meta=meta, content_type=content_type,
540 def delete_object(self, container, object, params={}, account=None):
541 """deletes an object"""
542 account = account or self.account
543 return self.delete('/%s/%s/%s' % (account, container, object),
546 def retrieve_object_metadata(self, container, object, restricted=False,
547 version=None, account=None):
549 set restricted to True to get only user defined metadata
551 account = account or self.account
552 path = '/%s/%s/%s' % (account, container, object)
553 prefix = 'x-object-meta-' if restricted else None
554 params = {'version':version} if version else {}
555 return self._get_metadata(path, prefix, params=params)
557 def update_object_metadata(self, container, object, account=None,
560 updates object's metadata
562 account = account or self.account
563 path = '/%s/%s/%s' % (account, container, object)
564 return self._update_metadata(path, 'object', **meta)
566 def delete_object_metadata(self, container, object, meta=[], account=None):
568 deletes object's metadata
570 account = account or self.account
571 path = '/%s/%s' % (account, container, object)
572 return self._delete_metadata(path, 'object', meta)
574 class Pithos_Client(OOS_Client):
575 """Pithos Storage Client. Extends OOS_Client"""
577 def _update_metadata(self, path, entity, **meta):
579 adds new and updates the values of previously set metadata
581 params = {'update':None}
583 prefix = 'x-%s-meta-' % entity
584 for k,v in meta.items():
585 k = '%s%s' % (prefix, k)
587 return self.post(path, headers=headers, params=params)
589 def _delete_metadata(self, path, entity, meta=[]):
591 delete previously set metadata
593 params = {'update':None}
595 prefix = 'x-%s-meta-' % entity
597 headers['%s%s' % (prefix, m)] = ''
598 return self.post(path, headers=headers, params=params)
600 # Storage Account Services
602 def list_containers(self, format='text', if_modified_since=None,
603 if_unmodified_since=None, limit=None, marker=None,
604 shared=False, until=None, account=None):
605 """returns a list with the account containers"""
606 account = account or self.account
607 params = {'until':until} if until else {}
609 params['shared'] = None
610 headers = {'if-modified-since':if_modified_since,
611 'if-unmodified-since':if_unmodified_since}
612 return OOS_Client.list_containers(self, account=account, format=format,
613 limit=limit, marker=marker,
614 params=params, **headers)
616 def retrieve_account_metadata(self, restricted=False, until=None,
618 """returns the account metadata"""
619 account = account or self.account
620 params = {'until':until} if until else {}
621 return OOS_Client.retrieve_account_metadata(self, account=account,
622 restricted=restricted,
625 def set_account_groups(self, account=None, **groups):
626 """create account groups"""
627 account = account or self.account
628 path = '/%s' % account
630 for k, v in groups.items():
631 headers['x-account-group-%s' % k] = v
632 params = {'update':None}
633 return self.post(path, headers=headers, params=params)
635 def retrieve_account_groups(self, account=None):
636 """returns the account groups"""
637 account = account or self.account
638 meta = self.retrieve_account_metadata(account=account)
639 prefix = 'x-account-group-'
640 prefixlen = len(prefix)
642 for key, val in meta.items():
643 if prefix and not key.startswith(prefix):
645 elif prefix and key.startswith(prefix):
646 key = key[prefixlen:]
650 def unset_account_groups(self, groups=[], account=None):
651 """delete account groups"""
652 account = account or self.account
653 path = '/%s' % account
656 headers['x-account-group-%s' % elem] = ''
657 params = {'update':None}
658 return self.post(path, headers=headers, params=params)
660 def reset_account_groups(self, account=None, **groups):
661 """overrides account groups"""
662 account = account or self.account
663 path = '/%s' % account
665 for k, v in groups.items():
667 headers['x-account-group-%s' % k] = v
668 meta = self.retrieve_account_metadata(restricted=True)
669 prefix = 'x-account-meta-'
670 for k,v in meta.items():
671 k = '%s%s' % (prefix, k)
673 return self.post(path, headers=headers)
675 # Storage Container Services
677 def list_objects(self, container, format='text',
678 limit=None, marker=None, prefix=None, delimiter=None,
679 path=None, shared=False, include_trashed=False, params={},
680 if_modified_since=None, if_unmodified_since=None, meta='',
681 until=None, account=None):
682 """returns a list with the container objects"""
683 account = account or self.account
684 params = {'until':until, 'meta':meta}
686 params['shared'] = None
687 args = locals().copy()
688 for elem in ['self', 'container', 'params', 'until', 'meta']:
690 return OOS_Client.list_objects(self, container, params=params, **args)
692 def retrieve_container_metadata(self, container, restricted=False,
693 until=None, account=None):
694 """returns container's metadata"""
695 account = account or self.account
696 params = {'until':until} if until else {}
697 return OOS_Client.retrieve_container_metadata(self, container,
699 restricted=restricted,
702 def set_container_policies(self, container, account=None,
704 """sets containers policies"""
705 account = account or self.account
706 path = '/%s/%s' % (account, container)
708 for key, val in policies.items():
709 headers['x-container-policy-%s' % key] = val
710 return self.post(path, headers=headers)
712 def delete_container(self, container, until=None, account=None):
713 """deletes a container or the container history until the date provided"""
714 account = account or self.account
715 params = {'until':until} if until else {}
716 return OOS_Client.delete_container(self, container, account=account,
719 # Storage Object Services
721 def retrieve_object(self, container, object, params={}, format='text',
722 range=None, if_range=None,
723 if_match=None, if_none_match=None,
724 if_modified_since=None, if_unmodified_since=None,
725 account=None, **headers):
726 """returns an object"""
727 account = account or self.account
729 l = ['range', 'if_range', 'if_match', 'if_none_match',
730 'if_modified_since', 'if_unmodified_since']
731 l = [elem for elem in l if eval(elem)]
733 headers.update({elem:eval(elem)})
735 params['hashmap'] = None
736 return OOS_Client.retrieve_object(self, container, object,
737 account=account, format=format,
738 params=params, **headers)
740 def retrieve_object_version(self, container, object, version,
741 format='text', range=None, if_range=None,
742 if_match=None, if_none_match=None,
743 if_modified_since=None, if_unmodified_since=None,
745 """returns a specific object version"""
746 account = account or self.account
747 args = locals().copy()
748 l = ['self', 'container', 'object']
751 params = {'version':version}
752 return self.retrieve_object(container, object, params=params, **args)
754 def retrieve_object_versionlist(self, container, object, range=None,
755 if_range=None, if_match=None,
756 if_none_match=None, if_modified_since=None,
757 if_unmodified_since=None, account=None):
758 """returns the object version list"""
759 account = account or self.account
760 args = locals().copy()
761 l = ['self', 'container', 'object']
765 return self.retrieve_object_version(container, object, version='list',
766 format='json', **args)
768 def create_zero_length_object(self, container, object,
769 meta={}, etag=None, content_type=None,
770 content_encoding=None,
771 content_disposition=None,
772 x_object_manifest=None, x_object_sharing=None,
773 x_object_public=None, account=None):
774 """createas a zero length object"""
775 account = account or self.account
776 args = locals().copy()
777 for elem in ['self', 'container', 'object']:
779 return OOS_Client.create_zero_length_object(self, container, object,
782 def create_object(self, container, object, f=stdin, format='text',
783 meta={}, params={}, etag=None, content_type=None,
784 content_encoding=None, content_disposition=None,
785 x_object_manifest=None, x_object_sharing=None,
786 x_object_public=None, account=None):
787 """creates an object"""
788 account = account or self.account
789 args = locals().copy()
790 for elem in ['self', 'container', 'object']:
792 return OOS_Client.create_object(self, container, object, **args)
794 def create_object_using_chunks(self, container, object,
795 f=stdin, blocksize=1024, meta={}, etag=None,
796 content_type=None, content_encoding=None,
797 content_disposition=None,
798 x_object_sharing=None, x_object_manifest=None,
799 x_object_public=None, account=None):
800 """creates an object (incremental upload)"""
801 account = account or self.account
802 path = '/%s/%s/%s' % (account, container, object)
804 l = ['etag', 'content_type', 'content_encoding', 'content_disposition',
805 'x_object_sharing', 'x_object_manifest', 'x_object_public']
806 l = [elem for elem in l if eval(elem)]
808 headers.update({elem:eval(elem)})
809 headers.setdefault('content-type', 'application/octet-stream')
811 for k,v in meta.items():
813 headers['x-object-meta-%s' %k.strip()] = v
815 return self._chunked_transfer(path, 'PUT', f, headers=headers,
818 def create_object_by_hashmap(self, container, object, hashmap={},
819 meta={}, etag=None, content_encoding=None,
820 content_disposition=None, content_type=None,
821 x_object_sharing=None, x_object_manifest=None,
822 x_object_public = None, account=None):
823 """creates an object by uploading hashes representing data instead of data"""
824 account = account or self.account
825 args = locals().copy()
826 for elem in ['self', 'container', 'object', 'hashmap']:
830 data = json.dumps(hashmap)
832 raise Fault('Invalid formatting')
833 args['params'] = {'hashmap':None}
834 args['format'] = 'json'
836 return self.create_object(container, object, f=StringIO(data), **args)
838 def create_manifestation(self, container, object, manifest, account=None):
839 """creates a manifestation"""
840 account = account or self.account
841 headers={'x_object_manifest':manifest}
842 return self.create_object(container, object, f=None, account=account,
845 def update_object(self, container, object, f=stdin,
846 offset=None, meta={}, replace=False, content_length=None,
847 content_type=None, content_range=None,
848 content_encoding=None, content_disposition=None,
849 x_object_bytes=None, x_object_manifest=None,
850 x_object_sharing=None, x_object_public=None,
851 x_source_object=None, account=None):
852 """updates an object"""
853 account = account or self.account
854 args = locals().copy()
855 for elem in ['self', 'container', 'object', 'replace']:
858 args['params'] = {'update':None}
859 return OOS_Client.update_object(self, container, object, **args)
861 def update_object_using_chunks(self, container, object, f=stdin,
862 blocksize=1024, offset=None, meta={},
863 replace=False, content_type=None, content_encoding=None,
864 content_disposition=None, x_object_bytes=None,
865 x_object_manifest=None, x_object_sharing=None,
866 x_object_public=None, account=None):
867 """updates an object (incremental upload)"""
868 account = account or self.account
869 args = locals().copy()
870 for elem in ['self', 'container', 'object', 'replace']:
873 args['params'] = {'update':None}
874 return OOS_Client.update_object_using_chunks(self, container, object, **args)
876 def update_from_other_source(self, container, object, source,
877 offset=None, meta={}, content_range=None,
878 content_encoding=None, content_disposition=None,
879 x_object_bytes=None, x_object_manifest=None,
880 x_object_sharing=None, x_object_public=None, account=None):
881 """updates an object"""
882 account = account or self.account
883 args = locals().copy()
884 for elem in ['self', 'container', 'object', 'source']:
887 args['x_source_object'] = source
888 return self.update_object(container, object, f=None, **args)
890 def delete_object(self, container, object, until=None, account=None):
891 """deletes an object or the object history until the date provided"""
892 account = account or self.account
893 params = {'until':until} if until else {}
894 return OOS_Client.delete_object(self, container, object, params, account)
896 def trash_object(self, container, object):
897 """trashes an object"""
898 account = account or self.account
899 path = '/%s/%s' % (container, object)
900 meta = {'trash':'true'}
901 return self._update_metadata(path, 'object', **meta)
903 def restore_object(self, container, object, account=None):
904 """restores a trashed object"""
905 account = account or self.account
906 return self.delete_object_metadata(container, object, account, ['trash'])
908 def publish_object(self, container, object, account=None):
909 """sets a previously created object publicly accessible"""
910 account = account or self.account
911 path = '/%s/%s/%s' % (account, container, object)
913 headers['x_object_public'] = True
914 params = {'update':None}
915 return self.post(path, headers=headers, params=params)
917 def unpublish_object(self, container, object, account=None):
918 """unpublish an object"""
919 account = account or self.account
920 path = '/%s/%s/%s' % (account, container, object)
922 headers['x_object_public'] = False
923 params = {'update':None}
924 return self.post(path, headers=headers, params=params)
926 def copy_object(self, src_container, src_object, dst_container, dst_object,
927 meta={}, public=False, version=None, account=None,
929 """copies an object"""
930 account = account or self.account
932 headers['x_object_public'] = public
934 headers['x_source_version'] = version
935 return OOS_Client.copy_object(self, src_container, src_object,
936 dst_container, dst_object, meta=meta,
937 account=account, content_type=content_type,
940 def move_object(self, src_container, src_object, dst_container,
941 dst_object, meta={}, public=False,
942 account=None, content_type=None):
943 """moves an object"""
945 headers['x_object_public'] = public
946 return OOS_Client.move_object(self, src_container, src_object,
947 dst_container, dst_object, meta=meta,
948 account=account, content_type=content_type,
951 def list_shared_by_others(self, limit=None, marker=None, format='text'):
952 """lists other accounts that share objects to the user"""
953 l = ['limit', 'marker']
955 for elem in [elem for elem in l if eval(elem)]:
956 params[elem] = eval(elem)
957 return self._list('', format, params)
959 def share_object(self, container, object, l, read=True):
960 """gives access(read by default) to an object to a user/group list"""
961 action = 'read' if read else 'write'
962 sharing = '%s=%s' % (action, ','.join(l))
963 self.update_object(container, object, f=None, x_object_sharing=sharing)
965 def _encode_headers(headers):
967 for k, v in headers.items():
969 if v and type(v) == types.StringType:
970 v = urllib.quote(v, '/=,-* :"')