Skip to content

Commit 41c5694

Browse files
bpo-21720: Restore the Python 2.7 logic in handling a fromlist. (#4118)
BytesWarning no longer emitted when the fromlist argument of __import__() or the __all__ attribute of the module contain bytes instances.
1 parent 4eaf7f9 commit 41c5694

File tree

4 files changed

+1842
-1780
lines changed

4 files changed

+1842
-1780
lines changed

Lib/importlib/_bootstrap.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -994,7 +994,7 @@ def _gcd_import(name, package=None, level=0):
994994
return _find_and_load(name, _gcd_import)
995995

996996

997-
def _handle_fromlist(module, fromlist, import_):
997+
def _handle_fromlist(module, fromlist, import_, *, recursive=False):
998998
"""Figure out what __import__ should return.
999999
10001000
The import_ parameter is a callable which takes the name of module to
@@ -1005,13 +1005,19 @@ def _handle_fromlist(module, fromlist, import_):
10051005
# The hell that is fromlist ...
10061006
# If a package was imported, try to import stuff from fromlist.
10071007
if hasattr(module, '__path__'):
1008-
if '*' in fromlist:
1009-
fromlist = list(fromlist)
1010-
fromlist.remove('*')
1011-
if hasattr(module, '__all__'):
1012-
fromlist.extend(module.__all__)
10131008
for x in fromlist:
1014-
if not hasattr(module, x):
1009+
if not isinstance(x, str):
1010+
if recursive:
1011+
where = module.__name__ + '.__all__'
1012+
else:
1013+
where = "``from list''"
1014+
raise TypeError(f"Item in {where} must be str, "
1015+
f"not {type(x).__name__}")
1016+
elif x == '*':
1017+
if not recursive and hasattr(module, '__all__'):
1018+
_handle_fromlist(module, module.__all__, import_,
1019+
recursive=True)
1020+
elif not hasattr(module, x):
10151021
from_name = '{}.{}'.format(module.__name__, x)
10161022
try:
10171023
_call_with_frames_removed(import_, from_name)

Lib/test/test_importlib/import_/test_fromlist.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Test that the semantics relating to the 'fromlist' argument are correct."""
22
from .. import util
3+
import warnings
34
import unittest
45

56

@@ -73,6 +74,13 @@ def test_module_from_package(self):
7374
self.assertTrue(hasattr(module, 'module'))
7475
self.assertEqual(module.module.__name__, 'pkg.module')
7576

77+
def test_nonexistent_from_package(self):
78+
with util.mock_modules('pkg.__init__') as importer:
79+
with util.import_state(meta_path=[importer]):
80+
module = self.__import__('pkg', fromlist=['non_existent'])
81+
self.assertEqual(module.__name__, 'pkg')
82+
self.assertFalse(hasattr(module, 'non_existent'))
83+
7684
def test_module_from_package_triggers_ModuleNotFoundError(self):
7785
# If a submodule causes an ModuleNotFoundError because it tries
7886
# to import a module which doesn't exist, that should let the
@@ -122,6 +130,41 @@ def test_star_with_others(self):
122130
self.assertEqual(module.module1.__name__, 'pkg.module1')
123131
self.assertEqual(module.module2.__name__, 'pkg.module2')
124132

133+
def test_nonexistent_in_all(self):
134+
with util.mock_modules('pkg.__init__') as importer:
135+
with util.import_state(meta_path=[importer]):
136+
importer['pkg'].__all__ = ['non_existent']
137+
module = self.__import__('pkg', fromlist=['*'])
138+
self.assertEqual(module.__name__, 'pkg')
139+
self.assertFalse(hasattr(module, 'non_existent'))
140+
141+
def test_star_in_all(self):
142+
with util.mock_modules('pkg.__init__') as importer:
143+
with util.import_state(meta_path=[importer]):
144+
importer['pkg'].__all__ = ['*']
145+
module = self.__import__('pkg', fromlist=['*'])
146+
self.assertEqual(module.__name__, 'pkg')
147+
self.assertFalse(hasattr(module, '*'))
148+
149+
def test_invalid_type(self):
150+
with util.mock_modules('pkg.__init__') as importer:
151+
with util.import_state(meta_path=[importer]), \
152+
warnings.catch_warnings():
153+
warnings.simplefilter('error', BytesWarning)
154+
with self.assertRaisesRegex(TypeError, r'\bfrom\b'):
155+
self.__import__('pkg', fromlist=[b'attr'])
156+
with self.assertRaisesRegex(TypeError, r'\bfrom\b'):
157+
self.__import__('pkg', fromlist=iter([b'attr']))
158+
159+
def test_invalid_type_in_all(self):
160+
with util.mock_modules('pkg.__init__') as importer:
161+
with util.import_state(meta_path=[importer]), \
162+
warnings.catch_warnings():
163+
warnings.simplefilter('error', BytesWarning)
164+
importer['pkg'].__all__ = [b'attr']
165+
with self.assertRaisesRegex(TypeError, r'\bpkg\.__all__\b'):
166+
self.__import__('pkg', fromlist=['*'])
167+
125168

126169
(Frozen_FromList,
127170
Source_FromList
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
BytesWarning no longer emitted when the *fromlist* argument of
2+
``__import__()`` or the ``__all__`` attribute of the module contain bytes
3+
instances.

0 commit comments

Comments
 (0)