/nose/importer.py
Python | 154 lines | 114 code | 15 blank | 25 comment | 20 complexity | 21e2d2549ef8e87454a9d8bea9b6ad08 MD5 | raw file
1"""Implements an importer that looks only in specific path (ignoring 2sys.path), and uses a per-path cache in addition to sys.modules. This is 3necessary because test modules in different directories frequently have the 4same names, which means that the first loaded would mask the rest when using 5the builtin importer. 6""" 7import logging 8import os 9import sys 10from nose.config import Config 11 12from imp import find_module, load_module, acquire_lock, release_lock 13 14log = logging.getLogger(__name__) 15 16class Importer(object): 17 """An importer class that does only path-specific imports. That 18 is, the given module is not searched for on sys.path, but only at 19 the path or in the directory specified. 20 """ 21 def __init__(self, config=None): 22 if config is None: 23 config = Config() 24 self.config = config 25 26 def importFromPath(self, path, fqname): 27 """Import a dotted-name package whose tail is at path. In other words, 28 given foo.bar and path/to/foo/bar.py, import foo from path/to/foo then 29 bar from path/to/foo/bar, returning bar. 30 """ 31 # find the base dir of the package 32 path_parts = os.path.normpath(os.path.abspath(path)).split(os.sep) 33 name_parts = fqname.split('.') 34 if path_parts[-1].startswith('__init__'): 35 path_parts.pop() 36 path_parts = path_parts[:-(len(name_parts))] 37 dir_path = os.sep.join(path_parts) 38 # then import fqname starting from that dir 39 return self.importFromDir(dir_path, fqname) 40 41 def importFromDir(self, dir, fqname): 42 """Import a module *only* from path, ignoring sys.path and 43 reloading if the version in sys.modules is not the one we want. 44 """ 45 dir = os.path.normpath(os.path.abspath(dir)) 46 log.debug("Import %s from %s", fqname, dir) 47 48 # FIXME reimplement local per-dir cache? 49 50 # special case for __main__ 51 if fqname == '__main__': 52 return sys.modules[fqname] 53 54 if self.config.addPaths: 55 add_path(dir, self.config) 56 57 path = [dir] 58 parts = fqname.split('.') 59 part_fqname = '' 60 mod = parent = fh = None 61 62 for part in parts: 63 if part_fqname == '': 64 part_fqname = part 65 else: 66 part_fqname = "%s.%s" % (part_fqname, part) 67 try: 68 acquire_lock() 69 log.debug("find module part %s (%s) in %s", 70 part, part_fqname, path) 71 fh, filename, desc = find_module(part, path) 72 old = sys.modules.get(part_fqname) 73 if old is not None: 74 # test modules frequently have name overlap; make sure 75 # we get a fresh copy of anything we are trying to load 76 # from a new path 77 log.debug("sys.modules has %s as %s", part_fqname, old) 78 if (self.sameModule(old, filename) 79 or (self.config.firstPackageWins and 80 getattr(old, '__path__', None))): 81 mod = old 82 else: 83 del sys.modules[part_fqname] 84 mod = load_module(part_fqname, fh, filename, desc) 85 else: 86 mod = load_module(part_fqname, fh, filename, desc) 87 finally: 88 if fh: 89 fh.close() 90 release_lock() 91 if parent: 92 setattr(parent, part, mod) 93 if hasattr(mod, '__path__'): 94 path = mod.__path__ 95 parent = mod 96 return mod 97 98 def sameModule(self, mod, filename): 99 mod_paths = [] 100 if hasattr(mod, '__path__'): 101 for path in mod.__path__: 102 mod_paths.append(os.path.dirname( 103 os.path.normpath( 104 os.path.abspath(path)))) 105 elif hasattr(mod, '__file__'): 106 mod_paths.append(os.path.dirname( 107 os.path.normpath( 108 os.path.abspath(mod.__file__)))) 109 else: 110 # builtin or other module-like object that 111 # doesn't have __file__; must be new 112 return False 113 new_path = os.path.dirname(os.path.normpath(filename)) 114 for mod_path in mod_paths: 115 log.debug( 116 "module already loaded? mod: %s new: %s", 117 mod_path, new_path) 118 if mod_path == new_path: 119 return True 120 return False 121 122 123def add_path(path, config=None): 124 """Ensure that the path, or the root of the current package (if 125 path is in a package), is in sys.path. 126 """ 127 128 # FIXME add any src-looking dirs seen too... need to get config for that 129 130 log.debug('Add path %s' % path) 131 if not path: 132 return [] 133 added = [] 134 parent = os.path.dirname(path) 135 if (parent 136 and os.path.exists(os.path.join(path, '__init__.py'))): 137 added.extend(add_path(parent, config)) 138 elif not path in sys.path: 139 log.debug("insert %s into sys.path", path) 140 sys.path.insert(0, path) 141 added.append(path) 142 if config and config.srcDirs: 143 for dirname in config.srcDirs: 144 dirpath = os.path.join(path, dirname) 145 if os.path.isdir(dirpath): 146 sys.path.insert(0, dirpath) 147 added.append(dirpath) 148 return added 149 150 151def remove_path(path): 152 log.debug('Remove path %s' % path) 153 if path in sys.path: 154 sys.path.remove(path)