Index: pychecker/pychecker/checker.py =================================================================== --- pychecker.orig/pychecker/checker.py +++ pychecker/pychecker/checker.py @@ -99,7 +99,7 @@ def _flattenList(list) : return new_list def getModules(arg_list) : - "Returns a list of module names that can be imported" + "Returns a list of (module names, dirPath) that can be imported" new_arguments = [] for arg in arg_list : @@ -116,6 +116,7 @@ def getModules(arg_list) : modules = [] for arg in _flattenList(new_arguments) : + arg_dir = None # actual modules will not give us a dir to load from # is it a .py file? for suf, suflen in zip(PY_SUFFIXES, PY_SUFFIX_LENS): if len(arg) > suflen and arg[-suflen:] == suf: @@ -125,10 +126,12 @@ def getModules(arg_list) : continue module_name = os.path.basename(arg)[:-suflen] - if arg_dir not in sys.path : - sys.path.insert(0, arg_dir) + # THOMAS: this breaks loading two .py files with same name + # from different dirs; we would always get the first one + #if arg_dir not in sys.path : + # sys.path.insert(0, arg_dir) arg = module_name - modules.append(arg) + modules.append((arg, arg_dir)) return modules @@ -161,11 +164,13 @@ def _q_find_module(p, path): if os.path.exists(f): return _q_file(file(f)), f, ('.ptl', 'U', 1) -def _findModule(name) : +def _findModule(name, moduleDir=None) : """Returns the result of an imp.find_module(), ie, (file, filename, smt) name can be a module or a package name. It is *not* a filename.""" path = sys.path[:] + if moduleDir: + path.insert(0, moduleDir) packages = string.split(name, '.') for p in packages : # smt = (suffix, mode, type) @@ -235,8 +240,9 @@ def _getClassTokens(c) : class Class : "Class to hold all information about a class" - def __init__(self, name, module) : + def __init__(self, name, pcmodule) : self.name = name + module = pcmodule.module self.classObject = getattr(module, name) modname = getattr(self.classObject, '__module__', None) @@ -260,7 +266,9 @@ class Class : self.classObject__name__ = name self.module = sys.modules.get(modname) - if not self.module: + # if the pcmodule has moduleDir, it means we processed it before, + # and deleted it from sys.modules + if not self.module and not pcmodule.moduleDir: self.module = module if modname not in cfg().blacklist: sys.stderr.write("warning: couldn't find real module " @@ -497,8 +505,13 @@ def _getPyFile(filename): class PyCheckerModule : "Class to hold all information for a module" - def __init__(self, moduleName, check = 1) : + def __init__(self, moduleName, check = 1, moduleDir=None) : + """ + @param moduleDir: if specified, the directory where the module can + be loaded from + """ self.moduleName = moduleName + self.moduleDir = moduleDir self.variables = {} self.functions = {} self.classes = {} @@ -508,7 +521,12 @@ class PyCheckerModule : self.main_code = None self.module = None self.check = check - _allModules[moduleName] = self + # FIXME: to make sure we have separate dict entries for different files + # with the same module name, we fudge in the moduleDir + __name = moduleName + if moduleDir: + __name = moduleName + moduleDir + _allModules[__name] = self def __str__(self) : return self.moduleName @@ -528,7 +546,9 @@ class PyCheckerModule : c.addMembers(classObject) def addClass(self, name) : - self.classes[name] = c = Class(name, self.module) + #self.classes[name] = c = Class(name, self.module) + # give ourselves, so Class has more context + self.classes[name] = c = Class(name, self) try: objName = utils.safestr(c.classObject) except TypeError: @@ -558,16 +578,19 @@ class PyCheckerModule : filename = self.module.__file__ except AttributeError : filename = self.moduleName + if self.moduleDir: + filename = self.moduleDir + ': ' + filename return _getPyFile(filename) def load(self): try : - # there's no need to reload modules we already have - module = sys.modules.get(self.moduleName) - if module : - if not _allModules[self.moduleName].module : - return self._initModule(module) - return 1 + # there's no need to reload modules we already have if no moduleDir + if not self.moduleDir: + module = sys.modules.get(self.moduleName) + if module : + if not _allModules[self.moduleName].module : + return self._initModule(module) + return 1 return self._initModule(self.setupMainCode()) except (SystemExit, KeyboardInterrupt) : @@ -627,9 +650,19 @@ class PyCheckerModule : return 1 def setupMainCode(self) : - file, filename, smt = _findModule(self.moduleName) + file, filename, smt = _findModule(self.moduleName, self.moduleDir) # FIXME: if the smt[-1] == imp.PKG_DIRECTORY : load __all__ + # HACK: to make sibling imports work, we add self.moduleDir to sys.path + # temporarily, and remove it later + if self.moduleDir: + oldsyspath = sys.path[:] + sys.path.insert(0, self.moduleDir) module = imp.load_module(self.moduleName, file, filename, smt) + if self.moduleDir: + sys.path = oldsyspath + # to make sure that subsequent modules with the same moduleName + # do not persist, and getting their namespace clobbered, delete it + del sys.modules[self.moduleName] self._setupMainCode(file, filename, module) return module @@ -736,10 +769,10 @@ def processFiles(files, cfg = None, pre_ warnings = [] utils.initConfig(_cfg) - for moduleName in getModules(files) : + for file, (moduleName, moduleDir) in zip(files, getModules(files)) : if callable(pre_process_cb) : - pre_process_cb(moduleName) - module = PyCheckerModule(moduleName) + pre_process_cb("%s (%s)" % (moduleName, file)) + module = PyCheckerModule(moduleName, moduleDir=moduleDir) if not module.load() : w = Warning(module.filename(), 1, msgs.Internal("NOT PROCESSED UNABLE TO IMPORT"))