[Checkins]
SVN: Sandbox/janwijbrand/importchecker/src/importchecker/importchecker.py
copy original importchecker module
Jan-Wijbrand Kolman
janwijbrand at gmail.com
Tue May 6 06:28:11 EDT 2008
Log message for revision 86496:
copy original importchecker module
Changed:
A Sandbox/janwijbrand/importchecker/src/importchecker/importchecker.py
-=-
Copied: Sandbox/janwijbrand/importchecker/src/importchecker/importchecker.py (from rev 86484, Zope3/trunk/utilities/importchecker.py)
===================================================================
--- Sandbox/janwijbrand/importchecker/src/importchecker/importchecker.py (rev 0)
+++ Sandbox/janwijbrand/importchecker/src/importchecker/importchecker.py 2008-05-06 10:28:10 UTC (rev 86496)
@@ -0,0 +1,328 @@
+#!/usr/bin/env python2.4
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Import checker
+
+This utility finds unused imports in Python modules. Its output is
+grep-like and thus emacs-friendly.
+
+$Id$
+"""
+
+import compiler
+import os, os.path
+import sys
+
+def _findDottedNamesHelper(node, result):
+ more_node = node
+ name = node.__class__.__name__
+ if name == 'Getattr':
+ dotted = []
+ while name == 'Getattr':
+ dotted.append(node.attrname)
+ node = node.expr
+ name = node.__class__.__name__
+ if name == 'Name':
+ dotted.append(node.name)
+ dotted.reverse()
+ for i in range(1, len(dotted)):
+ result.append('.'.join(dotted[:i]))
+ result.append('.'.join(dotted))
+ return
+ elif name == 'Name':
+ result.append(node.name)
+ return
+ elif name == 'AssAttr':
+ # Can be on an import as well.
+ # for instance
+ # from x import y
+ # y.k = v
+ expr = node.expr
+ result.append(getattr(expr, 'name', ''))
+ return
+ for child in more_node.getChildNodes():
+ _findDottedNamesHelper(child, result)
+
+
+def findDottedNames(node):
+ """Find dotted names in an AST tree node
+ """
+ result = []
+ _findDottedNamesHelper(node, result)
+ return result
+
+
+class ImportFinder:
+ """An instance of this class will be used to walk over a compiler AST
+ tree (a module). During that operation, the appropriate methods of
+ this visitor will be called
+ """
+
+ def __init__(self):
+ self._map = {}
+
+ def visitFrom(self, stmt):
+ """Will be called for 'from foo import bar' statements
+ """
+ module_name, names = stmt.asList()
+ if module_name == '__future__':
+ # we don't care what's imported from the future
+ return
+ names_dict = {}
+ for orig_name, as_name in names:
+ # we don't care about from import *
+ if orig_name == '*':
+ continue
+ if as_name is None:
+ name = orig_name
+ else:
+ name = as_name
+ names_dict[name] = orig_name
+ self._map.setdefault(module_name, {'names': names_dict,
+ 'lineno': stmt.lineno})
+
+ def visitImport(self, stmt):
+ """Will be called for 'import foo.bar' statements
+ """
+ for orig_name, as_name in stmt.names:
+ if as_name is None:
+ name = orig_name
+ else:
+ name = as_name
+ self._map.setdefault(orig_name, {'names': {name: orig_name},
+ 'lineno': stmt.lineno})
+
+ def getMap(self):
+ return self._map
+
+
+def findImports(mod):
+ """Find import statements in module and put the result in a mapping.
+ """
+ visitor = ImportFinder()
+ compiler.walk(mod, visitor)
+ return visitor.getMap()
+
+
+class Module:
+ """This represents a python module.
+ """
+
+ def __init__(self, path):
+ mod = compiler.parseFile(path)
+ self._path = path
+ self._map = findImports(mod)
+ dottednames = {}
+ self._dottednames = findDottedNames(mod)
+
+ def getPath(self):
+ """Return the path to this module's file.
+ """
+ return self._path
+
+ def getImportedModuleNames(self):
+ """Return the names of imported modules.
+ """
+ return self._map.keys()
+
+ def getImportNames(self):
+ """Return the names of imports; add dottednames as well.
+ """
+ result = []
+ map = self._map
+ for module_name in map.keys():
+ for usedname, originalname in map[module_name]['names'].items():
+ result.append((originalname, module_name))
+ # add any other name that we could be using
+ for dottedname in self._dottednames:
+ usednamedot = usedname + '.'
+ if dottedname.startswith(usednamedot):
+ attrname = dottedname[len(usednamedot):].split('.')[0]
+ result.append((attrname, module_name))
+ return result
+
+ def getUnusedImports(self):
+ """Get unused imports of this module (the whole import info).
+ """
+ result = []
+ for value in self._map.values():
+ for usedname, originalname in value['names'].items():
+ if usedname not in self._dottednames:
+ result.append((originalname, value['lineno']))
+ return result
+
+
+class ModuleFinder:
+
+ def __init__(self):
+ self._files = []
+
+ def visit(self, arg, dirname, names):
+ """This method will be called when we walk the filesystem
+ tree. It looks for python modules and stored their filenames.
+ """
+ for name in names:
+ # get all .py files that aren't weirdo emacs droppings
+ if name.endswith('.py') and not name.startswith('.#'):
+ self._files.append(os.path.join(dirname, name))
+
+ def getModuleFilenames(self):
+ return self._files
+
+
+def findModules(path):
+ """Find python modules in the given path and return their absolute
+ filenames in a sequence.
+ """
+ finder = ModuleFinder()
+ os.path.walk(path, finder.visit, ())
+ return finder.getModuleFilenames()
+
+
+class ImportDatabase:
+ """This database keeps tracks of imports.
+
+ It allows to NOT report cases where a module imports something
+ just so that another module can import it (import dependencies).
+ """
+
+ def __init__(self, root_path):
+ self._root_path = root_path
+ self._modules = {}
+ self._names = {}
+
+ def resolveDottedModuleName(self, dotted_name, module):
+ """Return path to file representing module, or None if no such
+ thing. Can do this relative from module.
+ """
+ dotted_path = dotted_name.replace('.', '/')
+ # try relative import first
+ path = os.path.join(os.path.dirname(module.getPath()), dotted_path)
+ path = self._resolveHelper(path)
+ if path is not None:
+ return path
+ # absolute import (assumed to be from this tree)
+ if os.path.isfile(os.path.join(self._root_path, '__init__.py')):
+ startpath, dummy = os.path.split(self._root_path)
+ else:
+ startpath = self._root_path
+ return self._resolveHelper(os.path.join(startpath, dotted_path))
+
+ def _resolveHelper(self, path):
+ if os.path.isfile(path + '.py'):
+ return path + '.py'
+ if os.path.isdir(path):
+ path = os.path.join(path, '__init__.py')
+ if os.path.isfile(path):
+ return path
+ return None
+
+ def findModules(self):
+ """Find modules in the given path.
+ """
+ for modulepath in findModules(self._root_path):
+ module = Module(modulepath)
+ self.addModule(module)
+
+ def addModule(self, module):
+ """Add information about a module to the database. A module in
+ this case is not a python module object, but an instance of
+ the above defined Module class.w
+ """
+ self_path = module.getPath()
+ # do nothing if we already know about it
+ if self._modules.has_key(self_path):
+ return
+
+ self._modules[self_path] = module
+
+ # add imported names to internal names mapping; this will
+ # allow us identify dependent imports later
+ names = self._names
+ for name, from_module_name in module.getImportNames():
+ path = self.resolveDottedModuleName(from_module_name, module)
+ t = (path, name)
+ modulepaths = names.get(t, {})
+ if not modulepaths.has_key(self_path):
+ modulepaths[self_path] = 1
+ names[t] = modulepaths
+
+ def getUnusedImports(self):
+ """Get unused imports of all known modules.
+ """
+ result = {}
+ for path, module in self._modules.items():
+ result[path] = self.getUnusedImportsInModule(module)
+ return result
+
+ def getUnusedImportsInModule(self, module):
+ """Get all unused imports in a module.
+ """
+ result = []
+ for name, lineno in module.getUnusedImports():
+ if not self.isNameImportedFrom(name, module):
+ result.append((name, lineno))
+ return result
+
+ def isNameImportedFrom(self, name, module):
+ """Return true if name is imported from module by another module.
+ """
+ return self._names.has_key((module.getPath(), name))
+
+ def getModulesImportingNameFrom(self, name, module):
+ """Return list of known modules that import name from module.
+ """
+ result = []
+ for path in self._names.get((module.getPath(), name), {}).keys():
+ result.append(self._modules[path])
+ return result
+
+
+def main():
+ try:
+ path = sys.argv[1]
+ except IndexError:
+ print "No path supplied"
+ sys.exit(1)
+
+ path = os.path.abspath(path)
+ if not os.path.isdir(path):
+ print "Unknown path:", path
+ sys.exit(1)
+
+ l = len(path) + 1
+ db = ImportDatabase(path)
+ db.findModules()
+ unused_imports = db.getUnusedImports()
+ module_paths = unused_imports.keys()
+ module_paths.sort()
+ for path in module_paths:
+ info = unused_imports[path]
+ path = path[l:]
+ if not info:
+ continue
+ line2names = {}
+ for name, line in info:
+ names = line2names.get(line, [])
+ names.append(name)
+ line2names[line] = names
+ lines = line2names.keys()
+ lines.sort()
+ for line in lines:
+ names = ', '.join(line2names[line])
+ print "%s:%s: %s" % (path, line, names)
+
+if __name__ == '__main__':
+ main()
+
More information about the Checkins
mailing list