Revision f7ab99df pithos/lib/client.py

b/pithos/lib/client.py
34 34
        self.api = api
35 35
        self.verbose = verbose or debug
36 36
        self.debug = debug
37

  
37
    
38 38
    def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
39 39
                          blocksize=1024):
40 40
        http = HTTPConnection(self.host)
......
81 81
                print '%s: %s' % (key.capitalize(), val)
82 82
            print
83 83
        
84
        length = hasattr(headers, 'Content-length') \
85
        and headers['Content-length'] or None
86
        
84
        length = resp.getheader('Content-length', None)
87 85
        data = resp.read(length)
88 86
        if self.debug:
89 87
            print data
......
92 90
        if int(resp.status) in ERROR_CODES.keys():
93 91
            raise Fault(data, int(resp.status))
94 92
        
93
        #print '*',  resp.status, headers, data
95 94
        return resp.status, headers, data
96

  
95
    
97 96
    def req(self, method, path, body=None, headers=None, format='text',
98 97
            params=None):
99 98
        full_path = '/%s/%s%s?format=%s' % (self.api, self.account, path,
......
131 130
                print '%s: %s' % (key.capitalize(), val)
132 131
            print
133 132
        
134
        length = hasattr(headers, 'Content-length') \
135
        and headers['Content-length'] or None
136
        
133
        length = resp.getheader('Content-length', None)
137 134
        data = resp.read(length)
138 135
        if self.debug:
139 136
            print data
......
144 141
        
145 142
        #print '*',  resp.status, headers, data
146 143
        return resp.status, headers, data
147

  
144
    
148 145
    def delete(self, path, format='text'):
149 146
        return self.req('DELETE', path, format=format)
150

  
147
    
151 148
    def get(self, path, format='text', headers=None, params=None):
152 149
        return self.req('GET', path, headers=headers, format=format,
153 150
                        params=params)
154

  
151
    
155 152
    def head(self, path, format='text', params=None):
156 153
        return self.req('HEAD', path, format=format, params=params)
157

  
154
    
158 155
    def post(self, path, body=None, format='text', headers=None):
159 156
        return self.req('POST', path, body, headers=headers, format=format)
160

  
157
    
161 158
    def put(self, path, body=None, format='text', headers=None):
162 159
        return self.req('PUT', path, body, headers=headers, format=format)
163

  
160
    
164 161
    def _list(self, path, detail=False, params=None, headers=None):
165 162
        format = 'json' if detail else 'text'
166 163
        status, headers, data = self.get(path, format=format, headers=headers,
......
170 167
        else:
171 168
            data = data.strip().split('\n')
172 169
        return data
173

  
170
    
174 171
    def _get_metadata(self, path, prefix=None, params=None):
175 172
        status, headers, data = self.head(path, params=params)
176
        prefixlen = prefix and len(prefix) or 0
173
        prefixlen = len(prefix) if prefix else 0
177 174
        meta = {}
178 175
        for key, val in headers.items():
179 176
            if prefix and not key.startswith(prefix):
......
182 179
                key = key[prefixlen:]
183 180
            meta[key] = val
184 181
        return meta
185

  
186
    def _set_metadata(self, path, entity, **meta):
187
        headers = {}
182
    
183
    def _update_metadata(self, path, entity, **meta):
184
        """
185
         adds new and updates the values of previously set metadata
186
        """
188 187
        for key, val in meta.items():
189
            http_key = 'X-%s-Meta-%s' %(entity.capitalize(), key.capitalize())
188
            meta.pop(key)
189
            meta['X-%s-Meta-%s' %(entity.capitalize(), key.capitalize())] = val
190
        prev_meta = self._get_metadata(path)
191
        prev_meta.update(meta)
192
        headers = {}
193
        for key, val in prev_meta.items():
194
            headers[key.capitalize()] = val
195
        self.post(path, headers=headers)
196
    
197
    def _delete_metadata(self, path, entity, meta=[]):
198
        """
199
        delete previously set metadata
200
        """
201
        prev_meta = self._get_metadata(path)
202
        headers = {}
203
        for key, val in prev_meta.items():
204
            if key.split('-')[-1] in meta:
205
                continue
206
            http_key = key.capitalize()
190 207
            headers[http_key] = val
191 208
        self.post(path, headers=headers)
192

  
209
    
193 210
    # Storage Account Services
194

  
211
    
195 212
    def list_containers(self, detail=False, params=None, headers=None):
196 213
        return self._list('', detail, params, headers)
197

  
214
    
198 215
    def account_metadata(self, restricted=False, until=None):
199
        prefix = restricted and 'x-account-meta-' or None
200
        params = until and {'until':until} or None
216
        prefix = 'x-account-meta-' if restricted else None
217
        params = {'until':until} if until else None
201 218
        return self._get_metadata('', prefix, params=params)
202

  
219
    
203 220
    def update_account_metadata(self, **meta):
204
        self._set_metadata('', 'account', **meta)
205

  
221
        self._update_metadata('', 'account', **meta)
222
        
223
    def delete_account_metadata(self, meta=[]):
224
        self._delete_metadata('', 'account', meta)
225
    
206 226
    # Storage Container Services
207

  
227
    
208 228
    def list_objects(self, container, detail=False, params=None, headers=None):
209 229
        return self._list('/' + container, detail, params, headers)
210

  
230
    
211 231
    def create_container(self, container, headers=None):
212 232
        status, header, data = self.put('/' + container, headers=headers)
213 233
        if status == 202:
......
215 235
        elif status != 201:
216 236
            raise Fault(data, int(status))
217 237
        return True
218

  
238
    
219 239
    def delete_container(self, container):
220 240
        self.delete('/' + container)
221

  
241
    
222 242
    def retrieve_container_metadata(self, container, restricted=False,
223 243
                                    until=None):
224
        prefix = restricted and 'x-container-meta-' or None
225
        params = until and {'until':until} or None
244
        prefix = 'x-container-meta-' if restricted else None
245
        params = {'until':until} if until else None
226 246
        return self._get_metadata('/%s' % container, prefix, params=params)
227

  
247
    
228 248
    def update_container_metadata(self, container, **meta):
229
        self._set_metadata('/' + container, 'container', **meta)
230

  
249
        self._update_metadata('/' + container, 'container', **meta)
250
        
251
    def delete_container_metadata(self, container, meta=[]):
252
        path = '/%s' % (container)
253
        self._delete_metadata(path, 'container', meta)
254
    
231 255
    # Storage Object Services
232

  
256
    
233 257
    def retrieve_object(self, container, object, detail=False, headers=None,
234 258
                        version=None):
235 259
        path = '/%s/%s' % (container, object)
236 260
        format = 'json' if detail else 'text'
237
        params = version and {'version':version} or None 
261
        params = {'version':version} if version else None 
238 262
        status, headers, data = self.get(path, format, headers, params)
239 263
        return data
240

  
264
    
265
    def create_directory_marker(self, container, object):
266
        if not object:
267
            raise Fault('Directory markers have to be nested in a container')
268
        h = {'Content-Type':'application/directory'}
269
        self.create_object(container, object, f=None, headers=h)
270
    
241 271
    def create_object(self, container, object, f=stdin, chunked=False,
242 272
                      blocksize=1024, headers=None):
243 273
        """
......
247 277
        """
248 278
        path = '/%s/%s' % (container, object)
249 279
        if not chunked and f != stdin:
250
            data = f and f.read() or None
280
            data = f.read() if f else None
251 281
            return self.put(path, data, headers=headers)
252 282
        else:
253 283
            return self._chunked_transfer(path, 'PUT', f, headers=headers,
254 284
                                   blocksize=1024)
255

  
285
    
256 286
    def update_object(self, container, object, f=stdin, chunked=False,
257 287
                      blocksize=1024, headers=None):
258 288
        if not f:
......
264 294
        else:
265 295
            self._chunked_transfer(path, 'POST', f, headers=headers,
266 296
                                   blocksize=1024)
267

  
297
    
268 298
    def _change_obj_location(self, src_container, src_object, dst_container,
269 299
                             dst_object, remove=False, headers=None):
270 300
        path = '/%s/%s' % (dst_container, dst_object)
......
276 306
            headers['X-Copy-From'] = '/%s/%s' % (src_container, src_object)
277 307
        headers['Content-Length'] = 0
278 308
        self.put(path, headers=headers)
279

  
309
    
280 310
    def copy_object(self, src_container, src_object, dst_container,
281 311
                             dst_object, headers=None):
282 312
        self._change_obj_location(src_container, src_object,
283 313
                                   dst_container, dst_object,
284 314
                                   headers=headers)
285

  
315
    
286 316
    def move_object(self, src_container, src_object, dst_container,
287 317
                             dst_object, headers=None):
288 318
        self._change_obj_location(src_container, src_object,
289 319
                                   dst_container, dst_object, True, headers)
290

  
320
    
291 321
    def delete_object(self, container, object):
292 322
        self.delete('/%s/%s' % (container, object))
293

  
323
    
294 324
    def retrieve_object_metadata(self, container, object, restricted=False,
295 325
                                 version=None):
296 326
        path = '/%s/%s' % (container, object)
297
        prefix = restricted and 'x-object-meta-' or None
298
        params = version and {'version':version} or None
327
        prefix = 'x-object-meta-' if restricted else None
328
        params = {'version':version} if version else None
299 329
        return self._get_metadata(path, prefix, params=params)
300

  
330
    
301 331
    def update_object_metadata(self, container, object, **meta):
302 332
        path = '/%s/%s' % (container, object)
303
        self._set_metadata(path, 'object', **meta)
333
        self._update_metadata(path, 'object', **meta)
334
    
335
    def delete_object_metadata(self, container, object, meta=[]):
336
        path = '/%s/%s' % (container, object)
337
        self._delete_metadata(path, 'object', meta)
338
    
339
    def trash_object(self, container, object):
340
        """
341
        trashes an object
342
        actually resets all object metadata with trash = true 
343
        """
344
        path = '/%s/%s' % (container, object)
345
        meta = {'trash':'true'}
346
        self._update_metadata(path, 'object', **meta)
347
    
348
    def restore_object(self, container, object):
349
        """
350
        restores a trashed object
351
        actualy just resets all object metadata except trash
352
        """
353
        self.delete_object_metadata(container, object, ['trash'])
354

  

Also available in: Unified diff