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'}
62 class Fault(Exception):
63 def __init__(self, data='', status=None):
64 if data == '' and status in ERROR_CODES.keys():
65 data = ERROR_CODES[status]
66 Exception.__init__(self, data)
72 def __init__(self, url, token, account, verbose=False, debug=False):
73 """`url` can also include a port, e.g '127.0.0.1:8000'."""
76 self.account = account
77 self.verbose = verbose or debug
81 def _req(self, method, path, body=None, headers={}, format='text', params={}):
82 p = urlparse(self.url)
83 if p.scheme == 'http':
84 conn = HTTPConnection(p.netloc)
85 elif p.scheme == 'https':
86 conn = HTTPSConnection(p.netloc)
88 raise Exception('Unknown URL scheme')
90 full_path = _prepare_path(p.path + path, format, params)
93 kwargs['headers'] = _prepare_headers(headers)
94 kwargs['headers']['X-Auth-Token'] = self.token
97 kwargs['headers'].setdefault(
98 'content-type', 'application/octet-stream')
99 kwargs['headers'].setdefault('content-length', len(body)
102 #print '#', method, full_path, kwargs
103 #t1 = datetime.datetime.utcnow()
104 conn.request(method, full_path, **kwargs)
106 resp = conn.getresponse()
107 #t2 = datetime.datetime.utcnow()
108 #print 'response time:', str(t2-t1)
109 return _handle_response(resp, self.verbose, self.debug)
111 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
112 blocksize=1024, params={}):
113 """perfomrs a chunked request"""
114 p = urlparse(self.url)
115 if p.scheme == 'http':
116 conn = HTTPConnection(p.netloc)
117 elif p.scheme == 'https':
118 conn = HTTPSConnection(p.netloc)
120 raise Exception('Unknown URL scheme')
122 full_path = _prepare_path(p.path + path, params=params)
124 headers.setdefault('content-type', 'application/octet-stream')
126 conn.putrequest(method, full_path)
127 conn.putheader('x-auth-token', self.token)
128 conn.putheader('transfer-encoding', 'chunked')
129 for k, v in _prepare_headers(headers).items():
138 block = f.read(blocksize)
141 data = '%x\r\n%s\r\n' % (len(block), block)
154 resp = conn.getresponse()
155 return _handle_response(resp, self.verbose, self.debug)
157 def delete(self, path, format='text', params={}):
158 return self._req('DELETE', path, format=format, params=params)
160 def get(self, path, format='text', headers={}, params={}):
161 return self._req('GET', path, headers=headers, format=format,
164 def head(self, path, format='text', params={}):
165 return self._req('HEAD', path, format=format, params=params)
167 def post(self, path, body=None, format='text', headers=None, params={}):
168 return self._req('POST', path, body, headers=headers, format=format,
171 def put(self, path, body=None, format='text', headers=None, params={}):
172 return self._req('PUT', path, body, headers=headers, format=format,
175 def _list(self, path, format='text', params={}, **headers):
176 status, headers, data = self.get(path, format=format, headers=headers,
179 data = json.loads(data) if data else ''
180 elif format == 'xml':
181 data = minidom.parseString(data)
183 data = data.split('\n')[:-1] if data else ''
186 def _get_metadata(self, path, prefix=None, params={}):
187 status, headers, data = self.head(path, params=params)
188 prefixlen = len(prefix) if prefix else 0
190 for key, val in headers.items():
191 if prefix and not key.startswith(prefix):
193 elif prefix and key.startswith(prefix):
194 key = key[prefixlen:]
198 def _filter(self, l, d):
200 filter out from l elements having the metadata values provided
204 if isinstance(elem, types.DictionaryType):
206 k = 'x_object_meta_%s' % key
207 if k in elem.keys() and elem[k] == d[key]:
213 class OOS_Client(Client):
214 """Openstack Object Storage Client"""
216 def _update_metadata(self, path, entity, **meta):
217 """adds new and updates the values of previously set metadata"""
218 ex_meta = self.retrieve_account_metadata(restricted=True)
221 prefix = 'x-%s-meta-' % entity
222 for k, v in ex_meta.items():
223 k = '%s%s' % (prefix, k)
225 return self.post(path, headers=headers)
227 def _reset_metadata(self, path, entity, **meta):
229 overwrites all user defined metadata
232 prefix = 'x-%s-meta-' % entity
233 for k, v in meta.items():
234 k = '%s%s' % (prefix, k)
236 return self.post(path, headers=headers)
238 def _delete_metadata(self, path, entity, meta=[]):
239 """delete previously set metadata"""
240 ex_meta = self.retrieve_account_metadata(restricted=True)
242 prefix = 'x-%s-meta-' % entity
243 for k in ex_meta.keys():
245 headers['%s%s' % (prefix, k)] = ex_meta[k]
246 return self.post(path, headers=headers)
248 # Storage Account Services
250 def list_containers(self, format='text', limit=None,
251 marker=None, params={}, account=None, **headers):
252 """lists containers"""
253 account = account or self.account
254 path = '/%s' % account
255 params.update({'limit': limit, 'marker': marker})
256 return self._list(path, format, params, **headers)
258 def retrieve_account_metadata(self, restricted=False, account=None, **params):
259 """returns the account metadata"""
260 account = account or self.account
261 path = '/%s' % account
262 prefix = 'x-account-meta-' if restricted else None
263 return self._get_metadata(path, prefix, params)
265 def update_account_metadata(self, account=None, **meta):
266 """updates the account metadata"""
267 account = account or self.account
268 path = '/%s' % account
269 return self._update_metadata(path, 'account', **meta)
271 def delete_account_metadata(self, meta=[], account=None):
272 """deletes the account metadata"""
273 account = account or self.account
274 path = '/%s' % account
275 return self._delete_metadata(path, 'account', meta)
277 def reset_account_metadata(self, account=None, **meta):
278 """resets account metadata"""
279 account = account or self.account
280 path = '/%s' % account
281 return self._reset_metadata(path, 'account', **meta)
283 # Storage Container Services
285 def _filter_trashed(self, l):
286 return self._filter(l, {'trash': 'true'})
288 def list_objects(self, container, format='text',
289 limit=None, marker=None, prefix=None, delimiter=None,
290 path=None, include_trashed=False, params={}, account=None,
292 """returns a list with the container objects"""
293 account = account or self.account
294 params.update({'limit': limit, 'marker': marker, 'prefix': prefix,
295 'delimiter': delimiter, 'path': path})
296 l = self._list('/%s/%s' % (account, container), format, params,
298 #TODO support filter trashed with xml also
299 if format != 'xml' and not include_trashed:
300 l = self._filter_trashed(l)
303 def create_container(self, container, account=None, meta={}, **headers):
304 """creates a container"""
305 account = account or self.account
308 for k, v in meta.items():
309 headers['x-container-meta-%s' % k.strip().upper()] = v.strip()
310 status, header, data = self.put('/%s/%s' % (account, container),
315 raise Fault(data, int(status))
318 def delete_container(self, container, params={}, account=None):
319 """deletes a container"""
320 account = account or self.account
321 return self.delete('/%s/%s' % (account, container), params=params)
323 def retrieve_container_metadata(self, container, restricted=False,
324 account=None, **params):
325 """returns the container metadata"""
326 account = account or self.account
327 prefix = 'x-container-meta-' if restricted else None
328 return self._get_metadata('/%s/%s' % (account, container), prefix,
331 def update_container_metadata(self, container, account=None, **meta):
332 """unpdates the container metadata"""
333 account = account or self.account
334 return self._update_metadata('/%s/%s' % (account, container),
337 def delete_container_metadata(self, container, meta=[], account=None):
338 """deletes the container metadata"""
339 account = account or self.account
340 path = '/%s/%s' % (account, container)
341 return self._delete_metadata(path, 'container', meta)
343 # Storage Object Services
345 def request_object(self, container, object, format='text', params={},
346 account=None, **headers):
347 """returns tuple containing the status, headers and data response for an object request"""
348 account = account or self.account
349 path = '/%s/%s/%s' % (account, container, object)
350 status, headers, data = self.get(path, format, headers, params)
351 return status, headers, data
353 def retrieve_object(self, container, object, format='text', params={},
354 account=None, **headers):
355 """returns an object's data"""
356 account = account or self.account
357 t = self.request_object(container, object, format, params, account,
361 data = json.loads(data) if data else ''
362 elif format == 'xml':
363 data = minidom.parseString(data)
366 def retrieve_object_hashmap(
367 self, container, object, format='json', params={},
368 account=None, **headers):
369 """returns the hashmap representing object's data"""
372 params.update({'hashmap': None})
373 return self.retrieve_object(container, object, params, format, account, **headers)
375 def create_directory_marker(self, container, object, account=None):
376 """creates a dierectory marker"""
377 account = account or self.account
379 raise Fault('Directory markers have to be nested in a container')
380 h = {'content_type': 'application/directory'}
381 return self.create_zero_length_object(
382 container, object, account=account,
385 def create_object(self, container, object, f=stdin, format='text', meta={},
386 params={}, etag=None, content_type=None, content_encoding=None,
387 content_disposition=None, account=None, **headers):
388 """creates a zero-length object"""
389 account = account or self.account
390 path = '/%s/%s/%s' % (account, container, object)
391 for k, v in headers.items():
395 l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
396 l = [elem for elem in l if eval(elem)]
398 headers.update({elem: eval(elem)})
399 headers.setdefault('content-type', 'application/octet-stream')
401 for k, v in meta.items():
402 headers['x-object-meta-%s' % k.strip()] = v.strip()
403 data = f.read() if f else None
404 return self.put(path, data, format, headers=headers, params=params)
406 def create_zero_length_object(self, container, object, meta={}, etag=None,
407 content_type=None, content_encoding=None,
408 content_disposition=None, account=None,
410 account = account or self.account
411 args = locals().copy()
412 for elem in ['self', 'container', 'headers', 'account']:
415 return self.create_object(container, account=account, f=None, **args)
417 def update_object(self, container, object, f=stdin,
418 offset=None, meta={}, params={}, content_length=None,
419 content_type=None, content_encoding=None,
420 content_disposition=None, account=None, **headers):
421 account = account or self.account
422 path = '/%s/%s/%s' % (account, container, object)
423 for k, v in headers.items():
427 l = ['content_encoding', 'content_disposition', 'content_type',
429 l = [elem for elem in l if eval(elem)]
431 headers.update({elem: eval(elem)})
433 if 'content_range' not in headers.keys():
434 if offset is not None:
435 headers['content_range'] = 'bytes %s-/*' % offset
437 headers['content_range'] = 'bytes */*'
439 for k, v in meta.items():
440 headers['x-object-meta-%s' % k.strip()] = v.strip()
441 data = f.read() if f else None
442 return self.post(path, data, headers=headers, params=params)
444 def update_object_using_chunks(self, container, object, f=stdin,
445 blocksize=1024, offset=None, meta={},
446 params={}, content_type=None, content_encoding=None,
447 content_disposition=None, account=None, **headers):
448 """updates an object (incremental upload)"""
449 account = account or self.account
450 path = '/%s/%s/%s' % (account, container, object)
451 headers = headers if not headers else {}
452 l = ['content_type', 'content_encoding', 'content_disposition']
453 l = [elem for elem in l if eval(elem)]
455 headers.update({elem: eval(elem)})
457 if offset is not None:
458 headers['content_range'] = 'bytes %s-/*' % offset
460 headers['content_range'] = 'bytes */*'
462 for k, v in meta.items():
464 headers['x-object-meta-%s' % k.strip()] = v
465 return self._chunked_transfer(path, 'POST', f, headers=headers,
466 blocksize=blocksize, params=params)
468 def _change_obj_location(self, src_container, src_object, dst_container,
469 dst_object, remove=False, meta={}, account=None,
470 content_type=None, delimiter=None, **headers):
471 account = account or self.account
472 path = '/%s/%s/%s' % (account, dst_container, dst_object)
473 headers = {} if not headers else headers
475 for k, v in meta.items():
476 headers['x-object-meta-%s' % k] = v
478 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
480 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
481 headers['content_length'] = 0
483 headers['content_type'] = content_type
485 params['ignore_content_type'] = ''
487 params['delimiter'] = delimiter
488 return self.put(path, headers=headers, params=params)
490 def copy_object(self, src_container, src_object, dst_container, dst_object,
491 meta={}, account=None, content_type=None, delimiter=None, **headers):
492 """copies an object"""
493 account = account or self.account
494 return self._change_obj_location(src_container, src_object,
495 dst_container, dst_object, account=account,
496 remove=False, meta=meta,
497 content_type=content_type, delimiter=delimiter, **headers)
499 def move_object(self, src_container, src_object, dst_container,
500 dst_object, meta={}, account=None,
501 content_type=None, **headers):
502 """moves an object"""
503 account = account or self.account
504 return self._change_obj_location(src_container, src_object,
505 dst_container, dst_object,
506 account=account, remove=True,
507 meta=meta, content_type=content_type,
510 def delete_object(self, container, object, params={}, account=None):
511 """deletes an object"""
512 account = account or self.account
513 return self.delete('/%s/%s/%s' % (account, container, object),
516 def retrieve_object_metadata(self, container, object, restricted=False,
517 version=None, account=None):
519 set restricted to True to get only user defined metadata
521 account = account or self.account
522 path = '/%s/%s/%s' % (account, container, object)
523 prefix = 'x-object-meta-' if restricted else None
524 params = {'version': version} if version else {}
525 return self._get_metadata(path, prefix, params=params)
527 def update_object_metadata(self, container, object, account=None,
530 updates object's metadata
532 account = account or self.account
533 path = '/%s/%s/%s' % (account, container, object)
534 return self._update_metadata(path, 'object', **meta)
536 def delete_object_metadata(self, container, object, meta=[], account=None):
538 deletes object's metadata
540 account = account or self.account
541 path = '/%s/%s' % (account, container, object)
542 return self._delete_metadata(path, 'object', meta)
545 class Pithos_Client(OOS_Client):
546 """Pithos Storage Client. Extends OOS_Client"""
548 def _update_metadata(self, path, entity, **meta):
550 adds new and updates the values of previously set metadata
552 params = {'update': None}
554 prefix = 'x-%s-meta-' % entity
555 for k, v in meta.items():
556 k = '%s%s' % (prefix, k)
558 return self.post(path, headers=headers, params=params)
560 def _delete_metadata(self, path, entity, meta=[]):
562 delete previously set metadata
564 params = {'update': None}
566 prefix = 'x-%s-meta-' % entity
568 headers['%s%s' % (prefix, m)] = ''
569 return self.post(path, headers=headers, params=params)
571 # Storage Account Services
573 def list_containers(self, format='text', if_modified_since=None,
574 if_unmodified_since=None, limit=None, marker=None,
575 shared=False, until=None, account=None, public=False):
576 """returns a list with the account containers"""
577 account = account or self.account
578 params = {'until': until} if until else {}
580 params['shared'] = None
582 params['public'] = None
583 headers = {'if-modified-since': if_modified_since,
584 'if-unmodified-since': if_unmodified_since}
585 return OOS_Client.list_containers(self, account=account, format=format,
586 limit=limit, marker=marker,
587 params=params, **headers)
589 def retrieve_account_metadata(self, restricted=False, until=None,
591 """returns the account metadata"""
592 account = account or self.account
593 params = {'until': until} if until else {}
594 return OOS_Client.retrieve_account_metadata(self, account=account,
595 restricted=restricted,
598 def set_account_groups(self, account=None, **groups):
599 """create account groups"""
600 account = account or self.account
601 path = '/%s' % account
603 for k, v in groups.items():
604 headers['x-account-group-%s' % k] = v
605 params = {'update': None}
606 return self.post(path, headers=headers, params=params)
608 def retrieve_account_groups(self, account=None):
609 """returns the account groups"""
610 account = account or self.account
611 meta = self.retrieve_account_metadata(account=account)
612 prefix = 'x-account-group-'
613 prefixlen = len(prefix)
615 for key, val in meta.items():
616 if prefix and not key.startswith(prefix):
618 elif prefix and key.startswith(prefix):
619 key = key[prefixlen:]
623 def unset_account_groups(self, groups=[], account=None):
624 """delete account groups"""
625 account = account or self.account
626 path = '/%s' % account
629 headers['x-account-group-%s' % elem] = ''
630 params = {'update': None}
631 return self.post(path, headers=headers, params=params)
633 def reset_account_groups(self, account=None, **groups):
634 """overrides account groups"""
635 account = account or self.account
636 path = '/%s' % account
638 for k, v in groups.items():
640 headers['x-account-group-%s' % k] = v
641 meta = self.retrieve_account_metadata(restricted=True)
642 prefix = 'x-account-meta-'
643 for k, v in meta.items():
644 k = '%s%s' % (prefix, k)
646 return self.post(path, headers=headers)
648 # Storage Container Services
649 def create_container(self, container, account=None, meta={}, policies={}):
650 """creates a container"""
652 for k, v in policies.items():
653 args['X-Container-Policy-%s' % k.capitalize()] = v
654 return OOS_Client.create_container(self, container, account, meta, **args)
656 def list_objects(self, container, format='text',
657 limit=None, marker=None, prefix=None, delimiter=None,
658 path=None, shared=False, include_trashed=False, params={},
659 if_modified_since=None, if_unmodified_since=None, meta='',
660 until=None, account=None, public=False):
661 """returns a list with the container objects"""
662 account = account or self.account
663 params = {'until': until, 'meta': meta}
665 params['shared'] = None
667 params['public'] = None
668 args = locals().copy()
669 for elem in ['self', 'container', 'params', 'until', 'meta']:
671 return OOS_Client.list_objects(self, container, params=params, **args)
673 def retrieve_container_metadata(self, container, restricted=False,
674 until=None, account=None):
675 """returns container's metadata"""
676 account = account or self.account
677 params = {'until': until} if until else {}
678 return OOS_Client.retrieve_container_metadata(self, container,
680 restricted=restricted,
683 def set_container_policies(self, container, account=None,
685 """sets containers policies"""
686 account = account or self.account
687 path = '/%s/%s' % (account, container)
689 for key, val in policies.items():
690 headers['x-container-policy-%s' % key] = val
691 return self.post(path, headers=headers)
693 def update_container_data(self, container, f=stdin):
694 """adds blocks of data to the container"""
695 account = self.account
696 path = '/%s/%s' % (account, container)
697 params = {'update': None}
698 headers = {'content_type': 'application/octet-stream'}
699 data = f.read() if f else None
700 headers['content_length'] = len(data)
701 return self.post(path, data, headers=headers, params=params)
703 def delete_container(self, container, until=None, account=None, delimiter=None):
704 """deletes a container or the container history until the date provided"""
705 account = account or self.account
706 params = {'until': until} if until else {}
708 params['delimiter'] = delimiter
709 return OOS_Client.delete_container(self, container, account=account,
712 # Storage Object Services
714 def retrieve_object(self, container, object, params={}, format='text',
715 range=None, if_range=None,
716 if_match=None, if_none_match=None,
717 if_modified_since=None, if_unmodified_since=None,
718 account=None, **headers):
719 """returns an object"""
720 account = account or self.account
722 l = ['range', 'if_range', 'if_match', 'if_none_match',
723 'if_modified_since', 'if_unmodified_since']
724 l = [elem for elem in l if eval(elem)]
726 headers.update({elem: eval(elem)})
728 params['hashmap'] = None
729 return OOS_Client.retrieve_object(self, container, object,
730 account=account, format=format,
731 params=params, **headers)
733 def retrieve_object_version(self, container, object, version,
734 format='text', range=None, if_range=None,
735 if_match=None, if_none_match=None,
736 if_modified_since=None, if_unmodified_since=None,
738 """returns a specific object version"""
739 account = account or self.account
740 args = locals().copy()
741 l = ['self', 'container', 'object']
744 params = {'version': version}
745 return self.retrieve_object(container, object, params=params, **args)
747 def retrieve_object_versionlist(self, container, object, range=None,
748 if_range=None, if_match=None,
749 if_none_match=None, if_modified_since=None,
750 if_unmodified_since=None, account=None):
751 """returns the object version list"""
752 account = account or self.account
753 args = locals().copy()
754 l = ['self', 'container', 'object']
758 return self.retrieve_object_version(container, object, version='list',
759 format='json', **args)
761 def create_zero_length_object(self, container, object,
762 meta={}, etag=None, content_type=None,
763 content_encoding=None,
764 content_disposition=None,
765 x_object_manifest=None, x_object_sharing=None,
766 x_object_public=None, account=None):
767 """createas a zero length object"""
768 account = account or self.account
769 args = locals().copy()
770 for elem in ['self', 'container', 'object']:
772 return OOS_Client.create_zero_length_object(self, container, object,
775 def create_folder(self, container, name,
777 content_encoding=None,
778 content_disposition=None,
779 x_object_manifest=None, x_object_sharing=None,
780 x_object_public=None, account=None):
781 args = locals().copy()
782 for elem in ['self', 'container', 'name']:
784 args['content_type'] = 'application/directory'
785 return self.create_zero_length_object(container, name, **args)
787 def create_object(self, container, object, f=stdin, format='text',
788 meta={}, params={}, etag=None, content_type=None,
789 content_encoding=None, content_disposition=None,
790 x_object_manifest=None, x_object_sharing=None,
791 x_object_public=None, account=None):
792 """creates an object"""
793 account = account or self.account
794 args = locals().copy()
795 for elem in ['self', 'container', 'object']:
798 params.update({'hashmap': None})
799 return OOS_Client.create_object(self, container, object, **args)
801 def create_object_using_chunks(self, container, object,
802 f=stdin, blocksize=1024, meta={}, etag=None,
803 content_type=None, content_encoding=None,
804 content_disposition=None,
805 x_object_sharing=None, x_object_manifest=None,
806 x_object_public=None, account=None):
807 """creates an object (incremental upload)"""
808 account = account or self.account
809 path = '/%s/%s/%s' % (account, container, object)
811 l = ['etag', 'content_type', 'content_encoding', 'content_disposition',
812 'x_object_sharing', 'x_object_manifest', 'x_object_public']
813 l = [elem for elem in l if eval(elem)]
815 headers.update({elem: eval(elem)})
816 headers.setdefault('content-type', 'application/octet-stream')
818 for k, v in meta.items():
820 headers['x-object-meta-%s' % k.strip()] = v
822 return self._chunked_transfer(path, 'PUT', f, headers=headers,
825 def create_object_by_hashmap(self, container, object, hashmap={},
826 meta={}, etag=None, content_encoding=None,
827 content_disposition=None, content_type=None,
828 x_object_sharing=None, x_object_manifest=None,
829 x_object_public=None, account=None):
830 """creates an object by uploading hashes representing data instead of data"""
831 account = account or self.account
832 args = locals().copy()
833 for elem in ['self', 'container', 'object', 'hashmap']:
837 data = json.dumps(hashmap)
839 raise Fault('Invalid formatting')
840 args['params'] = {'hashmap': None}
841 args['format'] = 'json'
843 return self.create_object(container, object, f=StringIO(data), **args)
845 def create_manifestation(self, container, object, manifest, account=None):
846 """creates a manifestation"""
847 account = account or self.account
848 headers = {'x_object_manifest': manifest}
849 return self.create_object(container, object, f=None, account=account,
852 def update_object(self, container, object, f=stdin,
853 offset=None, meta={}, replace=False, content_length=None,
854 content_type=None, content_range=None,
855 content_encoding=None, content_disposition=None,
856 x_object_bytes=None, x_object_manifest=None,
857 x_object_sharing=None, x_object_public=None,
858 x_source_object=None, account=None):
859 """updates an object"""
860 account = account or self.account
861 args = locals().copy()
862 for elem in ['self', 'container', 'object', 'replace']:
865 args['params'] = {'update': None}
866 return OOS_Client.update_object(self, container, object, **args)
868 def update_object_using_chunks(self, container, object, f=stdin,
869 blocksize=1024, offset=None, meta={},
870 replace=False, content_type=None, content_encoding=None,
871 content_disposition=None, x_object_bytes=None,
872 x_object_manifest=None, x_object_sharing=None,
873 x_object_public=None, account=None):
874 """updates an object (incremental upload)"""
875 account = account or self.account
876 args = locals().copy()
877 for elem in ['self', 'container', 'object', 'replace']:
880 args['params'] = {'update': None}
881 return OOS_Client.update_object_using_chunks(self, container, object, **args)
883 def update_from_other_source(self, container, object, source,
884 offset=None, meta={}, content_range=None,
885 content_encoding=None, content_disposition=None,
886 x_object_bytes=None, x_object_manifest=None,
887 x_object_sharing=None, x_object_public=None, account=None):
888 """updates an object"""
889 account = account or self.account
890 args = locals().copy()
891 for elem in ['self', 'container', 'object', 'source']:
894 args['x_source_object'] = source
895 return self.update_object(container, object, f=None, **args)
897 def delete_object(self, container, object, until=None, account=None, delimiter=None):
898 """deletes an object or the object history until the date provided"""
899 account = account or self.account
900 params = {'until': until} if until else {}
902 params['delimiter'] = delimiter
903 return OOS_Client.delete_object(self, container, object, params, account)
905 def trash_object(self, container, object):
906 """trashes an object"""
907 account = account or self.account
908 path = '/%s/%s' % (container, object)
909 meta = {'trash': 'true'}
910 return self._update_metadata(path, 'object', **meta)
912 def restore_object(self, container, object, account=None):
913 """restores a trashed object"""
914 account = account or self.account
915 return self.delete_object_metadata(container, object, account, ['trash'])
917 def publish_object(self, container, object, account=None):
918 """sets a previously created object publicly accessible"""
919 account = account or self.account
920 path = '/%s/%s/%s' % (account, container, object)
922 headers['x_object_public'] = True
923 params = {'update': None}
924 return self.post(path, headers=headers, params=params)
926 def unpublish_object(self, container, object, account=None):
927 """unpublish an object"""
928 account = account or self.account
929 path = '/%s/%s/%s' % (account, container, object)
931 headers['x_object_public'] = False
932 params = {'update': None}
933 return self.post(path, headers=headers, params=params)
935 def copy_object(self, src_container, src_object, dst_container, dst_object,
936 meta={}, public=False, version=None, account=None,
937 content_type=None, delimiter=None):
938 """copies an object"""
939 account = account or self.account
941 headers['x_object_public'] = public
943 headers['x_source_version'] = version
944 return OOS_Client.copy_object(self, src_container, src_object,
945 dst_container, dst_object, meta=meta,
946 account=account, content_type=content_type,
950 def move_object(self, src_container, src_object, dst_container,
951 dst_object, meta={}, public=False,
952 account=None, content_type=None, delimiter=None):
953 """moves an object"""
955 headers['x_object_public'] = public
956 return OOS_Client.move_object(self, src_container, src_object,
957 dst_container, dst_object, meta=meta,
958 account=account, content_type=content_type,
962 def list_shared_by_others(self, limit=None, marker=None, format='text'):
963 """lists other accounts that share objects to the user"""
964 l = ['limit', 'marker']
966 for elem in [elem for elem in l if eval(elem)]:
967 params[elem] = eval(elem)
968 return self._list('', format, params)
970 def share_object(self, container, object, l, read=True):
971 """gives access(read by default) to an object to a user/group list"""
972 action = 'read' if read else 'write'
973 sharing = '%s=%s' % (action, ','.join(l))
974 self.update_object(container, object, f=None, x_object_sharing=sharing)
977 def _prepare_path(path, format='text', params={}):
978 full_path = '%s?format=%s' % (quote(path), format)
980 for k, v in params.items():
981 value = quote(str(v)) if v else ''
982 full_path = '%s&%s=%s' % (full_path, quote(k), value)
986 def _prepare_headers(headers):
987 for k, v in headers.items():
989 k = k.replace('_', '-')
990 headers[quote(k)] = quote(
991 v, safe='/=,:@ *"') if isinstance(v, types.StringType) else v
995 def _handle_response(response, verbose=False, debug=False):
996 headers = response.getheaders()
997 headers = dict((unquote(h), unquote(v)) for h, v in headers)
1000 print '%d %s' % (response.status, response.reason)
1001 for key, val in headers.items():
1002 print '%s: %s' % (key.capitalize(), val)
1005 length = response.getheader('content-length', None)
1006 data = response.read(length)
1011 if int(response.status) in ERROR_CODES.keys():
1012 raise Fault(data, int(response.status))
1014 #print '**', response.status, headers, data, '\n'
1015 return response.status, headers, data