1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
11 # 2. Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials
14 # provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
34 from httplib import HTTPConnection, HTTP
36 from xml.dom import minidom
37 from StringIO import StringIO
38 from urllib import quote, unquote
46 ERROR_CODES = {304:'Not Modified',
52 411:'Length Required',
53 412:'Precondition Failed',
54 413:'Request Entity Too Large',
55 416:'Range Not Satisfiable',
56 422:'Unprocessable Entity',
57 503:'Service Unavailable',
60 class Fault(Exception):
61 def __init__(self, data='', status=None):
62 if data == '' and status in ERROR_CODES.keys():
63 data = ERROR_CODES[status]
64 Exception.__init__(self, data)
69 def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
70 """`host` can also include a port, e.g '127.0.0.1:8000'."""
73 self.account = account
75 self.verbose = verbose or debug
79 def _req(self, method, path, body=None, headers={}, format='text', params={}):
80 full_path = _prepare_path(path, self.api, format, params)
82 conn = HTTPConnection(self.host)
84 kwargs['headers'] = _prepare_headers(headers)
85 kwargs['headers']['X-Auth-Token'] = self.token
88 kwargs['headers'].setdefault('content-type', 'application/octet-stream')
89 kwargs['headers'].setdefault('content-length', len(body) if body else 0)
91 #print '#', method, full_path, kwargs
92 #t1 = datetime.datetime.utcnow()
93 conn.request(method, full_path, **kwargs)
95 resp = conn.getresponse()
96 #t2 = datetime.datetime.utcnow()
97 #print 'response time:', str(t2-t1)
98 return _handle_response(resp, self.verbose, self.debug)
100 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
101 blocksize=1024, params={}):
102 """perfomrs a chunked request"""
103 full_path = _prepare_path(path, self.api, params=params)
105 conn = HTTPConnection(self.host)
106 conn.putrequest(method, full_path)
107 conn.putheader('x-auth-token', self.token)
108 conn.putheader('content-type', 'application/octet-stream')
109 conn.putheader('transfer-encoding', 'chunked')
110 for k,v in _prepare_headers(headers).items():
119 block = f.read(blocksize)
122 data = '%x\r\n%s\r\n' % (len(block), block)
135 resp = conn.getresponse()
136 return _handle_response(resp, self.verbose, self.debug)
138 def delete(self, path, format='text', params={}):
139 return self._req('DELETE', path, format=format, params=params)
141 def get(self, path, format='text', headers={}, params={}):
142 return self._req('GET', path, headers=headers, format=format,
145 def head(self, path, format='text', params={}):
146 return self._req('HEAD', path, format=format, params=params)
148 def post(self, path, body=None, format='text', headers=None, params={}):
149 return self._req('POST', path, body, headers=headers, format=format,
152 def put(self, path, body=None, format='text', headers=None, params={}):
153 return self._req('PUT', path, body, headers=headers, format=format,
156 def _list(self, path, format='text', params={}, **headers):
157 status, headers, data = self.get(path, format=format, headers=headers,
160 data = json.loads(data) if data else ''
161 elif format == 'xml':
162 data = minidom.parseString(data)
164 data = data.split('\n')[:-1] if data else ''
167 def _get_metadata(self, path, prefix=None, params={}):
168 status, headers, data = self.head(path, params=params)
169 prefixlen = len(prefix) if prefix else 0
171 for key, val in headers.items():
172 if prefix and not key.startswith(prefix):
174 elif prefix and key.startswith(prefix):
175 key = key[prefixlen:]
179 def _filter(self, l, d):
181 filter out from l elements having the metadata values provided
185 if type(elem) == types.DictionaryType:
187 k = 'x_object_meta_%s' % key
188 if k in elem.keys() and elem[k] == d[key]:
193 class OOS_Client(Client):
194 """Openstack Object Storage Client"""
196 def _update_metadata(self, path, entity, **meta):
197 """adds new and updates the values of previously set metadata"""
198 ex_meta = self.retrieve_account_metadata(restricted=True)
201 prefix = 'x-%s-meta-' % entity
202 for k,v in ex_meta.items():
203 k = '%s%s' % (prefix, k)
205 return self.post(path, headers=headers)
207 def _reset_metadata(self, path, entity, **meta):
209 overwrites all user defined metadata
212 prefix = 'x-%s-meta-' % entity
213 for k,v in meta.items():
214 k = '%s%s' % (prefix, k)
216 return self.post(path, headers=headers)
218 def _delete_metadata(self, path, entity, meta=[]):
219 """delete previously set metadata"""
220 ex_meta = self.retrieve_account_metadata(restricted=True)
222 prefix = 'x-%s-meta-' % entity
223 for k in ex_meta.keys():
225 headers['%s%s' % (prefix, k)] = ex_meta[k]
226 return self.post(path, headers=headers)
228 # Storage Account Services
230 def list_containers(self, format='text', limit=None,
231 marker=None, params={}, account=None, **headers):
232 """lists containers"""
233 account = account or self.account
234 path = '/%s' % account
235 params.update({'limit':limit, 'marker':marker})
236 return self._list(path, format, params, **headers)
238 def retrieve_account_metadata(self, restricted=False, account=None, **params):
239 """returns the account metadata"""
240 account = account or self.account
241 path = '/%s' % account
242 prefix = 'x-account-meta-' if restricted else None
243 return self._get_metadata(path, prefix, params)
245 def update_account_metadata(self, account=None, **meta):
246 """updates the account metadata"""
247 account = account or self.account
248 path = '/%s' % account
249 return self._update_metadata(path, 'account', **meta)
251 def delete_account_metadata(self, meta=[], account=None):
252 """deletes the account metadata"""
253 account = account or self.account
254 path = '/%s' % account
255 return self._delete_metadata(path, 'account', meta)
257 def reset_account_metadata(self, account=None, **meta):
258 """resets account metadata"""
259 account = account or self.account
260 path = '/%s' % account
261 return self._reset_metadata(path, 'account', **meta)
263 # Storage Container Services
265 def _filter_trashed(self, l):
266 return self._filter(l, {'trash':'true'})
268 def list_objects(self, container, format='text',
269 limit=None, marker=None, prefix=None, delimiter=None,
270 path=None, include_trashed=False, params={}, account=None,
272 """returns a list with the container objects"""
273 account = account or self.account
274 params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
275 'delimiter':delimiter, 'path':path})
276 l = self._list('/%s/%s' % (account, container), format, params,
278 #TODO support filter trashed with xml also
279 if format != 'xml' and not include_trashed:
280 l = self._filter_trashed(l)
283 def create_container(self, container, account=None, **meta):
284 """creates a container"""
285 account = account or self.account
287 for k,v in meta.items():
288 headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
289 status, header, data = self.put('/%s/%s' % (account, container),
294 raise Fault(data, int(status))
297 def delete_container(self, container, params={}, account=None):
298 """deletes a container"""
299 account = account or self.account
300 return self.delete('/%s/%s' % (account, container), params=params)
302 def retrieve_container_metadata(self, container, restricted=False,
303 account=None, **params):
304 """returns the container metadata"""
305 account = account or self.account
306 prefix = 'x-container-meta-' if restricted else None
307 return self._get_metadata('/%s/%s' % (account, container), prefix,
310 def update_container_metadata(self, container, account=None, **meta):
311 """unpdates the container metadata"""
312 account = account or self.account
313 return self._update_metadata('/%s/%s' % (account, container),
316 def delete_container_metadata(self, container, meta=[], account=None):
317 """deletes the container metadata"""
318 account = account or self.account
319 path = '/%s/%s' % (account, container)
320 return self._delete_metadata(path, 'container', meta)
322 # Storage Object Services
324 def request_object(self, container, object, format='text', params={},
325 account=None, **headers):
326 """returns tuple containing the status, headers and data response for an object request"""
327 account = account or self.account
328 path = '/%s/%s/%s' % (account, container, object)
329 status, headers, data = self.get(path, format, headers, params)
330 return status, headers, data
332 def retrieve_object(self, container, object, format='text', params={},
333 account=None, **headers):
334 """returns an object's data"""
335 account = account or self.account
336 t = self.request_object(container, object, format, params, account,
340 data = json.loads(data) if data else ''
341 elif format == 'xml':
342 data = minidom.parseString(data)
345 def retrieve_object_hashmap(self, container, object, params={},
346 account=None, **headers):
347 """returns the hashmap representing object's data"""
348 args = locals().copy()
349 for elem in ['self', 'container', 'object']:
351 return self.retrieve_object(container, object, format='json', **args)
353 def create_directory_marker(self, container, object, account=None):
354 """creates a dierectory marker"""
355 account = account or self.account
357 raise Fault('Directory markers have to be nested in a container')
358 h = {'content_type':'application/directory'}
359 return self.create_zero_length_object(container, object, account=account,
362 def create_object(self, container, object, f=stdin, format='text', meta={},
363 params={}, etag=None, content_type=None, content_encoding=None,
364 content_disposition=None, account=None, **headers):
365 """creates a zero-length object"""
366 account = account or self.account
367 path = '/%s/%s/%s' % (account, container, object)
368 for k, v in headers.items():
372 l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
373 l = [elem for elem in l if eval(elem)]
375 headers.update({elem:eval(elem)})
376 headers.setdefault('content-type', 'application/octet-stream')
378 for k,v in meta.items():
379 headers['x-object-meta-%s' %k.strip()] = v.strip()
380 data = f.read() if f else None
381 return self.put(path, data, format, headers=headers, params=params)
383 def create_zero_length_object(self, container, object, meta={}, etag=None,
384 content_type=None, content_encoding=None,
385 content_disposition=None, account=None,
387 account = account or self.account
388 args = locals().copy()
389 for elem in ['self', 'container', 'headers', 'account']:
392 return self.create_object(container, account=account, f=None, **args)
394 def update_object(self, container, object, f=stdin,
395 offset=None, meta={}, params={}, content_length=None,
396 content_type=None, content_encoding=None,
397 content_disposition=None, account=None, **headers):
398 account = account or self.account
399 path = '/%s/%s/%s' % (account, container, object)
400 for k, v in headers.items():
404 l = ['content_encoding', 'content_disposition', 'content_type',
406 l = [elem for elem in l if eval(elem)]
408 headers.update({elem:eval(elem)})
410 if 'content_range' not in headers.keys():
412 headers['content_range'] = 'bytes %s-/*' % offset
414 headers['content_range'] = 'bytes */*'
416 for k,v in meta.items():
417 headers['x-object-meta-%s' %k.strip()] = v.strip()
418 data = f.read() if f else None
419 return self.post(path, data, headers=headers, params=params)
421 def update_object_using_chunks(self, container, object, f=stdin,
422 blocksize=1024, offset=None, meta={},
423 params={}, content_type=None, content_encoding=None,
424 content_disposition=None, account=None, **headers):
425 """updates an object (incremental upload)"""
426 account = account or self.account
427 path = '/%s/%s/%s' % (account, container, object)
428 headers = headers if not headers else {}
429 l = ['content_type', 'content_encoding', 'content_disposition']
430 l = [elem for elem in l if eval(elem)]
432 headers.update({elem:eval(elem)})
435 headers['content_range'] = 'bytes %s-/*' % offset
437 headers['content_range'] = 'bytes */*'
439 for k,v in meta.items():
441 headers['x-object-meta-%s' %k.strip()] = v
442 return self._chunked_transfer(path, 'POST', f, headers=headers,
443 blocksize=blocksize, params=params)
445 def _change_obj_location(self, src_container, src_object, dst_container,
446 dst_object, remove=False, meta={}, account=None,
447 content_type=None, **headers):
448 account = account or self.account
449 path = '/%s/%s/%s' % (account, dst_container, dst_object)
450 headers = {} if not headers else headers
451 for k, v in meta.items():
452 headers['x-object-meta-%s' % k] = v
454 headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
456 headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
457 headers['content_length'] = 0
459 headers['content_type'] = content_type
460 return self.put(path, headers=headers)
462 def copy_object(self, src_container, src_object, dst_container, dst_object,
463 meta={}, account=None, content_type=None, **headers):
464 """copies an object"""
465 account = account or self.account
466 return self._change_obj_location(src_container, src_object,
467 dst_container, dst_object, account=account,
468 remove=False, meta=meta,
469 content_type=content_type, **headers)
471 def move_object(self, src_container, src_object, dst_container,
472 dst_object, meta={}, account=None,
473 content_type=None, **headers):
474 """moves an object"""
475 account = account or self.account
476 return self._change_obj_location(src_container, src_object,
477 dst_container, dst_object,
478 account=account, remove=True,
479 meta=meta, content_type=content_type,
482 def delete_object(self, container, object, params={}, account=None):
483 """deletes an object"""
484 account = account or self.account
485 return self.delete('/%s/%s/%s' % (account, container, object),
488 def retrieve_object_metadata(self, container, object, restricted=False,
489 version=None, account=None):
491 set restricted to True to get only user defined metadata
493 account = account or self.account
494 path = '/%s/%s/%s' % (account, container, object)
495 prefix = 'x-object-meta-' if restricted else None
496 params = {'version':version} if version else {}
497 return self._get_metadata(path, prefix, params=params)
499 def update_object_metadata(self, container, object, account=None,
502 updates object's metadata
504 account = account or self.account
505 path = '/%s/%s/%s' % (account, container, object)
506 return self._update_metadata(path, 'object', **meta)
508 def delete_object_metadata(self, container, object, meta=[], account=None):
510 deletes object's metadata
512 account = account or self.account
513 path = '/%s/%s' % (account, container, object)
514 return self._delete_metadata(path, 'object', meta)
516 class Pithos_Client(OOS_Client):
517 """Pithos Storage Client. Extends OOS_Client"""
519 def _update_metadata(self, path, entity, **meta):
521 adds new and updates the values of previously set metadata
523 params = {'update':None}
525 prefix = 'x-%s-meta-' % entity
526 for k,v in meta.items():
527 k = '%s%s' % (prefix, k)
529 return self.post(path, headers=headers, params=params)
531 def _delete_metadata(self, path, entity, meta=[]):
533 delete previously set metadata
535 params = {'update':None}
537 prefix = 'x-%s-meta-' % entity
539 headers['%s%s' % (prefix, m)] = ''
540 return self.post(path, headers=headers, params=params)
542 # Storage Account Services
544 def list_containers(self, format='text', if_modified_since=None,
545 if_unmodified_since=None, limit=None, marker=None,
546 shared=False, until=None, account=None):
547 """returns a list with the account containers"""
548 account = account or self.account
549 params = {'until':until} if until else {}
551 params['shared'] = None
552 headers = {'if-modified-since':if_modified_since,
553 'if-unmodified-since':if_unmodified_since}
554 return OOS_Client.list_containers(self, account=account, format=format,
555 limit=limit, marker=marker,
556 params=params, **headers)
558 def retrieve_account_metadata(self, restricted=False, until=None,
560 """returns the account metadata"""
561 account = account or self.account
562 params = {'until':until} if until else {}
563 return OOS_Client.retrieve_account_metadata(self, account=account,
564 restricted=restricted,
567 def set_account_groups(self, account=None, **groups):
568 """create account groups"""
569 account = account or self.account
570 path = '/%s' % account
572 for k, v in groups.items():
573 headers['x-account-group-%s' % k] = v
574 params = {'update':None}
575 return self.post(path, headers=headers, params=params)
577 def retrieve_account_groups(self, account=None):
578 """returns the account groups"""
579 account = account or self.account
580 meta = self.retrieve_account_metadata(account=account)
581 prefix = 'x-account-group-'
582 prefixlen = len(prefix)
584 for key, val in meta.items():
585 if prefix and not key.startswith(prefix):
587 elif prefix and key.startswith(prefix):
588 key = key[prefixlen:]
592 def unset_account_groups(self, groups=[], account=None):
593 """delete account groups"""
594 account = account or self.account
595 path = '/%s' % account
598 headers['x-account-group-%s' % elem] = ''
599 params = {'update':None}
600 return self.post(path, headers=headers, params=params)
602 def reset_account_groups(self, account=None, **groups):
603 """overrides account groups"""
604 account = account or self.account
605 path = '/%s' % account
607 for k, v in groups.items():
609 headers['x-account-group-%s' % k] = v
610 meta = self.retrieve_account_metadata(restricted=True)
611 prefix = 'x-account-meta-'
612 for k,v in meta.items():
613 k = '%s%s' % (prefix, k)
615 return self.post(path, headers=headers)
617 # Storage Container Services
619 def list_objects(self, container, format='text',
620 limit=None, marker=None, prefix=None, delimiter=None,
621 path=None, shared=False, include_trashed=False, params={},
622 if_modified_since=None, if_unmodified_since=None, meta='',
623 until=None, account=None):
624 """returns a list with the container objects"""
625 account = account or self.account
626 params = {'until':until, 'meta':meta}
628 params['shared'] = None
629 args = locals().copy()
630 for elem in ['self', 'container', 'params', 'until', 'meta']:
632 return OOS_Client.list_objects(self, container, params=params, **args)
634 def retrieve_container_metadata(self, container, restricted=False,
635 until=None, account=None):
636 """returns container's metadata"""
637 account = account or self.account
638 params = {'until':until} if until else {}
639 return OOS_Client.retrieve_container_metadata(self, container,
641 restricted=restricted,
644 def set_container_policies(self, container, account=None,
646 """sets containers policies"""
647 account = account or self.account
648 path = '/%s/%s' % (account, container)
650 for key, val in policies.items():
651 headers['x-container-policy-%s' % key] = val
652 return self.post(path, headers=headers)
654 def update_container_data(self, container, f=stdin):
655 """adds blocks of data to the container"""
656 account = self.account
657 path = '/%s/%s' % (account, container)
658 params = {'update': None}
659 headers = {'content_type': 'application/octet-stream'}
660 data = f.read() if f else None
661 headers['content_length'] = len(data)
662 return self.post(path, data, headers=headers, params=params)
664 def delete_container(self, container, until=None, account=None):
665 """deletes a container or the container history until the date provided"""
666 account = account or self.account
667 params = {'until':until} if until else {}
668 return OOS_Client.delete_container(self, container, account=account,
671 # Storage Object Services
673 def retrieve_object(self, container, object, params={}, format='text',
674 range=None, if_range=None,
675 if_match=None, if_none_match=None,
676 if_modified_since=None, if_unmodified_since=None,
677 account=None, **headers):
678 """returns an object"""
679 account = account or self.account
681 l = ['range', 'if_range', 'if_match', 'if_none_match',
682 'if_modified_since', 'if_unmodified_since']
683 l = [elem for elem in l if eval(elem)]
685 headers.update({elem:eval(elem)})
687 params['hashmap'] = None
688 return OOS_Client.retrieve_object(self, container, object,
689 account=account, format=format,
690 params=params, **headers)
692 def retrieve_object_version(self, container, object, version,
693 format='text', range=None, if_range=None,
694 if_match=None, if_none_match=None,
695 if_modified_since=None, if_unmodified_since=None,
697 """returns a specific object version"""
698 account = account or self.account
699 args = locals().copy()
700 l = ['self', 'container', 'object']
703 params = {'version':version}
704 return self.retrieve_object(container, object, params=params, **args)
706 def retrieve_object_versionlist(self, container, object, range=None,
707 if_range=None, if_match=None,
708 if_none_match=None, if_modified_since=None,
709 if_unmodified_since=None, account=None):
710 """returns the object version list"""
711 account = account or self.account
712 args = locals().copy()
713 l = ['self', 'container', 'object']
717 return self.retrieve_object_version(container, object, version='list',
718 format='json', **args)
720 def create_zero_length_object(self, container, object,
721 meta={}, etag=None, content_type=None,
722 content_encoding=None,
723 content_disposition=None,
724 x_object_manifest=None, x_object_sharing=None,
725 x_object_public=None, account=None):
726 """createas a zero length object"""
727 account = account or self.account
728 args = locals().copy()
729 for elem in ['self', 'container', 'object']:
731 return OOS_Client.create_zero_length_object(self, container, object,
734 def create_object(self, container, object, f=stdin, format='text',
735 meta={}, params={}, etag=None, content_type=None,
736 content_encoding=None, content_disposition=None,
737 x_object_manifest=None, x_object_sharing=None,
738 x_object_public=None, account=None):
739 """creates an object"""
740 account = account or self.account
741 args = locals().copy()
742 for elem in ['self', 'container', 'object']:
745 params.update({'hashmap':None})
746 return OOS_Client.create_object(self, container, object, **args)
748 def create_object_using_chunks(self, container, object,
749 f=stdin, blocksize=1024, meta={}, etag=None,
750 content_type=None, content_encoding=None,
751 content_disposition=None,
752 x_object_sharing=None, x_object_manifest=None,
753 x_object_public=None, account=None):
754 """creates an object (incremental upload)"""
755 account = account or self.account
756 path = '/%s/%s/%s' % (account, container, object)
758 l = ['etag', 'content_type', 'content_encoding', 'content_disposition',
759 'x_object_sharing', 'x_object_manifest', 'x_object_public']
760 l = [elem for elem in l if eval(elem)]
762 headers.update({elem:eval(elem)})
763 headers.setdefault('content-type', 'application/octet-stream')
765 for k,v in meta.items():
767 headers['x-object-meta-%s' %k.strip()] = v
769 return self._chunked_transfer(path, 'PUT', f, headers=headers,
772 def create_object_by_hashmap(self, container, object, hashmap={},
773 meta={}, etag=None, content_encoding=None,
774 content_disposition=None, content_type=None,
775 x_object_sharing=None, x_object_manifest=None,
776 x_object_public = None, account=None):
777 """creates an object by uploading hashes representing data instead of data"""
778 account = account or self.account
779 args = locals().copy()
780 for elem in ['self', 'container', 'object', 'hashmap']:
784 data = json.dumps(hashmap)
786 raise Fault('Invalid formatting')
787 args['params'] = {'hashmap':None}
788 args['format'] = 'json'
790 return self.create_object(container, object, f=StringIO(data), **args)
792 def create_manifestation(self, container, object, manifest, account=None):
793 """creates a manifestation"""
794 account = account or self.account
795 headers={'x_object_manifest':manifest}
796 return self.create_object(container, object, f=None, account=account,
799 def update_object(self, container, object, f=stdin,
800 offset=None, meta={}, replace=False, content_length=None,
801 content_type=None, content_range=None,
802 content_encoding=None, content_disposition=None,
803 x_object_bytes=None, x_object_manifest=None,
804 x_object_sharing=None, x_object_public=None,
805 x_source_object=None, account=None):
806 """updates an object"""
807 account = account or self.account
808 args = locals().copy()
809 for elem in ['self', 'container', 'object', 'replace']:
812 args['params'] = {'update':None}
813 return OOS_Client.update_object(self, container, object, **args)
815 def update_object_using_chunks(self, container, object, f=stdin,
816 blocksize=1024, offset=None, meta={},
817 replace=False, content_type=None, content_encoding=None,
818 content_disposition=None, x_object_bytes=None,
819 x_object_manifest=None, x_object_sharing=None,
820 x_object_public=None, account=None):
821 """updates an object (incremental upload)"""
822 account = account or self.account
823 args = locals().copy()
824 for elem in ['self', 'container', 'object', 'replace']:
827 args['params'] = {'update':None}
828 return OOS_Client.update_object_using_chunks(self, container, object, **args)
830 def update_from_other_source(self, container, object, source,
831 offset=None, meta={}, content_range=None,
832 content_encoding=None, content_disposition=None,
833 x_object_bytes=None, x_object_manifest=None,
834 x_object_sharing=None, x_object_public=None, account=None):
835 """updates an object"""
836 account = account or self.account
837 args = locals().copy()
838 for elem in ['self', 'container', 'object', 'source']:
841 args['x_source_object'] = source
842 return self.update_object(container, object, f=None, **args)
844 def delete_object(self, container, object, until=None, account=None):
845 """deletes an object or the object history until the date provided"""
846 account = account or self.account
847 params = {'until':until} if until else {}
848 return OOS_Client.delete_object(self, container, object, params, account)
850 def trash_object(self, container, object):
851 """trashes an object"""
852 account = account or self.account
853 path = '/%s/%s' % (container, object)
854 meta = {'trash':'true'}
855 return self._update_metadata(path, 'object', **meta)
857 def restore_object(self, container, object, account=None):
858 """restores a trashed object"""
859 account = account or self.account
860 return self.delete_object_metadata(container, object, account, ['trash'])
862 def publish_object(self, container, object, account=None):
863 """sets a previously created object publicly accessible"""
864 account = account or self.account
865 path = '/%s/%s/%s' % (account, container, object)
867 headers['x_object_public'] = True
868 params = {'update':None}
869 return self.post(path, headers=headers, params=params)
871 def unpublish_object(self, container, object, account=None):
872 """unpublish an object"""
873 account = account or self.account
874 path = '/%s/%s/%s' % (account, container, object)
876 headers['x_object_public'] = False
877 params = {'update':None}
878 return self.post(path, headers=headers, params=params)
880 def copy_object(self, src_container, src_object, dst_container, dst_object,
881 meta={}, public=False, version=None, account=None,
883 """copies an object"""
884 account = account or self.account
886 headers['x_object_public'] = public
888 headers['x_source_version'] = version
889 return OOS_Client.copy_object(self, src_container, src_object,
890 dst_container, dst_object, meta=meta,
891 account=account, content_type=content_type,
894 def move_object(self, src_container, src_object, dst_container,
895 dst_object, meta={}, public=False,
896 account=None, content_type=None):
897 """moves an object"""
899 headers['x_object_public'] = public
900 return OOS_Client.move_object(self, src_container, src_object,
901 dst_container, dst_object, meta=meta,
902 account=account, content_type=content_type,
905 def list_shared_by_others(self, limit=None, marker=None, format='text'):
906 """lists other accounts that share objects to the user"""
907 l = ['limit', 'marker']
909 for elem in [elem for elem in l if eval(elem)]:
910 params[elem] = eval(elem)
911 return self._list('', format, params)
913 def share_object(self, container, object, l, read=True):
914 """gives access(read by default) to an object to a user/group list"""
915 action = 'read' if read else 'write'
916 sharing = '%s=%s' % (action, ','.join(l))
917 self.update_object(container, object, f=None, x_object_sharing=sharing)
919 def _prepare_path(path, api, format='text', params={}):
920 slash = '/' if api else ''
921 full_path = '%s%s%s?format=%s' % (slash, api, quote(path), format)
923 for k,v in params.items():
924 value = quote(str(v)) if v else ''
925 full_path = '%s&%s=%s' %(full_path, quote(k), value)
928 def _prepare_headers(headers):
929 for k,v in headers.items():
931 k = k.replace('_', '-')
932 headers[quote(k)] = quote(v, safe='/=,:@ *"') if type(v) == types.StringType else v
935 def _handle_response(response, verbose=False, debug=False):
936 headers = response.getheaders()
937 headers = dict((unquote(h), unquote(v)) for h,v in headers)
940 print '%d %s' % (response.status, response.reason)
941 for key, val in headers.items():
942 print '%s: %s' % (key.capitalize(), val)
945 length = response.getheader('content-length', None)
946 data = response.read(length)
951 if int(response.status) in ERROR_CODES.keys():
952 raise Fault(data, int(response.status))
954 #print '**', response.status, headers, data, '\n'
955 return response.status, headers, data