[Zope3-checkins] SVN: Zope3/trunk/src/zope/dependencytool/ Dependency tool:

Fred L. Drake, Jr. fred at zope.com
Fri May 21 16:46:47 EDT 2004


Log message for revision 24864:
Dependency tool:
- add -p/--packages option to report package names instead of module
  names
- add some tests (still minimal)
- fix bug in properly handling relative imports
- don't try to reduce the list of dependencies by removing contained
  packages; this would need to support the publication boundaries,
  which we don't support here (yet)



-=-
Modified: Zope3/trunk/src/zope/dependencytool/finddeps.py
===================================================================
--- Zope3/trunk/src/zope/dependencytool/finddeps.py	2004-05-21 19:45:52 UTC (rev 24863)
+++ Zope3/trunk/src/zope/dependencytool/finddeps.py	2004-05-21 20:46:47 UTC (rev 24864)
@@ -36,11 +36,12 @@
     -m / --module
         Specify the dotted name of the module that is to be inspected.
 
+    -p / --packages
+        List only package names, not individual module names.
+
     -z / --zcml
         Also look through ZCML files for dependencies.
 
-Important: Make sure that the PYTHONPATH is set to or includes 'ZOPE3/src'.
-
 $Id$
 """
 import sys
@@ -84,26 +85,37 @@
 
 def makeDottedName(path):
     """Convert a path to a dotted module name, using sys.path."""
-    syspaths = sys.path[1:]
-    syspaths.append(os.getcwd())
+    dirname, basename = os.path.split(path)
+    basename = os.path.splitext(basename)[0]
+    path = os.path.join(dirname, basename)
+    syspaths = sys.path[:]
+    if "" in syspaths:
+        # This is the directory that contains the driver script; there
+        # are no modules there.
+        syspaths.remove("")
     for syspath in syspaths:
         syspath = os.path.join(syspath, '')
         if path.startswith(syspath):
             return path[len(syspath):].replace(os.sep, ".")
 
-    raise ValueError, 'Cannot create dotted name.'
+    raise ValueError, 'Cannot create dotted name for %r' % path
 
 
-def getDependenciesOfPythonFile(path):
-    finder = ImportFinder()
-    finder.find_imports(open(path, 'rU'), path)
+def getDependenciesOfPythonFile(path, packages):
+    finder = ImportFinder(packages)
+    module_name = makeDottedName(path)
+    if '.' in module_name:
+        package = module_name[:module_name.rfind('.')]
+    else:
+        package = None
+    finder.find_imports(open(path, 'rU'), path, package)
     return finder.get_imports()
 
 
-def getDependenciesOfZCMLFile(path):
+def getDependenciesOfZCMLFile(path, packages):
     """Get dependencies from ZCML file."""
-    localModule = stripZopePrefix(os.path.dirname(path))
-    localModule = localModule.replace(os.sep, '.')
+    s = makeDottedName(path)
+    localPackage = s[:s.rfind(".")]
     deps = []
     lineno = 0
     for line in open(path, 'r'):
@@ -115,7 +127,7 @@
 
             for name in match:
                 if name.startswith('.'):
-                    name = localModule + name
+                    name = localPackage + name
                 try:
                     __import__(name)
                 except:
@@ -139,9 +151,10 @@
     filteredDeps = []
     for dep in deps:
         try:
-            module = __import__(dep.name)
+            __import__(dep.name)
         except ImportError:
             continue
+        module = sys.modules[dep.name]
         # built-ins (like sys) do not have a file associated
         if not hasattr(module, '__file__'):
             continue
@@ -152,62 +165,18 @@
     return filteredDeps
 
 
-def filterLocalModules(deps, path):
-    """Filter out local module imports."""
-    # File-based modules cannot have relative imports
-    if os.path.isfile(path):
-        return deps
-
-    # Filter relative imports
-    filteredDeps = []
-    for dep in deps:
-        module = dep.name.split('.')[0]
-        modulePath = os.path.join(path, module)
-        if not (os.path.exists(modulePath)
-                or os.path.exists(modulePath+'.py')):
-            filteredDeps.append(dep)
-    deps = filteredDeps
-
-    # Filter absolute imports
-    dottedName = makeDottedName(path)
-    filteredDeps = []
-    for dep in deps:
-        if not dep.name.startswith(dottedName):
-            filteredDeps.append(dep)
-
-    return filteredDeps
-
-
-def filterMostGeneral(deps):
-    """Return only the parent module and no children.
-
-    for example (foo, foo.bar) --> (foo,)
-    """
-    newdeps = []
-    for dep in deps:
-        subpackage = False
-        for parentdep in deps:
-            if parentdep is not dep and dep.isSubPackageOf(parentdep):
-                subpackage = True
-                break
-        if not subpackage:
-            newdeps.append(dep)
-    return newdeps
-
-
 def makeUnique(deps):
     """Remove entries that appear multiple times"""
     uniqueDeps = {}
     for dep in deps:
-        if not dep.name in uniqueDeps.keys():
-            uniqueDeps[dep.name] = dep
-        else:
+        if dep.name in uniqueDeps:
             uniqueDeps[dep.name].addOccurence(*dep.occurences[0])
-
+        else:
+            uniqueDeps[dep.name] = dep
     return uniqueDeps.values()
 
 
-def getDependencies(path, zcml=False):
+def getDependencies(path, zcml=False, packages=False):
     """Get all dependencies of a package or module.
 
     If the path is a package, all Python source files are searched inside it.
@@ -217,23 +186,23 @@
         for file in os.listdir(path):
             filePath = os.path.join(path, file)
             if pythonfile.match(file):
-                deps += getDependenciesOfPythonFile(filePath)
+                deps += getDependenciesOfPythonFile(filePath, packages)
             elif zcml and zcmlfile.match(file):
-                deps += getDependenciesOfZCMLFile(filePath)
+                deps += getDependenciesOfZCMLFile(filePath, packages)
             elif os.path.isdir(filePath):
                 filenames = os.listdir(filePath)
                 if (  'PUBLICATION.cfg' not in filenames
                       and 'SETUP.cfg' not in filenames
                       and 'DEPENDENCIES.cfg' not in filenames
                       and '__init__.py' in filenames):
-                    deps += getDependencies(filePath)
+                    deps += getDependencies(filePath, zcml, packages)
 
     elif os.path.isfile(path):
         ext = os.path.splitext(path)[1]
         if ext == ".py":
-            deps = getDependenciesOfPythonFile(path)
+            deps = getDependenciesOfPythonFile(path, packages)
         elif ext == ".zcml":
-            deps = getDependenciesOfZCMLFile(path)
+            deps = getDependenciesOfZCMLFile(path, packages)
         else:
             print >>sys.stderr, ("dependencies can only be"
                                  " extracted from Python and ZCML files")
@@ -246,20 +215,20 @@
     return deps
 
 
-def getCleanedDependencies(path, zcml=False):
+def getCleanedDependencies(path, zcml=False, packages=False):
     """Return clean dependency list."""
-    deps = getDependencies(path, zcml)
+    deps = getDependencies(path, zcml, packages)
     deps = filterStandardModules(deps)
-    deps = filterLocalModules(deps, path)
-    deps = filterMostGeneral(deps)
     deps = makeUnique(deps)
     deps.sort()
     return deps
 
 
-def getAllCleanedDependencies(path, zcml=False, deps=None, paths=None):
+def getAllCleanedDependencies(path, zcml=False, deps=None, paths=None,
+                              packages=False):
     """Return a list of all cleaned dependencies in a path."""
     # zope and zope/app are too general to be considered.
+    # XXX why?  dependencies are dependencies.
     if path.endswith('src/zope/') or path.endswith('src/zope/app/'):
         return deps
 
@@ -267,7 +236,7 @@
         deps = []
         paths = []
 
-    newdeps = getCleanedDependencies(path)
+    newdeps = getCleanedDependencies(path, zcml, packages)
     for dep in newdeps:
         if dep.name not in paths:
             deps.append(dep)
@@ -276,18 +245,17 @@
             dirname, basename = os.path.split(modulePath)
             if basename in ('__init__.py', '__init__.pyc', '__init__.pyo'):
                 modulePath = os.path.join(dirname, '')
-            getAllCleanedDependencies(modulePath, zcml, deps, paths)
-    deps = filterMostGeneral(deps)
+            getAllCleanedDependencies(modulePath, zcml, deps, paths, packages)
     deps.sort()
     return deps
 
 
