Revision 2bcb595a

b/kamaki/cli.py
660 660
        self.client.set_members(image_id, member)
661 661

  
662 662

  
663
class store_command(object):
664
    """base class for all store_* commands"""
663
class _store_account_command(object):
664
    """Base class for account level storage commands"""
665 665
    
666
    def update_parser(cls, parser):
666
    def update_parser(self, parser):
667 667
        parser.add_option('--account', dest='account', metavar='NAME',
668 668
                          help="Specify an account to use")
669
        parser.add_option('--container', dest='container', metavar='NAME',
670
                          help="Specify a container to use")
671 669
    
672 670
    def progress(self, message):
673 671
        """Return a generator function to be used for progress tracking"""
......
688 686
    def main(self):
689 687
        if self.options.account is not None:
690 688
            self.client.account = self.options.account
689

  
690

  
691
class _store_container_command(_store_account_command):
692
    """Base class for container level storage commands"""
693
    
694
    def update_parser(self, parser):
695
        super(_store_container_command, self).update_parser(parser)
696
        parser.add_option('--container', dest='container', metavar='NAME',
697
                          help="Specify a container to use")
698
    
699
    def main(self):
700
        super(_store_container_command, self).main()
691 701
        if self.options.container is not None:
692 702
            self.client.container = self.options.container
693 703

  
694 704

  
695 705
@command(api='storage')
696
class store_create(object):
706
class store_create(_store_account_command):
697 707
    """Create a container"""
698 708
    
699
    def update_parser(cls, parser):
700
        parser.add_option('--account', dest='account', metavar='NAME',
701
                          help="Specify an account to use")
702
    
703 709
    def main(self, container):
704 710
        if self.options.account:
705 711
            self.client.account = self.options.account
......
707 713

  
708 714

  
709 715
@command(api='storage')
710
class store_container(object):
716
class store_container(_store_account_command):
711 717
    """Get container info"""
712 718
    
713
    def update_parser(cls, parser):
714
        parser.add_option('--account', dest='account', metavar='NAME',
715
                          help="Specify an account to use")
716
    
717 719
    def main(self, container):
718 720
        if self.options.account:
719 721
            self.client.account = self.options.account
......
722 724

  
723 725

  
724 726
@command(api='storage')
725
class store_upload(store_command):
727
class store_list(_store_container_command):
728
    """List objects"""
729
    
730
    def format_size(self, size):
731
        units = ('B', 'K', 'M', 'G', 'T')
732
        size = float(size)
733
        for unit in units:
734
            if size <= 1024:
735
                break
736
            size /= 1024
737
        s = ('%.1f' % size).rstrip('.0')
738
        return s + unit
739
    
740
    
741
    def main(self, path=''):
742
        super(store_list, self).main()
743
        for object in self.client.list_objects():
744
            size = self.format_size(object['bytes'])
745
            print '%6s %s' % (size, object['name'])
746
        
747

  
748
@command(api='storage')
749
class store_upload(_store_container_command):
726 750
    """Upload a file"""
727 751
    
728 752
    def main(self, path, remote_path=None):
......
738 762

  
739 763

  
740 764
@command(api='storage')
741
class store_download(store_command):
765
class store_download(_store_container_command):
742 766
    """Download a file"""
743 767
        
744 768
    def main(self, remote_path, local_path='-'):
......
764 788

  
765 789

  
766 790
@command(api='storage')
767
class store_delete(store_command):
791
class store_delete(_store_container_command):
768 792
    """Delete a file"""
769 793
    
770 794
    def main(self, path):
b/kamaki/clients/__init__.py
46 46
# Add a convenience json property to the responses
47 47
def _json(self):
48 48
    try:
49
        return json.loads(self.content)
49
        return json.loads(self.content) if self.content else {}
50 50
    except ValueError:
51 51
        raise ClientError("Invalid JSON reply", self.status_code)
52 52
requests.Response.json = property(_json)
b/kamaki/clients/pithos.py
94 94
        assert size == file_size
95 95
                
96 96
        path = '/%s/%s/%s' % (self.account, self.container, object)
97
        params = {'hashmap': '', 'format': 'json'}
97
        params = dict(format='json', hashmap='')
98 98
        hashmap = dict(bytes=size, hashes=hashes.keys())
99 99
        r = self.put(path, params=params, json=hashmap, success=(201, 409))
100 100
        
b/kamaki/clients/storage.py
84 84
    def get_object(self, object):
85 85
        self.assert_container()
86 86
        path = '/%s/%s/%s' % (self.account, self.container, object)
87
        r = self.get(path, raw=True)
87
        r = self.get(path, raw=True, success=200)
88 88
        size = int(r.headers['content-length'])
89 89
        return r.raw, size
90 90
    
......
92 92
        self.assert_container()
93 93
        path = '/%s/%s/%s' % (self.account, self.container, object)
94 94
        self.delete(path, success=204)
95
    
96
    def list_objects(self, path=''):
97
        self.assert_container()
98
        path = '/%s/%s' % (self.account, self.container)
99
        params = dict(format='json')
100
        r = self.get(path, params=params, success=(200, 204))
101
        return r.json
b/setup.py
51 51
        'console_scripts': ['kamaki = kamaki.cli:main']
52 52
    },
53 53
    install_requires=[
54
        'requests>=0.10.2',
54
        'requests>=0.10.6',
55 55
        'clint>=0.3'
56 56
    ]
57 57
)

Also available in: Unified diff