remove leftover comments
[pithos] / pithos / lib / client.py
1 from httplib import HTTPConnection, HTTP
2 from sys import stdin
3
4 import json
5 import types
6
7 class Fault(Exception):
8     def __init__(self, data=''):
9         Exception.__init__(self, data)
10         self.data = data
11
12
13 class Client(object):
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'."""
16         
17         self.host = host
18         self.account = account
19         self.api = api
20         self.verbose = verbose or debug
21         self.debug = debug
22     
23     def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
24                           blocksize=1024):
25         http = HTTPConnection(self.host)
26         
27         # write header
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')
32         if headers:
33             for header,value in headers.items():
34                 http.putheader(header, value)
35         http.endheaders()
36         
37         # write body
38         data = ''
39         while True:
40             if f.closed:
41                 break
42             block = f.read(blocksize)
43             if block == '':
44                 break
45             data = '%s\r\n%s\r\n' % (hex(len(block)), block)
46             try:
47                 http.send(data)
48             except:
49                 #retry
50                 http.send(data)
51         data = '0x0\r\n'
52         try:
53             http.send(data)
54         except:
55             #retry
56             http.send(data)
57         
58         # get response
59         resp = http.getresponse()
60         headers = dict(resp.getheaders())
61         
62         if self.verbose:
63             print '%d %s' % (resp.status, resp.reason)
64             for key, val in headers.items():
65                 print '%s: %s' % (key.capitalize(), val)
66             print
67         
68         data = resp.read()
69         if self.debug:
70             print data
71             print
72         
73         return resp.status, headers, data
74
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)
77         if params:
78             for k,v in params.items():
79                 if v:
80                     full_path = '%s&%s=%s' %(full_path, k, v)
81         conn = HTTPConnection(self.host)
82         
83         
84         kwargs = {}
85         kwargs['headers'] = headers or {}
86         if not 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
90         if body:
91             kwargs['body'] = body
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())
97         
98         if self.verbose:
99             print '%d %s' % (resp.status, resp.reason)
100             for key, val in headers.items():
101                 print '%s: %s' % (key.capitalize(), val)
102             print
103         
104         data = resp.read()
105         if self.debug:
106             print data
107             print
108         
109         return resp.status, headers, data
110
111     def delete(self, path, format='text'):
112         return self.req('DELETE', path, format=format)
113
114     def get(self, path, format='text', headers=None, params=None):
115         return self.req('GET', path, headers=headers, format=format,
116                         params=params)
117
118     def head(self, path, format='text'):
119         return self.req('HEAD', path, format=format)
120
121     def post(self, path, body=None, format='text', headers=None):
122         return self.req('POST', path, body, headers=headers, format=format)
123
124     def put(self, path, body=None, format='text', headers=None):
125         return self.req('PUT', path, body, headers=headers, format=format)
126
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,
130                                          params=params)
131         if detail:
132             data = json.loads(data)
133         else:
134             data = [data]
135         return data
136
137     def _get_metadata(self, path, prefix):
138         status, headers, data = self.head(path)
139         if status == '404':
140             return None
141         prefixlen = len(prefix)
142         meta = {}
143         for key, val in headers.items():
144             if key.startswith(prefix):
145                 key = key[prefixlen:]
146                 meta[key] = val
147         return meta
148
149     def _set_metadata(self, path, entity, **meta):
150         headers = {}
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)
155
156     # Storage Account Services
157
158     def list_containers(self, detail=False, params=None, headers=None):
159         return self._list('', detail, params, headers)
160
161     def account_metadata(self):
162         return self._get_metadata('', 'x-account-meta-')
163
164     def update_account_metadata(self, **meta):
165         self._set_metadata('', 'account', **meta)
166
167     # Storage Container Services
168
169     def list_objects(self, container, detail=False, params=None, headers=None):
170         return self._list('/' + container, detail, params, headers)
171
172     def create_container(self, container, headers=None):
173         status, header, data = self.put('/' + container, headers=headers)
174         if status == 202:
175             return False
176         elif status != 201:
177             raise Fault(data)
178         return True
179
180     def delete_container(self, container):
181         self.delete('/' + container)
182
183     def retrieve_container_metadata(self, container):
184         return self._get_metadata('/%s' % container, 'x-container-meta-')
185
186     def update_container_metadata(self, container, **meta):
187         self._set_metadata('/' + container, 'container', **meta)
188
189     # Storage Object Services
190
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)
195         return data
196
197     def create_object(self, container, object, f=stdin, chunked=False,
198                       blocksize=1024, headers=None):
199         if not f:
200             return
201         path = '/%s/%s' % (container, object)
202         if not chunked and f != stdin:
203             data = f.read()
204             self.put(path, data, headers=headers)
205         else:
206             self._chunked_transfer(path, 'PUT', f, headers=headers,
207                                    blocksize=1024)
208
209     def update_object(self, container, object, f=stdin, chunked=False,
210                       blocksize=1024, headers=None):
211         if not f:
212             return
213         path = '/%s/%s' % (container, object)
214         if not chunked and f != stdin:
215             data = f.read()
216             self.post(path, data, headers=headers)
217         else:
218             self._chunked_transfer(path, 'POST', f, headers=headers,
219                                    blocksize=1024)
220
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)
224         headers = {}
225         if remove:
226             headers['X-Move-From'] = '/%s/%s' % (src_container, src_object)
227         else:
228             headers['X-Copy-From'] = '/%s/%s' % (src_container, src_object)
229         headers['Content-Length'] = 0
230         self.put(path, headers=headers)
231
232     def copy_object(self, src_container, src_object, dst_container,
233                              dst_object):
234         self._change_obj_location(src_container, src_object,
235                                    dst_container, dst_object)
236
237     def move_object(self, src_container, src_object, dst_container,
238                              dst_object):
239         self._change_obj_location(src_container, src_object,
240                                    dst_container, dst_object, True)
241
242     def delete_object(self, container, object):
243         self.delete('/%s/%s' % (container, object))
244
245     def retrieve_object_metadata(self, container, object):
246         path = '/%s/%s' % (container, object)
247         return self._get_metadata(path, 'x-object-meta-')
248
249     def update_object_metadata(self, container, object, **meta):
250         path = '/%s/%s' % (container, object)
251         self._set_metadata(path, 'object', **meta)