-def showDependencies(path, zcml=False, long=False, all=False):
+def showDependencies(path, zcml=False, long=False, all=False, packages=False):
     """Show the dependencies of a module on the screen."""
     if all:
-        deps = getAllCleanedDependencies(path, zcml)
+        deps = getAllCleanedDependencies(path, zcml, packages)
     else:
-        deps = getCleanedDependencies(path, zcml)
+        deps = getCleanedDependencies(path, zcml, packages)
 
     if long:
         print '='*(8+len(path))
@@ -311,13 +279,14 @@
     try:
         opts, args = getopt.getopt(
             argv[1:],
-            'd:m:ahlz',
-            ['all', 'help', 'dir=', 'module=', 'long', 'zcml'])
+            'd:m:pahlz',
+            ['all', 'help', 'dir=', 'module=', 'long', 'packages', 'zcml'])
     except getopt.error, msg:
         usage(1, msg)
 
     all = False
     long = False
+    packages = False
     path = None
     zcml = False
     for opt, arg in opts:
@@ -330,6 +299,7 @@
         elif opt in ('-d', '--dir'):
             cwd = os.getcwd()
             # This is for symlinks. Thanks to Fred for this trick.
+            # XXX wha????
             if os.environ.has_key('PWD'):
                 cwd = os.environ['PWD']
             path = os.path.normpath(os.path.join(cwd, arg))
@@ -339,12 +309,14 @@
                 path = os.path.dirname(module.__file__)
             except ImportError:
                 usage(1, "Could not import module %s" % module)
+        elif opt in ('-p', '--packages'):
+            packages = True
         elif opt in ('-z', '--zcml'):
             zcml = True
     if path is None:
         usage(1, 'The module must be specified either by path, '
               'dotted name or ZCML file.')
-    showDependencies(path, zcml, long, all)
+    showDependencies(path, zcml, long, all, packages)
 
 
 if __name__ == '__main__':

Modified: Zope3/trunk/src/zope/dependencytool/importfinder.py
===================================================================
--- Zope3/trunk/src/zope/dependencytool/importfinder.py	2004-05-21 19:45:52 UTC (rev 24863)
+++ Zope3/trunk/src/zope/dependencytool/importfinder.py	2004-05-21 20:46:47 UTC (rev 24864)
@@ -11,7 +11,7 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""
+"""Helper to locate all the imports from a single source file.
 
 $Id$
 """
@@ -41,7 +41,13 @@
 
 class ImportFinder:
 
-    def __init__(self):
+    def __init__(self, packages=False):
+        """Initialize the import finder.
+
+        `packages` -- if true, reports package names rather than
+          module names
+        """
+        self.packages = packages
         self.module_checks = {}
         self.deps = []
         self.imported_names = {}
@@ -49,8 +55,15 @@
     def get_imports(self):
         return self.deps
 
-    def find_imports(self, f, path):
+    def find_imports(self, f, path, package=None):
+        """Find all the imported names in a source file.
+
+        `f` -- open file
+        `path` -- path of the source file
+        `package` -- Python package the source file is contained in, or None
+        """
         self.path = path
+        self.package = package
         self.state = START
         self.post_name_state = None
         prevline = None
@@ -63,6 +76,23 @@
             raise
 
     def add_import(self, name, lineno):
+        """Record an import for `name`.
+
+        `name` -- full dotted name as found in an import statement
+          (may still be relative)
+
+        `lineno` -- line number the import was found on
+        """
+        # is this a relative import?
+        if self.package:
+            fullname = "%s.%s" % (self.package, name)
+            self.check_module_name(fullname)
+            if not self.module_checks[fullname]:
+                fullname = fullname[:fullname.rfind(".")]
+                self.check_module_name(fullname)
+            if self.module_checks[fullname]:
+                # this was a relative import; use the full name:
+                name = fullname
         if name not in self.module_checks:
             self.check_module_name(name)
             if not self.module_checks[name] and "." in name:
@@ -73,6 +103,16 @@
         # A few oddball cases import __main__ (support for
         # command-line scripts), so we need to filter that out.
         if self.module_checks[name] and name != "__main__":
