summaryrefslogtreecommitdiffstats
path: root/Lib/pkgutil.py
diff options
context:
space:
mode:
authorPhillip J. Eby <pje@telecommunity.com>2006-04-18 00:59:55 (GMT)
committerPhillip J. Eby <pje@telecommunity.com>2006-04-18 00:59:55 (GMT)
commitceb3087e1c6456ab3c6db533bb7bc1b5c4ca97a9 (patch)
tree00d16183466426c9518332533f409bfd0e36022a /Lib/pkgutil.py
parentb507972cddd6a204d252ea87b839a38fb51225fe (diff)
downloadcpython-ceb3087e1c6456ab3c6db533bb7bc1b5c4ca97a9.zip
cpython-ceb3087e1c6456ab3c6db533bb7bc1b5c4ca97a9.tar.gz
cpython-ceb3087e1c6456ab3c6db533bb7bc1b5c4ca97a9.tar.bz2
Second phase of refactoring for runpy, pkgutil, pydoc, and setuptools
to share common PEP 302 support code, as described here: http://mail.python.org/pipermail/python-dev/2006-April/063724.html pydoc now supports PEP 302 importers, by way of utility functions in pkgutil, such as 'walk_packages()'. It will properly document modules that are in zip files, and is backward compatible to Python 2.3 (setuptools installs for Python <2.5 will bundle it so pydoc doesn't break when used with eggs.) What has not changed is that pydoc command line options do not support zip paths or other importer paths, and the webserver index does not support sys.meta_path. Those are probably okay as limitations. Tasks remaining: write docs and Misc/NEWS for pkgutil/pydoc changes, and update setuptools to use pkgutil wherever possible, then add it to the stdlib.
Diffstat (limited to 'Lib/pkgutil.py')
-rw-r--r--Lib/pkgutil.py171
1 files changed, 166 insertions, 5 deletions
diff --git a/Lib/pkgutil.py b/Lib/pkgutil.py
index d4fe6ca..24de5d1 100644
--- a/Lib/pkgutil.py
+++ b/Lib/pkgutil.py
@@ -11,6 +11,7 @@ from types import ModuleType
__all__ = [
'get_importer', 'iter_importers', 'get_loader', 'find_loader',
+ 'walk_packages', 'iter_modules',
'ImpImporter', 'ImpLoader', 'read_code', 'extend_path',
]
@@ -27,6 +28,95 @@ def read_code(stream):
return marshal.load(stream)
+def simplegeneric(func):
+ """Make a trivial single-dispatch generic function"""
+ registry = {}
+ def wrapper(*args,**kw):
+ ob = args[0]
+ try:
+ cls = ob.__class__
+ except AttributeError:
+ cls = type(ob)
+ try:
+ mro = cls.__mro__
+ except AttributeError:
+ try:
+ class cls(cls,object): pass
+ mro = cls.__mro__[1:]
+ except TypeError:
+ mro = object, # must be an ExtensionClass or some such :(
+ for t in mro:
+ if t in registry:
+ return registry[t](*args,**kw)
+ else:
+ return func(*args,**kw)
+ try:
+ wrapper.__name__ = func.__name__
+ except (TypeError,AttributeError):
+ pass # Python 2.3 doesn't allow functions to be renamed
+
+ def register(typ, func=None):
+ if func is None:
+ return lambda f: register(typ, f)
+ registry[typ] = func
+ return func
+
+ wrapper.__dict__ = func.__dict__
+ wrapper.__doc__ = func.__doc__
+ wrapper.register = register
+ return wrapper
+
+
+def walk_packages(path=None, prefix='', onerror=None):
+ """Yield submodule names+loaders recursively, for path or sys.path"""
+
+ def seen(p,m={}):
+ if p in m: return True
+ m[p] = True
+
+ for importer, name, ispkg in iter_modules(path, prefix):
+ yield importer, name, ispkg
+
+ if ispkg:
+ try:
+ __import__(name)
+ except ImportError:
+ if onerror is not None:
+ onerror()
+ else:
+ path = getattr(sys.modules[name], '__path__', None) or []
+
+ # don't traverse path items we've seen before
+ path = [p for p in path if not seen(p)]
+
+ for item in walk_packages(path, name+'.'):
+ yield item
+
+
+def iter_modules(path=None, prefix=''):
+ """Yield submodule names+loaders for path or sys.path"""
+ if path is None:
+ importers = iter_importers()
+ else:
+ importers = map(get_importer, path)
+
+ yielded = {}
+ for i in importers:
+ for name, ispkg in iter_importer_modules(i, prefix):
+ if name not in yielded:
+ yielded[name] = 1
+ yield i, name, ispkg
+
+
+#@simplegeneric
+def iter_importer_modules(importer, prefix=''):
+ if not hasattr(importer,'iter_modules'):
+ return []
+ return importer.iter_modules(prefix)
+
+iter_importer_modules = simplegeneric(iter_importer_modules)
+
+
class ImpImporter:
"""PEP 302 Importer that wraps Python's "classic" import algorithm
@@ -49,13 +139,45 @@ class ImpImporter:
if self.path is None:
path = None
else:
- path = [self.path]
+ path = [os.path.realpath(self.path)]
try:
file, filename, etc = imp.find_module(subname, path)
except ImportError:
return None
return ImpLoader(fullname, file, filename, etc)
+ def iter_modules(self, prefix=''):
+ if self.path is None or not os.path.isdir(self.path):
+ return
+
+ yielded = {}
+ import inspect
+
+ filenames = os.listdir(self.path)
+ filenames.sort() # handle packages before same-named modules
+
+ for fn in filenames:
+ modname = inspect.getmodulename(fn)
+ if modname=='__init__' or modname in yielded:
+ continue
+
+ path = os.path.join(self.path, fn)
+ ispkg = False
+
+ if not modname and os.path.isdir(path) and '.' not in fn:
+ modname = fn
+ for fn in os.listdir(path):
+ subname = inspect.getmodulename(fn)
+ if subname=='__init__':
+ ispkg = True
+ break
+ else:
+ continue # not a package
+
+ if modname and '.' not in modname:
+ yielded[modname] = 1
+ yield prefix + modname, ispkg
+
class ImpLoader:
"""PEP 302 Loader that wraps Python's "classic" import algorithm
@@ -97,7 +219,8 @@ class ImpLoader:
"module %s" % (self.fullname, fullname))
return fullname
- def is_package(self):
+ def is_package(self, fullname):
+ fullname = self._fix_name(fullname)
return self.etc[2]==imp.PKG_DIRECTORY
def get_code(self, fullname=None):
@@ -136,6 +259,7 @@ class ImpLoader:
self.source = self._get_delegate().get_source()
return self.source
+
def _get_delegate(self):
return ImpImporter(self.filename).find_module('__init__')
@@ -149,6 +273,45 @@ class ImpLoader:
return None
+try:
+ import zipimport
+ from zipimport import zipimporter
+
+ def iter_zipimport_modules(importer, prefix=''):
+ dirlist = zipimport._zip_directory_cache[importer.archive].keys()
+ dirlist.sort()
+ _prefix = importer.prefix
+ plen = len(_prefix)
+ yielded = {}
+ import inspect
+ for fn in dirlist:
+ if not fn.startswith(_prefix):
+ continue
+
+ fn = fn[plen:].split(os.sep)
+
+ if len(fn)==2 and fn[1].startswith('__init__.py'):
+ if fn[0] not in yielded:
+ yielded[fn[0]] = 1
+ yield fn[0], True
+
+ if len(fn)!=1:
+ continue
+
+ modname = inspect.getmodulename(fn[0])
+ if modname=='__init__':
+ continue
+
+ if modname and '.' not in modname and modname not in yielded:
+ yielded[modname] = 1
+ yield prefix + modname, False
+
+ iter_importer_modules.register(zipimporter, iter_zipimport_modules)
+
+except ImportError:
+ pass
+
+
def get_importer(path_item):
"""Retrieve a PEP 302 importer for the given path item
@@ -183,7 +346,7 @@ def get_importer(path_item):
return importer
-def iter_importers(fullname):
+def iter_importers(fullname=""):
"""Yield PEP 302 importers for the given module name
If fullname contains a '.', the importers will be for the package
@@ -224,7 +387,6 @@ def iter_importers(fullname):
if '.' not in fullname:
yield ImpImporter()
-
def get_loader(module_or_name):
"""Get a PEP 302 "loader" object for module_or_name
@@ -250,7 +412,6 @@ def get_loader(module_or_name):
fullname = module_or_name
return find_loader(fullname)
-
def find_loader(fullname):
"""Find a PEP 302 "loader" object for fullname