[3.6] bpo-21720: Restore the Python 2.7 logic in handling a fromlist. (GH-4118) (#4128)

BytesWarning no longer emitted when the fromlist argument of
__import__() or the __all__ attribute of the module contain bytes
instances..
(cherry picked from commit 41c56940c6)
This commit is contained in:
Serhiy Storchaka 2017-10-26 12:02:54 +03:00 committed by GitHub
parent 614ea48986
commit 2b5cbbb13c
4 changed files with 1653 additions and 1591 deletions

View file

@ -994,7 +994,7 @@ def _gcd_import(name, package=None, level=0):
return _find_and_load(name, _gcd_import) return _find_and_load(name, _gcd_import)
def _handle_fromlist(module, fromlist, import_): def _handle_fromlist(module, fromlist, import_, *, recursive=False):
"""Figure out what __import__ should return. """Figure out what __import__ should return.
The import_ parameter is a callable which takes the name of module to The import_ parameter is a callable which takes the name of module to
@ -1005,13 +1005,19 @@ def _handle_fromlist(module, fromlist, import_):
# The hell that is fromlist ... # The hell that is fromlist ...
# If a package was imported, try to import stuff from fromlist. # If a package was imported, try to import stuff from fromlist.
if hasattr(module, '__path__'): if hasattr(module, '__path__'):
if '*' in fromlist:
fromlist = list(fromlist)
fromlist.remove('*')
if hasattr(module, '__all__'):
fromlist.extend(module.__all__)
for x in fromlist: for x in fromlist:
if not hasattr(module, x): if not isinstance(x, str):
if recursive:
where = module.__name__ + '.__all__'
else:
where = "``from list''"
raise TypeError(f"Item in {where} must be str, "
f"not {type(x).__name__}")
elif x == '*':
if not recursive and hasattr(module, '__all__'):
_handle_fromlist(module, module.__all__, import_,
recursive=True)
elif not hasattr(module, x):
from_name = '{}.{}'.format(module.__name__, x) from_name = '{}.{}'.format(module.__name__, x)
try: try:
_call_with_frames_removed(import_, from_name) _call_with_frames_removed(import_, from_name)

View file

@ -1,5 +1,6 @@
"""Test that the semantics relating to the 'fromlist' argument are correct.""" """Test that the semantics relating to the 'fromlist' argument are correct."""
from .. import util from .. import util
import warnings
import unittest import unittest
@ -73,6 +74,13 @@ class HandlingFromlist:
self.assertTrue(hasattr(module, 'module')) self.assertTrue(hasattr(module, 'module'))
self.assertEqual(module.module.__name__, 'pkg.module') self.assertEqual(module.module.__name__, 'pkg.module')
def test_nonexistent_from_package(self):
with util.mock_modules('pkg.__init__') as importer:
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg', fromlist=['non_existent'])
self.assertEqual(module.__name__, 'pkg')
self.assertFalse(hasattr(module, 'non_existent'))
def test_module_from_package_triggers_ModuleNotFoundError(self): def test_module_from_package_triggers_ModuleNotFoundError(self):
# If a submodule causes an ModuleNotFoundError because it tries # If a submodule causes an ModuleNotFoundError because it tries
# to import a module which doesn't exist, that should let the # to import a module which doesn't exist, that should let the
@ -122,6 +130,41 @@ class HandlingFromlist:
self.assertEqual(module.module1.__name__, 'pkg.module1') self.assertEqual(module.module1.__name__, 'pkg.module1')
self.assertEqual(module.module2.__name__, 'pkg.module2') self.assertEqual(module.module2.__name__, 'pkg.module2')
def test_nonexistent_in_all(self):
with util.mock_modules('pkg.__init__') as importer:
with util.import_state(meta_path=[importer]):
importer['pkg'].__all__ = ['non_existent']
module = self.__import__('pkg', fromlist=['*'])
self.assertEqual(module.__name__, 'pkg')
self.assertFalse(hasattr(module, 'non_existent'))
def test_star_in_all(self):
with util.mock_modules('pkg.__init__') as importer:
with util.import_state(meta_path=[importer]):
importer['pkg'].__all__ = ['*']
module = self.__import__('pkg', fromlist=['*'])
self.assertEqual(module.__name__, 'pkg')
self.assertFalse(hasattr(module, '*'))
def test_invalid_type(self):
with util.mock_modules('pkg.__init__') as importer:
with util.import_state(meta_path=[importer]), \
warnings.catch_warnings():
warnings.simplefilter('error', BytesWarning)
with self.assertRaisesRegex(TypeError, r'\bfrom\b'):
self.__import__('pkg', fromlist=[b'attr'])
with self.assertRaisesRegex(TypeError, r'\bfrom\b'):
self.__import__('pkg', fromlist=iter([b'attr']))
def test_invalid_type_in_all(self):
with util.mock_modules('pkg.__init__') as importer:
with util.import_state(meta_path=[importer]), \
warnings.catch_warnings():
warnings.simplefilter('error', BytesWarning)
importer['pkg'].__all__ = [b'attr']
with self.assertRaisesRegex(TypeError, r'\bpkg\.__all__\b'):
self.__import__('pkg', fromlist=['*'])
(Frozen_FromList, (Frozen_FromList,
Source_FromList Source_FromList

View file

@ -0,0 +1,3 @@
BytesWarning no longer emitted when the *fromlist* argument of
``__import__()`` or the ``__all__`` attribute of the module contain bytes
instances.

File diff suppressed because it is too large Load diff