From d5f92239818eef182fadc7c6f81f70912090573d Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Wed, 7 Sep 2016 18:37:17 -0700 Subject: Issue #17211: Yield a namedtuple in pkgutil. Patch by Ramchandra Apte. --- Doc/library/pkgutil.rst | 8 ++++++-- Lib/pkgutil.py | 29 +++++++++++++++++------------ Lib/test/test_pkgutil.py | 5 +++-- Lib/test/test_runpy.py | 11 ++++++----- Misc/NEWS | 3 +++ 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Doc/library/pkgutil.rst b/Doc/library/pkgutil.rst index c5ba8d8..1d038f9 100644 --- a/Doc/library/pkgutil.rst +++ b/Doc/library/pkgutil.rst @@ -11,6 +11,10 @@ This module provides utilities for the import system, in particular package support. +.. class:: ModuleInfo(module_finder, name, ispkg) + + A namedtuple that holds a brief summary of a module's info. + .. function:: extend_path(path, name) @@ -139,7 +143,7 @@ support. .. 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* should be either ``None`` or a list of paths to look for modules in. @@ -160,7 +164,7 @@ support. .. 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* should be either ``None`` or a list of paths to look for modules in. diff --git a/Lib/pkgutil.py b/Lib/pkgutil.py index 7fc356c..e37ad45 100644 --- a/Lib/pkgutil.py +++ b/Lib/pkgutil.py @@ -1,5 +1,6 @@ """Utilities to support packages.""" +from collections import namedtuple from functools import singledispatch as simplegeneric import importlib import importlib.util @@ -14,9 +15,14 @@ __all__ = [ 'get_importer', 'iter_importers', 'get_loader', 'find_loader', 'walk_packages', 'iter_modules', 'get_data', '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): """Return the finder-specific module spec.""" # Works with legacy finders. @@ -45,7 +51,7 @@ def read_code(stream): 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. '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): return True m[p] = True - for importer, name, ispkg in iter_modules(path, prefix): - yield importer, name, ispkg + for info in iter_modules(path, prefix): + yield info - if ispkg: + if info.ispkg: try: - __import__(name) + __import__(info.name) except ImportError: if onerror is not None: - onerror(name) + onerror(info.name) except Exception: if onerror is not None: - onerror(name) + onerror(info.name) else: raise 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 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=''): - """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. 'path' should be either None or a list of paths to look for @@ -111,7 +117,6 @@ def iter_modules(path=None, prefix=''): 'prefix' is a string to output on the front of every module name on output. """ - if path is None: importers = iter_importers() else: @@ -122,7 +127,7 @@ def iter_modules(path=None, prefix=''): for name, ispkg in iter_importer_modules(i, prefix): if name not in yielded: yielded[name] = 1 - yield i, name, ispkg + yield ModuleInfo(i, name, ispkg) @simplegeneric diff --git a/Lib/test/test_pkgutil.py b/Lib/test/test_pkgutil.py index ae2aa1b..fc04dcf 100644 --- a/Lib/test/test_pkgutil.py +++ b/Lib/test/test_pkgutil.py @@ -81,8 +81,9 @@ class PkgutilTests(unittest.TestCase): self.assertEqual(res2, RESOURCE_DATA) names = [] - for loader, name, ispkg in pkgutil.iter_modules([zip_file]): - names.append(name) + for moduleinfo in pkgutil.iter_modules([zip_file]): + self.assertIsInstance(moduleinfo, pkgutil.ModuleInfo) + names.append(moduleinfo.name) self.assertEqual(names, ['test_getdata_zipfile']) del sys.path[0] diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py index db55db7..02b4d62 100644 --- a/Lib/test/test_runpy.py +++ b/Lib/test/test_runpy.py @@ -577,13 +577,14 @@ from ..uncle.cousin import nephew self.addCleanup(self._del_pkg, pkg_dir) for depth in range(2, max_depth+1): self._add_relative_modules(pkg_dir, "", depth) - for finder, mod_name, ispkg in pkgutil.walk_packages([pkg_dir]): - self.assertIsInstance(finder, + for moduleinfo in pkgutil.walk_packages([pkg_dir]): + self.assertIsInstance(moduleinfo, pkgutil.ModuleInfo) + self.assertIsInstance(moduleinfo.module_finder, importlib.machinery.FileFinder) - if ispkg: - expected_packages.remove(mod_name) + if moduleinfo.ispkg: + expected_packages.remove(moduleinfo.name) else: - expected_modules.remove(mod_name) + expected_modules.remove(moduleinfo.name) self.assertEqual(len(expected_packages), 0, expected_packages) self.assertEqual(len(expected_modules), 0, expected_modules) diff --git a/Misc/NEWS b/Misc/NEWS index c60e870..0ea22a9 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -7845,6 +7845,9 @@ Library - Issue #16809: Tkinter's splitlist() and split() methods now accept Tcl_Obj argument. +- Issue #17211: Yield a namedtuple in pkgutil. + Patch by Ramchandra Apte. + - Issue #18324: set_payload now correctly handles binary input. This also supersedes the previous fixes for #14360, #1717, and #16564. -- cgit v0.12