Changeset 44

Show
Ignore:
Timestamp:
06/08/06 12:54:49 (6 years ago)
Author:
mk
Message:

Moved unzip_package and untar_package out of Cheesecake class to _util module.
Logfile is only left if package scoring failed.
Added doctest for isiterable().
Fixed download_pkg() check for HTTP 404 error.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/mk/cheesecake/_util.py

    r42 r44  
    1 #!/usr/bin/env python 
    2 """ 
    3 Cheesecake: How tasty is your code? 
     1import os 
     2import sys 
     3import tarfile 
     4import zipfile 
    45 
    5 The idea of the Cheesecake project is to rank Python packages 
    6 based on various empiric "kwalitee" factors, such as: 
    7  
    8         * whether the package can be downloaded 
    9         * whether the package can be unpacked 
    10         * whether the package can be installed into an alternate directory 
    11         * existence of certain files such as README, INSTALL, LICENSE, setup.py etc. 
    12         * existence of certain directories such as doc, test, demo, examples 
    13         * percentage of modules/functions/classes/methods with docstrings 
    14         * percentage of functions/methods that are unit tested 
    15         * average pylint score for all non-test and non-demo modules 
    16         * whether the package can be unpacked 
    17         * whether the package can be installed into an alternate directory 
    18 """ 
    19  
    20 import os, sys 
    216from subprocess import call, ProcessError, Popen, PIPE, STDOUT 
    227 
     
    10994    msg = char * (PAD_TEXT + PAD_VALUE + 1) 
    11095    return msg 
     96 
     97def unzip_package(package, destination): 
     98    """Unzip given `package` to the `destination` directory. 
     99 
     100    Return name of unpacked directory or None on error. 
     101    """ 
     102    try: 
     103        z = zipfile.ZipFile(package) 
     104    except zipfile.error: 
     105        return None 
     106 
     107    # Get directory structure from zip and create it in destination directory. 
     108    for name in z.namelist(): 
     109        (dir, file) = os.path.split(name) 
     110        unpack_dir = dir 
     111        target_dir = os.path.join(destination, dir) 
     112        if not os.path.exists(target_dir): 
     113            os.makedirs(target_dir) 
     114 
     115    # Extract files to directory structure 
     116    for i, name in enumerate(z.namelist()): 
     117        if not name.endswith('/'): 
     118            outfile = open(os.path.join(destination, name), 'wb') 
     119            outfile.write(z.read(name)) 
     120            outfile.flush() 
     121            outfile.close() 
     122 
     123    return unpack_dir.split(os.sep)[0] 
     124 
     125def untar_package(package, destination): 
     126    """Untar given `package` to the `destination` directory. 
     127 
     128    Return name of unpacked directory or None on error. 
     129    """ 
     130    try: 
     131        t = tarfile.open(package) 
     132    except tarfile.ReadError, e: 
     133        return None 
     134 
     135    for member in t.getmembers(): 
     136        t.extract(member, destination) 
     137 
     138    tarinfo = t.members[0] 
     139    return tarinfo.name.split(os.sep)[0] 
  • branches/mk/cheesecake/cheesecake_index.py

    r43 r44  
    1919 
    2020import os, sys, re, shutil 
    21 import tarfile, zipfile 
    2221import tempfile 
    2322from optparse import OptionParser 
     
    2625from math import ceil 
    2726 
    28 from _util import run_cmd, pad_with_dots, pad_left_spaces, pad_msg, pad_line, command_successful 
     27from _util import pad_with_dots, pad_left_spaces, pad_msg, pad_line 
     28from _util import run_cmd, command_successful 
     29from _util import unzip_package, untar_package 
    2930from _util import StdoutRedirector 
    3031import logger 
     
    4344 
    4445def isiterable(obj): 
    45     return hasattr(obj, '__iter__') 
     46    """Check whether object is iterable. 
     47 
     48    >>> isiterable([1,2,3]) 
     49    True 
     50    >>> isiterable("string") 
     51    True 
     52    >>> isiterable(object) 
     53    False 
     54    """ 
     55    return hasattr(obj, '__iter__') or isinstance(obj, basestring) 
    4656 
    4757def has_extension(filename, ext): 
     
    571581 
    572582    def reset_rules(self, rules): 
    573         if isiterable(rules): 
     583        if isinstance(rules, basestring): 
     584            pass 
     585        elif isiterable(rules): 
    574586            for rule in rules: 
    575587                self.reset_rules(rule) 
     
    785797        self.quiet = quiet 
    786798 
    787         self.package_types = ["tar.gz", "tgz", "zip"] 
     799        self.package_types = { 
     800            "tar.gz": untar_package, 
     801            "tgz": untar_package, 
     802            "zip": unzip_package, 
     803        } 
     804 
    788805        self.sandbox_pkg_file = "" 
    789806        self.sandbox_pkg_dir = "" 
     
    810827        Don't use logging, since it can be called before logging has been setup. 
    811828        """ 
    812         self.cleanup() 
    813         os.unlink(os.path.join(self.sandbox, self.logfile)) 
     829        self.cleanup(remove_log_file=False) 
    814830 
    815831        msg += "\n" + pad_msg("CHEESECAKE INDEX", 0) 
     832        msg += "\nDetailed info available in log file %s" % self.logfile 
     833 
    816834        raise CheesecakeError(msg) 
    817   
    818     def cleanup(self): 
     835 
     836    def cleanup(self, remove_log_file=True): 
    819837        """Delete temporary directories and files that were created 
    820838        in the sandbox. At the end delete the sandbox itself. 
     
    831849 
    832850        delete_dir(self.sandbox) 
     851 
     852        if remove_log_file: 
     853            os.unlink(os.path.join(self.sandbox, self.logfile)) 
    833854 
    834855    def set_defaults(self): 
     
    873894        """Default settings for logging. 
    874895 
    875         If verbose, log goes to console, else it goes to logfile 
    876         log.debug goes to logfile 
    877         log.info goes to console 
    878         log.warn and log.error go to both logfile and stdout 
     896        If verbose, log goes to console, else it goes to logfile. 
     897        log.debug and log.info goes to logfile. 
     898        log.warn and log.error go to both logfile and stdout. 
    879899        """ 
    880900        if logfile: 
     
    891911        else: 
    892912            self.log = logger.MultipleProducer('cheesecake logfile') 
    893         if self.quiet: 
    894             self.log.info = logger.MultipleProducer('cheesecake logfile') 
    895         else: 
    896             self.log.info = logger.MultipleProducer('cheesecake console') 
     913 
     914        self.log.info = logger.MultipleProducer('cheesecake logfile') 
    897915        self.log.debug = logger.MultipleProducer('cheesecake logfile') 
    898916        self.log.warn = logger.MultipleProducer('cheesecake console') 
    899917        self.log.error = logger.MultipleProducer('cheesecake console') 
    900  
    901         self.log.debug("package = ", self.short_pkg_name) 
    902918 
    903919    def retrieve_pkg(self): 
     
    908924        else: 
    909925            self.copy_pkg() 
    910  
    911     def get_package_from_url(self): 
    912         """Use ``urlparse`` to obtain package path from URL. 
    913         """ 
    914         (scheme,location,path,param,query,fragment_id) = urlparse(self.url) 
    915         return self.get_package_from_path(path)         
    916  
    917     def get_package_from_path(self, path): 
    918         """Get package name as file portion of path. 
    919         """ 
    920         dir, file = os.path.split(path) 
    921         self.short_pkg_name = file 
    922         for package_type in self.package_types: 
    923             s = re.search("(.+)\.%s" % package_type, file) 
    924             if s: 
    925                 self.short_pkg_name = s.group(1) 
    926                 break 
    927         return file 
    928926 
    929927    def get_pkg_from_pypi(self): 
     
    10021000        #self.log("Downloaded package %s to %s" % (self.package, downloaded_filename)) 
    10031001 
    1004         if re.search("Content-Type: details/html", str(headers))
     1002        if headers.gettype() in ["text/html"]
    10051003            f = open(downloaded_filename) 
    10061004            if re.search("404 Not Found", "".join(f.readlines())): 
     
    10221020    def unpack_pkg(self): 
    10231021        """Unpack the package in the sandbox directory. 
    1024          
    1025         Currently supported archive types: 
    1026  
    1027         * .tar.gz (handled with ``tarfile`` module) 
    1028         * .zip (handled with ``zipfile`` module) 
     1022 
     1023        Check `package_types` attribute for list of currently supported 
     1024        archive types. 
    10291025 
    10301026        :Ivariables: 
     
    10331029        self.package_type = "" 
    10341030 
    1035         for type in self.package_types
     1031        for type in self.package_types.keys()
    10361032            s = re.search(r"(.+)\.%s" % type, self.package) 
    10371033            if s: 
     
    10511047            shutil.rmtree(self.sandbox_pkg_dir) 
    10521048 
    1053         if self.package_type in ["tar.gz", "tgz"]: 
    1054             self.untar_pkg() 
    1055         elif self.package_type == "zip": 
    1056             self.unzip_pkg() 
     1049        # Call appropriate function to unpack the package. 
     1050        unpack = self.package_types[self.package_type] 
     1051        self.unpack_dir = unpack(self.sandbox_pkg_file, self.sandbox) 
     1052 
     1053        if self.unpack_dir is None: 
     1054            self.raise_exception("Could not unpack package %s ... exiting" % \ 
     1055                                 self.sandbox_pkg_file) 
     1056 
     1057        self.unpacked = True 
    10571058 
    10581059        if self.unpack_dir != self.package_name: 
    10591060            self.original_package_name = self.package_name 
    10601061            self.package_name = self.unpack_dir 
    1061  
    1062         if not self.quiet: 
    1063             self.log.info("Detailed info available in log file %s" % self.logfile) 
    1064  
    1065     def untar_pkg(self): 
    1066         """Untar the package in the sandbox directory. 
    1067  
    1068         Uses tarfile module. 
    1069         """ 
    1070         try: 
    1071             t = tarfile.open(self.sandbox_pkg_file) 
    1072         except tarfile.ReadError, e: 
    1073             self.raise_exception("Could not read tar file %s ... exiting" % self.sandbox_pkg_file) 
    1074  
    1075         for member in t.getmembers(): 
    1076             t.extract(member, self.sandbox) 
    1077  
    1078         tarinfo = t.members[0] 
    1079         self.unpack_dir = tarinfo.name.split(os.sep)[0] 
    1080  
    1081         self.unpacked = True 
    1082              
    1083     def unzip_pkg(self): 
    1084         """Unzip the package in the sandbox directory. 
    1085  
    1086         Uses zipfile module. 
    1087         """ 
    1088         try: 
    1089             z = zipfile.ZipFile(self.sandbox_pkg_file) 
    1090         except zipfile.error: 
    1091             self.raise_exception("Error unzipping file %s ... exiting" % self.sandbox_pkg_file) 
    1092  
    1093         # Get directory structure from zip and create it in sandbox 
    1094         for name in z.namelist(): 
    1095             (dir, file) = os.path.split(name) 
    1096             unpack_dir = dir 
    1097             target_dir = os.path.join(self.sandbox, dir) 
    1098             if not os.path.exists(target_dir): 
    1099                 os.makedirs(target_dir) 
    1100  
    1101         # Extract files to directory structure 
    1102         for i, name in enumerate(z.namelist()): 
    1103             if not name.endswith('/'): 
    1104                 outfile = open(os.path.join(self.sandbox, name), 'wb') 
    1105                 outfile.write(z.read(name)) 
    1106                 outfile.flush() 
    1107                 outfile.close() 
    1108  
    1109         self.unpack_dir = unpack_dir.split(os.sep)[0] 
    1110  
    1111         self.unpacked = True 
    11121062 
    11131063    def walk_pkg(self): 
     
    12021152        return cheesecake_index 
    12031153 
     1154################################################################################ 
     1155## Command line. 
     1156################################################################################ 
    12041157 
    12051158def process_cmdline_args(): 
    1206     """ 
    1207     Parse command-line options 
     1159    """Parse command-line options. 
    12081160    """ 
    12091161    parser = OptionParser() 
     
    12331185 
    12341186def main(): 
    1235     """ 
    1236     Display Cheesecake index for package specified via command-line options 
     1187    """Display Cheesecake index for package specified via command-line options. 
    12371188    """ 
    12381189    options = process_cmdline_args() 
  • branches/mk/cheesecake/codeparser.py

    r39 r44  
    9191    """ 
    9292    def __init__(self, pyfile, log=None): 
    93         """ 
     93        """Initialize Code Parser object. 
     94 
    9495        :Parameters: 
    9596          `pyfile` : str 
     
    177178 
    178179    def docstring_count(self): 
    179         """Return number of docstrings found in this module 
     180        """Return number of docstrings found in this module. 
    180181        """ 
    181182        return len(self.docstrings) 
    182183 
    183184    def docstring_count_by_type(self, type): 
    184         """Return number of docstrings of given type found in this module 
     185        """Return number of docstrings of given type found in this module. 
    185186        """ 
    186187        return len(self.docstrings_by_format[type]) 
    187188 
    188189    def functions_called(self): 
    189         """ 
    190         Return list of functions called by functions/methods 
    191         defined in this module 
     190        """Return list of functions called by functions/methods 
     191        defined in this module. 
    192192        """ 
    193193        return self.system.func_called.keys() 
  • branches/mk/tests/test_index_install.py

    r40 r44  
    1414            return 
    1515        self.cheesecake.cleanup() 
    16         os.unlink(self.cheesecake.logfile) 
    1716 
    1817    def test_index_install_correct_package(self): 
  • branches/mk/tests/test_index_installability.py

    r40 r44  
    1919            return 
    2020        self.cheesecake.cleanup() 
    21         os.unlink(self.cheesecake.logfile) 
    2221 
    2322    def test_index_installability_local_path(self): 
  • branches/mk/tests/test_index_unpack.py

    r40 r44  
    1111    def setUp(self): 
    1212        self.cheesecake = None 
     13        self.logfile = None 
    1314 
    1415    def tearDown(self): 
     
    1617            return 
    1718        self.cheesecake.cleanup() 
    18         os.unlink(self.cheesecake.logfile) 
     19 
     20        if self.logfile: 
     21            os.unlink(self.logfile) 
    1922 
    2023    def _run_valid(self, package_file): 
     
    3740        self._run_valid("package1.zip") 
    3841 
    39     def _run_invalid(self, package_file, message): 
     42    def _run_invalid(self, package_file): 
     43        self.logfile = tempfile.mktemp() 
     44 
    4045        try: 
    4146            self.cheesecake = Cheesecake(path=os.path.join(datadir, package_file), 
    42                                          sandbox=default_temp_directory) 
     47                                         sandbox=default_temp_directory, 
     48                                         logfile=self.logfile) 
    4349            assert 0 # This statement should not be reached 
    4450        except CheesecakeError, e: 
    45             msg = message % os.path.join(default_temp_directory, package_file) 
    46             msg += pad_msg("CHEESECAKE INDEX", 0) 
     51            msg = "Could not unpack package %s ... exiting" % \ 
     52                  os.path.join(default_temp_directory, package_file) 
     53            msg += "\n" + pad_msg("CHEESECAKE INDEX", 0) 
     54            msg += "\nDetailed info available in log file %s" % self.logfile 
    4755            assert str(e) == msg 
    4856 
     57            # If run failed log file should not be deleted. 
     58            assert os.path.isfile(self.logfile) 
     59 
    4960    def test_index_unpack_invalid_tar_gz(self): 
    50         self._run_invalid("invalid_package.tar.gz", 
    51                           "Could not read tar file %s ... exiting\n") 
     61        self._run_invalid("invalid_package.tar.gz") 
    5262 
    5363    def test_index_unpack_invalid_tgz(self): 
    54         self._run_invalid("invalid_package.tgz", 
    55                           "Could not read tar file %s ... exiting\n") 
     64        self._run_invalid("invalid_package.tgz") 
    5665 
    5766    def test_index_unpack_invalid_zip(self): 
    58         self._run_invalid("invalid_package.zip", 
    59                           "Error unzipping file %s ... exiting\n") 
     67        self._run_invalid("invalid_package.zip") 
  • branches/mk/tests/test_index_unpack_dir.py

    r40 r44  
    1414            return 
    1515        self.cheesecake.cleanup() 
    16         os.unlink(self.cheesecake.logfile) 
    1716 
    1817class TestIndexUnpackDirCorrectPackage(IndexUnpackDirTest): 
  • branches/mk/tests/test_index_url_download.py

    r40 r44  
    1313 
    1414    def _run_it(self, test_fun): 
     15        logfile = tempfile.mktemp() 
     16 
    1517        try: 
    16             test_fun(
     18            test_fun(logfile
    1719        finally: 
    1820            if self.cheesecake: 
    1921                self.cheesecake.cleanup() 
    20                 os.unlink(self.cheesecake.logfile) 
    2122 
    2223    def test_index_url_download_valid_url(self): 
     
    2930 
    3031        for url in urls: 
    31             def test_fun(): 
     32            def test_fun(logfile): 
    3233                try: 
    33                     self.cheesecake = Cheesecake(url=url
     34                    self.cheesecake = Cheesecake(url=url, logfile=logfile
    3435 
    3536                    index = self.cheesecake.index["INSTALLABILITY"]["url_download"] 
     
    4546                    msg = "[Errno socket error] (111, 'Connection refused')\n" 
    4647                    msg += pad_msg("CHEESECAKE INDEX", 0) 
     48                    msg += "\nDetailed info available in log file %s" % logfile 
    4749                    assert str(e) == msg 
    4850 
     
    5052 
    5153    def test_index_url_download_invalid_url(self): 
    52         def test_fun(): 
     54        def test_fun(logfile): 
    5355            try: 
    5456                self.cheesecake = Cheesecake(url="http://www.agilistas.org/cheesecake/not_there.tar.gz", 
    55                                              sandbox=default_temp_directory
     57                                             sandbox=default_temp_directory, logfile=logfile
    5658                assert 0 # This statement should not be reached 
    5759            except CheesecakeError, e: 
    58                 msg = "Could not read tar file %s ... exiting\n" % \ 
    59                       os.path.join(default_temp_directory, 'not_there.tar.gz'
    60                 msg += pad_msg("CHEESECAKE INDEX", 0) 
     60                msg = "Got '404 Not Found' error while trying to download package ... exiting" 
     61                msg += "\n" + pad_msg("CHEESECAKE INDEX", 0
     62                msg += "\nDetailed info available in log file %s" % logfile 
    6163                assert str(e) == msg 
    6264 
  • branches/mk/tests/test_init_cleanup.py

    r40 r44  
    1 import _path_cheesecake 
    2 from cheesecake.cheesecake_index import Cheesecake 
    31import os 
    42import shutil 
    53import tempfile 
    64 
     5import _path_cheesecake 
     6from cheesecake.cheesecake_index import Cheesecake 
     7 
    78datadir = os.path.abspath(os.path.join(os.path.dirname(__file__), "data")) 
    89 
    910class TestInitCleanup(object): 
    10  
    1111    def tearDown(self): 
    1212        if hasattr(self, 'cheesecake'): 
     
    3232        assert os.path.isfile(self.logfile) 
    3333 
    34     def test_cleanup(self): 
    35         self.cheesecake = Cheesecake(path=os.path.join(datadir, "package1.tar.gz")) 
    36         self.cheesecake.cleanup() 
    37         assert not os.path.exists(self.cheesecake.sandbox_pkg_dir) 
    38         assert not os.path.exists(self.cheesecake.sandbox_pkg_file) 
    39         assert not os.path.exists(self.cheesecake.sandbox) 
    40         # Log file should not have been deleted 
    41         self.logfile = self.cheesecake.logfile 
    42         assert os.path.isfile(self.logfile) 
    43  
    4434    def test_cleanup_after_install(self): 
    4535        self.cheesecake = Cheesecake(path=os.path.join(datadir, "package1.tar.gz")) 
     
    4939        assert not os.path.exists(self.cheesecake.sandbox_install_dir) 
    5040        assert not os.path.exists(self.cheesecake.sandbox) 
    51         # Log file should not have been deleted 
    5241        self.logfile = self.cheesecake.logfile 
    53         assert os.path.isfile(self.logfile) 
     42        assert not os.path.isfile(self.logfile)