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
38 from urllib import quote, unquote
46 ERROR_CODES = {304:'Not Modified',
52 411:'Length Required',
53 412:'Precondition Failed',
54 413:'Request Entity Too Large',
55 416:'Range Not Satisfiable',
56 422:'Unprocessable Entity',
57 503:'Service Unavailable',
60 class Fault(Exception):
61 def __init__(self, data='', status=None):
62 if data == '' and status in ERROR_CODES.keys():
63 data = ERROR_CODES[status]
64 Exception.__init__(self, data)
69 def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
70 """`host` can also include a port, e.g '127.0.0.1:8000'."""
73 self.account = account
75 self.verbose = verbose or debug
79 def _req(self, method, path, body=None, headers={}, format='text', params={}):
80 slash = '/' if self.api else ''
81 full_path = '%s%s%s?format=%s' % (slash, self.api, quote(path), format)
83 for k,v in params.items():
85 full_path = '%s&%s=%s' %(full_path, quote(k), quote(str(v)))
87 full_path = '%s&%s=' %(full_path, k)
88 conn = HTTPConnection(self.host)
91 for k,v in headers.items():
93 k = k.replace('_', '-')
94 headers[k] = quote(v, safe='/=,:@ *"') if type(v) == types.StringType else v
96 kwargs['headers'] = headers
97 kwargs['headers']['X-Auth-Token'] = self.token
100 kwargs['headers'].setdefault('content-type', 'application/octet-stream')
101 kwargs['headers'].setdefault('content-length', len(body) if body else 0)
103 #print '#', method, full_path, kwargs
104 t1 = datetime.datetime.utcnow()
105 conn.request(method, full_path, **kwargs)
107 resp = conn.getresponse()
108 t2 = datetime.datetime.utcnow()
109 #print 'response time:', str(t2-t1)
110 headers = resp.getheaders()
111 headers = dict((unquote(h), unquote(v)) for h,v in headers)
114 print '%d %s' % (resp.status, resp.reason)
115 for key, val in headers.items():
116 print '%s: %s' % (key.capitalize(), val)
119 length = resp.getheader('content-length', None)
120 data = resp.read(length)
125 if int(resp.status) in ERROR_CODES.keys():
126 raise Fault(data, int(resp.status))
128 #print '**', resp.status, headers, data, '\n'
129 return resp.status, headers, data
131 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
132 blocksize=1024, params={}):
133 """perfomrs a chunked request"""
134 http = HTTPConnection(self.host)
137 full_path = '/%s%s?' % (self.api, path)
139 for k,v in params.items():
141 full_path = '%s&%s=%s' %(full_path, k, v)
143 full_path = '%s&%s=' %(full_path, k)
145 full_path = urllib.quote(full_path, '?&:=/')
147 http.putrequest(method, full_path)
148 http.putheader('x-auth-token', self.token)
149 http.putheader('content-type', 'application/octet-stream')
150 http.putheader('transfer-encoding', 'chunked')
152 for header,value in headers.items():
153 http.putheader(header, value)
161 block = f.read(blocksize)
164 data = '%x\r\n%s\r\n' % (len(block), block)
178 resp = http.getresponse()
180 headers = dict(resp.getheaders())
183 print '%d %s' % (resp.status, resp.reason)
184 for key, val in headers.items():
185 print '%s: %s' % (key.capitalize(), val)
188 length = resp.getheader('Content-length', None)
189 data = resp.read(length)
194 if int(resp.status) in ERROR_CODES.keys():
195 raise Fault(data, int(resp.status))
197 #print '*', resp.status, headers, data
198 return resp.status, headers, data
200 def delete(self, path, format='text', params={}):
201 return self._req('DELETE', path, format=format, params=params)
203 def get(self, path, format='text', headers={}, params={}):
204 return self._req('GET', path, headers=headers, format=format,
207 def head(self, path, format='text', params={}):
208 return self._req('HEAD', path, format=format, params=params)
210 def post(self, path, body=None, format='text', headers=None, params={}):
211 return self._req('POST', path, body, headers=headers, format=format,
214 def put(self, path, body=None, format='text', headers=None, params={}):
215 return self._req('PUT', path, body, headers=headers, format=format,
218 def _list(self, path, format='text', params={}, **headers):
219 status, headers, data = self.get(path, format=format, headers=headers,
222 data = json.loads(data) if data else ''
223 elif format == 'xml':
224 data = minidom.parseString(data)
226 data = data.split('\n')[:-1] if data else ''
229 def _get_metadata(self, path, prefix=None, params={}):
230 status, headers, data = self.head(path, params=params)
231 prefixlen = len(prefix) if prefix else 0
233 for key, val in headers.items():
234 if prefix and not key.startswith(prefix):
236 elif prefix and key.startswith(prefix):
237 key = key[prefixlen:]
241 def _filter(self, l, d):
243 filter out from l elements having the metadata values provided
247 if type(elem) == types.DictionaryType:
249 k = 'x_object_meta_%s' % key
250 if k in elem.keys() and elem[k] == d[key]:
255 class OOS_Client(Client):
256 """Openstack Object Storage Client"""
258 def _update_metadata(self, path, entity, **meta):
259 """adds new and updates the values of previously set metadata"""
260 ex_meta = self.retrieve_account_metadata(restricted=True)
263 prefix = 'x-%s-meta-' % entity
264 for k,v in ex_meta.items():
265 k = '%s%s' % (prefix, k)
267 return self.post(path, headers=headers)
269 def _reset_metadata(self, path, entity, **meta):
271 overwrites all user defined metadata
274 prefix = 'x-%s-meta-' % entity
275 for k,v in meta.items():
276 k = '%s%s' % (prefix, k)
278 return self.post(path, headers=headers)
280 def _delete_metadata(self, path, entity, meta=[]):
281 """delete previously set metadata"""
282 ex_meta = self.retrieve_account_metadata(restricted=True)
284 prefix = 'x-%s-meta-' % entity
285 for k in ex_meta.keys():
287 headers['%s%s' % (prefix, k)] = ex_meta[k]
288 return self.post(path, headers=headers)
290 # Storage Account Services
292 def list_containers(self, format='text', limit=None,
293 marker=None, params={}, account=None, **headers):
294 """lists containers"""
295 account = account or self.account
296 path = '/%s' % account
297 params.update({'limit':limit, 'marker':marker})
298 return self._list(path, format, params, **headers)
300 def retrieve_account_metadata(self, restricted=False, account=None, **params):
301 """returns the account metadata"""
302 account = account or self.account
303 path = '/%s' % account
304 prefix = 'x-account-meta-' if restricted else None
305 return self._get_metadata(path, prefix, params)
307 def update_account_metadata(self, account=None, **meta):
308 """updates the account metadata"""
309 account = account or self.account
310 path = '/%s' % account
311 return self._update_metadata(path, 'account', **meta)
313 def delete_account_metadata(self, meta=[], account=None):
314 """deletes the account metadata"""
315 account = account or self.account
316 path = '/%s' % account
317 return self._delete_metadata(path, 'account', meta)
319 def reset_account_metadata(self, account=None, **meta):
320 """resets account metadata"""
321 account = account or self.account
322 path = '/%s' % account
323 return self._reset_metadata(path, 'account', **meta)
325 # Storage Container Services
327 def _filter_trashed(self, l):
328 return self._filter(l, {'trash':'true'})
330 def list_objects(self, container, format='text',
331 limit=None, marker=None, prefix=None, delimiter=None,
332 path=None, include_trashed=False, params={}, account=None,
334 """returns a list with the container objects"""
335 account = account or self.account
336 params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
337 'delimiter':delimiter, 'path':path})
338 l = self._list('/%s/%s' % (account, container), format, params,
340 #TODO support filter trashed with xml also
341 if format != 'xml' and not include_trashed:
342 l = self._filter_trashed(l)
345 def create_container(self, container, account=None, **meta):
346 """creates a container"""
347 account = account or self.account
349 for k,v in meta.items():
350 headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
351 status, header, data = self.put('/%s/%s' % (account, container),
356 raise Fault(data, int(status))
359 def delete_container(self, container, params={}, account=None):
360 """deletes a container"""
361 account = account or self.account
362 return self.delete('/%s/%s' % (account, container), params=params)
364 def retrieve_container_metadata(self, container, restricted=False,
365 account=None, **params):
366 """returns the container metadata"""
367 account = account or self.account
368 prefix = 'x-container-meta-' if restricted else None
369 return self._get_metadata('/%s/%s' % (account, container), prefix,
372 def update_container_metadata(self, container, account=None, **meta):
373 """unpdates the container metadata"""
374 account = account or self.account
375 return self._update_metadata('/%s/%s' % (account, container),
378 def delete_container_metadata(self, container, meta=[], account=None):
379 """deletes the container metadata"""
380 account = account or self.account
381 path = '/%s/%s' % (account, container)
382 return self._delete_metadata(path, 'container', meta)
384 # Storage Object Services
386 def request_object(self, container, object, format='text', params={},
387 account=None, **headers):
388 """returns tuple containing the status, headers and data response for an object request"""
389 account = account or self.account
390 path = '/%s/%s/%s' % (account, container, object)
391 status, headers, data = self.get(path, format, headers, params)
392 return status, headers, data
394 def retrieve_object(self, container, object, format='text', params={},
395 account=None, **headers):
396 """returns an object's data"""
397 account = account or self.account
398 t = self.request_object(container, object, format, params, account,
402 data = json.loads(data) if data else ''
403 elif format == 'xml':
404 data = minidom.parseString(data)
407 def retrieve_object_hashmap(self, container, object, params={},
408 account=None, **headers):
409 """returns the hashmap representing object's data"""
410 args = locals().copy()
411 for elem in ['self', 'container', 'object']:
413 data = self.retrieve_object(container, object, format='json', **args)
414 return data['hashes']
416 def create_directory_marker(self, container, object, account=None):
417 """creates a dierectory marker"""
418 account = account or self.account
420 raise Fault('Directory markers have to be nested in a container')
421 h = {'content_type':'application/directory'}
422 return self.create_zero_length_object(container, object, account=account,
425 def create_object(self, container, object, f=stdin, format='text', meta={},
426 params={}, etag=None, content_type=None, content_encoding=None,
427 content_disposition=None, account=None, **headers):
428 """creates a zero-length object"""
429 account = account or self.account
430 path = '/%s/%s/%s' % (account, container, object)
431 for k, v in headers.items():
435 l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
436 l = [elem for elem in l if eval(elem)]
438 headers.update({elem:eval(elem)})
439 headers.setdefault('content-type', 'application/octet-stream')
441 for k,v in meta.items():
442 headers['x-object-meta-%s' %k.strip()] = v.strip()
443 data = f.read() if f else None
444 return self.put(path, data, format, headers=headers, params=params)
446 def create_zero_length_object(self, container, object, meta={}, etag=None,
447 content_type=None, content_encoding=None,
448 content_disposition=None, account=None,
450 account = account or self.account
451 args = locals().copy()
452 for elem in ['self', 'container', 'headers', 'account']:
455 return self.create_object(container, account=account, f=None, **args)
457 def update_object(self, container, object, f=stdin,
458 offset=None, meta={}, params={}, content_length=None,
459 content_type=None, content_encoding=None,
460 content_disposition=None, account=None, **headers):
461 account = account or self.account
462 path = '/%s/%s/%s' % (account, container, object)
463 for k, v in headers.items():
467 l = ['content_encoding', 'content_disposition', 'content_type',
469 l = [elem for elem in l if eval(elem)]
471 headers.update({elem:eval(elem)})
473 if 'content_range' not in headers.keys():
475 headers['content_range'] = 'bytes %s-/*' % offset
477 headers['content_range'] = 'bytes */*'
479 for k,v in meta.items():
480 headers['x-object-meta-%s' %k.strip()] = v.strip()
481 data = f.read() if f else None
482 return self.post(path, data, headers=headers, params=params)
484 def update_object_using_chunks(self, container, object, f=stdin,
485 blocksize=1024, offset=None, meta={},
486 params={}, content_type=None, content_encoding=None,
487 content_disposition=None, account=None, **headers):
488 """updates an object (incremental upload)"""
489 account = account or self.account
490 path = '/%s/%s/%s' % (account, container, object)
491 headers = headers if not headers else {}
492 l = ['content_type', 'content_encoding', 'content_disposition']
493 l = [elem for elem in l if eval(elem)]
495 headers.update({elem:eval(elem)})
498 headers['content_range'] = 'bytes %s-/*' % offset
500 headers['content_range'] = 'bytes */*'
502 for k,v in meta.items():
504 headers['x-object-meta-%s' %k.strip()] = v
505 return self._chunked_transfer(path, 'POST', f, headers=headers,
506 blocksize=blocksize, params=params)
508 def _change_obj_location(self, src_container, src_object, dst_container,
509 dst_object, remove=False, meta={}, account=None,
510 content_type=None, **headers):
511 account = account or self.account
512 path = '/%s/%s/%s' % (account, dst_container, dst_object)
513 headers = {} if not headers else headers
514 for k, v in meta.items():
515 headers['x-object-meta-%s' % k] = v
517 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
519 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
520 headers['content_length'] = 0
522 headers['content_type'] = content_type
523 return self.put(path, headers=headers)
525 def copy_object(self, src_container, src_object, dst_container, dst_object,
526 meta={}, account=None, content_type=None, **headers):
527 """copies an object"""
528 account = account or self.account
529 return self._change_obj_location(src_container, src_object,
530 dst_container, dst_object, account=account,
531 remove=False, meta=meta,
532 content_type=content_type, **headers)
534 def move_object(self, src_container, src_object, dst_container,
535 dst_object, meta={}, account=None,
536 content_type=None, **headers):
537 """moves an object"""
538 account = account or self.account
539 return self._change_obj_location(src_container, src_object,
540 dst_container, dst_object,
541 account=account, remove=True,
542 meta=meta, content_type=content_type,
545 def delete_object(self, container, object, params={}, account=None):
546 """deletes an object"""
547 account = account or self.account
548 return self.delete('/%s/%s/%s' % (account, container, object),
551 def retrieve_object_metadata(self, container, object, restricted=False,
552 version=None, account=None):
554 set restricted to True to get only user defined metadata
556 account = account or self.account
557 path = '/%s/%s/%s' % (account, container, object)
558 prefix = 'x-object-meta-' if restricted else None
559 params = {'version':version} if version else {}
560 return self._get_metadata(path, prefix, params=params)
562 def update_object_metadata(self, container, object, account=None,
565 updates object's metadata
567 account = account or self.account
568 path = '/%s/%s/%s' % (account, container, object)
569 return self._update_metadata(path, 'object', **meta)
571 def delete_object_metadata(self, container, object, meta=[], account=None):
573 deletes object's metadata
575 account = account or self.account
576 path = '/%s/%s' % (account, container, object)
577 return self._delete_metadata(path, 'object', meta)
579 class Pithos_Client(OOS_Client):
580 """Pithos Storage Client. Extends OOS_Client"""
582 def _update_metadata(self, path, entity, **meta):
584 adds new and updates the values of previously set metadata
586 params = {'update':None}
588 prefix = 'x-%s-meta-' % entity
589 for k,v in meta.items():
590 k = '%s%s' % (prefix, k)
592 return self.post(path, headers=headers, params=params)
594 def _delete_metadata(self, path, entity, meta=[]):
596 delete previously set metadata
598 params = {'update':None}
600 prefix = 'x-%s-meta-' % entity
602 headers['%s%s' % (prefix, m)] = ''
603 return self.post(path, headers=headers, params=params)
605 # Storage Account Services
607 def list_containers(self, format='text', if_modified_since=None,
608 if_unmodified_since=None, limit=None, marker=None,
609 shared=False, until=None, account=None):
610 """returns a list with the account containers"""
611 account = account or self.account
612 params = {'until':until} if until else {}
614 params['shared'] = None
615 headers = {'if-modified-since':if_modified_since,
616 'if-unmodified-since':if_unmodified_since}
617 return OOS_Client.list_containers(self, account=account, format=format,
618 limit=limit, marker=marker,
619 params=params, **headers)
621 def retrieve_account_metadata(self, restricted=False, until=None,
623 """returns the account metadata"""
624 account = account or self.account
625 params = {'until':until} if until else {}
626 return OOS_Client.retrieve_account_metadata(self, account=account,
627 restricted=restricted,
630 def set_account_groups(self, account=None, **groups):
631 """create account groups"""
632 account = account or self.account
633 path = '/%s' % account
635 for k, v in groups.items():
636 headers['x-account-group-%s' % k] = v
637 params = {'update':None}
638 return self.post(path, headers=headers, params=params)
640 def retrieve_account_groups(self, account=None):
641 """returns the account groups"""
642 account = account or self.account
643 meta = self.retrieve_account_metadata(account=account)
644 prefix = 'x-account-group-'
645 prefixlen = len(prefix)
647 for key, val in meta.items():
648 if prefix and not key.startswith(prefix):
650 elif prefix and key.startswith(prefix):
651 key = key[prefixlen:]
655 def unset_account_groups(self, groups=[], account=None):
656 """delete account groups"""
657 account = account or self.account
658 path = '/%s' % account
661 headers['x-account-group-%s' % elem] = ''
662 params = {'update':None}
663 return self.post(path, headers=headers, params=params)
665 def reset_account_groups(self, account=None, **groups):
666 """overrides account groups"""
667 account = account or self.account
668 path = '/%s' % account
670 for k, v in groups.items():
672 headers['x-account-group-%s' % k] = v
673 meta = self.retrieve_account_metadata(restricted=True)
674 prefix = 'x-account-meta-'
675 for k,v in meta.items():
676 k = '%s%s' % (prefix, k)
678 return self.post(path, headers=headers)
680 # Storage Container Services
682 def list_objects(self, container, format='text',
683 limit=None, marker=None, prefix=None, delimiter=None,
684 path=None, shared=False, include_trashed=False, params={},
685 if_modified_since=None, if_unmodified_since=None, meta='',
686 until=None, account=None):
687 """returns a list with the container objects"""
688 account = account or self.account
689 params = {'until':until, 'meta':meta}
691 params['shared'] = None
692 args = locals().copy()
693 for elem in ['self', 'container', 'params', 'until', 'meta']:
695 return OOS_Client.list_objects(self, container, params=params, **args)
697 def retrieve_container_metadata(self, container, restricted=False,
698 until=None, account=None):
699 """returns container's metadata"""
700 account = account or self.account
701 params = {'until':until} if until else {}
702 return OOS_Client.retrieve_container_metadata(self, container,
704 restricted=restricted,
707 def set_container_policies(self, container, account=None,
709 """sets containers policies"""
710 account = account or self.account
711 path = '/%s/%s' % (account, container)
713 for key, val in policies.items():
714 headers['x-container-policy-%s' % key] = val
715 return self.post(path, headers=headers)
717 def update_container_data(self, container, f=stdin):
718 """adds blocks of data to the container"""
719 account = self.account
720 path = '/%s/%s' % (account, container)
721 params = {'update': None}
722 headers = {'content_type': 'application/octet-stream'}
723 data = f.read() if f else None
724 headers['content_length'] = len(data)
725 return self.post(path, data, headers=headers, params=params)
727 def delete_container(self, container, until=None, account=None):
728 """deletes a container or the container history until the date provided"""
729 account = account or self.account
730 params = {'until':until} if until else {}
731 return OOS_Client.delete_container(self, container, account=account,
734 # Storage Object Services
736 def retrieve_object(self, container, object, params={}, format='text',
737 range=None, if_range=None,
738 if_match=None, if_none_match=None,
739 if_modified_since=None, if_unmodified_since=None,
740 account=None, **headers):
741 """returns an object"""
742 account = account or self.account
744 l = ['range', 'if_range', 'if_match', 'if_none_match',
745 'if_modified_since', 'if_unmodified_since']
746 l = [elem for elem in l if eval(elem)]
748 headers.update({elem:eval(elem)})
750 params['hashmap'] = None
751 return OOS_Client.retrieve_object(self, container, object,
752 account=account, format=format,
753 params=params, **headers)
755 def retrieve_object_version(self, container, object, version,
756 format='text', range=None, if_range=None,
757 if_match=None, if_none_match=None,
758 if_modified_since=None, if_unmodified_since=None,
760 """returns a specific object version"""
761 account = account or self.account
762 args = locals().copy()
763 l = ['self', 'container', 'object']
766 params = {'version':version}
767 return self.retrieve_object(container, object, params=params, **args)
769 def retrieve_object_versionlist(self, container, object, range=None,
770 if_range=None, if_match=None,
771 if_none_match=None, if_modified_since=None,
772 if_unmodified_since=None, account=None):
773 """returns the object version list"""
774 account = account or self.account
775 args = locals().copy()
776 l = ['self', 'container', 'object']
780 return self.retrieve_object_version(container, object, version='list',
781 format='json', **args)
783 def create_zero_length_object(self, container, object,
784 meta={}, etag=None, content_type=None,
785 content_encoding=None,
786 content_disposition=None,
787 x_object_manifest=None, x_object_sharing=None,
788 x_object_public=None, account=None):
789 """createas a zero length object"""
790 account = account or self.account
791 args = locals().copy()
792 for elem in ['self', 'container', 'object']:
794 return OOS_Client.create_zero_length_object(self, container, object,
797 def create_object(self, container, object, f=stdin, format='text',
798 meta={}, params={}, etag=None, content_type=None,
799 content_encoding=None, content_disposition=None,
800 x_object_manifest=None, x_object_sharing=None,
801 x_object_public=None, account=None):
802 """creates an object"""
803 account = account or self.account
804 args = locals().copy()
805 for elem in ['self', 'container', 'object']:
808 params.update({'hashmap':None})
809 return OOS_Client.create_object(self, container, object, **args)
811 def create_object_using_chunks(self, container, object,
812 f=stdin, blocksize=1024, meta={}, etag=None,
813 content_type=None, content_encoding=None,
814 content_disposition=None,
815 x_object_sharing=None, x_object_manifest=None,
816 x_object_public=None, account=None):
817 """creates an object (incremental upload)"""
818 account = account or self.account
819 path = '/%s/%s/%s' % (account, container, object)
821 l = ['etag', 'content_type', 'content_encoding', 'content_disposition',
822 'x_object_sharing', 'x_object_manifest', 'x_object_public']
823 l = [elem for elem in l if eval(elem)]
825 headers.update({elem:eval(elem)})
826 headers.setdefault('content-type', 'application/octet-stream')
828 for k,v in meta.items():
830 headers['x-object-meta-%s' %k.strip()] = v
832 return self._chunked_transfer(path, 'PUT', f, headers=headers,
835 def create_object_by_hashmap(self, container, object, hashmap={},
836 meta={}, etag=None, content_encoding=None,
837 content_disposition=None, content_type=None,
838 x_object_sharing=None, x_object_manifest=None,
839 x_object_public = None, account=None):
840 """creates an object by uploading hashes representing data instead of data"""
841 account = account or self.account
842 args = locals().copy()
843 for elem in ['self', 'container', 'object', 'hashmap']:
847 data = json.dumps(hashmap)
849 raise Fault('Invalid formatting')
850 args['params'] = {'hashmap':None}
851 args['format'] = 'json'
853 return self.create_object(container, object, f=StringIO(data), **args)
855 def create_manifestation(self, container, object, manifest, account=None):
856 """creates a manifestation"""
857 account = account or self.account
858 headers={'x_object_manifest':manifest}
859 return self.create_object(container, object, f=None, account=account,
862 def update_object(self, container, object, f=stdin,
863 offset=None, meta={}, replace=False, content_length=None,
864 content_type=None, content_range=None,
865 content_encoding=None, content_disposition=None,
866 x_object_bytes=None, x_object_manifest=None,
867 x_object_sharing=None, x_object_public=None,
868 x_source_object=None, account=None):
869 """updates an object"""
870 account = account or self.account
871 args = locals().copy()
872 for elem in ['self', 'container', 'object', 'replace']:
875 args['params'] = {'update':None}
876 return OOS_Client.update_object(self, container, object, **args)
878 def update_object_using_chunks(self, container, object, f=stdin,
879 blocksize=1024, offset=None, meta={},
880 replace=False, content_type=None, content_encoding=None,
881 content_disposition=None, x_object_bytes=None,
882 x_object_manifest=None, x_object_sharing=None,
883 x_object_public=None, account=None):
884 """updates an object (incremental upload)"""
885 account = account or self.account
886 args = locals().copy()
887 for elem in ['self', 'container', 'object', 'replace']:
890 args['params'] = {'update':None}
891 return OOS_Client.update_object_using_chunks(self, container, object, **args)
893 def update_from_other_source(self, container, object, source,
894 offset=None, meta={}, content_range=None,
895 content_encoding=None, content_disposition=None,
896 x_object_bytes=None, x_object_manifest=None,
897 x_object_sharing=None, x_object_public=None, account=None):
898 """updates an object"""
899 account = account or self.account
900 args = locals().copy()
901 for elem in ['self', 'container', 'object', 'source']:
904 args['x_source_object'] = source
905 return self.update_object(container, object, f=None, **args)
907 def delete_object(self, container, object, until=None, account=None):
908 """deletes an object or the object history until the date provided"""
909 account = account or self.account
910 params = {'until':until} if until else {}
911 return OOS_Client.delete_object(self, container, object, params, account)
913 def trash_object(self, container, object):
914 """trashes an object"""
915 account = account or self.account
916 path = '/%s/%s' % (container, object)
917 meta = {'trash':'true'}
918 return self._update_metadata(path, 'object', **meta)
920 def restore_object(self, container, object, account=None):
921 """restores a trashed object"""
922 account = account or self.account
923 return self.delete_object_metadata(container, object, account, ['trash'])
925 def publish_object(self, container, object, account=None):
926 """sets a previously created object publicly accessible"""
927 account = account or self.account
928 path = '/%s/%s/%s' % (account, container, object)
930 headers['x_object_public'] = True
931 params = {'update':None}
932 return self.post(path, headers=headers, params=params)
934 def unpublish_object(self, container, object, account=None):
935 """unpublish an object"""
936 account = account or self.account
937 path = '/%s/%s/%s' % (account, container, object)
939 headers['x_object_public'] = False
940 params = {'update':None}
941 return self.post(path, headers=headers, params=params)
943 def copy_object(self, src_container, src_object, dst_container, dst_object,
944 meta={}, public=False, version=None, account=None,
946 """copies an object"""
947 account = account or self.account
949 headers['x_object_public'] = public
951 headers['x_source_version'] = version
952 return OOS_Client.copy_object(self, src_container, src_object,
953 dst_container, dst_object, meta=meta,
954 account=account, content_type=content_type,
957 def move_object(self, src_container, src_object, dst_container,
958 dst_object, meta={}, public=False,
959 account=None, content_type=None):
960 """moves an object"""
962 headers['x_object_public'] = public
963 return OOS_Client.move_object(self, src_container, src_object,
964 dst_container, dst_object, meta=meta,
965 account=account, content_type=content_type,
968 def list_shared_by_others(self, limit=None, marker=None, format='text'):
969 """lists other accounts that share objects to the user"""
970 l = ['limit', 'marker']
972 for elem in [elem for elem in l if eval(elem)]:
973 params[elem] = eval(elem)
974 return self._list('', format, params)
976 def share_object(self, container, object, l, read=True):
977 """gives access(read by default) to an object to a user/group list"""
978 action = 'read' if read else 'write'
979 sharing = '%s=%s' % (action, ','.join(l))
980 self.update_object(container, object, f=None, x_object_sharing=sharing)