[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