1 # Copyright 2011-2012 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, HTTPSConnection, HTTP
36 from xml.dom import minidom
37 from StringIO import StringIO
38 from urllib import quote, unquote
39 from urlparse import urlparse
47 ERROR_CODES = {304:'Not Modified',
53 411:'Length Required',
54 412:'Precondition Failed',
55 413:'Request Entity Too Large',
56 416:'Range Not Satisfiable',
57 422:'Unprocessable Entity',
58 500:'Internal Server Error',
59 501:'Not Implemented'}
61 class Fault(Exception):
62 def __init__(self, data='', status=None):
63 if data == '' and status in ERROR_CODES.keys():
64 data = ERROR_CODES[status]
65 Exception.__init__(self, data)
70 def __init__(self, url, token, account, verbose=False, debug=False):
71 """`url` can also include a port, e.g '127.0.0.1:8000'."""
74 self.account = account
75 self.verbose = verbose or debug
79 def _req(self, method, path, body=None, headers={}, format='text', params={}):
80 p = urlparse(self.url)
81 if p.scheme == 'http':
82 conn = HTTPConnection(p.netloc)
83 elif p.scheme == 'https':
84 conn = HTTPSConnection(p.netloc)
86 raise Exception('Unknown URL scheme')
88 full_path = _prepare_path(p.path + path, format, params)
91 kwargs['headers'] = _prepare_headers(headers)
92 kwargs['headers']['X-Auth-Token'] = self.token
95 kwargs['headers'].setdefault('content-type', 'application/octet-stream')
96 kwargs['headers'].setdefault('content-length', len(body) if body else 0)
98 #print '#', method, full_path, kwargs
99 #t1 = datetime.datetime.utcnow()
100 conn.request(method, full_path, **kwargs)
102 resp = conn.getresponse()
103 #t2 = datetime.datetime.utcnow()
104 #print 'response time:', str(t2-t1)
105 return _handle_response(resp, self.verbose, self.debug)
107 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
108 blocksize=1024, params={}):
109 """perfomrs a chunked request"""
110 p = urlparse(self.url)
111 if p.scheme == 'http':
112 conn = HTTPConnection(p.netloc)
113 elif p.scheme == 'https':
114 conn = HTTPSConnection(p.netloc)
116 raise Exception('Unknown URL scheme')
118 full_path = _prepare_path(p.path + path, params=params)
120 headers.setdefault('content-type', 'application/octet-stream')
122 conn.putrequest(method, full_path)
123 conn.putheader('x-auth-token', self.token)
124 conn.putheader('transfer-encoding', 'chunked')
125 for k,v in _prepare_headers(headers).items():
134 block = f.read(blocksize)
137 data = '%x\r\n%s\r\n' % (len(block), block)
150 resp = conn.getresponse()
151 return _handle_response(resp, self.verbose, self.debug)
153 def delete(self, path, format='text', params={}):
154 return self._req('DELETE', path, format=format, params=params)
156 def get(self, path, format='text', headers={}, params={}):
157 return self._req('GET', path, headers=headers, format=format,
160 def head(self, path, format='text', params={}):
161 return self._req('HEAD', path, format=format, params=params)
163 def post(self, path, body=None, format='text', headers=None, params={}):
164 return self._req('POST', path, body, headers=headers, format=format,
167 def put(self, path, body=None, format='text', headers=None, params={}):
168 return self._req('PUT', path, body, headers=headers, format=format,
171 def _list(self, path, format='text', params={}, **headers):
172 status, headers, data = self.get(path, format=format, headers=headers,
175 data = json.loads(data) if data else ''
176 elif format == 'xml':
177 data = minidom.parseString(data)
179 data = data.split('\n')[:-1] if data else ''
182 def _get_metadata(self, path, prefix=None, params={}):
183 status, headers, data = self.head(path, params=params)
184 prefixlen = len(prefix) if prefix else 0
186 for key, val in headers.items():
187 if prefix and not key.startswith(prefix):
189 elif prefix and key.startswith(prefix):
190 key = key[prefixlen:]
194 def _filter(self, l, d):
196 filter out from l elements having the metadata values provided
200 if type(elem) == types.DictionaryType:
202 k = 'x_object_meta_%s' % key
203 if k in elem.keys() and elem[k] == d[key]:
208 class OOS_Client(Client):
209 """Openstack Object Storage Client"""
211 def _update_metadata(self, path, entity, **meta):
212 """adds new and updates the values of previously set metadata"""
213 ex_meta = self.retrieve_account_metadata(restricted=True)
216 prefix = 'x-%s-meta-' % entity
217 for k,v in ex_meta.items():
218 k = '%s%s' % (prefix, k)
220 return self.post(path, headers=headers)
222 def _reset_metadata(self, path, entity, **meta):
224 overwrites all user defined metadata
227 prefix = 'x-%s-meta-' % entity
228 for k,v in meta.items():
229 k = '%s%s' % (prefix, k)
231 return self.post(path, headers=headers)
233 def _delete_metadata(self, path, entity, meta=[]):
234 """delete previously set metadata"""
235 ex_meta = self.retrieve_account_metadata(restricted=True)
237 prefix = 'x-%s-meta-' % entity
238 for k in ex_meta.keys():
240 headers['%s%s' % (prefix, k)] = ex_meta[k]
241 return self.post(path, headers=headers)
243 # Storage Account Services
245 def list_containers(self, format='text', limit=None,
246 marker=None, params={}, account=None, **headers):
247 """lists containers"""
248 account = account or self.account
249 path = '/%s' % account
250 params.update({'limit':limit, 'marker':marker})
251 return self._list(path, format, params, **headers)
253 def retrieve_account_metadata(self, restricted=False, account=None, **params):
254 """returns the account metadata"""
255 account = account or self.account
256 path = '/%s' % account
257 prefix = 'x-account-meta-' if restricted else None
258 return self._get_metadata(path, prefix, params)
260 def update_account_metadata(self, account=None, **meta):
261 """updates the account metadata"""
262 account = account or self.account
263 path = '/%s' % account
264 return self._update_metadata(path, 'account', **meta)
266 def delete_account_metadata(self, meta=[], account=None):
267 """deletes the account metadata"""
268 account = account or self.account
269 path = '/%s' % account
270 return self._delete_metadata(path, 'account', meta)
272 def reset_account_metadata(self, account=None, **meta):
273 """resets account metadata"""
274 account = account or self.account
275 path = '/%s' % account
276 return self._reset_metadata(path, 'account', **meta)
278 # Storage Container Services
280 def _filter_trashed(self, l):
281 return self._filter(l, {'trash':'true'})
283 def list_objects(self, container, format='text',
284 limit=None, marker=None, prefix=None, delimiter=None,
285 path=None, include_trashed=False, params={}, account=None,
287 """returns a list with the container objects"""
288 account = account or self.account
289 params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
290 'delimiter':delimiter, 'path':path})
291 l = self._list('/%s/%s' % (account, container), format, params,
293 #TODO support filter trashed with xml also
294 if format != 'xml' and not include_trashed:
295 l = self._filter_trashed(l)
298 def create_container(self, container, account=None, meta={}, **headers):
299 """creates a container"""
300 account = account or self.account
303 for k,v in meta.items():
304 headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
305 status, header, data = self.put('/%s/%s' % (account, container),
310 raise Fault(data, int(status))
313 def delete_container(self, container, params={}, account=None):
314 """deletes a container"""
315 account = account or self.account
316 return self.delete('/%s/%s' % (account, container), params=params)
318 def retrieve_container_metadata(self, container, restricted=False,
319 account=None, **params):
320 """returns the container metadata"""
321 account = account or self.account
322 prefix = 'x-container-meta-' if restricted else None
323 return self._get_metadata('/%s/%s' % (account, container), prefix,
326 def update_container_metadata(self, container, account=None, **meta):
327 """unpdates the container metadata"""
328 account = account or self.account
329 return self._update_metadata('/%s/%s' % (account, container),
332 def delete_container_metadata(self, container, meta=[], account=None):
333 """deletes the container metadata"""
334 account = account or self.account
335 path = '/%s/%s' % (account, container)
336 return self._delete_metadata(path, 'container', meta)
338 # Storage Object Services
340 def request_object(self, container, object, format='text', params={},
341 account=None, **headers):
342 """returns tuple containing the status, headers and data response for an object request"""
343 account = account or self.account
344 path = '/%s/%s/%s' % (account, container, object)
345 status, headers, data = self.get(path, format, headers, params)
346 return status, headers, data
348 def retrieve_object(self, container, object, format='text', params={},
349 account=None, **headers):
350 """returns an object's data"""
351 account = account or self.account
352 t = self.request_object(container, object, format, params, account,
356 data = json.loads(data) if data else ''
357 elif format == 'xml':
358 data = minidom.parseString(data)
361 def retrieve_object_hashmap(self, container, object, format='json', params={},
362 account=None, **headers):
363 """returns the hashmap representing object's data"""
366 params.update({'hashmap':None})
367 return self.retrieve_object(container, object, params, format, account, **headers)
369 def create_directory_marker(self, container, object, account=None):
370 """creates a dierectory marker"""
371 account = account or self.account
373 raise Fault('Directory markers have to be nested in a container')
374 h = {'content_type':'application/directory'}
375 return self.create_zero_length_object(container, object, account=account,
378 def create_object(self, container, object, f=stdin, format='text', meta={},
379 params={}, etag=None, content_type=None, content_encoding=None,
380 content_disposition=None, account=None, **headers):
381 """creates a zero-length object"""
382 account = account or self.account
383 path = '/%s/%s/%s' % (account, container, object)
384 for k, v in headers.items():
388 l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
389 l = [elem for elem in l if eval(elem)]
391 headers.update({elem:eval(elem)})
392 headers.setdefault('content-type', 'application/octet-stream')
394 for k,v in meta.items():
395 headers['x-object-meta-%s' %k.strip()] = v.strip()
396 data = f.read() if f else None
397 return self.put(path, data, format, headers=headers, params=params)
399 def create_zero_length_object(self, container, object, meta={}, etag=None,
400 content_type=None, content_encoding=None,
401 content_disposition=None, account=None,
403 account = account or self.account
404 args = locals().copy()
405 for elem in ['self', 'container', 'headers', 'account']:
408 return self.create_object(container, account=account, f=None, **args)
410 def update_object(self, container, object, f=stdin,
411 offset=None, meta={}, params={}, content_length=None,
412 content_type=None, content_encoding=None,
413 content_disposition=None, account=None, **headers):
414 account = account or self.account
415 path = '/%s/%s/%s' % (account, container, object)
416 for k, v in headers.items():
420 l = ['content_encoding', 'content_disposition', 'content_type',
422 l = [elem for elem in l if eval(elem)]
424 headers.update({elem:eval(elem)})
426 if 'content_range' not in headers.keys():
428 headers['content_range'] = 'bytes %s-/*' % offset
430 headers['content_range'] = 'bytes */*'
432 for k,v in meta.items():
433 headers['x-object-meta-%s' %k.strip()] = v.strip()
434 data = f.read() if f else None
435 return self.post(path, data, headers=headers, params=params)
437 def update_object_using_chunks(self, container, object, f=stdin,
438 blocksize=1024, offset=None, meta={},
439 params={}, content_type=None, content_encoding=None,
440 content_disposition=None, account=None, **headers):
441 """updates an object (incremental upload)"""
442 account = account or self.account
443 path = '/%s/%s/%s' % (account, container, object)
444 headers = headers if not headers else {}
445 l = ['content_type', 'content_encoding', 'content_disposition']
446 l = [elem for elem in l if eval(elem)]
448 headers.update({elem:eval(elem)})
451 headers['content_range'] = 'bytes %s-/*' % offset
453 headers['content_range'] = 'bytes */*'
455 for k,v in meta.items():
457 headers['x-object-meta-%s' %k.strip()] = v
458 return self._chunked_transfer(path, 'POST', f, headers=headers,
459 blocksize=blocksize, params=params)
461 def _change_obj_location(self, src_container, src_object, dst_container,
462 dst_object, remove=False, meta={}, account=None,
463 content_type=None, **headers):
464 account = account or self.account
465 path = '/%s/%s/%s' % (account, dst_container, dst_object)
466 headers = {} if not headers else headers
468 for k, v in meta.items():
469 headers['x-object-meta-%s' % k] = v
471 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
473 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
474 headers['content_length'] = 0
476 headers['content_type'] = content_type
478 params['ignore_content_type'] = ''
479 return self.put(path, headers=headers, params=params)
481 def copy_object(self, src_container, src_object, dst_container, dst_object,
482 meta={}, account=None, content_type=None, **headers):
483 """copies an object"""
484 account = account or self.account
485 return self._change_obj_location(src_container, src_object,
486 dst_container, dst_object, account=account,
487 remove=False, meta=meta,
488 content_type=content_type, **headers)
490 def move_object(self, src_container, src_object, dst_container,
491 dst_object, meta={}, account=None,
492 content_type=None, **headers):
493 """moves an object"""
494 account = account or self.account
495 return self._change_obj_location(src_container, src_object,
496 dst_container, dst_object,
497 account=account, remove=True,
498 meta=meta, content_type=content_type,
501 def delete_object(self, container, object, params={}, account=None):
502 """deletes an object"""
503 account = account or self.account
504 return self.delete('/%s/%s/%s' % (account, container, object),
507 def retrieve_object_metadata(self, container, object, restricted=False,
508 version=None, account=None):
510 set restricted to True to get only user defined metadata
512 account = account or self.account
513 path = '/%s/%s/%s' % (account, container, object)
514 prefix = 'x-object-meta-' if restricted else None
515 params = {'version':version} if version else {}
516 return self._get_metadata(path, prefix, params=params)
518 def update_object_metadata(self, container, object, account=None,
521 updates object's metadata
523 account = account or self.account
524 path = '/%s/%s/%s' % (account, container, object)
525 return self._update_metadata(path, 'object', **meta)
527 def delete_object_metadata(self, container, object, meta=[], account=None):
529 deletes object's metadata
531 account = account or self.account
532 path = '/%s/%s' % (account, container, object)
533 return self._delete_metadata(path, 'object', meta)
535 class Pithos_Client(OOS_Client):
536 """Pithos Storage Client. Extends OOS_Client"""
538 def _update_metadata(self, path, entity, **meta):
540 adds new and updates the values of previously set metadata
542 params = {'update':None}
544 prefix = 'x-%s-meta-' % entity
545 for k,v in meta.items():
546 k = '%s%s' % (prefix, k)
548 return self.post(path, headers=headers, params=params)
550 def _delete_metadata(self, path, entity, meta=[]):
552 delete previously set metadata
554 params = {'update':None}
556 prefix = 'x-%s-meta-' % entity
558 headers['%s%s' % (prefix, m)] = ''
559 return self.post(path, headers=headers, params=params)
561 # Storage Account Services
563 def list_containers(self, format='text', if_modified_since=None,
564 if_unmodified_since=None, limit=None, marker=None,
565 shared=False, until=None, account=None, public=False):
566 """returns a list with the account containers"""
567 account = account or self.account
568 params = {'until':until} if until else {}
570 params['shared'] = None
572 params['public'] = None
573 headers = {'if-modified-since':if_modified_since,
574 'if-unmodified-since':if_unmodified_since}
575 return OOS_Client.list_containers(self, account=account, format=format,
576 limit=limit, marker=marker,
577 params=params, **headers)
579 def retrieve_account_metadata(self, restricted=False, until=None,
581 """returns the account metadata"""
582 account = account or self.account
583 params = {'until':until} if until else {}
584 return OOS_Client.retrieve_account_metadata(self, account=account,
585 restricted=restricted,
588 def set_account_groups(self, account=None, **groups):
589 """create account groups"""
590 account = account or self.account
591 path = '/%s' % account
593 for k, v in groups.items():
594 headers['x-account-group-%s' % k] = v
595 params = {'update':None}
596 return self.post(path, headers=headers, params=params)
598 def retrieve_account_groups(self, account=None):
599 """returns the account groups"""
600 account = account or self.account
601 meta = self.retrieve_account_metadata(account=account)
602 prefix = 'x-account-group-'
603 prefixlen = len(prefix)
605 for key, val in meta.items():
606 if prefix and not key.startswith(prefix):
608 elif prefix and key.startswith(prefix):
609 key = key[prefixlen:]
613 def unset_account_groups(self, groups=[], account=None):
614 """delete account groups"""
615 account = account or self.account
616 path = '/%s' % account
619 headers['x-account-group-%s' % elem] = ''
620 params = {'update':None}
621 return self.post(path, headers=headers, params=params)
623 def reset_account_groups(self, account=None, **groups):
624 """overrides account groups"""
625 account = account or self.account
626 path = '/%s' % account
628 for k, v in groups.items():
630 headers['x-account-group-%s' % k] = v
631 meta = self.retrieve_account_metadata(restricted=True)
632 prefix = 'x-account-meta-'
633 for k,v in meta.items():
634 k = '%s%s' % (prefix, k)
636 return self.post(path, headers=headers)
638 # Storage Container Services
639 def create_container(self, container, account=None, meta={}, policies={}):
640 """creates a container"""
642 for k, v in policies.items():
643 args['X-Container-Policy-%s' % k.capitalize()] = v
644 return OOS_Client.create_container(self, container, account, meta, **args)
646 def list_objects(self, container, format='text',
647 limit=None, marker=None, prefix=None, delimiter=None,
648 path=None, shared=False, include_trashed=False, params={},
649 if_modified_since=None, if_unmodified_since=None, meta='',
650 until=None, account=None, public=False):
651 """returns a list with the container objects"""
652 account = account or self.account
653 params = {'until':until, 'meta':meta}
655 params['shared'] = None
657 params['public'] = None
658 args = locals().copy()
659 for elem in ['self', 'container', 'params', 'until', 'meta']:
661 return OOS_Client.list_objects(self, container, params=params, **args)
663 def retrieve_container_metadata(self, container, restricted=False,
664 until=None, account=None):
665 """returns container's metadata"""
666 account = account or self.account
667 params = {'until':until} if until else {}
668 return OOS_Client.retrieve_container_metadata(self, container,
670 restricted=restricted,
673 def set_container_policies(self, container, account=None,
675 """sets containers policies"""
676 account = account or self.account
677 path = '/%s/%s' % (account, container)
679 for key, val in policies.items():
680 headers['x-container-policy-%s' % key] = val
681 return self.post(path, headers=headers)
683 def update_container_data(self, container, f=stdin):
684 """adds blocks of data to the container"""
685 account = self.account
686 path = '/%s/%s' % (account, container)
687 params = {'update': None}
688 headers = {'content_type': 'application/octet-stream'}
689 data = f.read() if f else None
690 headers['content_length'] = len(data)
691 return self.post(path, data, headers=headers, params=params)
693 def delete_container(self, container, until=None, account=None):
694 """deletes a container or the container history until the date provided"""
695 account = account or self.account
696 params = {'until':until} if until else {}
697 return OOS_Client.delete_container(self, container, account=account,
700 # Storage Object Services
702 def retrieve_object(self, container, object, params={}, format='text',
703 range=None, if_range=None,
704 if_match=None, if_none_match=None,
705 if_modified_since=None, if_unmodified_since=None,
706 account=None, **headers):
707 """returns an object"""
708 account = account or self.account
710 l = ['range', 'if_range', 'if_match', 'if_none_match',
711 'if_modified_since', 'if_unmodified_since']
712 l = [elem for elem in l if eval(elem)]
714 headers.update({elem:eval(elem)})
716 params['hashmap'] = None
717 return OOS_Client.retrieve_object(self, container, object,
718 account=account, format=format,
719 params=params, **headers)
721 def retrieve_object_version(self, container, object, version,
722 format='text', range=None, if_range=None,
723 if_match=None, if_none_match=None,
724 if_modified_since=None, if_unmodified_since=None,
726 """returns a specific object version"""
727 account = account or self.account
728 args = locals().copy()
729 l = ['self', 'container', 'object']
732 params = {'version':version}
733 return self.retrieve_object(container, object, params=params, **args)
735 def retrieve_object_versionlist(self, container, object, range=None,
736 if_range=None, if_match=None,
737 if_none_match=None, if_modified_since=None,
738 if_unmodified_since=None, account=None):
739 """returns the object version list"""
740 account = account or self.account
741 args = locals().copy()
742 l = ['self', 'container', 'object']
746 return self.retrieve_object_version(container, object, version='list',
747 format='json', **args)
749 def create_zero_length_object(self, container, object,
750 meta={}, etag=None, content_type=None,
751 content_encoding=None,
752 content_disposition=None,
753 x_object_manifest=None, x_object_sharing=None,
754 x_object_public=None, account=None):
755 """createas a zero length object"""
756 account = account or self.account
757 args = locals().copy()
758 for elem in ['self', 'container', 'object']:
760 return OOS_Client.create_zero_length_object(self, container, object,
763 def create_object(self, container, object, f=stdin, format='text',
764 meta={}, params={}, etag=None, content_type=None,
765 content_encoding=None, content_disposition=None,
766 x_object_manifest=None, x_object_sharing=None,
767 x_object_public=None, account=None):
768 """creates an object"""
769 account = account or self.account
770 args = locals().copy()
771 for elem in ['self', 'container', 'object']:
774 params.update({'hashmap':None})
775 return OOS_Client.create_object(self, container, object, **args)
777 def create_object_using_chunks(self, container, object,
778 f=stdin, blocksize=1024, meta={}, etag=None,
779 content_type=None, content_encoding=None,
780 content_disposition=None,
781 x_object_sharing=None, x_object_manifest=None,
782 x_object_public=None, account=None):
783 """creates an object (incremental upload)"""
784 account = account or self.account
785 path = '/%s/%s/%s' % (account, container, object)
787 l = ['etag', 'content_type', 'content_encoding', 'content_disposition',
788 'x_object_sharing', 'x_object_manifest', 'x_object_public']
789 l = [elem for elem in l if eval(elem)]
791 headers.update({elem:eval(elem)})
792 headers.setdefault('content-type', 'application/octet-stream')
794 for k,v in meta.items():
796 headers['x-object-meta-%s' %k.strip()] = v
798 return self._chunked_transfer(path, 'PUT', f, headers=headers,
801 def create_object_by_hashmap(self, container, object, hashmap={},
802 meta={}, etag=None, content_encoding=None,
803 content_disposition=None, content_type=None,
804 x_object_sharing=None, x_object_manifest=None,
805 x_object_public = None, account=None):
806 """creates an object by uploading hashes representing data instead of data"""
807 account = account or self.account
808 args = locals().copy()
809 for elem in ['self', 'container', 'object', 'hashmap']:
813 data = json.dumps(hashmap)
815 raise Fault('Invalid formatting')
816 args['params'] = {'hashmap':None}
817 args['format'] = 'json'
819 return self.create_object(container, object, f=StringIO(data), **args)
821 def create_manifestation(self, container, object, manifest, account=None):
822 """creates a manifestation"""
823 account = account or self.account
824 headers={'x_object_manifest':manifest}
825 return self.create_object(container, object, f=None, account=account,
828 def update_object(self, container, object, f=stdin,
829 offset=None, meta={}, replace=False, content_length=None,
830 content_type=None, content_range=None,
831 content_encoding=None, content_disposition=None,
832 x_object_bytes=None, x_object_manifest=None,
833 x_object_sharing=None, x_object_public=None,
834 x_source_object=None, account=None):
835 """updates an object"""
836 account = account or self.account
837 args = locals().copy()
838 for elem in ['self', 'container', 'object', 'replace']:
841 args['params'] = {'update':None}
842 return OOS_Client.update_object(self, container, object, **args)
844 def update_object_using_chunks(self, container, object, f=stdin,
845 blocksize=1024, offset=None, meta={},
846 replace=False, content_type=None, content_encoding=None,
847 content_disposition=None, x_object_bytes=None,
848 x_object_manifest=None, x_object_sharing=None,
849 x_object_public=None, account=None):
850 """updates an object (incremental upload)"""
851 account = account or self.account
852 args = locals().copy()
853 for elem in ['self', 'container', 'object', 'replace']:
856 args['params'] = {'update':None}
857 return OOS_Client.update_object_using_chunks(self, container, object, **args)
859 def update_from_other_source(self, container, object, source,
860 offset=None, meta={}, content_range=None,
861 content_encoding=None, content_disposition=None,
862 x_object_bytes=None, x_object_manifest=None,
863 x_object_sharing=None, x_object_public=None, account=None):
864 """updates an object"""
865 account = account or self.account
866 args = locals().copy()
867 for elem in ['self', 'container', 'object', 'source']:
870 args['x_source_object'] = source
871 return self.update_object(container, object, f=None, **args)
873 def delete_object(self, container, object, until=None, account=None):
874 """deletes an object or the object history until the date provided"""
875 account = account or self.account
876 params = {'until':until} if until else {}
877 return OOS_Client.delete_object(self, container, object, params, account)
879 def trash_object(self, container, object):
880 """trashes an object"""
881 account = account or self.account
882 path = '/%s/%s' % (container, object)
883 meta = {'trash':'true'}
884 return self._update_metadata(path, 'object', **meta)
886 def restore_object(self, container, object, account=None):
887 """restores a trashed object"""
888 account = account or self.account
889 return self.delete_object_metadata(container, object, account, ['trash'])
891 def publish_object(self, container, object, account=None):
892 """sets a previously created object publicly accessible"""
893 account = account or self.account
894 path = '/%s/%s/%s' % (account, container, object)
896 headers['x_object_public'] = True
897 params = {'update':None}
898 return self.post(path, headers=headers, params=params)
900 def unpublish_object(self, container, object, account=None):
901 """unpublish an object"""
902 account = account or self.account
903 path = '/%s/%s/%s' % (account, container, object)
905 headers['x_object_public'] = False
906 params = {'update':None}
907 return self.post(path, headers=headers, params=params)
909 def copy_object(self, src_container, src_object, dst_container, dst_object,
910 meta={}, public=False, version=None, account=None,
912 """copies an object"""
913 account = account or self.account
915 headers['x_object_public'] = public
917 headers['x_source_version'] = version
918 return OOS_Client.copy_object(self, src_container, src_object,
919 dst_container, dst_object, meta=meta,
920 account=account, content_type=content_type,
923 def move_object(self, src_container, src_object, dst_container,
924 dst_object, meta={}, public=False,
925 account=None, content_type=None):
926 """moves an object"""
928 headers['x_object_public'] = public
929 return OOS_Client.move_object(self, src_container, src_object,
930 dst_container, dst_object, meta=meta,
931 account=account, content_type=content_type,
934 def list_shared_by_others(self, limit=None, marker=None, format='text'):
935 """lists other accounts that share objects to the user"""
936 l = ['limit', 'marker']
938 for elem in [elem for elem in l if eval(elem)]:
939 params[elem] = eval(elem)
940 return self._list('', format, params)
942 def share_object(self, container, object, l, read=True):
943 """gives access(read by default) to an object to a user/group list"""
944 action = 'read' if read else 'write'
945 sharing = '%s=%s' % (action, ','.join(l))
946 self.update_object(container, object, f=None, x_object_sharing=sharing)
948 def _prepare_path(path, format='text', params={}):
949 full_path = '%s?format=%s' % (quote(path), format)
951 for k,v in params.items():
952 value = quote(str(v)) if v else ''
953 full_path = '%s&%s=%s' %(full_path, quote(k), value)
956 def _prepare_headers(headers):
957 for k,v in headers.items():
959 k = k.replace('_', '-')
960 headers[quote(k)] = quote(v, safe='/=,:@ *"') if type(v) == types.StringType else v
963 def _handle_response(response, verbose=False, debug=False):
964 headers = response.getheaders()
965 headers = dict((unquote(h), unquote(v)) for h,v in headers)
968 print '%d %s' % (response.status, response.reason)
969 for key, val in headers.items():
970 print '%s: %s' % (key.capitalize(), val)
973 length = response.getheader('content-length', None)
974 data = response.read(length)
979 if int(response.status) in ERROR_CODES.keys():
980 raise Fault(data, int(response.status))
982 #print '**', response.status, headers, data, '\n'
983 return response.status, headers, data