Commit 41c56940 authored by Serhiy Storchaka's avatar Serhiy Storchaka Committed by GitHub

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.
parent 4eaf7f94
...@@ -994,7 +994,7 @@ def _gcd_import(name, package=None, level=0): ...@@ -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_): ...@@ -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)
......
"""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: ...@@ -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: ...@@ -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
......
BytesWarning no longer emitted when the *fromlist* argument of
``__import__()`` or the ``__all__`` attribute of the module contain bytes
instances.
This source diff could not be displayed because it is too large. You can view the blob instead.
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