1 from httplib import HTTPConnection, HTTP
7 class Fault(Exception):
8 def __init__(self, data=''):
9 Exception.__init__(self, data)
14 def __init__(self, host, account, api='v1', verbose=False, debug=False):
15 """`host` can also include a port, e.g '127.0.0.1:8000'."""
18 self.account = account
20 self.verbose = verbose or debug
23 def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
25 http = HTTPConnection(self.host)
28 path = '/%s/%s%s' % (self.api, self.account, path)
29 http.putrequest(method, path)
30 http.putheader('Content-Type', 'application/octet-stream')
31 http.putheader('Transfer-Encoding', 'chunked')
33 for header,value in headers.items():
34 http.putheader(header, value)
42 block = f.read(blocksize)
45 data = '%s\r\n%s\r\n' % (hex(len(block)), block)
59 resp = http.getresponse()
60 headers = dict(resp.getheaders())
63 print '%d %s' % (resp.status, resp.reason)
64 for key, val in headers.items():
65 print '%s: %s' % (key.capitalize(), val)
73 return resp.status, headers, data
75 def req(self, method, path, body=None, headers=None, format='text', params=None):
76 full_path = '/%s/%s%s?format=%s' % (self.api, self.account, path, format)
78 for k,v in params.items():
80 full_path = '%s&%s=%s' %(full_path, k, v)
81 conn = HTTPConnection(self.host)
85 kwargs['headers'] = headers or {}
87 'Transfer-Encoding' not in headers \
88 or headers['Transfer-Encoding'] != 'chunked':
89 kwargs['headers']['Content-Length'] = len(body) if body else 0
92 kwargs['headers']['Content-Type'] = 'application/octet-stream'
93 #print '****', method, full_path, kwargs
94 conn.request(method, full_path, **kwargs)
95 resp = conn.getresponse()
96 headers = dict(resp.getheaders())
99 print '%d %s' % (resp.status, resp.reason)
100 for key, val in headers.items():
101 print '%s: %s' % (key.capitalize(), val)
109 return resp.status, headers, data
111 def delete(self, path, format='text'):
112 return self.req('DELETE', path, format=format)
114 def get(self, path, format='text', headers=None, params=None):
115 return self.req('GET', path, headers=headers, format=format,
118 def head(self, path, format='text'):
119 return self.req('HEAD', path, format=format)
121 def post(self, path, body=None, format='text', headers=None):
122 return self.req('POST', path, body, headers=headers, format=format)
124 def put(self, path, body=None, format='text', headers=None):
125 return self.req('PUT', path, body, headers=headers, format=format)
127 def _list(self, path, detail=False, params=None, headers=None):
128 format = 'json' if detail else 'text'
129 status, headers, data = self.get(path, format=format, headers=headers,
132 data = json.loads(data)
137 def _get_metadata(self, path, prefix):
138 status, headers, data = self.head(path)
141 prefixlen = len(prefix)
143 for key, val in headers.items():
144 if key.startswith(prefix):
145 key = key[prefixlen:]
149 def _set_metadata(self, path, entity, **meta):
151 for key, val in meta.items():
152 http_key = 'X-%s-Meta-%s' %(entity.capitalize(), key.capitalize())
153 headers[http_key] = val
154 self.post(path, headers=headers)
156 # Storage Account Services
158 def list_containers(self, detail=False, params=None, headers=None):
159 return self._list('', detail, params, headers)
161 def account_metadata(self):
162 return self._get_metadata('', 'x-account-meta-')
164 def update_account_metadata(self, **meta):
165 self._set_metadata('', 'account', **meta)
167 # Storage Container Services
169 def list_objects(self, container, detail=False, params=None, headers=None):
170 return self._list('/' + container, detail, params, headers)
172 def create_container(self, container, headers=None):
173 status, header, data = self.put('/' + container, headers=headers)
180 def delete_container(self, container):
181 self.delete('/' + container)
183 def retrieve_container_metadata(self, container):
184 return self._get_metadata('/%s' % container, 'x-container-meta-')
186 def update_container_metadata(self, container, **meta):
187 self._set_metadata('/' + container, 'container', **meta)
189 # Storage Object Services
191 def retrieve_object(self, container, object, detail=False, headers=None):
192 path = '/%s/%s' % (container, object)
193 format = 'json' if detail else 'text'
194 status, headers, data = self.get(path, format, headers)
197 def create_object(self, container, object, f=stdin, chunked=False,
198 blocksize=1024, headers=None):
201 path = '/%s/%s' % (container, object)
202 if not chunked and f != stdin:
204 self.put(path, data, headers=headers)
206 self._chunked_transfer(path, 'PUT', f, headers=headers,
209 def update_object(self, container, object, f=stdin, chunked=False,
210 blocksize=1024, headers=None):
213 path = '/%s/%s' % (container, object)
214 if not chunked and f != stdin:
216 self.post(path, data, headers=headers)
218 self._chunked_transfer(path, 'POST', f, headers=headers,
221 def _change_obj_location(self, src_container, src_object, dst_container,
222 dst_object, remove=False):
223 path = '/%s/%s' % (dst_container, dst_object)
226 headers['X-Move-From'] = '/%s/%s' % (src_container, src_object)
228 headers['X-Copy-From'] = '/%s/%s' % (src_container, src_object)
229 headers['Content-Length'] = 0
230 self.put(path, headers=headers)
232 def copy_object(self, src_container, src_object, dst_container,
234 self._change_obj_location(src_container, src_object,
235 dst_container, dst_object)
237 def move_object(self, src_container, src_object, dst_container,
239 self._change_obj_location(src_container, src_object,
240 dst_container, dst_object, True)
242 def delete_object(self, container, object):
243 self.delete('/%s/%s' % (container, object))
245 def retrieve_object_metadata(self, container, object):
246 path = '/%s/%s' % (container, object)
247 return self._get_metadata(path, 'x-object-meta-')
249 def update_object_metadata(self, container, object, **meta):
250 path = '/%s/%s' % (container, object)
251 self._set_metadata(path, 'object', **meta)