Kaydet (Commit) d5f92239 authored tarafından Eric Snow's avatar Eric Snow

Issue #17211: Yield a namedtuple in pkgutil.

Patch by Ramchandra Apte.
üst 8e7cdb25
...@@ -11,6 +11,10 @@ ...@@ -11,6 +11,10 @@
This module provides utilities for the import system, in particular package This module provides utilities for the import system, in particular package
support. support.
.. class:: ModuleInfo(module_finder, name, ispkg)
A namedtuple that holds a brief summary of a module's info.
.. function:: extend_path(path, name) .. function:: extend_path(path, name)
...@@ -139,7 +143,7 @@ support. ...@@ -139,7 +143,7 @@ support.
.. function:: iter_modules(path=None, prefix='') .. function:: iter_modules(path=None, prefix='')
Yields ``(module_finder, name, ispkg)`` for all submodules on *path*, or, if Yields :class:`ModuleInfo` for all submodules on *path*, or, if
*path* is ``None``, all top-level modules on ``sys.path``. *path* is ``None``, all top-level modules on ``sys.path``.
*path* should be either ``None`` or a list of paths to look for modules in. *path* should be either ``None`` or a list of paths to look for modules in.
...@@ -160,7 +164,7 @@ support. ...@@ -160,7 +164,7 @@ support.
.. function:: walk_packages(path=None, prefix='', onerror=None) .. function:: walk_packages(path=None, prefix='', onerror=None)
Yields ``(module_finder, name, ispkg)`` for all modules recursively on Yields :class:`ModuleInfo` for all modules recursively on
*path*, or, if *path* is ``None``, all accessible modules. *path*, or, if *path* is ``None``, all accessible modules.
*path* should be either ``None`` or a list of paths to look for modules in. *path* should be either ``None`` or a list of paths to look for modules in.
......
"""Utilities to support packages.""" """Utilities to support packages."""
from collections import namedtuple
from functools import singledispatch as simplegeneric from functools import singledispatch as simplegeneric
import importlib import importlib
import importlib.util import importlib.util
...@@ -14,9 +15,14 @@ __all__ = [ ...@@ -14,9 +15,14 @@ __all__ = [
'get_importer', 'iter_importers', 'get_loader', 'find_loader', 'get_importer', 'iter_importers', 'get_loader', 'find_loader',
'walk_packages', 'iter_modules', 'get_data', 'walk_packages', 'iter_modules', 'get_data',
'ImpImporter', 'ImpLoader', 'read_code', 'extend_path', 'ImpImporter', 'ImpLoader', 'read_code', 'extend_path',
'ModuleInfo',
] ]
ModuleInfo = namedtuple('ModuleInfo', 'module_finder name ispkg')
ModuleInfo.__doc__ = 'A namedtuple with minimal info about a module.'
def _get_spec(finder, name): def _get_spec(finder, name):
"""Return the finder-specific module spec.""" """Return the finder-specific module spec."""
# Works with legacy finders. # Works with legacy finders.
...@@ -45,7 +51,7 @@ def read_code(stream): ...@@ -45,7 +51,7 @@ def read_code(stream):
def walk_packages(path=None, prefix='', onerror=None): def walk_packages(path=None, prefix='', onerror=None):
"""Yields (module_finder, name, ispkg) for all modules recursively """Yields ModuleInfo for all modules recursively
on path, or, if path is None, all accessible modules. on path, or, if path is None, all accessible modules.
'path' should be either None or a list of paths to look for 'path' should be either None or a list of paths to look for
...@@ -78,31 +84,31 @@ def walk_packages(path=None, prefix='', onerror=None): ...@@ -78,31 +84,31 @@ def walk_packages(path=None, prefix='', onerror=None):
return True return True
m[p] = True m[p] = True
for importer, name, ispkg in iter_modules(path, prefix): for info in iter_modules(path, prefix):
yield importer, name, ispkg yield info
if ispkg: if info.ispkg:
try: try:
__import__(name) __import__(info.name)
except ImportError: except ImportError:
if onerror is not None: if onerror is not None:
onerror(name) onerror(info.name)
except Exception: except Exception:
if onerror is not None: if onerror is not None:
onerror(name) onerror(info.name)
else: else:
raise raise
else: else:
path = getattr(sys.modules[name], '__path__', None) or [] path = getattr(sys.modules[info.name], '__path__', None) or []
# don't traverse path items we've seen before # don't traverse path items we've seen before
path = [p for p in path if not seen(p)] path = [p for p in path if not seen(p)]
yield from walk_packages(path, name+'.', onerror) yield from walk_packages(path, info.name+'.', onerror)
def iter_modules(path=None, prefix=''): def iter_modules(path=None, prefix=''):
"""Yields (module_finder, name, ispkg) for all submodules on path, """Yields ModuleInfo for all submodules on path,
or, if path is None, all top-level modules on sys.path. or, if path is None, all top-level modules on sys.path.
'path' should be either None or a list of paths to look for 'path' should be either None or a list of paths to look for
...@@ -111,7 +117,6 @@ def iter_modules(path=None, prefix=''): ...@@ -111,7 +117,6 @@ def iter_modules(path=None, prefix=''):
'prefix' is a string to output on the front of every module name 'prefix' is a string to output on the front of every module name
on output. on output.
""" """
if path is None: if path is None:
importers = iter_importers() importers = iter_importers()
else: else:
...@@ -122,7 +127,7 @@ def iter_modules(path=None, prefix=''): ...@@ -122,7 +127,7 @@ def iter_modules(path=None, prefix=''):
for name, ispkg in iter_importer_modules(i, prefix): for name, ispkg in iter_importer_modules(i, prefix):
if name not in yielded: if name not in yielded:
yielded[name] = 1 yielded[name] = 1
yield i, name, ispkg yield ModuleInfo(i, name, ispkg)
@simplegeneric @simplegeneric
......
...@@ -81,8 +81,9 @@ class PkgutilTests(unittest.TestCase): ...@@ -81,8 +81,9 @@ class PkgutilTests(unittest.TestCase):
self.assertEqual(res2, RESOURCE_DATA) self.assertEqual(res2, RESOURCE_DATA)
names = [] names = []
for loader, name, ispkg in pkgutil.iter_modules([zip_file]): for moduleinfo in pkgutil.iter_modules([zip_file]):
names.append(name) self.assertIsInstance(moduleinfo, pkgutil.ModuleInfo)
names.append(moduleinfo.name)
self.assertEqual(names, ['test_getdata_zipfile']) self.assertEqual(names, ['test_getdata_zipfile'])
del sys.path[0] del sys.path[0]
......
...@@ -577,13 +577,14 @@ from ..uncle.cousin import nephew ...@@ -577,13 +577,14 @@ from ..uncle.cousin import nephew
self.addCleanup(self._del_pkg, pkg_dir) self.addCleanup(self._del_pkg, pkg_dir)
for depth in range(2, max_depth+1): for depth in range(2, max_depth+1):
self._add_relative_modules(pkg_dir, "", depth) self._add_relative_modules(pkg_dir, "", depth)
for finder, mod_name, ispkg in pkgutil.walk_packages([pkg_dir]): for moduleinfo in pkgutil.walk_packages([pkg_dir]):
self.assertIsInstance(finder, self.assertIsInstance(moduleinfo, pkgutil.ModuleInfo)
self.assertIsInstance(moduleinfo.module_finder,
importlib.machinery.FileFinder) importlib.machinery.FileFinder)
if ispkg: if moduleinfo.ispkg:
expected_packages.remove(mod_name) expected_packages.remove(moduleinfo.name)
else: else:
expected_modules.remove(mod_name) expected_modules.remove(moduleinfo.name)
self.assertEqual(len(expected_packages), 0, expected_packages) self.assertEqual(len(expected_packages), 0, expected_packages)
self.assertEqual(len(expected_modules), 0, expected_modules) self.assertEqual(len(expected_modules), 0, expected_modules)
......
...@@ -7845,6 +7845,9 @@ Library ...@@ -7845,6 +7845,9 @@ Library
- Issue #16809: Tkinter's splitlist() and split() methods now accept Tcl_Obj - Issue #16809: Tkinter's splitlist() and split() methods now accept Tcl_Obj
argument. argument.
- Issue #17211: Yield a namedtuple in pkgutil.
Patch by Ramchandra Apte.
- Issue #18324: set_payload now correctly handles binary input. This also - Issue #18324: set_payload now correctly handles binary input. This also
supersedes the previous fixes for #14360, #1717, and #16564. supersedes the previous fixes for #14360, #1717, and #16564.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment