Changeset 80

Show
Ignore:
Timestamp:
07/06/06 18:15:29 (2 years ago)
Author:
mk
Message:

Indices can now hold dependencies (via decide() interface).
Certain steps (like installation of a package) during scoring are run only

on demand - to satisfy indices dependencies.

Implemented "static" profile by setting appropriate dependencies on

IndexInstall? (closes ticket #37).

Files:

Legend:

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

    r79 r80  
    1818""" 
    1919 
    20 import os, sys, re, shutil 
     20import os 
     21import re 
     22import shutil 
     23import sys 
    2124import tempfile 
     25 
    2226from optparse import OptionParser 
    2327from urllib import urlretrieve 
    2428from urlparse import urlparse 
    2529from math import ceil 
     30 
     31import logger 
    2632 
    2733from util import pad_with_dots, pad_left_spaces, pad_right_spaces, pad_msg, pad_line 
     
    3036from util import mkdirs 
    3137from util import StdoutRedirector 
    32 import logger 
    3338from config import get_pkg_config 
    3439from codeparser import CodeParser 
     
    4045## Helpers. 
    4146################################################################################ 
     47 
     48if 'sorted' not in dir(__builtins__): 
     49    def sorted(L): 
     50        new_list = L[:] 
     51        new_list.sort() 
     52        return new_list 
    4253 
    4354def isiterable(obj): 
     
    113124    return filter(lambda x: discover_file_type(x) == file_type, file_list) 
    114125 
     126def get_package_name_from_path(path): 
     127    """Get package name as file portion of path. 
     128 
     129    >>> get_package_name_from_path('/some/random/path/package.tar.gz') 
     130    'package.tar.gz' 
     131    >>> get_package_name_from_path('/path/underscored_name.zip') 
     132    'underscored_name.zip' 
     133    >>> get_package_name_from_path('/path/unknown.extension.txt') 
     134    'unknown.extension.txt' 
     135    """ 
     136    dir, filename = os.path.split(path) 
     137    return filename 
     138 
     139def get_package_name_from_url(url): 
     140    """Use ``urlparse`` to obtain package name from URL. 
     141 
     142    >>> get_package_name_from_url('http://www.example.com/file.tar.bz2') 
     143    'file.tar.bz2' 
     144    >>> get_package_name_from_url('https://www.example.com/some/dir/file.txt') 
     145    'file.txt' 
     146    """ 
     147    (scheme,location,path,param,query,fragment_id) = urlparse(url) 
     148    return get_package_name_from_path(path) 
     149 
     150def get_package_name_and_type(package, known_extensions): 
     151    """Return package name and type. 
     152 
     153    Package type must exists in known_extensions list. Otherwise None is 
     154    returned. 
     155 
     156    >>> extensions = ['tar.gz', 'zip'] 
     157    >>> get_package_name_and_type('underscored_name.zip', extensions) 
     158    ('underscored_name', 'zip') 
     159    >>> get_package_name_and_type('unknown.extension.txt', extensions) 
     160    """ 
     161    for package_type in known_extensions: 
     162        if package.endswith('.'+package_type): 
     163            # Package name is name of package without file extension (ex. twill-7.3). 
     164            return package[:package.rfind('.'+package_type)], package_type 
     165 
    115166def get_method_arguments(method): 
    116167    """Return tuple of arguments for given method, excluding self. 
     
    261312    details = "" 
    262313 
    263     def __init__(self, indices=[]): 
    264         if not self.subindices: 
     314    def __init__(self, *indices): 
     315        # When indices are given explicitly they override the default. 
     316        if indices: 
    265317            self.subindices = [] 
    266  
    267         # Create dictionary for fast reference. 
    268         self._indices_dict = make_indices_dict(self.subindices) 
    269  
    270         for index in indices: 
    271             self.add_subindex(index) 
     318            self._indices_dict = {} 
     319            for index in indices: 
     320                self.add_subindex(index) 
     321        else: 
     322            if self.subindices: 
     323                new_subindices = [] 
     324                for index in self.subindices: 
     325                    # index must be a class subclassing from Index. 
     326                    assert isinstance(index, type) 
     327                    assert issubclass(index, Index) 
     328                    new_subindices.append(index()) 
     329                self.subindices = new_subindices 
     330            else: 
     331                self.subindices = [] 
     332            # Create dictionary for fast reference. 
     333            self._indices_dict = make_indices_dict(self.subindices) 
    272334 
    273335        self._compute_arguments = get_method_arguments(self.compute) 
     
    303365        return self.value 
    304366 
     367    def decide(self, cheesecake, when): 
     368        """Decide if this index should be computed. 
     369 
     370        If index has children, it will automatically remove all for which 
     371        decide() return false. 
     372        """ 
     373        if self.subindices: 
     374            # Iterate over copy, as we may remove some elements. 
     375            for index in self.subindices[:]: 
     376                print "Trying %s." % index.name 
     377                if not getattr(index, 'decide_' + when)(cheesecake): 
     378                    self.remove_subindex(index.name) 
     379            return self.subindices 
     380        return True 
     381 
     382    def decide_before_download(self, cheesecake): 
     383        return self.decide(cheesecake, 'before_download') 
     384 
     385    def decide_after_download(self, cheesecake): 
     386        return self.decide(cheesecake, 'after_download') 
     387 
    305388    def _get_max_value(self): 
    306389        if self.subindices: 
     
    310393 
    311394    max_value = property(_get_max_value) 
     395 
     396    def _get_requirements(self): 
     397        if self.subindices: 
     398            return list(self._compute_arguments) + \ 
     399                   reduce(lambda x,y: x + y.requirements, self.subindices, []) 
     400        return list(self._compute_arguments) 
     401 
     402    requirements = property(_get_requirements) 
    312403 
    313404    def add_subindex(self, index): 
     
    464555        return self.value 
    465556 
     557    def decide_before_download(self, cheesecake): 
     558        return cheesecake.url 
     559 
    466560class IndexUnpack(Index): 
    467561    max_value = 25 
     
    491585        return self.value 
    492586 
     587    def decide_after_download(self, cheesecake): 
     588        return cheesecake.package_type != 'egg' 
     589 
    493590class IndexSetupPy(FilesIndex): 
    494591    name = "setup.py" 
     
    508605 
    509606        return self.value 
     607 
     608    def decide_after_download(self, cheesecake): 
     609        return cheesecake.package_type != 'egg' 
    510610 
    511611class IndexInstall(Index): 
     
    521621 
    522622        return self.value 
     623 
     624    def decide_before_download(self, cheesecake): 
     625        return not cheesecake.static_only 
    523626 
    524627class IndexPyPIDownload(Index): 
     
    551654        return self.value 
    552655 
     656    def decide_before_download(self, cheesecake): 
     657        return cheesecake.name 
     658 
    553659class IndexGeneratedFiles(Index): 
    554660    generated_files_penalty = -20 
     661    max_value = 0 
    555662 
    556663    def compute(self, files_list): 
     
    568675        return self.value 
    569676 
     677    def decide_after_download(self, cheesecake): 
     678        return cheesecake.package_type != 'egg' 
     679 
    570680class IndexInstallability(Index): 
    571681    name = "INSTALLABILITY" 
    572682 
    573683    subindices = [ 
    574         IndexUnpack(), 
    575         IndexUnpackDir(), 
    576         IndexSetupPy(), 
    577         IndexInstall(), 
    578         IndexGeneratedFiles(), 
     684        IndexPyPIDownload, 
     685        IndexUrlDownload, 
     686        IndexUnpack, 
     687        IndexUnpackDir, 
     688        IndexSetupPy, 
     689        IndexInstall, 
     690        IndexGeneratedFiles, 
    579691    ] 
    580692 
     
    662774 
    663775    subindices = [ 
    664         IndexRequiredFiles()
    665         IndexDocstrings()
    666         IndexFormattedDocstrings()
     776        IndexRequiredFiles
     777        IndexDocstrings
     778        IndexFormattedDocstrings
    667779    ] 
    668780 
     
    778890 
    779891    subindices = [ 
    780         IndexPyLint()
    781         # IndexUnitTests(), TODO 
     892        IndexPyLint
     893        #IndexUnitTests, TODO 
    782894    ] 
    783895 
     
    795907    name = "Cheesecake" 
    796908    subindices = [ 
    797         IndexInstallability()
    798         IndexDocumentation()
    799         IndexCodeKwalitee()
     909        IndexInstallability
     910        IndexDocumentation
     911        IndexCodeKwalitee
    800912    ] 
    801913 
    802914 
     915class Step(object): 
     916    """Single step during computation of package score. 
     917    """ 
     918    def __init__(self, provides): 
     919        self.provides = provides 
     920 
     921    def decide(self, cheesecake): 
     922        """Decide if step should be run. 
     923 
     924        It checks if there's at least one index from current profile that need 
     925        variables provided by this step. Override this method for other behaviour. 
     926        """ 
     927        for provide in self.provides: 
     928            if provide in cheesecake.index.requirements: 
     929                return True 
     930        return False 
     931 
     932class StepByVariable(Step): 
     933    """Step which is always run if given Cheesecake instance variable is true. 
     934    """ 
     935    def __init__(self, variable_name, provides): 
     936        self.variable_name = variable_name 
     937        Step.__init__(self, provides) 
     938 
     939    def decide(self, cheesecake): 
     940        if getattr(cheesecake, self.variable_name, None): 
     941            return True 
     942 
     943        # Fallback to the default. 
     944        return Step.decide(self, cheesecake) 
     945 
    803946class Cheesecake(object): 
    804     """ 
    805     Computes 'goodness' of Python packages 
     947    """Computes 'goodness' of Python packages. 
    806948 
    807949    Generates "cheesecake index" that takes into account things like: 
     
    816958        * average pylint score for all non-test and non-demo modules 
    817959    """ 
    818     index = CheesecakeIndex() 
     960 
     961    steps = {} 
     962 
     963    package_types = { 
     964        "tar.gz": untar_package, 
     965        "tgz": untar_package, 
     966        "zip": unzip_package, 
     967        "egg": unegg_package, 
     968    } 
    819969 
    820970    def __init__(self, name="", url="", path="", sandbox=None, config=None, 
     
    827977        self.package_path = path 
    828978 
    829         if not self.name and not self.url and not self.package_path: 
    830             self.raise_exception("No package name, URL or path specified ... exiting") 
    831  
     979        if self.name: 
     980            self.package = self.name 
     981        elif self.url: 
     982            self.package = get_package_name_from_url(self.url) 
     983        elif self.package_path: 
     984            self.package = get_package_name_from_path(self.package_path) 
     985        else: 
     986            self.raise_exception("No package name, URL or path specified... exiting") 
     987 
     988        # Setup a sandbox. 
    832989        self.sandbox = sandbox or tempfile.mkdtemp(prefix='cheesecake') 
    833990        if not os.path.isdir(self.sandbox): 
     
    839996        self.static_only = static_only 
    840997 
    841         self.package_types = { 
    842             "tar.gz": untar_package, 
    843             "tgz": untar_package, 
    844             "zip": unzip_package, 
    845             "egg": unegg_package, 
    846         } 
    847  
    848998        self.sandbox_pkg_file = "" 
    849999        self.sandbox_pkg_dir = "" 
    8501000        self.sandbox_install_dir = "" 
    8511001 
    852         # Include indices revelant to current situation. 
    853         if self.name: 
    854             self.index["INSTALLABILITY"].add_subindex(IndexPyPIDownload()) 
    855         if self.url: 
    856             self.index["INSTALLABILITY"].add_subindex(IndexUrlDownload()) 
    857  
    858         # Get package name. 
    859         self.determine_pkg_name() 
    860  
    8611002        # Configure logging as soon as possible. 
    8621003        self.configure_logging(logfile) 
    8631004 
    864         # Get package,  
    865         self.retrieve_pkg() 
     1005        # Setup Cheesecake index. 
     1006        self.index = CheesecakeIndex() 
     1007 
     1008        self.index.decide_before_download(self) 
     1009        self.log.debug("Profile requirements: %s." % ', '.join(sorted(self.index.requirements))) 
     1010 
     1011        # Get the package. 
     1012        self.run_step('get_pkg_from_pypi') 
     1013        self.run_step('download_pkg') 
     1014        self.run_step('copy_pkg') 
    8661015 
    8671016        # Get package name and type. 
    868         self.package_name, self.package_type = self.get_package_name_and_type(self.package) 
     1017        name_and_type = get_package_name_and_type(self.package, self.package_types.keys()) 
     1018 
     1019        if not name_and_type: 
     1020            msg = "Could not determine package type for package '%s'" % self.package 
     1021            msg += "\nCurrently recognized types: " + ", ".join(self.package_types.keys()) 
     1022            self.raise_exception(msg) 
     1023 
     1024        self.package_name, self.package_type = name_and_type 
    8691025        self.log.debug("Package name: " + self.package_name) 
    8701026        self.log.debug("Package type: " + self.package_type) 
    8711027 
     1028        # Make last indices decisions. 
     1029        self.index.decide_after_download(self) 
     1030 
    8721031        # Unpack package and list its files. 
    873         self.unpack_pkg(
    874         self.walk_pkg(
     1032        self.run_step('unpack_pkg'
     1033        self.run_step('walk_pkg'
    8751034 
    8761035        # Install package. 
    877         if self.static_only: 
    878             self.index["INSTALLABILITY"].remove_subindex('install') 
    879         else: 
    880             self.install_pkg() 
    881  
    882         # When checking an egg exclude irrelevant indices. 
    883         if self.package_type == 'egg': 
    884             self.index["INSTALLABILITY"].remove_subindex('setup.py') 
    885             self.index["INSTALLABILITY"].remove_subindex('unpack_dir') 
    886             self.index["INSTALLABILITY"].remove_subindex('generated_files') 
     1036        self.run_step('install_pkg') 
    8871037 
    8881038    def raise_exception(self, msg): 
     
    9271077        pass 
    9281078 
    929     def determine_pkg_name(self): 
    930         if self.name: 
    931             self.package = self.name 
    932             self.short_pkg_name = self.name 
    933         elif self.package_path: 
    934             self.package = self.get_package_from_path(self.package_path) 
    935         else: 
    936             self.package = self.get_package_from_url() 
    937  
    938     def get_package_from_url(self): 
    939         """Use ``urlparse`` to obtain package path from URL. 
    940         """ 
    941         (scheme,location,path,param,query,fragment_id) = urlparse(self.url) 
    942         return self.get_package_from_path(path) 
    943  
    944     def get_package_from_path(self, path): 
    945         """Get package name as file portion of path. 
    946         """ 
    947         dir, file = os.path.split(path) 
    948         self.short_pkg_name = file 
    949         for package_type in self.package_types: 
    950             s = re.search("(.+)\.%s" % package_type, file) 
    951             if s: 
    952                 self.short_pkg_name = s.group(1) 
    953                 break 
    954         return file 
    955  
    9561079    def configure_logging(self, logfile=None): 
    9571080        """Default settings for logging. 
     
    9641087            self.logfile = logfile 
    9651088        else: 
    966             self.logfile = os.path.join(tempfile.gettempdir(), self.short_pkg_name + ".log") 
     1089            self.logfile = os.path.join(tempfile.gettempdir(), self.package + ".log") 
    9671090 
    9681091        logger.setconsumer('logfile', open(str(self.logfile), 'w', buffering=1)) 
     
    9801103        self.log.error = logger.MultipleProducer('cheesecake console') 
    9811104 
    982     def retrieve_pkg(self): 
    983         if self.name: 
    984             self.get_pkg_from_pypi() 
    985         elif self.url: 
    986             self.download_pkg() 
    987         else: 
    988             self.copy_pkg() 
    989  
     1105    def run_step(self, step_name): 
     1106        """Run step if its decide() method returns True. 
     1107        """ 
     1108        step = self.steps[step_name] 
     1109        if step.decide(self): 
     1110            step_method = getattr(self, step_name) 
     1111            step_method() 
     1112 
     1113    steps['get_pkg_from_pypi'] = StepByVariable('name', 
     1114                                                ['download_url', 
     1115                                                 'distance_from_pypi', 
     1116                                                 'found_on_cheeseshop', 
     1117                                                 'found_locally', 
     1118                                                 'sandbox_pkg_file']) 
    9901119    def get_pkg_from_pypi(self): 
    9911120        """Download package using setuptools utilities. 
     
    10701199 
    10711200        self.sandbox_pkg_file = output 
    1072         self.package = self.get_package_from_path(output) 
     1201        self.package = get_package_name_from_path(output) 
    10731202        self.log.info("Downloaded package %s from %s" % (self.package, self.download_url)) 
    10741203 
     
    10791208            self.found_on_cheeseshop = True 
    10801209 
     1210    steps['download_pkg'] = StepByVariable('url', 
     1211                                           ['sandbox_pkg_file', 
     1212                                            'downloaded_from_url']) 
    10811213    def download_pkg(self): 
    10821214        """Use ``urllib.urlretrieve`` to download package to file in sandbox dir. 
     
    10991231 
    11001232        self.downloaded_from_url = True 
    1101          
     1233 
     1234    steps['copy_pkg'] = StepByVariable('package_path', 
     1235                                       ['sandbox_pkg_file']) 
    11021236    def copy_pkg(self): 
    11031237        """Copy package file to sandbox directory. 
     
    11091243        shutil.copyfile(self.package_path, self.sandbox_pkg_file) 
    11101244 
    1111     def get_package_name_and_type(self, package): 
    1112         """Return package name and type. 
    1113  
    1114         Raise an exception when package type cannot be recognized. 
    1115         """ 
    1116         for type in self.package_types.keys(): 
    1117             s = re.search(r"(.+)\.%s" % type, package) 
    1118             if s: 
    1119                 # Package name is name of package without file extension (ex. twill-7.3). 
    1120                 return s.group(1), type 
    1121  
    1122         msg = "Could not determine package type for package '%s'" % package 
    1123         msg += "\nCurrently recognized types: " + ", ".join(self.package_types.keys()) 
    1124         self.raise_exception(msg) 
    1125  
     1245    steps['unpack_pkg'] = Step(['original_package_name', 
     1246                                'sandbox_pkg_dir', 
     1247                                'unpacked', 
     1248                                'unpack_dir']) 
    11261249    def unpack_pkg(self): 
    11271250        """Unpack the package in the sandbox directory. 
     
    11511274            self.package_name = self.unpack_dir 
    11521275 
     1276    steps['walk_pkg'] = Step(['dirs_list', 
     1277                              'docstring_cnt', 
     1278                              'docformat_cnt', 
     1279                              'files_list', 
     1280                              'functions', 
     1281                              'object_cnt', 
     1282                              'package_dir']) 
    11531283    def walk_pkg(self): 
    11541284        """Get package files and directories. 
     
    11961326                                                      ', '.join(self.dirs_list))) 
    11971327 
     1328    steps['install_pkg'] = Step(['installed']) 
    11981329    def install_pkg(self): 
    11991330        """Verify that package can be installed in alternate directory. 
  • branches/mk/tests/unit/_mockup_cheesecake.py

    r55 r80  
    66import _path_cheesecake 
    77from cheesecake.cheesecake_index import Cheesecake 
     8from cheesecake.cheesecake_index import CheesecakeIndex 
    89 
    910 
     
    1213 
    1314    class CheesecakeMockup(Cheesecake): 
     15        def run_step(self, step_name): 
     16            if step_name == 'install_pkg': 
     17                return 
     18            Cheesecake.run_step(self, step_name) 
     19 
    1420        def __init__(self, sandbox, package_name, logfile): 
    1521            self.name = package_name 
    1622            self.package_name = package_name 
     23            self.package = package_name 
    1724            self.sandbox = sandbox 
    1825 
     
    2229            self.unpack_dir = sandbox 
    2330 
    24             self.determine_pkg_name() 
    2531            self.configure_logging(logfile) 
    26             self.set_defaults() 
     32            self.index = CheesecakeIndex() 
    2733 
    2834    def setUp(self): 
  • branches/mk/tests/unit/test_index_class.py

    r75 r80  
    2424    >>> index in big_index.subindices 
    2525    False 
     26 
     27Test requirements. 
     28    >>> class NewIndex(Index): 
     29    ...     def compute(self, one, two, three): 
     30    ...         pass 
     31    >>> new = NewIndex() 
     32    >>> new.requirements 
     33    ['one', 'two', 'three'] 
     34 
     35Now create other index and add it to the NewIndex. 
     36    >>> class OtherIndex(Index): 
     37    ...     def compute(self, four): 
     38    ...         pass 
     39    >>> other = OtherIndex() 
     40    >>> other.requirements 
     41    ['four'] 
     42    >>> new.add_subindex(other) 
     43    >>> new.requirements 
     44    ['one', 'two', 'three', 'four'] 
    2645""" 
  • branches/mk/tests/unit/test_index_installability.py

    r76 r80  
    1111from cheesecake.cheesecake_index import IndexInstall 
    1212from cheesecake.cheesecake_index import IndexUrlDownload 
     13from cheesecake.cheesecake_index import IndexGeneratedFiles 
    1314 
    1415 
     
    2627 
    2728        index = self.cheesecake.index["INSTALLABILITY"] 
    28         parts = [IndexUnpack, IndexUnpackDir, IndexSetupPy, IndexInstall
     29        parts = [IndexUnpack, IndexUnpackDir, IndexSetupPy, IndexInstall, IndexGeneratedFiles
    2930 
    3031        assert index.max_value == sum(map(lambda x: x.max_value, parts)) 
     
    3738 
    3839        index = self.cheesecake.index["INSTALLABILITY"] 
    39         parts = [IndexUnpack, IndexUnpackDir, IndexSetupPy, IndexInstall, IndexUrlDownload
     40        parts = [IndexUrlDownload, IndexUnpack, IndexUnpackDir, IndexSetupPy, IndexInstall, IndexGeneratedFiles
    4041 
    4142        assert index.max_value == sum(map(lambda x: x.max_value, parts))