mirror of https://github.com/python/cpython
gh-123930: Better error for "from imports" when script shadows module (#123929)
This commit is contained in:
parent
3f24bde0b6
commit
500f5338a8
|
@ -274,7 +274,7 @@ Improved error messages
|
||||||
File "/home/me/random.py", line 3, in <module>
|
File "/home/me/random.py", line 3, in <module>
|
||||||
print(random.randint(5))
|
print(random.randint(5))
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
AttributeError: module 'random' has no attribute 'randint' (consider renaming '/home/me/random.py' since it has the same name as the standard library module named 'random' and the import system gives it precedence)
|
AttributeError: module 'random' has no attribute 'randint' (consider renaming '/home/me/random.py' since it has the same name as the standard library module named 'random' and prevents importing that standard library module)
|
||||||
|
|
||||||
Similarly, if a script has the same name as a third-party
|
Similarly, if a script has the same name as a third-party
|
||||||
module that it attempts to import and this results in errors,
|
module that it attempts to import and this results in errors,
|
||||||
|
@ -289,7 +289,7 @@ Improved error messages
|
||||||
File "/home/me/numpy.py", line 3, in <module>
|
File "/home/me/numpy.py", line 3, in <module>
|
||||||
np.array([1, 2, 3])
|
np.array([1, 2, 3])
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
AttributeError: module 'numpy' has no attribute 'array' (consider renaming '/home/me/numpy.py' if it has the same name as a third-party module you intended to import)
|
AttributeError: module 'numpy' has no attribute 'array' (consider renaming '/home/me/numpy.py' if it has the same name as a library you intended to import)
|
||||||
|
|
||||||
(Contributed by Shantanu Jain in :gh:`95754`.)
|
(Contributed by Shantanu Jain in :gh:`95754`.)
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@ extern "C" {
|
||||||
extern void _PyModule_Clear(PyObject *);
|
extern void _PyModule_Clear(PyObject *);
|
||||||
extern void _PyModule_ClearDict(PyObject *);
|
extern void _PyModule_ClearDict(PyObject *);
|
||||||
extern int _PyModuleSpec_IsInitializing(PyObject *);
|
extern int _PyModuleSpec_IsInitializing(PyObject *);
|
||||||
|
extern int _PyModuleSpec_GetFileOrigin(PyObject *, PyObject **);
|
||||||
|
extern int _PyModule_IsPossiblyShadowing(PyObject *);
|
||||||
|
|
||||||
extern int _PyModule_IsExtension(PyObject *obj);
|
extern int _PyModule_IsExtension(PyObject *obj);
|
||||||
|
|
||||||
|
|
|
@ -804,104 +804,133 @@ class ImportTests(unittest.TestCase):
|
||||||
str(cm.exception))
|
str(cm.exception))
|
||||||
|
|
||||||
def test_script_shadowing_stdlib(self):
|
def test_script_shadowing_stdlib(self):
|
||||||
with os_helper.temp_dir() as tmp:
|
script_errors = [
|
||||||
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
|
(
|
||||||
f.write("import fractions\nfractions.Fraction")
|
"import fractions\nfractions.Fraction",
|
||||||
|
rb"AttributeError: module 'fractions' has no attribute 'Fraction'"
|
||||||
expected_error = (
|
),
|
||||||
rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
|
(
|
||||||
rb"\(consider renaming '.*fractions.py' since it has the "
|
"from fractions import Fraction",
|
||||||
rb"same name as the standard library module named 'fractions' "
|
rb"ImportError: cannot import name 'Fraction' from 'fractions'"
|
||||||
rb"and the import system gives it precedence\)"
|
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
for script, error in script_errors:
|
||||||
|
with self.subTest(script=script), os_helper.temp_dir() as tmp:
|
||||||
|
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
|
||||||
|
f.write(script)
|
||||||
|
|
||||||
popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp)
|
expected_error = error + (
|
||||||
stdout, stderr = popen.communicate()
|
rb" \(consider renaming '.*fractions.py' since it has the "
|
||||||
self.assertRegex(stdout, expected_error)
|
rb"same name as the standard library module named 'fractions' "
|
||||||
|
rb"and prevents importing that standard library module\)"
|
||||||
|
)
|
||||||
|
|
||||||
popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp)
|
popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertRegex(stdout, expected_error)
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp)
|
popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertRegex(stdout, expected_error)
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
# and there's no error at all when using -P
|
popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp)
|
||||||
popen = script_helper.spawn_python('-P', 'fractions.py', cwd=tmp)
|
stdout, stderr = popen.communicate()
|
||||||
stdout, stderr = popen.communicate()
|
self.assertRegex(stdout, expected_error)
|
||||||
self.assertEqual(stdout, b'')
|
|
||||||
|
|
||||||
tmp_child = os.path.join(tmp, "child")
|
# and there's no error at all when using -P
|
||||||
os.mkdir(tmp_child)
|
popen = script_helper.spawn_python('-P', 'fractions.py', cwd=tmp)
|
||||||
|
stdout, stderr = popen.communicate()
|
||||||
|
self.assertEqual(stdout, b'')
|
||||||
|
|
||||||
# test the logic with different cwd
|
tmp_child = os.path.join(tmp, "child")
|
||||||
popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp_child)
|
os.mkdir(tmp_child)
|
||||||
stdout, stderr = popen.communicate()
|
|
||||||
self.assertRegex(stdout, expected_error)
|
|
||||||
|
|
||||||
popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp_child)
|
# test the logic with different cwd
|
||||||
stdout, stderr = popen.communicate()
|
popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp_child)
|
||||||
self.assertEqual(stdout, b'') # no error
|
stdout, stderr = popen.communicate()
|
||||||
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp_child)
|
popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp_child)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertEqual(stdout, b'') # no error
|
self.assertEqual(stdout, b'') # no error
|
||||||
|
|
||||||
|
popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp_child)
|
||||||
|
stdout, stderr = popen.communicate()
|
||||||
|
self.assertEqual(stdout, b'') # no error
|
||||||
|
|
||||||
def test_package_shadowing_stdlib_module(self):
|
def test_package_shadowing_stdlib_module(self):
|
||||||
with os_helper.temp_dir() as tmp:
|
script_errors = [
|
||||||
os.mkdir(os.path.join(tmp, "fractions"))
|
(
|
||||||
with open(os.path.join(tmp, "fractions", "__init__.py"), "w", encoding='utf-8') as f:
|
"fractions.Fraction",
|
||||||
f.write("shadowing_module = True")
|
rb"AttributeError: module 'fractions' has no attribute 'Fraction'"
|
||||||
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
),
|
||||||
f.write("""
|
(
|
||||||
import fractions
|
"from fractions import Fraction",
|
||||||
fractions.shadowing_module
|
rb"ImportError: cannot import name 'Fraction' from 'fractions'"
|
||||||
fractions.Fraction
|
|
||||||
""")
|
|
||||||
|
|
||||||
expected_error = (
|
|
||||||
rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
|
|
||||||
rb"\(consider renaming '.*fractions.__init__.py' since it has the "
|
|
||||||
rb"same name as the standard library module named 'fractions' "
|
|
||||||
rb"and the import system gives it precedence\)"
|
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
for script, error in script_errors:
|
||||||
|
with self.subTest(script=script), os_helper.temp_dir() as tmp:
|
||||||
|
os.mkdir(os.path.join(tmp, "fractions"))
|
||||||
|
with open(
|
||||||
|
os.path.join(tmp, "fractions", "__init__.py"), "w", encoding='utf-8'
|
||||||
|
) as f:
|
||||||
|
f.write("shadowing_module = True")
|
||||||
|
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||||
|
f.write("import fractions; fractions.shadowing_module\n")
|
||||||
|
f.write(script)
|
||||||
|
|
||||||
popen = script_helper.spawn_python(os.path.join(tmp, "main.py"), cwd=tmp)
|
expected_error = error + (
|
||||||
stdout, stderr = popen.communicate()
|
rb" \(consider renaming '.*[\\/]fractions[\\/]+__init__.py' since it has the "
|
||||||
self.assertRegex(stdout, expected_error)
|
rb"same name as the standard library module named 'fractions' "
|
||||||
|
rb"and prevents importing that standard library module\)"
|
||||||
|
)
|
||||||
|
|
||||||
popen = script_helper.spawn_python('-m', 'main', cwd=tmp)
|
popen = script_helper.spawn_python(os.path.join(tmp, "main.py"), cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertRegex(stdout, expected_error)
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
# and there's no shadowing at all when using -P
|
popen = script_helper.spawn_python('-m', 'main', cwd=tmp)
|
||||||
popen = script_helper.spawn_python('-P', 'main.py', cwd=tmp)
|
stdout, stderr = popen.communicate()
|
||||||
stdout, stderr = popen.communicate()
|
self.assertRegex(stdout, expected_error)
|
||||||
self.assertRegex(stdout, b"module 'fractions' has no attribute 'shadowing_module'")
|
|
||||||
|
# and there's no shadowing at all when using -P
|
||||||
|
popen = script_helper.spawn_python('-P', 'main.py', cwd=tmp)
|
||||||
|
stdout, stderr = popen.communicate()
|
||||||
|
self.assertRegex(stdout, b"module 'fractions' has no attribute 'shadowing_module'")
|
||||||
|
|
||||||
def test_script_shadowing_third_party(self):
|
def test_script_shadowing_third_party(self):
|
||||||
with os_helper.temp_dir() as tmp:
|
script_errors = [
|
||||||
with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
|
(
|
||||||
f.write("import numpy\nnumpy.array")
|
"import numpy\nnumpy.array",
|
||||||
|
rb"AttributeError: module 'numpy' has no attribute 'array'"
|
||||||
expected_error = (
|
),
|
||||||
rb"AttributeError: module 'numpy' has no attribute 'array' "
|
(
|
||||||
rb"\(consider renaming '.*numpy.py' if it has the "
|
"from numpy import array",
|
||||||
rb"same name as a third-party module you intended to import\)\s+\Z"
|
rb"ImportError: cannot import name 'array' from 'numpy'"
|
||||||
)
|
)
|
||||||
|
]
|
||||||
|
for script, error in script_errors:
|
||||||
|
with self.subTest(script=script), os_helper.temp_dir() as tmp:
|
||||||
|
with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
|
||||||
|
f.write(script)
|
||||||
|
|
||||||
popen = script_helper.spawn_python(os.path.join(tmp, "numpy.py"))
|
expected_error = error + (
|
||||||
stdout, stderr = popen.communicate()
|
rb" \(consider renaming '.*numpy.py' if it has the "
|
||||||
self.assertRegex(stdout, expected_error)
|
rb"same name as a library you intended to import\)\s+\Z"
|
||||||
|
)
|
||||||
|
|
||||||
popen = script_helper.spawn_python('-m', 'numpy', cwd=tmp)
|
popen = script_helper.spawn_python(os.path.join(tmp, "numpy.py"))
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertRegex(stdout, expected_error)
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
popen = script_helper.spawn_python('-c', 'import numpy', cwd=tmp)
|
popen = script_helper.spawn_python('-m', 'numpy', cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertRegex(stdout, expected_error)
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
|
popen = script_helper.spawn_python('-c', 'import numpy', cwd=tmp)
|
||||||
|
stdout, stderr = popen.communicate()
|
||||||
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
def test_script_maybe_not_shadowing_third_party(self):
|
def test_script_maybe_not_shadowing_third_party(self):
|
||||||
with os_helper.temp_dir() as tmp:
|
with os_helper.temp_dir() as tmp:
|
||||||
|
@ -911,15 +940,23 @@ fractions.Fraction
|
||||||
expected_error = (
|
expected_error = (
|
||||||
rb"AttributeError: module 'numpy' has no attribute 'attr'\s+\Z"
|
rb"AttributeError: module 'numpy' has no attribute 'attr'\s+\Z"
|
||||||
)
|
)
|
||||||
|
|
||||||
popen = script_helper.spawn_python('-c', 'import numpy; numpy.attr', cwd=tmp)
|
popen = script_helper.spawn_python('-c', 'import numpy; numpy.attr', cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertRegex(stdout, expected_error)
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
|
expected_error = (
|
||||||
|
rb"ImportError: cannot import name 'attr' from 'numpy' \(.*\)\s+\Z"
|
||||||
|
)
|
||||||
|
popen = script_helper.spawn_python('-c', 'from numpy import attr', cwd=tmp)
|
||||||
|
stdout, stderr = popen.communicate()
|
||||||
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
def test_script_shadowing_stdlib_edge_cases(self):
|
def test_script_shadowing_stdlib_edge_cases(self):
|
||||||
with os_helper.temp_dir() as tmp:
|
with os_helper.temp_dir() as tmp:
|
||||||
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
|
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
|
||||||
f.write("shadowing_module = True")
|
f.write("shadowing_module = True")
|
||||||
|
|
||||||
|
# Unhashable str subclass
|
||||||
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||||
f.write("""
|
f.write("""
|
||||||
import fractions
|
import fractions
|
||||||
|
@ -932,11 +969,28 @@ try:
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
print(str(e))
|
print(str(e))
|
||||||
""")
|
""")
|
||||||
|
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||||
|
stdout, stderr = popen.communicate()
|
||||||
|
self.assertEqual(stdout.rstrip(), b"unhashable type: 'substr'")
|
||||||
|
|
||||||
|
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||||
|
f.write("""
|
||||||
|
import fractions
|
||||||
|
fractions.shadowing_module
|
||||||
|
class substr(str):
|
||||||
|
__hash__ = None
|
||||||
|
fractions.__name__ = substr('fractions')
|
||||||
|
try:
|
||||||
|
from fractions import Fraction
|
||||||
|
except TypeError as e:
|
||||||
|
print(str(e))
|
||||||
|
""")
|
||||||
|
|
||||||
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertEqual(stdout.rstrip(), b"unhashable type: 'substr'")
|
self.assertEqual(stdout.rstrip(), b"unhashable type: 'substr'")
|
||||||
|
|
||||||
|
# Various issues with sys module
|
||||||
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||||
f.write("""
|
f.write("""
|
||||||
import fractions
|
import fractions
|
||||||
|
@ -961,22 +1015,49 @@ try:
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
print(str(e))
|
print(str(e))
|
||||||
""")
|
""")
|
||||||
|
|
||||||
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertEqual(
|
lines = stdout.splitlines()
|
||||||
stdout.splitlines(),
|
self.assertEqual(len(lines), 3)
|
||||||
[
|
for line in lines:
|
||||||
b"module 'fractions' has no attribute 'Fraction'",
|
self.assertEqual(line, b"module 'fractions' has no attribute 'Fraction'")
|
||||||
b"module 'fractions' has no attribute 'Fraction'",
|
|
||||||
b"module 'fractions' has no attribute 'Fraction'",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||||
f.write("""
|
f.write("""
|
||||||
import fractions
|
import fractions
|
||||||
fractions.shadowing_module
|
fractions.shadowing_module
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.stdlib_module_names = None
|
||||||
|
try:
|
||||||
|
from fractions import Fraction
|
||||||
|
except ImportError as e:
|
||||||
|
print(str(e))
|
||||||
|
|
||||||
|
del sys.stdlib_module_names
|
||||||
|
try:
|
||||||
|
from fractions import Fraction
|
||||||
|
except ImportError as e:
|
||||||
|
print(str(e))
|
||||||
|
|
||||||
|
sys.path = [0]
|
||||||
|
try:
|
||||||
|
from fractions import Fraction
|
||||||
|
except ImportError as e:
|
||||||
|
print(str(e))
|
||||||
|
""")
|
||||||
|
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||||
|
stdout, stderr = popen.communicate()
|
||||||
|
lines = stdout.splitlines()
|
||||||
|
self.assertEqual(len(lines), 3)
|
||||||
|
for line in lines:
|
||||||
|
self.assertRegex(line, rb"cannot import name 'Fraction' from 'fractions' \(.*\)")
|
||||||
|
|
||||||
|
# Various issues with origin
|
||||||
|
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||||
|
f.write("""
|
||||||
|
import fractions
|
||||||
|
fractions.shadowing_module
|
||||||
del fractions.__spec__.origin
|
del fractions.__spec__.origin
|
||||||
try:
|
try:
|
||||||
fractions.Fraction
|
fractions.Fraction
|
||||||
|
@ -992,37 +1073,61 @@ except AttributeError as e:
|
||||||
|
|
||||||
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertEqual(
|
lines = stdout.splitlines()
|
||||||
stdout.splitlines(),
|
self.assertEqual(len(lines), 2)
|
||||||
[
|
for line in lines:
|
||||||
b"module 'fractions' has no attribute 'Fraction'",
|
self.assertEqual(line, b"module 'fractions' has no attribute 'Fraction'")
|
||||||
b"module 'fractions' has no attribute 'Fraction'"
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_script_shadowing_stdlib_sys_path_modification(self):
|
|
||||||
with os_helper.temp_dir() as tmp:
|
|
||||||
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
|
|
||||||
f.write("shadowing_module = True")
|
|
||||||
|
|
||||||
expected_error = (
|
|
||||||
rb"AttributeError: module 'fractions' has no attribute 'Fraction' "
|
|
||||||
rb"\(consider renaming '.*fractions.py' since it has the "
|
|
||||||
rb"same name as the standard library module named 'fractions' "
|
|
||||||
rb"and the import system gives it precedence\)"
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||||
f.write("""
|
f.write("""
|
||||||
import sys
|
|
||||||
sys.path.insert(0, "this_folder_does_not_exist")
|
|
||||||
import fractions
|
import fractions
|
||||||
fractions.Fraction
|
fractions.shadowing_module
|
||||||
""")
|
del fractions.__spec__.origin
|
||||||
|
try:
|
||||||
|
from fractions import Fraction
|
||||||
|
except ImportError as e:
|
||||||
|
print(str(e))
|
||||||
|
|
||||||
|
fractions.__spec__.origin = 0
|
||||||
|
try:
|
||||||
|
from fractions import Fraction
|
||||||
|
except ImportError as e:
|
||||||
|
print(str(e))
|
||||||
|
""")
|
||||||
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||||
stdout, stderr = popen.communicate()
|
stdout, stderr = popen.communicate()
|
||||||
self.assertRegex(stdout, expected_error)
|
lines = stdout.splitlines()
|
||||||
|
self.assertEqual(len(lines), 2)
|
||||||
|
for line in lines:
|
||||||
|
self.assertRegex(line, rb"cannot import name 'Fraction' from 'fractions' \(.*\)")
|
||||||
|
|
||||||
|
def test_script_shadowing_stdlib_sys_path_modification(self):
|
||||||
|
script_errors = [
|
||||||
|
(
|
||||||
|
"import fractions\nfractions.Fraction",
|
||||||
|
rb"AttributeError: module 'fractions' has no attribute 'Fraction'"
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"from fractions import Fraction",
|
||||||
|
rb"ImportError: cannot import name 'Fraction' from 'fractions'"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
for script, error in script_errors:
|
||||||
|
with self.subTest(script=script), os_helper.temp_dir() as tmp:
|
||||||
|
with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
|
||||||
|
f.write("shadowing_module = True")
|
||||||
|
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
|
||||||
|
f.write('import sys; sys.path.insert(0, "this_folder_does_not_exist")\n')
|
||||||
|
f.write(script)
|
||||||
|
expected_error = error + (
|
||||||
|
rb" \(consider renaming '.*fractions.py' since it has the "
|
||||||
|
rb"same name as the standard library module named 'fractions' "
|
||||||
|
rb"and prevents importing that standard library module\)"
|
||||||
|
)
|
||||||
|
|
||||||
|
popen = script_helper.spawn_python("main.py", cwd=tmp)
|
||||||
|
stdout, stderr = popen.communicate()
|
||||||
|
self.assertRegex(stdout, expected_error)
|
||||||
|
|
||||||
|
|
||||||
@skip_if_dont_write_bytecode
|
@skip_if_dont_write_bytecode
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
Improve the error message when a script shadowing a module from the standard
|
||||||
|
library causes :exc:`ImportError` to be raised during a "from" import.
|
||||||
|
Similarly, improve the error message when a script shadowing a third party module
|
||||||
|
attempts to "from" import an attribute from that third party module while still initialising.
|
|
@ -836,15 +836,15 @@ _PyModuleSpec_IsUninitializedSubmodule(PyObject *spec, PyObject *name)
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
int
|
||||||
_get_file_origin_from_spec(PyObject *spec, PyObject **p_origin)
|
_PyModuleSpec_GetFileOrigin(PyObject *spec, PyObject **p_origin)
|
||||||
{
|
{
|
||||||
PyObject *has_location = NULL;
|
PyObject *has_location = NULL;
|
||||||
int rc = PyObject_GetOptionalAttr(spec, &_Py_ID(has_location), &has_location);
|
int rc = PyObject_GetOptionalAttr(spec, &_Py_ID(has_location), &has_location);
|
||||||
if (rc <= 0) {
|
if (rc <= 0) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
// If origin is not a location, or doesn't exist, or is not a str), we could consider falling
|
// If origin is not a location, or doesn't exist, or is not a str, we could consider falling
|
||||||
// back to module.__file__. But the cases in which module.__file__ is not __spec__.origin
|
// back to module.__file__. But the cases in which module.__file__ is not __spec__.origin
|
||||||
// are cases in which we probably shouldn't be guessing.
|
// are cases in which we probably shouldn't be guessing.
|
||||||
rc = PyObject_IsTrue(has_location);
|
rc = PyObject_IsTrue(has_location);
|
||||||
|
@ -867,8 +867,8 @@ _get_file_origin_from_spec(PyObject *spec, PyObject **p_origin)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
int
|
||||||
_is_module_possibly_shadowing(PyObject *origin)
|
_PyModule_IsPossiblyShadowing(PyObject *origin)
|
||||||
{
|
{
|
||||||
// origin must be a unicode subtype
|
// origin must be a unicode subtype
|
||||||
// Returns 1 if the module at origin could be shadowing a module of the
|
// Returns 1 if the module at origin could be shadowing a module of the
|
||||||
|
@ -993,11 +993,11 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *origin = NULL;
|
PyObject *origin = NULL;
|
||||||
if (_get_file_origin_from_spec(spec, &origin) < 0) {
|
if (_PyModuleSpec_GetFileOrigin(spec, &origin) < 0) {
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
int is_possibly_shadowing = _is_module_possibly_shadowing(origin);
|
int is_possibly_shadowing = _PyModule_IsPossiblyShadowing(origin);
|
||||||
if (is_possibly_shadowing < 0) {
|
if (is_possibly_shadowing < 0) {
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
@ -1018,20 +1018,23 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
|
||||||
"module '%U' has no attribute '%U' "
|
"module '%U' has no attribute '%U' "
|
||||||
"(consider renaming '%U' since it has the same "
|
"(consider renaming '%U' since it has the same "
|
||||||
"name as the standard library module named '%U' "
|
"name as the standard library module named '%U' "
|
||||||
"and the import system gives it precedence)",
|
"and prevents importing that standard library module)",
|
||||||
mod_name, name, origin, mod_name);
|
mod_name, name, origin, mod_name);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int rc = _PyModuleSpec_IsInitializing(spec);
|
int rc = _PyModuleSpec_IsInitializing(spec);
|
||||||
if (rc > 0) {
|
if (rc < 0) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
else if (rc > 0) {
|
||||||
if (is_possibly_shadowing) {
|
if (is_possibly_shadowing) {
|
||||||
assert(origin);
|
assert(origin);
|
||||||
// For third-party modules, only mention the possibility of
|
// For non-stdlib modules, only mention the possibility of
|
||||||
// shadowing if the module is being initialized.
|
// shadowing if the module is being initialized.
|
||||||
PyErr_Format(PyExc_AttributeError,
|
PyErr_Format(PyExc_AttributeError,
|
||||||
"module '%U' has no attribute '%U' "
|
"module '%U' has no attribute '%U' "
|
||||||
"(consider renaming '%U' if it has the same name "
|
"(consider renaming '%U' if it has the same name "
|
||||||
"as a third-party module you intended to import)",
|
"as a library you intended to import)",
|
||||||
mod_name, name, origin);
|
mod_name, name, origin);
|
||||||
}
|
}
|
||||||
else if (origin) {
|
else if (origin) {
|
||||||
|
@ -1049,7 +1052,8 @@ _Py_module_getattro_impl(PyModuleObject *m, PyObject *name, int suppress)
|
||||||
mod_name, name);
|
mod_name, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (rc == 0) {
|
else {
|
||||||
|
assert(rc == 0);
|
||||||
rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);
|
rc = _PyModuleSpec_IsUninitializedSubmodule(spec, name);
|
||||||
if (rc > 0) {
|
if (rc > 0) {
|
||||||
PyErr_Format(PyExc_AttributeError,
|
PyErr_Format(PyExc_AttributeError,
|
||||||
|
|
152
Python/ceval.c
152
Python/ceval.c
|
@ -2802,7 +2802,7 @@ PyObject *
|
||||||
_PyEval_ImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name)
|
_PyEval_ImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name)
|
||||||
{
|
{
|
||||||
PyObject *x;
|
PyObject *x;
|
||||||
PyObject *fullmodname, *pkgname, *pkgpath, *pkgname_or_unknown, *errmsg;
|
PyObject *fullmodname, *mod_name, *origin, *mod_name_or_unknown, *errmsg, *spec;
|
||||||
|
|
||||||
if (PyObject_GetOptionalAttr(v, name, &x) != 0) {
|
if (PyObject_GetOptionalAttr(v, name, &x) != 0) {
|
||||||
return x;
|
return x;
|
||||||
|
@ -2810,16 +2810,16 @@ _PyEval_ImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name)
|
||||||
/* Issue #17636: in case this failed because of a circular relative
|
/* Issue #17636: in case this failed because of a circular relative
|
||||||
import, try to fallback on reading the module directly from
|
import, try to fallback on reading the module directly from
|
||||||
sys.modules. */
|
sys.modules. */
|
||||||
if (PyObject_GetOptionalAttr(v, &_Py_ID(__name__), &pkgname) < 0) {
|
if (PyObject_GetOptionalAttr(v, &_Py_ID(__name__), &mod_name) < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (pkgname == NULL || !PyUnicode_Check(pkgname)) {
|
if (mod_name == NULL || !PyUnicode_Check(mod_name)) {
|
||||||
Py_CLEAR(pkgname);
|
Py_CLEAR(mod_name);
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
fullmodname = PyUnicode_FromFormat("%U.%U", pkgname, name);
|
fullmodname = PyUnicode_FromFormat("%U.%U", mod_name, name);
|
||||||
if (fullmodname == NULL) {
|
if (fullmodname == NULL) {
|
||||||
Py_DECREF(pkgname);
|
Py_DECREF(mod_name);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
x = PyImport_GetModule(fullmodname);
|
x = PyImport_GetModule(fullmodname);
|
||||||
|
@ -2827,63 +2827,121 @@ _PyEval_ImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name)
|
||||||
if (x == NULL && !_PyErr_Occurred(tstate)) {
|
if (x == NULL && !_PyErr_Occurred(tstate)) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
Py_DECREF(pkgname);
|
Py_DECREF(mod_name);
|
||||||
return x;
|
return x;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if (pkgname == NULL) {
|
if (mod_name == NULL) {
|
||||||
pkgname_or_unknown = PyUnicode_FromString("<unknown module name>");
|
mod_name_or_unknown = PyUnicode_FromString("<unknown module name>");
|
||||||
if (pkgname_or_unknown == NULL) {
|
if (mod_name_or_unknown == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pkgname_or_unknown = pkgname;
|
mod_name_or_unknown = mod_name;
|
||||||
}
|
}
|
||||||
|
// mod_name is no longer an owned reference
|
||||||
|
assert(mod_name_or_unknown);
|
||||||
|
assert(mod_name == NULL || mod_name == mod_name_or_unknown);
|
||||||
|
|
||||||
pkgpath = NULL;
|
origin = NULL;
|
||||||
if (PyModule_Check(v)) {
|
if (PyObject_GetOptionalAttr(v, &_Py_ID(__spec__), &spec) < 0) {
|
||||||
pkgpath = PyModule_GetFilenameObject(v);
|
Py_DECREF(mod_name_or_unknown);
|
||||||
if (pkgpath == NULL) {
|
return NULL;
|
||||||
if (!PyErr_ExceptionMatches(PyExc_SystemError)) {
|
|
||||||
Py_DECREF(pkgname_or_unknown);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
// module filename missing
|
|
||||||
_PyErr_Clear(tstate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (pkgpath == NULL || !PyUnicode_Check(pkgpath)) {
|
if (spec == NULL) {
|
||||||
Py_CLEAR(pkgpath);
|
|
||||||
errmsg = PyUnicode_FromFormat(
|
errmsg = PyUnicode_FromFormat(
|
||||||
"cannot import name %R from %R (unknown location)",
|
"cannot import name %R from %R (unknown location)",
|
||||||
name, pkgname_or_unknown
|
name, mod_name_or_unknown
|
||||||
|
);
|
||||||
|
goto done_with_errmsg;
|
||||||
|
}
|
||||||
|
if (_PyModuleSpec_GetFileOrigin(spec, &origin) < 0) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
int is_possibly_shadowing = _PyModule_IsPossiblyShadowing(origin);
|
||||||
|
if (is_possibly_shadowing < 0) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
int is_possibly_shadowing_stdlib = 0;
|
||||||
|
if (is_possibly_shadowing) {
|
||||||
|
PyObject *stdlib_modules = PySys_GetObject("stdlib_module_names");
|
||||||
|
if (stdlib_modules && PyAnySet_Check(stdlib_modules)) {
|
||||||
|
is_possibly_shadowing_stdlib = PySet_Contains(stdlib_modules, mod_name_or_unknown);
|
||||||
|
if (is_possibly_shadowing_stdlib < 0) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_possibly_shadowing_stdlib) {
|
||||||
|
assert(origin);
|
||||||
|
errmsg = PyUnicode_FromFormat(
|
||||||
|
"cannot import name %R from %R "
|
||||||
|
"(consider renaming %R since it has the same "
|
||||||
|
"name as the standard library module named %R "
|
||||||
|
"and prevents importing that standard library module)",
|
||||||
|
name, mod_name_or_unknown, origin, mod_name_or_unknown
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PyObject *spec;
|
int rc = _PyModuleSpec_IsInitializing(spec);
|
||||||
int rc = PyObject_GetOptionalAttr(v, &_Py_ID(__spec__), &spec);
|
|
||||||
if (rc > 0) {
|
|
||||||
rc = _PyModuleSpec_IsInitializing(spec);
|
|
||||||
Py_DECREF(spec);
|
|
||||||
}
|
|
||||||
if (rc < 0) {
|
if (rc < 0) {
|
||||||
Py_DECREF(pkgname_or_unknown);
|
goto done;
|
||||||
Py_DECREF(pkgpath);
|
}
|
||||||
return NULL;
|
else if (rc > 0) {
|
||||||
|
if (is_possibly_shadowing) {
|
||||||
|
assert(origin);
|
||||||
|
// For non-stdlib modules, only mention the possibility of
|
||||||
|
// shadowing if the module is being initialized.
|
||||||
|
errmsg = PyUnicode_FromFormat(
|
||||||
|
"cannot import name %R from %R "
|
||||||
|
"(consider renaming %R if it has the same name "
|
||||||
|
"as a library you intended to import)",
|
||||||
|
name, mod_name_or_unknown, origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (origin) {
|
||||||
|
errmsg = PyUnicode_FromFormat(
|
||||||
|
"cannot import name %R from partially initialized module %R "
|
||||||
|
"(most likely due to a circular import) (%S)",
|
||||||
|
name, mod_name_or_unknown, origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
errmsg = PyUnicode_FromFormat(
|
||||||
|
"cannot import name %R from partially initialized module %R "
|
||||||
|
"(most likely due to a circular import)",
|
||||||
|
name, mod_name_or_unknown
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(rc == 0);
|
||||||
|
if (origin) {
|
||||||
|
errmsg = PyUnicode_FromFormat(
|
||||||
|
"cannot import name %R from %R (%S)",
|
||||||
|
name, mod_name_or_unknown, origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
errmsg = PyUnicode_FromFormat(
|
||||||
|
"cannot import name %R from %R (unknown location)",
|
||||||
|
name, mod_name_or_unknown
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const char *fmt =
|
|
||||||
rc ?
|
|
||||||
"cannot import name %R from partially initialized module %R "
|
|
||||||
"(most likely due to a circular import) (%S)" :
|
|
||||||
"cannot import name %R from %R (%S)";
|
|
||||||
|
|
||||||
errmsg = PyUnicode_FromFormat(fmt, name, pkgname_or_unknown, pkgpath);
|
|
||||||
}
|
}
|
||||||
/* NULL checks for errmsg and pkgname done by PyErr_SetImportError. */
|
|
||||||
_PyErr_SetImportErrorWithNameFrom(errmsg, pkgname, pkgpath, name);
|
|
||||||
|
|
||||||
Py_XDECREF(errmsg);
|
done_with_errmsg:
|
||||||
Py_DECREF(pkgname_or_unknown);
|
/* NULL checks for errmsg, mod_name, origin done by PyErr_SetImportError. */
|
||||||
Py_XDECREF(pkgpath);
|
_PyErr_SetImportErrorWithNameFrom(errmsg, mod_name, origin, name);
|
||||||
|
Py_DECREF(errmsg);
|
||||||
|
|
||||||
|
done:
|
||||||
|
Py_XDECREF(origin);
|
||||||
|
Py_XDECREF(spec);
|
||||||
|
Py_DECREF(mod_name_or_unknown);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3243,5 +3301,3 @@ _PyEval_LoadName(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject *na
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue