Changeset 751

Show
Ignore:
Timestamp:
07/18/07 00:24:54 (1 year ago)
Author:
robin
Message:

run_magic_syspath

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • commando/trunk/commandolite.py

    r750 r751  
    11import os 
    22import sys 
     3import re 
    34import errno 
    45import signal 
     
    1011 
    1112from os.path import abspath, normpath, join, expanduser, expandvars, dirname 
    12 from os.path import isabs, exists, isfile, isdir 
    13  
    14 from runpy import get_loader, _get_filename, _run_module_code 
     13from os.path import basename, isabs, exists, isfile, isdir 
     14 
     15from glob import glob 
     16 
     17from runpy import get_loader, _get_filename, _run_module_code, run_module 
    1518 
    1619log = logging.getLogger(__name__) 
     
    151154#----------------------------------------------------------------------------- 
    152155# files, file names, paths 
     156 
     157# This is taken directly from setuptools pkg_resources 
     158pkg_resources_EGG_NAME = re.compile( 
     159    r"(?P<name>[^-]+)" 
     160    r"( -(?P<ver>[^-]+) (-py(?P<pyver>[^-]+) (-(?P<plat>.+))? )? )?", 
     161    re.VERBOSE | re.IGNORECASE 
     162).match 
     163 
     164pkg_resources_component_re = re.compile(r'(\d+ | [a-z]+ | \.| -)', re.VERBOSE) 
     165pkg_resources_ver_replace = {'pre':'c', 'preview':'c','-':'final-','rc':'c','dev':'@'}.get 
     166 
     167def pkg_resources_parse_version_parts(s): 
     168    for part in pkg_resources_component_re.split(s): 
     169        part = pkg_resources_ver_replace(part,part) 
     170        if not part or part=='.': 
     171            continue 
     172        if part[:1] in '0123456789': 
     173            yield part.zfill(8)    # pad for numeric comparison 
     174        else: 
     175            yield '*'+part 
     176 
     177    yield '*final'  # ensure that alpha/beta/candidate are before final 
     178 
     179 
     180def filter_best_eggs(source, remove_ifnoteggmatch=False): 
     181 
     182    eggs = {} 
     183    keep = [] 
     184    prune_2ndpass = [] 
     185 
     186    for s in source: 
     187        mo = pkg_resources_EGG_NAME(basename(s)) 
     188        if not mo: 
     189            if not remove_ifnoteggmatch: 
     190                keep.append(s) 
     191            continue 
     192 
     193        pkgname, ver, pyver = ( 
     194                mo.group('name'), mo.group('ver'), mo.group('pyver') 
     195                ) 
     196        if not (pkgname and ver and pyver): 
     197            if pkgname and not remove_ifnoteggmatch: 
     198                keep.append(s) 
     199            continue 
     200 
     201        if not pyver.startswith(sys.version[:3]): 
     202            continue 
     203 
     204        ver = tuple(pkg_resources_parse_version_parts(ver)) 
     205 
     206        if pkgname not in eggs: 
     207            eggs[pkgname] = (pkgname, ver, s) 
     208            keep.append(s) 
     209        elif ver >= eggs[pkgname][1]: 
     210            prune_2ndpass.append(s) 
     211        elif ver < eggs[pkgname][1]: 
     212            prune_2ndpass.append(eggs[pkgname][2]) 
     213            keep.append(s) 
     214 
     215    prune_2ndpass = frozenset(prune_2ndpass) 
     216 
     217    return [s for s in keep if s not in prune_2ndpass] 
     218 
     219 
     220 
    153221def abs_normpath(path, *parts): 
    154222    if not parts: 
     
    212280        raise ValueError, "path '%s' cannot end with '/'" % pathname 
    213281 
    214     paths = string.split(pathname, '/') 
     282    paths = pathname.split('/') 
    215283    while '.' in paths: 
    216284        paths.remove('.') 
     
    244312    return out 
    245313 
    246 def evaluate_packagedir(path, name=''): 
     314 
     315def evaluate_packagepath(path, name='', allow_egglinks=False): 
     316    if isegg_path(path, allow_links=allow_egglinks): 
     317        return name or path 
    247318    if (isdir(path) and '.' not in name and ( 
    248319        isfile(join(path, '__init__.py')) or 
     
    253324    return False 
    254325 
    255  
    256 def find_top_packages(where='.', evaluate_packagelocation=evaluate_packagedir): 
    257     """Find all directories under `where` which contain an __init__.py""" 
     326def isegg_path(path, allow_links=False): 
     327    bn = basename(path) 
     328    if not pkg_resources_EGG_NAME(bn): 
     329        return False 
     330    if path.endswith('.egg'): 
     331        return True 
     332    if path.endswith('.egg-link'): 
     333        if not allow_links: 
     334            return False 
     335        return True 
     336    return False 
     337 
     338 
     339def find_top_packages(where='.', 
     340        evaluate_packagelocation=evaluate_packagepath, 
     341        allow_descent=lambda path: isdir(path) and not isegg_path(path)): 
     342 
     343    """Find all top level package directories under `where` 
     344 
     345 
     346    If `where` is a relative path then, by default all resulting paths will 
     347    also be relative. If `where` is an absoloute path then the result paths 
     348    will also be absoloute. This behaviour can be modified by providing a 
     349    custom `evaluate_packagelocation` function. 
     350 
     351    Note that by default, egg directories and egg archive files are found as 
     352    top level package paths but the find process will not decends below the level 
     353    of an egg directory. This behaviour can be modified by judicious use of 
     354    `evaluate_packagelocation` and `allow_descent`. 
     355 
     356    """ 
    258357 
    259358    tops={} 
    260     out = [] 
    261359    stack=[convert_path(where)] 
    262360    while stack: 
     
    267365            if result: 
    268366                tops.setdefault(where, []).append(result) 
    269             if isdir(fn) and not result: 
     367            if allow_descent(fn) and not result: 
    270368                stack.append(fn) 
    271369 
    272370    return sorted(tops.items()) 
    273371 
     372 
     373def find_package_paths(*rootpaths, **kw): 
     374 
     375    pthset = set(kw.pop('pth', [])) 
     376    pth = [] 
     377 
     378    evpp = lambda p,name='': evaluate_packagepath(p) 
     379 
     380    for rp in rootpaths: 
     381        ep = evpp(rp) 
     382        if ep and ep not in pthset: 
     383            pthset.add(ep) 
     384            pth.append(ep) 
     385            continue 
     386        if not isdir(rp): 
     387            continue 
     388        for top in find_top_packages(rp, 
     389                evaluate_packagelocation=evpp 
     390                ): 
     391            for p in top[1]: 
     392                if p not in pthset: 
     393                    pth.append(p) 
     394                    pthset.add(p) 
     395 
     396    return filter_best_eggs(pth) 
     397 
     398 
     399 
     400#----------------------------------------------------------------------------- 
     401# python code execution 
     402 
     403 
     404def run_magic_syspath(argv=None, 
     405        import_modules=False, run_last_module=True): 
     406    """Run a module using a magicaly extended sys.path. 
     407 
     408    Discovers additional package paths by searching under directories listed as 
     409    non option arguments. Discovery is egg aware: it takes care to include only 
     410    the *best* version of each egg and only those eggs that match the python 
     411    interpreters version. 
     412 
     413    If the last argument names a legitemate python module then sys.path is 
     414    extended with the discovered paths, sys.argv is adjusted to reflect the 
     415    module name and the remaining arguments. Then runpy.run_module is used to 
     416    execute the module in this magicaly discovered path. 
     417 
     418    If any non option argument, other than the last, identifies a python module 
     419    then sys.path is updated and an attempt to import the module is made. This 
     420    feature allows import of packages whose import time behaviour would be 
     421    adversly affected by any paths added on behalf of subsequent modules. Note 
     422    that you can disable this by setting `import_modules=False` and 
     423    `run_last_module=True`. This will defer all import behaviour to the module 
     424    you chose to *run*. 
     425 
     426    If the last argument is not a python package then two lists are returned. 
     427    The first list contains the discovered path and the second list contains 
     428    any modules that were imported as it was discovered. The second list will 
     429    allways be empty if `import_modules` is False. 
     430 
     431    TODO: examples 
     432 
     433    """ 
     434 
     435    argv = argv or sys.argv[:] 
     436    lenargv = len(argv) 
     437    if lenargv < 2: 
     438        sys.exit(-1) 
     439 
     440    ia=1 
     441    for i in range(lenargv - 1): 
     442        ia = i + 1 
     443        a = argv[ia] 
     444        if a == '--': 
     445            del argv[ia] 
     446            break 
     447        elif a[:2] == '--' or a[:1] == '-': 
     448            break 
     449    else: 
     450        ia=lenargv 
     451 
     452    minfo = None 
     453 
     454    searchpaths = [] 
     455    pthset = set([]) 
     456    pathsadded = [] 
     457    pthextend = [] 
     458    sys_path_insertpos = 0 
     459 
     460    imported = [] # hold on to the references. 
     461 
     462    for i, a in enumerate(argv[1:ia]): 
     463 
     464        minfo = path_moduleinfo(a) 
     465        if minfo: 
     466            # if its a legitemate python module file, import it with the 
     467            # path we have discovered so far. 
     468            if minfo[1] in sys.modules: 
     469                continue 
     470 
     471            if minfo[0] not in pthset: 
     472                pthextend.append(minfo[0]) 
     473                pthset.add(minfo[0]) 
     474 
     475            # Don't import modules or update sys.path if import_modules is 
     476            # False 
     477            if not import_modules: 
     478                continue 
     479 
     480            # If the last argument is a module, then dont import it. We will 
     481            # run it instead. 
     482            if i+2 == ia: 
     483                continue 
     484 
     485            newpaths = find_package_paths(pth=pthset, 
     486                *searchpaths 
     487                ) 
     488            pthextend.extend(newpaths) 
     489 
     490            sys.path[sys_path_insertpos:sys_path_insertpos 
     491                    ] = pthextend[:] 
     492            sys_path_insertpos += len(pthextend) 
     493 
     494            pathsadded.extend(newpaths) 
     495            pthextend[:] = [] 
     496            searchpaths[:] = [] 
     497 
     498            imported.append(__import__(minfo[1])) 
     499 
     500            minfo = None 
     501            continue 
     502 
     503        else: 
     504            searchpaths.append(a) 
     505 
     506 
     507    newpaths = find_package_paths(pth=pthset, 
     508        *searchpaths 
     509        ) 
     510 
     511    pthextend.extend(newpaths) 
     512 
     513    if import_modules or run_last_module: 
     514        sys.path[sys_path_insertpos:sys_path_insertpos 
     515                ] = pthextend[:] 
     516 
     517    pathsadded.extend(newpaths) 
     518 
     519    if not run_last_module: 
     520        return pathsadded, imported 
     521 
     522    if minfo: 
     523        sys.argv[:] = [] 
     524        sys.argv.append(minfo[1]) 
     525        sys.argv.extend(argv[ia:]) 
     526        run_module(sys.argv[0], run_name='__main__', alter_sys=True) 
     527 
     528    return pathsadded, imported 
    274529 
    275530#----------------------------------------------------------------------------- 
  • commando/trunk/tests/test_pathdiscovery.py

    r749 r751  
    1 import sys 
    2 from os.path import abspath, normpath, expanduser, dirname 
     1import os, sys 
     2from os.path import abspath, normpath, expanduser, expandvars, dirname 
    33try: 
    44    import commandolite 
    55except ImportError: 
    6     abs_topdir = dirname(dirname(normpath(abspath(expanduser(__file__))))) 
     6    abs_topdir = dirname( 
     7            dirname( 
     8                normpath(abspath(expandvars(expanduser(__file__))))) 
     9            ) 
    710    if abs_topdir not in sys.path: 
    811        sys.path.insert(0, abs_topdir) 
    912    import commandolite 
    1013 
    11 from os.path import abspath, normpath, expanduser 
    12 from os.path import split, basename 
    13 from inspect import getmoduleinfo, getmodulename 
    14 import imp 
    1514 
    1615def test_path_moduleinfo(): 
     
    2423    #print commandolite.path_moduleinfo(sys.argv[1]) 
    2524    #print commandolite.find_packages(sys.argv[1]) 
    26     print commandolite.find_top_packages(commandolite.abs_normpath(sys.argv[1])) 
     25    #print commandolite.find_top_packages(commandolite.abs_normpath(sys.argv[1])) 
     26    print os.pathsep.join(commandolite.run_magic_syspath()[0]) 
    2727 
    2828