+            if self.packages:
+                __import__(name)
+                module = sys.modules[name]
+                if not hasattr(module, "__path__"):
+                    if "." in name:
+                        name = name[:name.rfind(".")]
+                    else:
+                        # just drop it on the floor, since we're not
+                        # interested in bare modules
+                        return
             self.deps.append(Dependency(name, self.path, lineno))
 
     def check_module_name(self, name):

Copied: Zope3/trunk/src/zope/dependencytool/tests/pkg/__init__.py (from rev 24841, Zope3/trunk/src/zope/dependencytool/tests/__init__.py)

Added: Zope3/trunk/src/zope/dependencytool/tests/pkg/module.py
===================================================================
--- Zope3/trunk/src/zope/dependencytool/tests/pkg/module.py	2004-05-21 19:45:52 UTC (rev 24863)
+++ Zope3/trunk/src/zope/dependencytool/tests/pkg/module.py	2004-05-21 20:46:47 UTC (rev 24864)
@@ -0,0 +1,20 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Module to be imported by the 'sample' module in the parent package.
+
+$Id$
+"""
+
+class SomeClass:
+    """Simple class with little to offer."""


Property changes on: Zope3/trunk/src/zope/dependencytool/tests/pkg/module.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Added: Zope3/trunk/src/zope/dependencytool/tests/sample.py
===================================================================
--- Zope3/trunk/src/zope/dependencytool/tests/sample.py	2004-05-21 19:45:52 UTC (rev 24863)
+++ Zope3/trunk/src/zope/dependencytool/tests/sample.py	2004-05-21 20:46:47 UTC (rev 24864)
@@ -0,0 +1,21 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""This is a sample module used by the tests.
+
+It exists only to import things.
+
+$Id$
+"""
+
+from pkg.module import SomeClass


Property changes on: Zope3/trunk/src/zope/dependencytool/tests/sample.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Added: Zope3/trunk/src/zope/dependencytool/tests/test_finddeps.py
===================================================================
--- Zope3/trunk/src/zope/dependencytool/tests/test_finddeps.py	2004-05-21 19:45:52 UTC (rev 24863)
+++ Zope3/trunk/src/zope/dependencytool/tests/test_finddeps.py	2004-05-21 20:46:47 UTC (rev 24864)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Tests for zope.dependencytool.finddeps.
+
+$Id$
+"""
+import unittest
+
+from zope.dependencytool import finddeps
+
+
+class HelperFunctionTestCase(unittest.TestCase):
+
+    def test_makeDottedName(self):
+        self.assertEqual(finddeps.makeDottedName(__file__), __name__)
+
+
+def test_suite():
+    return unittest.makeSuite(HelperFunctionTestCase)


Property changes on: Zope3/trunk/src/zope/dependencytool/tests/test_finddeps.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Added: Zope3/trunk/src/zope/dependencytool/tests/test_importfinder.py
===================================================================
--- Zope3/trunk/src/zope/dependencytool/tests/test_importfinder.py	2004-05-21 19:45:52 UTC (rev 24863)
+++ Zope3/trunk/src/zope/dependencytool/tests/test_importfinder.py	2004-05-21 20:46:47 UTC (rev 24864)
@@ -0,0 +1,46 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Tests for zope.dependencytool.importfinder.
+
+$Id$
+"""
+import os
+import unittest
+
+from zope.dependencytool.importfinder import ImportFinder
+
+
+here = os.path.dirname(__file__)
+
+THIS_PACKAGE = __name__[:__name__.rfind(".")]
+
+
+class ImportFinderTestCase(unittest.TestCase):
+
+    def test_relative_imports(self):
+        finder = ImportFinder()
+        path = os.path.join(here, "sample.py")
+        f = open(path, "rU")
+        try:
+            finder.find_imports(f, path, THIS_PACKAGE)
+        finally:
+            f.close()
+        imports = finder.get_imports()
+        self.assertEqual(len(imports), 1)
+        self.assertEqual(imports[0].name,
+                         "%s.pkg.module" % THIS_PACKAGE)
+
+
+def test_suite():
+    return unittest.makeSuite(ImportFinderTestCase)


Property changes on: Zope3/trunk/src/zope/dependencytool/tests/test_importfinder.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native




More information about the Zope3-Checkins mailing list