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)
86 full_path = urllib.quote(full_path, '?&:=/')
89 for k,v in headers.items():
91 k = k.replace('_', '-')
94 kwargs['headers'] = headers
95 kwargs['headers']['X-Auth-Token'] = self.token
98 kwargs['headers'].setdefault('content-type', 'application/octet-stream')
99 kwargs['headers'].setdefault('content-length', len(body) if body else 0)
101 #print '#', method, full_path, kwargs
102 t1 = datetime.datetime.utcnow()
103 conn.request(method, full_path, **kwargs)
105 resp = conn.getresponse()
106 t2 = datetime.datetime.utcnow()
107 #print 'response time:', str(t2-t1)
108 headers = dict(resp.getheaders())
111 print '%d %s' % (resp.status, resp.reason)
112 for key, val in headers.items():
113 print '%s: %s' % (key.capitalize(), val)
116 length = resp.getheader('content-length', None)
117 data = resp.read(length)
122 if int(resp.status) in ERROR_CODES.keys():
123 raise Fault(data, int(resp.status))
125 #print '**', resp.status, headers, data, '\n'
126 return resp.status, headers, data
128 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
129 blocksize=1024, params={}):
130 """perfomrs a chunked request"""
131 http = HTTPConnection(self.host)
134 full_path = '/%s%s?' % (self.api, path)
136 for k,v in params.items():
138 full_path = '%s&%s=%s' %(full_path, k, v)
140 full_path = '%s&%s=' %(full_path, k)
142 full_path = urllib.quote(full_path, '?&:=/')
144 http.putrequest(method, full_path)
145 http.putheader('x-auth-token', self.token)
146 http.putheader('content-type', 'application/octet-stream')
147 http.putheader('transfer-encoding', 'chunked')
149 for header,value in headers.items():
150 http.putheader(header, value)
158 block = f.read(blocksize)
161 data = '%x\r\n%s\r\n' % (len(block), block)
175 resp = http.getresponse()
177 headers = dict(resp.getheaders())
180 print '%d %s' % (resp.status, resp.reason)
181 for key, val in headers.items():
182 print '%s: %s' % (key.capitalize(), val)
185 length = resp.getheader('Content-length', None)
186 data = resp.read(length)
191 if int(resp.status) in ERROR_CODES.keys():
192 raise Fault(data, int(resp.status))
194 #print '*', resp.status, headers, data
195 return resp.status, headers, data
197 def delete(self, path, format='text', params={}):
198 return self._req('DELETE', path, format=format, params=params)
200 def get(self, path, format='text', headers={}, params={}):
201 return self._req('GET', path, headers=headers, format=format,
204 def head(self, path, format='text', params={}):
205 return self._req('HEAD', path, format=format, params=params)
207 def post(self, path, body=None, format='text', headers=None, params={}):
208 return self._req('POST', path, body, headers=headers, format=format,
211 def put(self, path, body=None, format='text', headers=None, params={}):
212 return self._req('PUT', path, body, headers=headers, format=format,
215 def _list(self, path, format='text', params={}, **headers):
216 status, headers, data = self.get(path, format=format, headers=headers,
219 data = json.loads(data) if data else ''
220 elif format == 'xml':
221 data = minidom.parseString(data)
223 data = data.split('\n') if data else ''
226 def _get_metadata(self, path, prefix=None, params={}):
227 status, headers, data = self.head(path, params=params)
228 prefixlen = len(prefix) if prefix else 0
230 for key, val in headers.items():
231 if prefix and not key.startswith(prefix):
233 elif prefix and key.startswith(prefix):
234 key = key[prefixlen:]
238 def _filter(self, l, d):
240 filter out from l elements having the metadata values provided
244 if type(elem) == types.DictionaryType:
246 k = 'x_object_meta_%s' % key
247 if k in elem.keys() and elem[k] == d[key]:
252 class OOS_Client(Client):
253 """Openstack Object Storage Client"""
255 def _update_metadata(self, path, entity, **meta):
256 """adds new and updates the values of previously set metadata"""
257 ex_meta = self.retrieve_account_metadata(restricted=True)
260 prefix = 'x-%s-meta-' % entity
261 for k,v in ex_meta.items():
262 k = '%s%s' % (prefix, k)
264 return self.post(path, headers=headers)
266 def _reset_metadata(self, path, entity, **meta):
268 overwrites all user defined metadata
271 prefix = 'x-%s-meta-' % entity
272 for k,v in meta.items():
273 k = '%s%s' % (prefix, k)
275 return self.post(path, headers=headers)
277 def _delete_metadata(self, path, entity, meta=[]):
278 """delete previously set metadata"""
279 ex_meta = self.retrieve_account_metadata(restricted=True)
281 prefix = 'x-%s-meta-' % entity
282 for k in ex_meta.keys():
284 headers['%s%s' % (prefix, k)] = ex_meta[k]
285 return self.post(path, headers=headers)
287 # Storage Account Services
289 def list_containers(self, format='text', limit=None,
290 marker=None, params={}, account=None, **headers):
291 """lists containers"""
292 account = account or self.account
293 path = '/%s' % account
294 params.update({'limit':limit, 'marker':marker})
295 return self._list(path, format, params, **headers)
297 def retrieve_account_metadata(self, restricted=False, account=None, **params):
298 """returns the account metadata"""
299 account = account or self.account
300 path = '/%s' % account
301 prefix = 'x-account-meta-' if restricted else None
302 return self._get_metadata(path, prefix, params)
304 def update_account_metadata(self, account=None, **meta):
305 """updates the account metadata"""
306 account = account or self.account
307 path = '/%s' % account
308 return self._update_metadata(path, 'account', **meta)
310 def delete_account_metadata(self, meta=[], account=None):
311 """deletes the account metadata"""
312 account = account or self.account
313 path = '/%s' % account
314 return self._delete_metadata(path, 'account', meta)
316 def reset_account_metadata(self, account=None, **meta):
317 """resets account metadata"""
318 account = account or self.account
319 path = '/%s' % account
320 return self._reset_metadata(path, 'account', **meta)
322 # Storage Container Services
324 def _filter_trashed(self, l):
325 return self._filter(l, {'trash':'true'})
327 def list_objects(self, container, format='text',
328 limit=None, marker=None, prefix=None, delimiter=None,
329 path=None, include_trashed=False, params={}, account=None,
331 """returns a list with the container objects"""
332 account = account or self.account
333 params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
334 'delimiter':delimiter, 'path':path})
335 l = self._list('/%s/%s' % (account, container), format, params,
337 #TODO support filter trashed with xml also
338 if format != 'xml' and not include_trashed:
339 l = self._filter_trashed(l)
342 def create_container(self, container, account=None, **meta):
343 """creates a container"""
344 account = account or self.account
346 for k,v in meta.items():
347 headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
348 status, header, data = self.put('/%s/%s' % (account, container),
353 raise Fault(data, int(status))
356 def delete_container(self, container, params={}, account=None):
357 """deletes a container"""
358 account = account or self.account
359 return self.delete('/%s/%s' % (account, container), params=params)
361 def retrieve_container_metadata(self, container, restricted=False,
362 account=None, **params):
363 """returns the container metadata"""
364 account = account or self.account
365 prefix = 'x-container-meta-' if restricted else None
366 return self._get_metadata('/%s/%s' % (account, container), prefix,
369 def update_container_metadata(self, container, account=None, **meta):
370 """unpdates the container metadata"""
371 account = account or self.account
372 return self._update_metadata('/%s/%s' % (account, container),
375 def delete_container_metadata(self, container, meta=[], account=None):
376 """deletes the container metadata"""
377 account = account or self.account
378 path = '/%s/%s' % (account, container)
379 return self._delete_metadata(path, 'container', meta)
381 # Storage Object Services
383 def request_object(self, container, object, format='text', params={},
384 account=None, **headers):
385 """returns tuple containing the status, headers and data response for an object request"""
386 account = account or self.account
387 path = '/%s/%s/%s' % (account, container, object)
388 status, headers, data = self.get(path, format, headers, params)
389 return status, headers, data
391 def retrieve_object(self, container, object, format='text', params={},
392 account=None, **headers):
393 """returns an object's data"""
394 account = account or self.account
395 t = self.request_object(container, object, format, params, account,
399 data = json.loads(data) if data else ''
400 elif format == 'xml':
401 data = minidom.parseString(data)
404 def retrieve_object_hashmap(self, container, object, params={},
405 account=None, **headers):
406 """returns the hashmap representing object's data"""
407 args = locals().copy()
408 for elem in ['self', 'container', 'object']:
410 data = self.retrieve_object(container, object, format='json', **args)
411 return data['hashes']
413 def create_directory_marker(self, container, object, account=None):
414 """creates a dierectory marker"""
415 account = account or self.account
417 raise Fault('Directory markers have to be nested in a container')
418 h = {'content_type':'application/directory'}
419 return self.create_zero_length_object(container, object, account=account,
422 def create_object(self, container, object, f=stdin, format='text', meta={},
423 params={}, etag=None, content_type=None, content_encoding=None,
424 content_disposition=None, account=None, **headers):
425 """creates a zero-length object"""
426 account = account or self.account
427 path = '/%s/%s/%s' % (account, container, object)
428 for k, v in headers.items():
432 l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
433 l = [elem for elem in l if eval(elem)]
435 headers.update({elem:eval(elem)})
436 headers.setdefault('content-type', 'application/octet-stream')
438 for k,v in meta.items():
439 headers['x-object-meta-%s' %k.strip()] = v.strip()
440 data = f.read() if f else None
441 return self.put(path, data, format, headers=headers, params=params)
443 def create_zero_length_object(self, container, object, meta={}, etag=None,
444 content_type=None, content_encoding=None,
445 content_disposition=None, account=None,
447 account = account or self.account
448 args = locals().copy()
449 for elem in ['self', 'container', 'headers', 'account']:
452 return self.create_object(container, account=account, f=None, **args)
454 def update_object(self, container, object, f=stdin,
455 offset=None, meta={}, params={}, content_length=None,
456 content_type=None, content_encoding=None,
457 content_disposition=None, account=None, **headers):
458 account = account or self.account
459 path = '/%s/%s/%s' % (account, container, object)
460 for k, v in headers.items():
464 l = ['content_encoding', 'content_disposition', 'content_type',
466 l = [elem for elem in l if eval(elem)]
468 headers.update({elem:eval(elem)})
470 if 'content_range' not in headers.keys():
472 headers['content_range'] = 'bytes %s-/*' % offset
474 headers['content_range'] = 'bytes */*'
476 for k,v in meta.items():
477 headers['x-object-meta-%s' %k.strip()] = v.strip()
478 data = f.read() if f else None
479 return self.post(path, data, headers=headers, params=params)
481 def update_object_using_chunks(self, container, object, f=stdin,
482 blocksize=1024, offset=None, meta={},
483 params={}, content_type=None, content_encoding=None,
484 content_disposition=None, account=None, **headers):
485 """updates an object (incremental upload)"""
486 account = account or self.account
487 path = '/%s/%s/%s' % (account, container, object)
488 headers = headers if not headers else {}
489 l = ['content_type', 'content_encoding', 'content_disposition']
490 l = [elem for elem in l if eval(elem)]
492 headers.update({elem:eval(elem)})
495 headers['content_range'] = 'bytes %s-/*' % offset
497 headers['content_range'] = 'bytes */*'
499 for k,v in meta.items():
501 headers['x-object-meta-%s' %k.strip()] = v
502 return self._chunked_transfer(path, 'POST', f, headers=headers,
503 blocksize=blocksize, params=params)
505 def _change_obj_location(self, src_container, src_object, dst_container,
506 dst_object, remove=False, meta={}, account=None,
507 content_type=None, **headers):
508 account = account or self.account
509 path = '/%s/%s/%s' % (account, dst_container, dst_object)
510 headers = {} if not headers else headers
511 for k, v in meta.items():
512 headers['x-object-meta-%s' % k] = v
514 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
516 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
517 headers['content_length'] = 0
519 headers['content_type'] = content_type
520 return self.put(path, headers=headers)
522 def copy_object(self, src_container, src_object, dst_container, dst_object,
523 meta={}, account=None, content_type=None, **headers):
524 """copies an object"""
525 account = account or self.account
526 return self._change_obj_location(src_container, src_object,
527 dst_container, dst_object, account=account,
528 remove=False, meta=meta,
529 content_type=content_type, **headers)
531 def move_object(self, src_container, src_object, dst_container,
532 dst_object, meta={}, account=None,
533 content_type=None, **headers):
534 """moves an object"""
535 account = account or self.account
536 return self._change_obj_location(src_container, src_object,
537 dst_container, dst_object,
538 account=account, remove=True,
539 meta=meta, content_type=content_type,
542 def delete_object(self, container, object, params={}, account=None):
543 """deletes an object"""
544 account = account or self.account
545 return self.delete('/%s/%s/%s' % (account, container, object),
548 def retrieve_object_metadata(self, container, object, restricted=False,
549 version=None, account=None):
551 set restricted to True to get only user defined metadata
553 account = account or self.account
554 path = '/%s/%s/%s' % (account, container, object)
555 prefix = 'x-object-meta-' if restricted else None
556 params = {'version':version} if version else {}
557 return self._get_metadata(path, prefix, params=params)
559 def update_object_metadata(self, container, object, account=None,
562 updates object's metadata
564 account = account or self.account
565 path = '/%s/%s/%s' % (account, container, object)
566 return self._update_metadata(path, 'object', **meta)
568 def delete_object_metadata(self, container, object, meta=[], account=None):
570 deletes object's metadata
572 account = account or self.account
573 path = '/%s/%s' % (account, container, object)
574 return self._delete_metadata(path, 'object', meta)
576 class Pithos_Client(OOS_Client):
577 """Pithos Storage Client. Extends OOS_Client"""
579 def _update_metadata(self, path, entity, **meta):
581 adds new and updates the values of previously set metadata
583 params = {'update':None}
585 prefix = 'x-%s-meta-' % entity
586 for k,v in meta.items():
587 k = '%s%s' % (prefix, k)
589 return self.post(path, headers=headers, params=params)
591 def _delete_metadata(self, path, entity, meta=[]):
593 delete previously set metadata
595 params = {'update':None}
597 prefix = 'x-%s-meta-' % entity
599 headers['%s%s' % (prefix, m)] = ''
600 return self.post(path, headers=headers, params=params)
602 # Storage Account Services
604 def list_containers(self, format='text', if_modified_since=None,
605 if_unmodified_since=None, limit=None, marker=None,
606 shared=False, until=None, account=None):
607 """returns a list with the account containers"""
608 account = account or self.account
609 params = {'until':until} if until else {}
611 params['shared'] = None
612 headers = {'if-modified-since':if_modified_since,
613 'if-unmodified-since':if_unmodified_since}
614 return OOS_Client.list_containers(self, account=account, format=format,
615 limit=limit, marker=marker,
616 params=params, **headers)
618 def retrieve_account_metadata(self, restricted=False, until=None,
620 """returns the account metadata"""
621 account = account or self.account
622 params = {'until':until} if until else {}
623 return OOS_Client.retrieve_account_metadata(self, account=account,
624 restricted=restricted,
627 def set_account_groups(self, account=None, **groups):
628 """create account groups"""
629 account = account or self.account
630 path = '/%s' % account
632 for k, v in groups.items():
633 headers['x-account-group-%s' % k] = v
634 params = {'update':None}
635 return self.post(path, headers=headers, params=params)
637 def retrieve_account_groups(self, account=None):
638 """returns the account groups"""
639 account = account or self.account
640 meta = self.retrieve_account_metadata(account=account)
641 prefix = 'x-account-group-'
642 prefixlen = len(prefix)
644 for key, val in meta.items():
645 if prefix and not key.startswith(prefix):
647 elif prefix and key.startswith(prefix):
648 key = key[prefixlen:]
652 def unset_account_groups(self, groups=[], account=None):
653 """delete account groups"""
654 account = account or self.account
655 path = '/%s' % account
658 headers['x-account-group-%s' % elem] = ''
659 params = {'update':None}
660 return self.post(path, headers=headers, params=params)
662 def reset_account_groups(self, account=None, **groups):
663 """overrides account groups"""
664 account = account or self.account
665 path = '/%s' % account
667 for k, v in groups.items():
669 headers['x-account-group-%s' % k] = v
670 meta = self.retrieve_account_metadata(restricted=True)
671 prefix = 'x-account-meta-'
672 for k,v in meta.items():
673 k = '%s%s' % (prefix, k)
675 return self.post(path, headers=headers)
677 # Storage Container Services
679 def list_objects(self, container, format='text',
680 limit=None, marker=None, prefix=None, delimiter=None,
681 path=None, shared=False, include_trashed=False, params={},
682 if_modified_since=None, if_unmodified_since=None, meta='',
683 until=None, account=None):
684 """returns a list with the container objects"""
685 account = account or self.account
686 params = {'until':until, 'meta':meta}
688 params['shared'] = None
689 args = locals().copy()
690 for elem in ['self', 'container', 'params', 'until', 'meta']:
692 return OOS_Client.list_objects(self, container, params=params, **args)
694 def retrieve_container_metadata(self, container, restricted=False,
695 until=None, account=None):
696 """returns container's metadata"""
697 account = account or self.account
698 params = {'until':until} if until else {}
699 return OOS_Client.retrieve_container_metadata(self, container,
701 restricted=restricted,
704 def set_container_policies(self, container, account=None,
706 """sets containers policies"""
707 account = account or self.account
708 path = '/%s/%s' % (account, container)
710 for key, val in policies.items():
711 headers['x-container-policy-%s' % key] = val
712 return self.post(path, headers=headers)
714 def delete_container(self, container, until=None, account=None):
715 """deletes a container or the container history until the date provided"""
716 account = account or self.account
717 params = {'until':until} if until else {}
718 return OOS_Client.delete_container(self, container, account=account,
721 # Storage Object Services
723 def retrieve_object(self, container, object, params={}, format='text',
724 range=None, if_range=None,
725 if_match=None, if_none_match=None,
726 if_modified_since=None, if_unmodified_since=None,
727 account=None, **headers):
728 """returns an object"""
729 account = account or self.account
731 l = ['range', 'if_range', 'if_match', 'if_none_match',
732 'if_modified_since', 'if_unmodified_since']
733 l = [elem for elem in l if eval(elem)]
735 headers.update({elem:eval(elem)})
737 params['hashmap'] = None
738 return OOS_Client.retrieve_object(self, container, object,
739 account=account, format=format,
740 params=params, **headers)
742 def retrieve_object_version(self, container, object, version,
743 format='text', range=None, if_range=None,
744 if_match=None, if_none_match=None,
745 if_modified_since=None, if_unmodified_since=None,
747 """returns a specific object version"""
748 account = account or self.account
749 args = locals().copy()
750 l = ['self', 'container', 'object']
753 params = {'version':version}
754 return self.retrieve_object(container, object, params=params, **args)
756 def retrieve_object_versionlist(self, container, object, range=None,
757 if_range=None, if_match=None,
758 if_none_match=None, if_modified_since=None,
759 if_unmodified_since=None, account=None):
760 """returns the object version list"""
761 account = account or self.account
762 args = locals().copy()
763 l = ['self', 'container', 'object']
767 return self.retrieve_object_version(container, object, version='list',
768 format='json', **args)
770 def create_zero_length_object(self, container, object,
771 meta={}, etag=None, content_type=None,
772 content_encoding=None,
773 content_disposition=None,
774 x_object_manifest=None, x_object_sharing=None,
775 x_object_public=None, account=None):
776 """createas a zero length object"""
777 account = account or self.account
778 args = locals().copy()
779 for elem in ['self', 'container', 'object']:
781 return OOS_Client.create_zero_length_object(self, container, object,
784 def create_object(self, container, object, f=stdin, format='text',
785 meta={}, params={}, etag=None, content_type=None,
786 content_encoding=None, content_disposition=None,
787 x_object_manifest=None, x_object_sharing=None,
788 x_object_public=None, account=None):
789 """creates an object"""
790 account = account or self.account
791 args = locals().copy()
792 for elem in ['self', 'container', 'object']:
795 params.update({'hashmap':None})
796 return OOS_Client.create_object(self, container, object, **args)
798 def create_object_using_chunks(self, container, object,
799 f=stdin, blocksize=1024, meta={}, etag=None,
800 content_type=None, content_encoding=None,
801 content_disposition=None,
802 x_object_sharing=None, x_object_manifest=None,
803 x_object_public=None, account=None):
804 """creates an object (incremental upload)"""
805 account = account or self.account
806 path = '/%s/%s/%s' % (account, container, object)
808 l = ['etag', 'content_type', 'content_encoding', 'content_disposition',
809 'x_object_sharing', 'x_object_manifest', 'x_object_public']
810 l = [elem for elem in l if eval(elem)]
812 headers.update({elem:eval(elem)})
813 headers.setdefault('content-type', 'application/octet-stream')
815 for k,v in meta.items():
817 headers['x-object-meta-%s' %k.strip()] = v
819 return self._chunked_transfer(path, 'PUT', f, headers=headers,
822 def create_object_by_hashmap(self, container, object, hashmap={},
823 meta={}, etag=None, content_encoding=None,
824 content_disposition=None, content_type=None,
825 x_object_sharing=None, x_object_manifest=None,
826 x_object_public = None, account=None):
827 """creates an object by uploading hashes representing data instead of data"""
828 account = account or self.account
829 args = locals().copy()
830 for elem in ['self', 'container', 'object', 'hashmap']:
834 data = json.dumps(hashmap)
836 raise Fault('Invalid formatting')
837 args['params'] = {'hashmap':None}
838 args['format'] = 'json'
840 return self.create_object(container, object, f=StringIO(data), **args)
842 def create_manifestation(self, container, object, manifest, account=None):
843 """creates a manifestation"""
844 account = account or self.account
845 headers={'x_object_manifest':manifest}
846 return self.create_object(container, object, f=None, account=account,
849 def update_object(self, container, object, f=stdin,
850 offset=None, meta={}, replace=False, content_length=None,
851 content_type=None, 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,
855 x_source_object=None, account=None):
856 """updates an object"""
857 account = account or self.account
858 args = locals().copy()
859 for elem in ['self', 'container', 'object', 'replace']:
862 args['params'] = {'update':None}
863 return OOS_Client.update_object(self, container, object, **args)
865 def update_object_using_chunks(self, container, object, f=stdin,
866 blocksize=1024, offset=None, meta={},
867 replace=False, content_type=None, content_encoding=None,
868 content_disposition=None, x_object_bytes=None,
869 x_object_manifest=None, x_object_sharing=None,
870 x_object_public=None, account=None):
871 """updates an object (incremental upload)"""
872 account = account or self.account
873 args = locals().copy()
874 for elem in ['self', 'container', 'object', 'replace']:
877 args['params'] = {'update':None}
878 return OOS_Client.update_object_using_chunks(self, container, object, **args)
880 def update_from_other_source(self, container, object, source,
881 offset=None, meta={}, content_range=None,
882 content_encoding=None, content_disposition=None,
883 x_object_bytes=None, x_object_manifest=None,
884 x_object_sharing=None, x_object_public=None, account=None):
885 """updates an object"""
886 account = account or self.account
887 args = locals().copy()
888 for elem in ['self', 'container', 'object', 'source']:
891 args['x_source_object'] = source
892 return self.update_object(container, object, f=None, **args)
894 def delete_object(self, container, object, until=None, account=None):
895 """deletes an object or the object history until the date provided"""
896 account = account or self.account
897 params = {'until':until} if until else {}
898 return OOS_Client.delete_object(self, container, object, params, account)
900 def trash_object(self, container, object):
901 """trashes an object"""
902 account = account or self.account
903 path = '/%s/%s' % (container, object)
904 meta = {'trash':'true'}
905 return self._update_metadata(path, 'object', **meta)
907 def restore_object(self, container, object, account=None):
908 """restores a trashed object"""
909 account = account or self.account
910 return self.delete_object_metadata(container, object, account, ['trash'])
912 def publish_object(self, container, object, account=None):
913 """sets a previously created object publicly accessible"""
914 account = account or self.account
915 path = '/%s/%s/%s' % (account, container, object)
917 headers['x_object_public'] = True
918 params = {'update':None}
919 return self.post(path, headers=headers, params=params)
921 def unpublish_object(self, container, object, account=None):
922 """unpublish an object"""
923 account = account or self.account
924 path = '/%s/%s/%s' % (account, container, object)
926 headers['x_object_public'] = False
927 params = {'update':None}
928 return self.post(path, headers=headers, params=params)
930 def copy_object(self, src_container, src_object, dst_container, dst_object,
931 meta={}, public=False, version=None, account=None,
933 """copies an object"""
934 account = account or self.account
936 headers['x_object_public'] = public
938 headers['x_source_version'] = version
939 return OOS_Client.copy_object(self, src_container, src_object,
940 dst_container, dst_object, meta=meta,
941 account=account, content_type=content_type,
944 def move_object(self, src_container, src_object, dst_container,
945 dst_object, meta={}, public=False,
946 account=None, content_type=None):
947 """moves an object"""
949 headers['x_object_public'] = public
950 return OOS_Client.move_object(self, src_container, src_object,
951 dst_container, dst_object, meta=meta,
952 account=account, content_type=content_type,
955 def list_shared_by_others(self, limit=None, marker=None, format='text'):
956 """lists other accounts that share objects to the user"""
957 l = ['limit', 'marker']
959 for elem in [elem for elem in l if eval(elem)]:
960 params[elem] = eval(elem)
961 return self._list('', format, params)
963 def share_object(self, container, object, l, read=True):
964 """gives access(read by default) to an object to a user/group list"""
965 action = 'read' if read else 'write'
966 sharing = '%s=%s' % (action, ','.join(l))
967 self.update_object(container, object, f=None, x_object_sharing=sharing)
969 def _encode_headers(headers):
971 for k, v in headers.items():
973 if v and type(v) == types.StringType:
974 v = urllib.quote(v, '/=,-* :"')