Revision f0eacc2c

b/pithos/api/tests.py
36 36
from django.utils import simplejson as json
37 37
from xml.dom import minidom
38 38
from StringIO import StringIO
39
import time as _time
39 40
import types
40 41
import hashlib
41 42
import os
......
45 46
import string
46 47
import re
47 48

  
48
#from pithos.backends import backend
49

  
50 49
DATE_FORMATS = ["%a %b %d %H:%M:%S %Y",
51 50
                "%A, %d-%b-%y %H:%M:%S GMT",
52 51
                "%a, %d %b %Y %H:%M:%S GMT"]
......
54 53
DEFAULT_HOST = 'pithos.dev.grnet.gr'
55 54
#DEFAULT_HOST = '127.0.0.1:8000'
56 55
DEFAULT_API = 'v1'
57
DEFAULT_USER = 'papagian'
58
DEFAULT_AUTH = '0004'
56
DEFAULT_USER = 'test'
57
DEFAULT_AUTH = '0000'
59 58

  
60 59
class BaseTestCase(unittest.TestCase):
61 60
    #TODO unauthorized request
62 61
    def setUp(self):
63
        self.client = Pithos_Client(DEFAULT_HOST, DEFAULT_AUTH, DEFAULT_USER, DEFAULT_API)
64
        self.headers = {
65
            'account': ('x-account-container-count',
66
                        'x-account-bytes-used',
67
                        'last-modified',
68
                        'content-length',
69
                        'date',
70
                        'content_type',
71
                        'server',),
72
            'object': ('etag',
73
                       'content-length',
74
                       'content_type',
75
                       'content-encoding',
76
                       'last-modified',
77
                       'date',
78
                       'x-object-manifest',
79
                       'content-range',
80
                       'x-object-modified-by',
81
                       'x-object-version',
82
                       'x-object-version-timestamp',
83
                       'server',),
84
            'container': ('x-container-object-count',
85
                          'x-container-bytes-used',
86
                          'content_type',
87
                          'last-modified',
88
                          'content-length',
89
                          'date',
90
                          'x-container-block-size',
91
                          'x-container-block-hash',
92
                          'x-container-policy-quota',
93
                          'x-container-policy-versioning',
94
                          'server',
95
                          'x-container-object-meta',
96
                          'x-container-policy-versioning',
97
                          'server',)}
98
        
99
        self.contentTypes = {'xml':'application/xml',
100
                             'json':'application/json',
101
                             '':'text/plain'}
62
        self.client = Pithos_Client(DEFAULT_HOST,
63
                                    DEFAULT_AUTH,
64
                                    DEFAULT_USER,
65
                                    DEFAULT_API)
66
        self.invalid_client = Pithos_Client(DEFAULT_HOST,
67
                                                  DEFAULT_AUTH,
68
                                                  'invalid',
69
                                                  DEFAULT_API)
70
        #self.headers = {
71
        #    'account': ('x-account-container-count',
72
        #                'x-account-bytes-used',
73
        #                'last-modified',
74
        #                'content-length',
75
        #                'date',
76
        #                'content_type',
77
        #                'server',),
78
        #    'object': ('etag',
79
        #               'content-length',
80
        #               'content_type',
81
        #               'content-encoding',
82
        #               'last-modified',
83
        #               'date',
84
        #               'x-object-manifest',
85
        #               'content-range',
86
        #               'x-object-modified-by',
87
        #               'x-object-version',
88
        #               'x-object-version-timestamp',
89
        #               'server',),
90
        #    'container': ('x-container-object-count',
91
        #                  'x-container-bytes-used',
92
        #                  'content_type',
93
        #                  'last-modified',
94
        #                  'content-length',
95
        #                  'date',
96
        #                  'x-container-block-size',
97
        #                  'x-container-block-hash',
98
        #                  'x-container-policy-quota',
99
        #                  'x-container-policy-versioning',
100
        #                  'server',
