1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
34 from httplib import HTTPConnection, HTTP
36 from xml.dom import minidom
37 from StringIO import StringIO
38 from urllib import quote
46 ERROR_CODES = {304:'Not Modified',
52 411:'Length Required',
53 412:'Precondition Failed',
54 413:'Request Entity Too Large',
55 416:'Range Not Satisfiable',
56 422:'Unprocessable Entity',
57 503:'Service Unavailable',
60 class Fault(Exception):
61 def __init__(self, data='', status=None):
62 if data == '' and status in ERROR_CODES.keys():
63 data = ERROR_CODES[status]
64 Exception.__init__(self, data)
69 def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
70 """`host` can also include a port, e.g '127.0.0.1:8000'."""
73 self.account = account
75 self.verbose = verbose or debug
79 def _req(self, method, path, body=None, headers={}, format='text', params={}):
80 slash = '/' if self.api else ''
81 full_path = '%s%s%s?format=%s' % (slash, self.api, quote(path), format)
83 for k,v in params.items():
85 full_path = '%s&%s=%s' %(full_path, quote(k), quote(unicode(v)))
87 full_path = '%s&%s=' %(full_path, k)
88 conn = HTTPConnection(self.host)
91 for k,v in headers.items():
93 k = k.replace('_', '-')
96 kwargs['headers'] = headers
97 kwargs['headers']['X-Auth-Token'] = self.token
100 kwargs['headers'].setdefault('content-type', 'application/octet-stream')
101 kwargs['headers'].setdefault('content-length', len(body) if body else 0)
103 #print '#', method, full_path, kwargs
104 t1 = datetime.datetime.utcnow()
105 conn.request(method, full_path, **kwargs)
107 resp = conn.getresponse()
108 t2 = datetime.datetime.utcnow()
109 #print 'response time:', str(t2-t1)
110 headers = dict(resp.getheaders())
113 print '%d %s' % (resp.status, resp.reason)
114 for key, val in headers.items():
115 print '%s: %s' % (key.capitalize(), val)
118 length = resp.getheader('content-length', None)
119 data = resp.read(length)
124 if int(resp.status) in ERROR_CODES.keys():
125 raise Fault(data, int(resp.status))
127 #print '**', resp.status, headers, data, '\n'
128 return resp.status, headers, data
130 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
131 blocksize=1024, params={}):
132 """perfomrs a chunked request"""
133 http = HTTPConnection(self.host)
136 full_path = '/%s%s?' % (self.api, path)
138 for k,v in params.items():
140 full_path = '%s&%s=%s' %(full_path, k, v)
142 full_path = '%s&%s=' %(full_path, k)
144 full_path = urllib.quote(full_path, '?&:=/')
146 http.putrequest(method, full_path)
147 http.putheader('x-auth-token', self.token)
148 http.putheader('content-type', 'application/octet-stream')
149 http.putheader('transfer-encoding', 'chunked')
151 for header,value in headers.items():
152 http.putheader(header, value)
160 block = f.read(blocksize)
163 data = '%x\r\n%s\r\n' % (len(block), block)
177 resp = http.getresponse()
179 headers = dict(resp.getheaders())
182 print '%d %s' % (resp.status, resp.reason)
183 for key, val in headers.items():
184 print '%s: %s' % (key.capitalize(), val)
187 length = resp.getheader('Content-length', None)
188 data = resp.read(length)
193 if int(resp.status) in ERROR_CODES.keys():
194 raise Fault(data, int(resp.status))
196 #print '*', resp.status, headers, data
197 return resp.status, headers, data
199 def delete(self, path, format='text', params={}):
200 return self._req('DELETE', path, format=format, params=params)
202 def get(self, path, format='text', headers={}, params={}):
203 return self._req('GET', path, headers=headers, format=format,
206 def head(self, path, format='text', params={}):
207 return self._req('HEAD', path, format=format, params=params)
209 def post(self, path, body=None, format='text', headers=None, params={}):
210 return self._req('POST', path, body, headers=headers, format=format,
213 def put(self, path, body=None, format='text', headers=None, params={}):
214 return self._req('PUT', path, body, headers=headers, format=format,
217 def _list(self, path, format='text', params={}, **headers):
218 status, headers, data = self.get(path, format=format, headers=headers,
221 data = json.loads(data) if data else ''
222 elif format == 'xml':
223 data = minidom.parseString(data)
225 data = data.split('\n')[:-1] if data else ''
228 def _get_metadata(self, path, prefix=None, params={}):
229 status, headers, data = self.head(path, params=params)
230 prefixlen = len(prefix) if prefix else 0
232 for key, val in headers.items():
233 if prefix and not key.startswith(prefix):
235 elif prefix and key.startswith(prefix):
236 key = key[prefixlen:]
240 def _filter(self, l, d):
242 filter out from l elements having the metadata values provided
246 if type(elem) == types.DictionaryType:
248 k = 'x_object_meta_%s' % key
249 if k in elem.keys() and elem[k] == d[key]:
254 class OOS_Client(Client):
255 """Openstack Object Storage Client"""
257 def _update_metadata(self, path, entity, **meta):
258 """adds new and updates the values of previously set metadata"""
259 ex_meta = self.retrieve_account_metadata(restricted=True)
262 prefix = 'x-%s-meta-' % entity
263 for k,v in ex_meta.items():
264 k = '%s%s' % (prefix, k)
266 return self.post(path, headers=headers)
268 def _reset_metadata(self, path, entity, **meta):
270 overwrites all user defined metadata
273 prefix = 'x-%s-meta-' % entity
274 for k,v in meta.items():
275 k = '%s%s' % (prefix, k)
277 return self.post(path, headers=headers)
279 def _delete_metadata(self, path, entity, meta=[]):
280 """delete previously set metadata"""
281 ex_meta = self.retrieve_account_metadata(restricted=True)
283 prefix = 'x-%s-meta-' % entity
284 for k in ex_meta.keys():
286 headers['%s%s' % (prefix, k)] = ex_meta[k]
287 return self.post(path, headers=headers)
289 # Storage Account Services
291 def list_containers(self, format='text', limit=None,
292 marker=None, params={}, account=None, **headers):
293 """lists containers"""
294 account = account or self.account
295 path = '/%s' % account
296 params.update({'limit':limit, 'marker':marker})
297 return self._list(path, format, params, **headers)
299 def retrieve_account_metadata(self, restricted=False, account=None, **params):
300 """returns the account metadata"""
301 account = account or self.account
302 path = '/%s' % account
303 prefix = 'x-account-meta-' if restricted else None
304 return self._get_metadata(path, prefix, params)
306 def update_account_metadata(self, account=None, **meta):
307 """updates the account metadata"""
308 account = account or self.account
309 path = '/%s' % account
310 return self._update_metadata(path, 'account', **meta)
312 def delete_account_metadata(self, meta=[], account=None):
313 """deletes the account metadata"""
314 account = account or self.account
315 path = '/%s' % account
316 return self._delete_metadata(path, 'account', meta)
318 def reset_account_metadata(self, account=None, **meta):
319 """resets account metadata"""
320 account = account or self.account
321 path = '/%s' % account
322 return self._reset_metadata(path, 'account', **meta)
324 # Storage Container Services
326 def _filter_trashed(self, l):
327 return self._filter(l, {'trash':'true'})
329 def list_objects(self, container, format='text',
330 limit=None, marker=None, prefix=None, delimiter=None,
331 path=None, include_trashed=False, params={}, account=None,
333 """returns a list with the container objects"""
334 account = account or self.account
335 params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
336 'delimiter':delimiter, 'path':path})
337 l = self._list('/%s/%s' % (account, container), format, params,
339 #TODO support filter trashed with xml also
340 if format != 'xml' and not include_trashed:
341 l = self._filter_trashed(l)
344 def create_container(self, container, account=None, **meta):
345 """creates a container"""
346 account = account or self.account
348 for k,v in meta.items():
349 headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
350 status, header, data = self.put('/%s/%s' % (account, container),
355 raise Fault(data, int(status))
358 def delete_container(self, container, params={}, account=None):
359 """deletes a container"""
360 account = account or self.account
361 return self.delete('/%s/%s' % (account, container), params=params)
363 def retrieve_container_metadata(self, container, restricted=False,
364 account=None, **params):
365 """returns the container metadata"""
366 account = account or self.account
367 prefix = 'x-container-meta-' if restricted else None
368 return self._get_metadata('/%s/%s' % (account, container), prefix,
371 def update_container_metadata(self, container, account=None, **meta):
372 """unpdates the container metadata"""
373 account = account or self.account
374 return self._update_metadata('/%s/%s' % (account, container),
377 def delete_container_metadata(self, container, meta=[], account=None):
378 """deletes the container metadata"""
379 account = account or self.account
380 path = '/%s/%s' % (account, container)
381 return self._delete_metadata(path, 'container', meta)
383 # Storage Object Services
385 def request_object(self, container, object, format='text', params={},
386 account=None, **headers):
387 """returns tuple containing the status, headers and data response for an object request"""
388 account = account or self.account
389 path = '/%s/%s/%s' % (account, container, object)
390 status, headers, data = self.get(path, format, headers, params)
391 return status, headers, data
393 def retrieve_object(self, container, object, format='text', params={},
394 account=None, **headers):
395 """returns an object's data"""
396 account = account or self.account
397 t = self.request_object(container, object, format, params, account,
401 data = json.loads(data) if data else ''
402 elif format == 'xml':
403 data = minidom.parseString(data)
406 def retrieve_object_hashmap(self, container, object, params={},
407 account=None, **headers):
408 """returns the hashmap representing object's data"""
409 args = locals().copy()
410 for elem in ['self', 'container', 'object']:
412 data = self.retrieve_object(container, object, format='json', **args)
413 return data['hashes']
415 def create_directory_marker(self, container, object, account=None):
416 """creates a dierectory marker"""
417 account = account or self.account
419 raise Fault('Directory markers have to be nested in a container')
420 h = {'content_type':'application/directory'}
421 return self.create_zero_length_object(container, object, account=account,
424 def create_object(self, container, object, f=stdin, format='text', meta={},
425 params={}, etag=None, content_type=None, content_encoding=None,
426 content_disposition=None, account=None, **headers):
427 """creates a zero-length object"""
428 account = account or self.account
429 path = '/%s/%s/%s' % (account, container, object)
430 for k, v in headers.items():
434 l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
435 l = [elem for elem in l if eval(elem)]
437 headers.update({elem:eval(elem)})
438 headers.setdefault('content-type', 'application/octet-stream')
440 for k,v in meta.items():
441 headers['x-object-meta-%s' %k.strip()] = v.strip()
442 data = f.read() if f else None
443 return self.put(path, data, format, headers=headers, params=params)
445 def create_zero_length_object(self, container, object, meta={}, etag=None,
446 content_type=None, content_encoding=None,
447 content_disposition=None, account=None,
449 account = account or self.account
450 args = locals().copy()
451 for elem in ['self', 'container', 'headers', 'account']:
454 return self.create_object(container, account=account, f=None, **args)
456 def update_object(self, container, object, f=stdin,
457 offset=None, meta={}, params={}, content_length=None,
458 content_type=None, content_encoding=None,
459 content_disposition=None, account=None, **headers):
460 account = account or self.account
461 path = '/%s/%s/%s' % (account, container, object)
462 for k, v in headers.items():
466 l = ['content_encoding', 'content_disposition', 'content_type',
468 l = [elem for elem in l if eval(elem)]
470 headers.update({elem:eval(elem)})
472 if 'content_range' not in headers.keys():
474 headers['content_range'] = 'bytes %s-/*' % offset
476 headers['content_range'] = 'bytes */*'
478 for k,v in meta.items():
479 headers['x-object-meta-%s' %k.strip()] = v.strip()
480 data = f.read() if f else None
481 return self.post(path, data, headers=headers, params=params)
483 def update_object_using_chunks(self, container, object, f=stdin,
484 blocksize=1024, offset=None, meta={},
485 params={}, content_type=None, content_encoding=None,
486 content_disposition=None, account=None, **headers):
487 """updates an object (incremental upload)"""
488 account = account or self.account
489 path = '/%s/%s/%s' % (account, container, object)
490 headers = headers if not headers else {}
491 l = ['content_type', 'content_encoding', 'content_disposition']
492 l = [elem for elem in l if eval(elem)]
494 headers.update({elem:eval(elem)})
497 headers['content_range'] = 'bytes %s-/*' % offset
499 headers['content_range'] = 'bytes */*'
501 for k,v in meta.items():
503 headers['x-object-meta-%s' %k.strip()] = v
504 return self._chunked_transfer(path, 'POST', f, headers=headers,
505 blocksize=blocksize, params=params)
507 def _change_obj_location(self, src_container, src_object, dst_container,
508 dst_object, remove=False, meta={}, account=None,
509 content_type=None, **headers):
510 account = account or self.account
511 path = '/%s/%s/%s' % (account, dst_container, dst_object)
512 headers = {} if not headers else headers
513 for k, v in meta.items():
514 headers['x-object-meta-%s' % k] = v
516 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
518 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
519 headers['content_length'] = 0
521 headers['content_type'] = content_type
522 return self.put(path, headers=headers)
524 def copy_object(self, src_container, src_object, dst_container, dst_object,
525 meta={}, account=None, content_type=None, **headers):
526 """copies an object"""
527 account = account or self.account
528 return self._change_obj_location(src_container, src_object,
529 dst_container, dst_object, account=account,
530 remove=False, meta=meta,
531 content_type=content_type, **headers)
533 def move_object(self, src_container, src_object, dst_container,
534 dst_object, meta={}, account=None,
535 content_type=None, **headers):
536 """moves an object"""
537 account = account or self.account
538 return self._change_obj_location(src_container, src_object,
539 dst_container, dst_object,
540 account=account, remove=True,
541 meta=meta, content_type=content_type,
544 def delete_object(self, container, object, params={}, account=None):
545 """deletes an object"""
546 account = account or self.account
547 return self.delete('/%s/%s/%s' % (account, container, object),
550 def retrieve_object_metadata(self, container, object, restricted=False,
551 version=None, account=None):
553 set restricted to True to get only user defined metadata
555 account = account or self.account
556 path = '/%s/%s/%s' % (account, container, object)
557 prefix = 'x-object-meta-' if restricted else None
558 params = {'version':version} if version else {}
559 return self._get_metadata(path, prefix, params=params)
561 def update_object_metadata(self, container, object, account=None,
564 updates object's metadata
566 account = account or self.account
567 path = '/%s/%s/%s' % (account, container, object)
568 return self._update_metadata(path, 'object', **meta)
570 def delete_object_metadata(self, container, object, meta=[], account=None):
572 deletes object's metadata
574 account = account or self.account
575 path = '/%s/%s' % (account, container, object)
576 return self._delete_metadata(path, 'object', meta)
578 class Pithos_Client(OOS_Client):
579 """Pithos Storage Client. Extends OOS_Client"""
581 def _update_metadata(self, path, entity, **meta):
583 adds new and updates the values of previously set metadata
585 params = {'update':None}
587 prefix = 'x-%s-meta-' % entity
588 for k,v in meta.items():
589 k = '%s%s' % (prefix, k)
591 return self.post(path, headers=headers, params=params)
593 def _delete_metadata(self, path, entity, meta=[]):
595 delete previously set metadata
597 params = {'update':None}
599 prefix = 'x-%s-meta-' % entity
601 headers['%s%s' % (prefix, m)] = ''
602 return self.post(path, headers=headers, params=params)
604 # Storage Account Services
606 def list_containers(self, format='text', if_modified_since=None,
607 if_unmodified_since=None, limit=None, marker=None,
608 shared=False, until=None, account=None):
609 """returns a list with the account containers"""
610 account = account or self.account
611 params = {'until':until} if until else {}
613 params['shared'] = None
614 headers = {'if-modified-since':if_modified_since,
615 'if-unmodified-since':if_unmodified_since}
616 return OOS_Client.list_containers(self, account=account, format=format,
617 limit=limit, marker=marker,
618 params=params, **headers)
620 def retrieve_account_metadata(self, restricted=False, until=None,
622 """returns the account metadata"""
623 account = account or self.account
624 params = {'until':until} if until else {}
625 return OOS_Client.retrieve_account_metadata(self, account=account,
626 restricted=restricted,
629 def set_account_groups(self, account=None, **groups):
630 """create account groups"""
631 account = account or self.account
632 path = '/%s' % account
634 for k, v in groups.items():
635 headers['x-account-group-%s' % k] = v
636 params = {'update':None}
637 return self.post(path, headers=headers, params=params)
639 def retrieve_account_groups(self, account=None):
640 """returns the account groups"""
641 account = account or self.account
642 meta = self.retrieve_account_metadata(account=account)
643 prefix = 'x-account-group-'
644 prefixlen = len(prefix)
646 for key, val in meta.items():
647 if prefix and not key.startswith(prefix):
649 elif prefix and key.startswith(prefix):
650 key = key[prefixlen:]
654 def unset_account_groups(self, groups=[], account=None):
655 """delete account groups"""
656 account = account or self.account
657 path = '/%s' % account
660 headers['x-account-group-%s' % elem] = ''
661 params = {'update':None}
662 return self.post(path, headers=headers, params=params)
664 def reset_account_groups(self, account=None, **groups):
665 """overrides account groups"""
666 account = account or self.account
667 path = '/%s' % account
669 for k, v in groups.items():
671 headers['x-account-group-%s' % k] = v
672 meta = self.retrieve_account_metadata(restricted=True)
673 prefix = 'x-account-meta-'
674 for k,v in meta.items():
675 k = '%s%s' % (prefix, k)
677 return self.post(path, headers=headers)
679 # Storage Container Services
681 def list_objects(self, container, format='text',
682 limit=None, marker=None, prefix=None, delimiter=None,
683 path=None, shared=False, include_trashed=False, params={},
684 if_modified_since=None, if_unmodified_since=None, meta='',
685 until=None, account=None):
686 """returns a list with the container objects"""
687 account = account or self.account
688 params = {'until':until, 'meta':meta}
690 params['shared'] = None
691 args = locals().copy()
692 for elem in ['self', 'container', 'params', 'until', 'meta']:
694 return OOS_Client.list_objects(self, container, params=params, **args)
696 def retrieve_container_metadata(self, container, restricted=False,
697 until=None, account=None):
698 """returns container's metadata"""
699 account = account or self.account
700 params = {'until':until} if until else {}
701 return OOS_Client.retrieve_container_metadata(self, container,
703 restricted=restricted,
706 def set_container_policies(self, container, account=None,
708 """sets containers policies"""
709 account = account or self.account
710 path = '/%s/%s' % (account, container)
712 for key, val in policies.items():
713 headers['x-container-policy-%s' % key] = val
714 return self.post(path, headers=headers)
716 def update_container_data(self, container, f=stdin):
717 """adds blocks of data to the container"""
718 account = self.account
719 path = '/%s/%s' % (account, container)
720 params = {'update': None}
721 headers = {'content_type': 'application/octet-stream'}
722 data = f.read() if f else None
723 headers['content_length'] = len(data)
724 return self.post(path, data, headers=headers, params=params)
726 def delete_container(self, container, until=None, account=None):
727 """deletes a container or the container history until the date provided"""
728 account = account or self.account
729 params = {'until':until} if until else {}
730 return OOS_Client.delete_container(self, container, account=account,
733 # Storage Object Services
735 def retrieve_object(self, container, object, params={}, format='text',
736 range=None, if_range=None,
737 if_match=None, if_none_match=None,
738 if_modified_since=None, if_unmodified_since=None,
739 account=None, **headers):
740 """returns an object"""
741 account = account or self.account
743 l = ['range', 'if_range', 'if_match', 'if_none_match',
744 'if_modified_since', 'if_unmodified_since']
745 l = [elem for elem in l if eval(elem)]
747 headers.update({elem:eval(elem)})
749 params['hashmap'] = None
750 return OOS_Client.retrieve_object(self, container, object,
751 account=account, format=format,
752 params=params, **headers)
754 def retrieve_object_version(self, container, object, version,
755 format='text', range=None, if_range=None,
756 if_match=None, if_none_match=None,
757 if_modified_since=None, if_unmodified_since=None,
759 """returns a specific object version"""
760 account = account or self.account
761 args = locals().copy()
762 l = ['self', 'container', 'object']
765 params = {'version':version}
766 return self.retrieve_object(container, object, params=params, **args)
768 def retrieve_object_versionlist(self, container, object, range=None,
769 if_range=None, if_match=None,
770 if_none_match=None, if_modified_since=None,
771 if_unmodified_since=None, account=None):
772 """returns the object version list"""
773 account = account or self.account
774 args = locals().copy()
775 l = ['self', 'container', 'object']
779 return self.retrieve_object_version(container, object, version='list',
780 format='json', **args)
782 def create_zero_length_object(self, container, object,
783 meta={}, etag=None, content_type=None,
784 content_encoding=None,
785 content_disposition=None,
786 x_object_manifest=None, x_object_sharing=None,
787 x_object_public=None, account=None):
788 """createas a zero length object"""
789 account = account or self.account
790 args = locals().copy()
791 for elem in ['self', 'container', 'object']:
793 return OOS_Client.create_zero_length_object(self, container, object,
796 def create_object(self, container, object, f=stdin, format='text',
797 meta={}, params={}, etag=None, content_type=None,
798 content_encoding=None, content_disposition=None,
799 x_object_manifest=None, x_object_sharing=None,
800 x_object_public=None, account=None):
801 """creates an object"""
802 account = account or self.account
803 args = locals().copy()
804 for elem in ['self', 'container', 'object']:
807 params.update({'hashmap':None})
808 return OOS_Client.create_object(self, container, object, **args)
810 def create_object_using_chunks(self, container, object,
811 f=stdin, blocksize=1024, meta={}, etag=None,
812 content_type=None, content_encoding=None,
813 content_disposition=None,
814 x_object_sharing=None, x_object_manifest=None,
815 x_object_public=None, account=None):
816 """creates an object (incremental upload)"""
817 account = account or self.account
818 path = '/%s/%s/%s' % (account, container, object)
820 l = ['etag', 'content_type', 'content_encoding', 'content_disposition',
821 'x_object_sharing', 'x_object_manifest', 'x_object_public']
822 l = [elem for elem in l if eval(elem)]
824 headers.update({elem:eval(elem)})
825 headers.setdefault('content-type', 'application/octet-stream')
827 for k,v in meta.items():
829 headers['x-object-meta-%s' %k.strip()] = v
831 return self._chunked_transfer(path, 'PUT', f, headers=headers,
834 def create_object_by_hashmap(self, container, object, hashmap={},
835 meta={}, etag=None, content_encoding=None,
836 content_disposition=None, content_type=None,
837 x_object_sharing=None, x_object_manifest=None,
838 x_object_public = None, account=None):
839 """creates an object by uploading hashes representing data instead of data"""
840 account = account or self.account
841 args = locals().copy()
842 for elem in ['self', 'container', 'object', 'hashmap']:
846 data = json.dumps(hashmap)
848 raise Fault('Invalid formatting')
849 args['params'] = {'hashmap':None}
850 args['format'] = 'json'
852 return self.create_object(container, object, f=StringIO(data), **args)
854 def create_manifestation(self, container, object, manifest, account=None):
855 """creates a manifestation"""
856 account = account or self.account
857 headers={'x_object_manifest':manifest}
858 return self.create_object(container, object, f=None, account=account,
861 def update_object(self, container, object, f=stdin,
862 offset=None, meta={}, replace=False, content_length=None,
863 content_type=None, content_range=None,
864 content_encoding=None, content_disposition=None,
865 x_object_bytes=None, x_object_manifest=None,
866 x_object_sharing=None, x_object_public=None,
867 x_source_object=None, account=None):
868 """updates an object"""
869 account = account or self.account
870 args = locals().copy()
871 for elem in ['self', 'container', 'object', 'replace']:
874 args['params'] = {'update':None}
875 return OOS_Client.update_object(self, container, object, **args)
877 def update_object_using_chunks(self, container, object, f=stdin,
878 blocksize=1024, offset=None, meta={},
879 replace=False, content_type=None, content_encoding=None,
880 content_disposition=None, x_object_bytes=None,
881 x_object_manifest=None, x_object_sharing=None,
882 x_object_public=None, account=None):
883 """updates an object (incremental upload)"""
884 account = account or self.account
885 args = locals().copy()
886 for elem in ['self', 'container', 'object', 'replace']:
889 args['params'] = {'update':None}
890 return OOS_Client.update_object_using_chunks(self, container, object, **args)
892 def update_from_other_source(self, container, object, source,
893 offset=None, meta={}, content_range=None,
894 content_encoding=None, content_disposition=None,
895 x_object_bytes=None, x_object_manifest=None,
896 x_object_sharing=None, x_object_public=None, account=None):
897 """updates an object"""
898 account = account or self.account
899 args = locals().copy()
900 for elem in ['self', 'container', 'object', 'source']:
903 args['x_source_object'] = source
904 return self.update_object(container, object, f=None, **args)
906 def delete_object(self, container, object, until=None, account=None):
907 """deletes an object or the object history until the date provided"""
908 account = account or self.account
909 params = {'until':until} if until else {}
910 return OOS_Client.delete_object(self, container, object, params, account)
912 def trash_object(self, container, object):
913 """trashes an object"""
914 account = account or self.account
915 path = '/%s/%s' % (container, object)
916 meta = {'trash':'true'}
917 return self._update_metadata(path, 'object', **meta)
919 def restore_object(self, container, object, account=None):
920 """restores a trashed object"""
921 account = account or self.account
922 return self.delete_object_metadata(container, object, account, ['trash'])
924 def publish_object(self, container, object, account=None):
925 """sets a previously created object publicly accessible"""
926 account = account or self.account
927 path = '/%s/%s/%s' % (account, container, object)
929 headers['x_object_public'] = True
930 params = {'update':None}
931 return self.post(path, headers=headers, params=params)
933 def unpublish_object(self, container, object, account=None):
934 """unpublish an object"""
935 account = account or self.account
936 path = '/%s/%s/%s' % (account, container, object)
938 headers['x_object_public'] = False
939 params = {'update':None}
940 return self.post(path, headers=headers, params=params)
942 def copy_object(self, src_container, src_object, dst_container, dst_object,
943 meta={}, public=False, version=None, account=None,
945 """copies an object"""
946 account = account or self.account
948 headers['x_object_public'] = public
950 headers['x_source_version'] = version
951 return OOS_Client.copy_object(self, src_container, src_object,
952 dst_container, dst_object, meta=meta,
953 account=account, content_type=content_type,
956 def move_object(self, src_container, src_object, dst_container,
957 dst_object, meta={}, public=False,
958 account=None, content_type=None):
959 """moves an object"""
961 headers['x_object_public'] = public
962 return OOS_Client.move_object(self, src_container, src_object,
963 dst_container, dst_object, meta=meta,
964 account=account, content_type=content_type,
967 def list_shared_by_others(self, limit=None, marker=None, format='text'):
968 """lists other accounts that share objects to the user"""
969 l = ['limit', 'marker']
971 for elem in [elem for elem in l if eval(elem)]:
972 params[elem] = eval(elem)
973 return self._list('', format, params)
975 def share_object(self, container, object, l, read=True):
976 """gives access(read by default) to an object to a user/group list"""
977 action = 'read' if read else 'write'
978 sharing = '%s=%s' % (action, ','.join(l))
979 self.update_object(container, object, f=None, x_object_sharing=sharing)
981 def _encode_headers(headers):
983 for k, v in headers.items():
985 if v and type(v) == types.StringType:
986 v = urllib.quote(v, '/=,-* :"')