Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b2dd880

Browse files
committed
Issue #15294: Fix a regression in pkgutil.extend_path()'s handling of nested namespace packages.
1 parent 7df5e58 commit b2dd880

3 files changed

Lines changed: 60 additions & 5 deletions

File tree

Lib/pkgutil.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -513,20 +513,30 @@ def extend_path(path, name):
513513
# frozen package. Return the path unchanged in that case.
514514
return path
515515

516-
pname = os.path.join(*name.split('.')) # Reconstitute as relative path
517516
sname_pkg = name + ".pkg"
518517

519518
path = path[:] # Start with a copy of the existing path
520519

521-
for dir in sys.path:
520+
parent_package, _, final_name = name.rpartition('.')
521+
if parent_package:
522+
try:
523+
search_path = sys.modules[parent_package].__path__
524+
except (KeyError, AttributeError):
525+
# We can't do anything: find_loader() returns None when
526+
# passed a dotted name.
527+
return path
528+
else:
529+
search_path = sys.path
530+
531+
for dir in search_path:
522532
if not isinstance(dir, str):
523533
continue
524534

525535
finder = get_importer(dir)
526536
if finder is not None:
527537
# Is this finder PEP 420 compliant?
528538
if hasattr(finder, 'find_loader'):
529-
loader, portions = finder.find_loader(name)
539+
loader, portions = finder.find_loader(final_name)
530540
else:
531541
# No, no need to call it
532542
loader = None

Lib/test/test_pkgutil.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from test.support import run_unittest
1+
from test.support import run_unittest, unload
22
import unittest
33
import sys
44
import imp
@@ -214,8 +214,50 @@ def test_mixed_namespace(self):
214214
# XXX: test .pkg files
215215

216216

217+
class NestedNamespacePackageTest(unittest.TestCase):
218+
219+
def setUp(self):
220+
self.basedir = tempfile.mkdtemp()
221+
self.old_path = sys.path[:]
222+
223+
def tearDown(self):
224+
sys.path[:] = self.old_path
225+
shutil.rmtree(self.basedir)
226+
227+
def create_module(self, name, contents):
228+
base, final = name.rsplit('.', 1)
229+
base_path = os.path.join(self.basedir, base.replace('.', os.path.sep))
230+
os.makedirs(base_path, exist_ok=True)
231+
with open(os.path.join(base_path, final + ".py"), 'w') as f:
232+
f.write(contents)
233+
234+
def test_nested(self):
235+
pkgutil_boilerplate = (
236+
'import pkgutil; '
237+
'__path__ = pkgutil.extend_path(__path__, __name__)')
238+
self.create_module('a.pkg.__init__', pkgutil_boilerplate)
239+
self.create_module('b.pkg.__init__', pkgutil_boilerplate)
240+
self.create_module('a.pkg.subpkg.__init__', pkgutil_boilerplate)
241+
self.create_module('b.pkg.subpkg.__init__', pkgutil_boilerplate)
242+
self.create_module('a.pkg.subpkg.c', 'c = 1')
243+
self.create_module('b.pkg.subpkg.d', 'd = 2')
244+
sys.path.insert(0, os.path.join(self.basedir, 'a'))
245+
sys.path.insert(0, os.path.join(self.basedir, 'b'))
246+
import pkg
247+
self.addCleanup(unload, 'pkg')
248+
self.assertEqual(len(pkg.__path__), 2)
249+
import pkg.subpkg
250+
self.addCleanup(unload, 'pkg.subpkg')
251+
self.assertEqual(len(pkg.subpkg.__path__), 2)
252+
from pkg.subpkg.c import c
253+
from pkg.subpkg.d import d
254+
self.assertEqual(c, 1)
255+
self.assertEqual(d, 2)
256+
257+
217258
def test_main():
218-
run_unittest(PkgutilTests, PkgutilPEP302Tests, ExtendPathTests)
259+
run_unittest(PkgutilTests, PkgutilPEP302Tests, ExtendPathTests,
260+
NestedNamespacePackageTest)
219261
# this is necessary if test is run repeated (like when finding leaks)
220262
import zipimport
221263
zipimport._zip_directory_cache.clear()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ Core and Builtins
3131
Library
3232
-------
3333

34+
- Issue #15294: Fix a regression in pkgutil.extend_path()'s handling of
35+
nested namespace packages.
36+
3437
- Issue #15056: imp.cache_from_source() and source_from_cache() raise
3538
NotImplementedError when sys.implementation.cache_tag is set to None.
3639

0 commit comments

Comments
 (0)