101
        #                  'x-container-object-meta',
102
        #                  'x-container-policy-versioning',
103
        #                  'server',)}
104
        #
105
        #self.contentTypes = {'xml':'application/xml',
106
        #                     'json':'application/json',
107
        #                     '':'text/plain'}
102 108
        self.extended = {
103 109
            'container':(
104 110
                'name',
......
146 152
    #            data = minidom.parseString(data)
147 153
    #    return status, headers, data
148 154
    
149
    def assert_headers(self, headers, type, **exp_meta):
150
        prefix = 'x-%s-meta-' %type
151
        system_headers = [h for h in headers if not h.startswith(prefix)]
152
        for k,v in headers.items():
153
            if k in system_headers:
154
                self.assertTrue(k in headers[type])
155
            elif exp_meta:
156
                k = k.split(prefix)[-1]
157
                self.assertEqual(v, exp_meta[k])
158
    
159
    #def assert_extended(self, data, format, type, size):
160
    #    if format == 'xml':
161
    #        self._assert_xml(data, type, size)
162
    #    elif format == 'json':
163
    #        self._assert_json(data, type, size)
164
    
165
    #def _assert_json(self, data, type, size):
166
    #    print '#', data
167
    #    convert = lambda s: s.lower()
168
    #    info = [convert(elem) for elem in self.extended[type]]
169
    #    data = json.loads(data)
170
    #    self.assertTrue(len(data) <= size)
171
    #    for item in info:
172
    #        for i in data:
173
    #            if 'subdir' in i.keys():
174
    #                continue
175
    #            self.assertTrue(item in i.keys())
176
    
177
    #def _assert_xml(self, data, type, size):
178
    #    print '#', data
179
    #    convert = lambda s: s.lower()
180
    #    info = [convert(elem) for elem in self.extended[type]]
181
    #    try:
182
    #        info.remove('content_encoding')
183
    #    except ValueError:
184
    #        pass
185
    #    xml = minidom.parseString(data)
186
    #    entities = xml.getElementsByTagName(type)
187
    #    self.assertTrue(len(entities) <= size)
188
    #    for e in entities:
189
    #        for item in info:
190
    #            self.assertTrue(e.hasAttribute(item))
155
    #def assert_headers(self, headers, type, **exp_meta):
156
    #    prefix = 'x-%s-meta-' %type
157
    #    system_headers = [h for h in headers if not h.startswith(prefix)]
158
    #    for k,v in headers.items():
159
    #        if k in system_headers:
160
    #            self.assertTrue(k in headers[type])
161
    #        elif exp_meta:
162
    #            k = k.split(prefix)[-1]
163
    #            self.assertEqual(v, exp_meta[k])
164
    
165
    def assert_extended(self, data, format, type, size):
166
        if format == 'xml':
167
            self._assert_xml(data, type, size)
168
        elif format == 'json':
169
            self._assert_json(data, type, size)
170
    
171
    def _assert_json(self, data, type, size):
172
        print '#', data
173
        convert = lambda s: s.lower()
174
        info = [convert(elem) for elem in self.extended[type]]
175
        self.assertTrue(len(data) <= size)
176
        for item in info:
177
            for i in data:
178
                if 'subdir' in i.keys():
179
                    continue
180
                self.assertTrue(item in i.keys())
181
    
182
    def _assert_xml(self, data, type, size):
183
        convert = lambda s: s.lower()
184
        info = [convert(elem) for elem in self.extended[type]]
185
        try:
186
            info.remove('content_encoding')
187
        except ValueError:
188
            pass
189
        xml = data
190
        entities = xml.getElementsByTagName(type)
191
        self.assertTrue(len(entities) <= size)
192
        for e in entities:
193
            for item in info:
194
                print '#', item
195
                self.assertTrue(e.hasAttribute(item))
191 196
    
192 197
    def assert_raises_fault(self, status, callableObj, *args, **kwargs):
193 198
        """
......
265 270
        for item in self.containers:
266 271
            self.client.create_container(item)
267 272
    
273
    def tearDown(self):
274
        self.client.delete_account_metadata(['foo'])
275
        BaseTestCase.tearDown(self)
276
    
268 277
    def test_get_account_meta(self):
269 278
        meta = self.client.retrieve_account_metadata()
270 279
        
......
277 286
            size = size + int(m['x-container-bytes-used'])
278 287
        self.assertEqual(meta['x-account-bytes-used'], str(size))
279 288
    
280
    #def test_get_account_401(self):
281
    #    response = self.get_account_meta('non-existing-account')
282
    #    print response
283
    #    self.assertEqual(response.status_code, 401)
289
    def test_get_account_401(self):
290
        self.assert_raises_fault(401,
291
                                 self.invalid_client.retrieve_account_metadata)
292
    
293
    def test_get_account_meta_until(self):
294
        t = datetime.datetime.utcnow()
295
        past = t - datetime.timedelta(minutes=-15)
296
        past = int(_time.mktime(past.timetuple()))
297
        
298
        meta = {'foo':'bar'}
299
        self.client.update_account_metadata(**meta)
300
        meta = self.client.retrieve_account_metadata(restricted=True,
301
                                                     until=past)
302
        self.assertTrue('foo' not in meta)
303
        
304
        meta = self.client.retrieve_account_metadata(restricted=True)
305
        self.assertTrue('foo' in meta)
306
    
307
    def test_get_account_meta_until_invalid_date(self):
308
        meta = {'foo':'bar'}
309
        self.client.update_account_metadata(**meta)
310
        meta = self.client.retrieve_account_metadata(restricted=True,
311
                                                     until='kshfksfh')
312
        self.assertTrue('foo' in meta)
284 313

  
285 314
class AccountGet(BaseTestCase):
286 315
    def setUp(self):
......
296 325
        containers = self.client.list_containers()
297 326
        self.assertEquals(self.containers, containers)
298 327
    
299
    #def test_list_204(self):
300
    #    response = self.list_containers('non-existing-account')
301
    #    self.assertEqual(response.status_code, 204)
328
    def test_list_401(self):
329
        self.assert_raises_fault(401, self.invalid_client.list_containers)
302 330
    
303 331
    def test_list_with_limit(self):
304 332
        limit = 2
......
318 346
        i = self.containers.index(m) + 1
319 347
        self.assertEquals(self.containers[i:(i+l)], containers)
320 348
    
321
    #def test_extended_list(self):
322
    #    self.list_containers(self.account, limit=3, format='xml')
323
    #    self.list_containers(self.account, limit=3, format='json')
324
    
325 349
    def test_list_json_with_marker(self):
326 350
        l = 2
327 351
        m = 'bananas'
328
        containers = self.client.list_containers(limit=l, marker=m, detail=True)
352
        containers = self.client.list_containers(limit=l, marker=m, format='json')
353
        self.assert_extended(containers, 'json', 'container', l)
329 354
        self.assertEqual(containers[0]['name'], 'kiwis')
330 355
        self.assertEqual(containers[1]['name'], 'oranges')
331 356
    
332
    #def test_list_xml_with_marker(self):
333
    #    l = 2
334
    #    m = 'oranges'
335
    #    status, headers, xml = self.list_containers(limit=l, marker=m, format='xml')
336
    #    nodes = xml.getElementsByTagName('name')
337
    #    self.assertEqual(len(nodes), 1)
338
    #    self.assertEqual(nodes[0].childNodes[0].data, 'pears')
357
    def test_list_xml_with_marker(self):
358
        l = 2
359
        m = 'oranges'
360
        xml = self.client.list_containers(limit=l, marker=m, format='xml')
361
        #self.assert_extended(xml, 'xml', 'container', l)
362
        nodes = xml.getElementsByTagName('name')
363
        self.assertEqual(len(nodes), 1)
364
        self.assertEqual(nodes[0].childNodes[0].data, 'pears')
339 365
    
340 366
    def test_if_modified_since(self):
341 367
        t = datetime.datetime.utcnow()
......
404 430
        self.client.update_account_metadata(**meta)
405 431
        self.assertEqual(meta, self.client.retrieve_account_metadata(restricted=True))
406 432
    
407
    #def test_invalid_account_update_meta(self):
408
    #    with AssertMappingInvariant(self.get_account_meta, self.account):
409
    #        meta = {'HTTP_X_ACCOUNT_META_TEST':'test',
410
    #               'HTTP_X_ACCOUNT_META_TOST':'tost'}
411
    #        response = self.update_account_meta('non-existing-account', **meta)
433
    def test_invalid_account_update_meta(self):
434
        meta = {'HTTP_X_ACCOUNT_META_TEST':'test',
435
               'HTTP_X_ACCOUNT_META_TOST':'tost'}
436
        self.assert_raises_fault(401,
437
                                 self.invalid_client.update_account_metadata,
438
                                 **meta)
412 439

  
413 440
class ContainerHead(BaseTestCase):
414 441
    def setUp(self):
......
486 513
        self.assertEquals(['photos/me.jpg'], objects)
487 514
    
488 515
    def test_extended_list_json(self):
489
        objects = self.client.list_objects(self.container[1], detail=True,
516
        objects = self.client.list_objects(self.container[1], format='json',
490 517
                                           limit=2, prefix='photos/animals',
491 518
                                           delimiter='/')
492 519
        self.assertEqual(objects[0]['subdir'], 'photos/animals/cats/')
493 520
        self.assertEqual(objects[1]['subdir'], 'photos/animals/dogs/')
494 521
    
495
    #def test_extended_list_xml(self):
496
    #    xml = self.client.list_objects(self.container[1], format='xml', limit=4,
497
    #                                   prefix='photos', delimiter='/')
498
    #    dirs = xml.getElementsByTagName('subdir')
499
    #    self.assertEqual(len(dirs), 2)
500
    #    self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
501
    #    self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
502
    #    
503
    #    objects = xml.getElementsByTagName('name')
504
    #    self.assertEqual(len(objects), 1)
505
    #    self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
522
    def test_extended_list_xml(self):
523
        xml = self.client.list_objects(self.container[1], format='xml', limit=4,
524
                                       prefix='photos', delimiter='/')
525
        dirs = xml.getElementsByTagName('subdir')
526
        self.assertEqual(len(dirs), 2)
527
        self.assertEqual(dirs[0].attributes['name'].value, 'photos/animals/')
528
        self.assertEqual(dirs[1].attributes['name'].value, 'photos/plants/')
529
        
530
        objects = xml.getElementsByTagName('name')
531
        self.assertEqual(len(objects), 1)
532
        self.assertEqual(objects[0].childNodes[0].data, 'photos/me.jpg')
506 533
    
507 534
    def test_list_meta_double_matching(self):
508 535
        meta = {'quality':'aaa', 'stock':'true'}
......
978 1005
        fname = 'largefile'
979 1006
        o = self.upload_random_data(self.containers[1], fname, l)
980 1007
        if o:
981
            data = self.client.retrieve_object(self.containers[1], fname, detail=True)
1008
            data = self.client.retrieve_object(self.containers[1], fname,
1009
                                               format='json')
982 1010
            body = json.loads(data)
983 1011
            hashes = body['hashes']
984 1012
            block_size = body['block_size']
......
1026 1054
        self.assert_raises_fault(422, self.upload_random_data, self.container,
1027 1055
                                 o_names[0], **meta)
1028 1056
    
1029
    def test_chucked_transfer(self):
1057
    def test_chunked_transfer(self):
1030 1058
        data = get_random_data()
1031 1059
        objname = 'object'
1032 1060
        self.client.create_object_using_chunks(self.container, objname,
......
1197 1225
                                    self.containers[0], self.obj['name']):
1198 1226
            self.test_update_object(499, 0, True)
1199 1227
    
1200
    #no use if the server resets the content-legth
1201 1228
    def test_update_object_invalid_range_and_length(self):
1202 1229
        with AssertContentInvariant(self.client.retrieve_object,
1203 1230
                                    self.containers[0], self.obj['name']):
1204 1231
            self.test_update_object(499, 0, True, -1)
1205 1232
    
1206
    #no use if the server resets the content-legth
1207 1233
    def test_update_object_invalid_range_with_no_content_length(self):
1208 1234
        with AssertContentInvariant(self.client.retrieve_object,
1209 1235
                                    self.containers[0], self.obj['name']):
b/pithos/lib/client.py
33 33

  
34 34
from httplib import HTTPConnection, HTTP
35 35
from sys import stdin
36
from xml.dom import minidom
36 37

  
37 38
import json
38 39
import types
......
93 94
        kwargs['headers']['X-Auth-Token'] = self.token
94 95
        if body:
95 96
            kwargs['body'] = body
96
        else:
97
        elif 'content-type' not in kwargs['headers']:
97 98
            kwargs['headers']['content-type'] = ''
98 99
        kwargs['headers'].setdefault('content-length', len(body) if body else 0)
99 100
        kwargs['headers'].setdefault('content-type', 'application/octet-stream')
......
141 142
    def put(self, path, body=None, format='text', headers=None):
142 143
        return self._req('PUT', path, body, headers=headers, format=format)
143 144
    
144
    def _list(self, path, detail=False, params={}, **headers):
145
        format = 'json' if detail else 'text'
145
    def _list(self, path, format='text', params={}, **headers):
146 146
        status, headers, data = self.get(path, format=format, headers=headers,
147 147
                                         params=params)
148
        if detail:
148
        if format == 'json':
149 149
            data = json.loads(data) if data else ''
150
        elif format == 'xml':
151
            print '#', data
152
            data = minidom.parseString(data)
150 153
        else:
151 154
            data = data.strip().split('\n') if data else ''
152 155
        return data
......
178 181
        return ll
179 182
    
180 183
class OOS_Client(Client):
181
    """Openstack Objest Storage Client"""
184
    """Openstack Object Storage Client"""
182 185
    
183 186
    def _update_metadata(self, path, entity, **meta):
184 187
        """adds new and updates the values of previously set metadata"""
......
203 206
    
204 207
    # Storage Account Services
205 208
    
206
    def list_containers(self, detail=False, limit=10000, marker=None, params={},
209
    def list_containers(self, format='text', limit=10000, marker=None, params={},
207 210
                        **headers):
208 211
        """lists containers"""
209 212
        if not params:
210 213
            params = {}
211 214
        params.update({'limit':limit, 'marker':marker})
212
        return self._list('', detail, params, **headers)
215
        return self._list('', format, params, **headers)
213 216
    
214 217
    def retrieve_account_metadata(self, restricted=False, **params):
215 218
        """returns the account metadata"""
......
229 232
    def _filter_trashed(self, l):
230 233
        return self._filter(l, {'trash':'true'})
231 234
    
232
    def list_objects(self, container, detail=False, limit=10000, marker=None,
235
    def list_objects(self, container, format='text', limit=10000, marker=None,
233 236
                     prefix=None, delimiter=None, path=None,
234 237
                     include_trashed=False, params={}, **headers):
235 238
        """returns a list with the container objects"""
236 239
        params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
237 240
                       'delimiter':delimiter, 'path':path})
238
        l = self._list('/' + container, detail, params, **headers)
239
        if not include_trashed:
241
        l = self._list('/' + container, format, params, **headers)
242
        #TODO support filter trashed with xml also
243
        if format != 'xml' and not include_trashed:
240 244
            l = self._filter_trashed(l)
241 245
        return l
242 246
    
......
272 276
    
273 277
    # Storage Object Services
274 278
    
275
    def request_object(self, container, object, detail=False, params={},
279
    def request_object(self, container, object, format='text', params={},
276 280
                        **headers):
277 281
        """returns tuple containing the status, headers and data response for an object request"""
278 282
        path = '/%s/%s' % (container, object)
279
        format = 'json' if detail else 'text' 
280 283
        status, headers, data = self.get(path, format, headers, params)
281 284
        return status, headers, data
282 285
    
283
    def retrieve_object(self, container, object, detail=False, params={},
286
    def retrieve_object(self, container, object, format='text', params={},
284 287
                             **headers):
285 288
        """returns an object's data"""
286
        t = self.request_object(container, object, detail, params, **headers)
289
        t = self.request_object(container, object, format, params, **headers)
287 290
        return t[2]
288 291
    
289 292
    def create_directory_marker(self, container, object):
......
469 472
        headers = {}
470 473
        prefix = 'x-%s-meta-' % entity
471 474
        for m in meta:
472
            headers['%s%s' % (prefix, m)] = None
475
            headers['%s%s' % (prefix, m)] = ''
473 476
        return self.post(path, headers=headers, params=params)
474 477
    
475 478
    # Storage Account Services
476 479
    
477
    def list_containers(self, detail=False, if_modified_since=None,
480
    def list_containers(self, format='text', if_modified_since=None,
478 481
                        if_unmodified_since=None, limit=1000, marker=None,
479 482
                        until=None):
480 483
        """returns a list with the account containers"""
481 484
        params = {'until':until} if until else None
482 485
        headers = {'if-modified-since':if_modified_since,
483 486
                   'if-unmodified-since':if_unmodified_since}
484
        return OOS_Client.list_containers(self, detail=detail, limit=limit,
487
        return OOS_Client.list_containers(self, format=format, limit=limit,
485 488
                                          marker=marker, params=params,
486 489
                                          **headers)
487 490
    
......
509 512
    
510 513
    # Storage Container Services
511 514
    
512
    def list_objects(self, container, detail=False, limit=10000, marker=None,
515
    def list_objects(self, container, format='text', limit=10000, marker=None,
513 516
                     prefix=None, delimiter=None, path=None,
514 517
                     include_trashed=False, params={}, if_modified_since=None,
515 518
                     if_unmodified_since=None, meta={}, until=None):
......
545 548
    
546 549
    # Storage Object Services
547 550
    
548
    def retrieve_object(self, container, object, params={}, detail=False, range=None,
551
    def retrieve_object(self, container, object, params={}, format='text', range=None,
549 552
                        if_range=None, if_match=None, if_none_match=None,
550 553
                        if_modified_since=None, if_unmodified_since=None,
551 554
                        **headers):
......
556 559
        l = [elem for elem in l if eval(elem)]
557 560
        for elem in l:
558 561
            headers.update({elem:eval(elem)})
559
        return OOS_Client.retrieve_object(self, container, object, detail=detail,
562
        return OOS_Client.retrieve_object(self, container, object, format=format,
560 563
                                          params=params, **headers)
561 564
    
562 565
    def retrieve_object_version(self, container, object, version, detail=False,
b/pithos/test-settings.py
60 60

  
61 61
# The backend to use and its initilization options.
62 62
if TEST:
63
    BACKEND = ('SimpleBackend', (os.path.join(PROJECT_PATH, 'data/testpithos.db'),))
63
    BACKEND = ('SimpleBackend', (os.path.join(PROJECT_PATH, 'data/test/'),))
64 64
else:
65
    BACKEND = ('SimpleBackend', (os.path.join(PROJECT_PATH, 'data/pithos.db'),))
65
    BACKEND = ('SimpleBackend', (os.path.join(PROJECT_PATH, 'data/pithos/'),))
66 66

  
67 67
# Local time zone for this installation. Choices can be found here:
68 68
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
......
149 149
    '0004': 'papagian',
150 150
    '0005': 'louridas',
151 151
    '0006': 'chstath',
152
    '0007': 'pkanavos'}
152
    '0007': 'pkanavos',
153
    '0008': 'mvasilak'}
b/tools/store
143 143
                          dest='if_unmodified_since', default=None,
144 144
                          help='show output if not modified since then')
145 145
        parser.add_option('--until', action='store', dest='until',
146
                          default=False, help='show metadata until that date')
146
                          default=None, help='show metadata until that date')
147 147
        parser.add_option('--format', action='store', dest='format',
148 148
                          default='%d/%m/%Y', help='format to parse until date')
149 149
    
......
154 154
            self.list_containers()
155 155
    
156 156
    def list_containers(self):
157
        attrs = ['detail', 'limit', 'marker', 'if_modified_since',
157
        attrs = ['limit', 'marker', 'if_modified_since',
158 158
                 'if_unmodified_since']
159 159
        args = self._build_args(attrs)
160
        args['format'] = 'json' if self.detail else 'text'
160 161
        
161
        if self.until:
162
        if getattr(self, 'until'):
162 163
            t = _time.strptime(self.until, self.format)
163 164
            args['until'] = int(_time.mktime(t))
164 165
        
......
168 169
    def list_objects(self, container):
169 170
        #prepate params
170 171
        params = {}
171
        attrs = ['detail', 'limit', 'marker', 'prefix', 'delimiter', 'path',
172
        attrs = ['limit', 'marker', 'prefix', 'delimiter', 'path',
172 173
                 'meta', 'if_modified_since', 'if_unmodified_since']
173 174
        args = self._build_args(attrs)
175
        args['format'] = 'json' if self.detail else 'text'
174 176
        
175 177
        if self.until:
176 178
            t = _time.strptime(self.until, self.format)
......
195 197
        parser.add_option('-r', action='store_true', dest='restricted',
196 198
                          default=False, help='show only user defined metadata')
197 199
        parser.add_option('--until', action='store', dest='until',
198
                          default=False, help='show metadata until that date')
200
                          default=None, help='show metadata until that date')
199 201
        parser.add_option('--format', action='store', dest='format',
200 202
                          default='%d/%m/%Y', help='format to parse until date')
201 203
        parser.add_option('--version', action='store', dest='version',
......
205 207
    def execute(self, path=''):
206 208
        container, sep, object = path.partition('/')
207 209
        args = {'restricted':self.restricted}
208
        if self.until:
210
        if getattr(self, 'until'):
209 211
            t = _time.strptime(self.until, self.format)
210 212
            args['until'] = int(_time.mktime(t))
211 213
            
......
243 245
    
244 246
    def add_options(self, parser):
245 247
        parser.add_option('--until', action='store', dest='until',
246
                          default=False, help='remove history until that date')
248
                          default=None, help='remove history until that date')
247 249
        parser.add_option('--format', action='store', dest='format',
248 250
                          default='%d/%m/%Y', help='format to parse until date')
249 251
    
250 252
    def execute(self, path):
251 253
        container, sep, object = path.partition('/')
252
        if self.until:
254
        until = None
255
        if getattr(self, 'until'):
253 256
            t = _time.strptime(self.until, self.format)
254 257
            until = int(_time.mktime(t))
255 258
        
......
293 296
                          help='get the full object version list')
294 297
    
295 298
    def execute(self, path):
296
        attrs = ['detail', 'if_match', 'if_none_match', 'if_modified_since',
299
        attrs = ['if_match', 'if_none_match', 'if_modified_since',
297 300
                 'if_unmodified_since']
298 301
        args = self._build_args(attrs)
299
        
302
        args['format'] = 'json' if self.detail else 'text'
300 303
        if self.range:
301 304
            args['range'] = 'bytes=%s' %self.range
302 305
        if getattr(self, 'if_range'):

Also available in: Unified diff