str(cm.exception))
def test_script_shadowing_stdlib(self):
- with os_helper.temp_dir() as tmp:
- with open(os.path.join(tmp, "fractions.py"), "w", encoding='utf-8') as f:
- f.write("import fractions\nfractions.Fraction")
-
- 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\)"
+ 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(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(os.path.join(tmp, "fractions.py"), cwd=tmp)
- stdout, stderr = popen.communicate()
- self.assertRegex(stdout, expected_error)
+ popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
- popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp)
- stdout, stderr = popen.communicate()
- self.assertRegex(stdout, expected_error)
+ popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
- popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp)
- stdout, stderr = popen.communicate()
- self.assertRegex(stdout, expected_error)
+ popen = script_helper.spawn_python('-c', 'import fractions', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
- # and there's no error at all when using -P
- popen = script_helper.spawn_python('-P', 'fractions.py', cwd=tmp)
- stdout, stderr = popen.communicate()
- self.assertEqual(stdout, b'')
+ # and there's no error at all when using -P
+ popen = script_helper.spawn_python('-P', 'fractions.py', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertEqual(stdout, b'')
- tmp_child = os.path.join(tmp, "child")
- os.mkdir(tmp_child)
+ tmp_child = os.path.join(tmp, "child")
+ os.mkdir(tmp_child)
- # test the logic with different cwd
- popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp_child)
- stdout, stderr = popen.communicate()
- self.assertRegex(stdout, expected_error)
+ # test the logic with different cwd
+ popen = script_helper.spawn_python(os.path.join(tmp, "fractions.py"), cwd=tmp_child)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
- popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp_child)
- stdout, stderr = popen.communicate()
- self.assertEqual(stdout, b'') # no error
+ popen = script_helper.spawn_python('-m', 'fractions', cwd=tmp_child)
+ stdout, stderr = popen.communicate()
+ 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
+ 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):
- with 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
-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\)"
+ script_errors = [
+ (
+ "fractions.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:
+ 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)
+
+ expected_error = error + (
+ rb" \(consider renaming '.*[\\/]fractions[\\/]+__init__.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(os.path.join(tmp, "main.py"), cwd=tmp)
- stdout, stderr = popen.communicate()
- self.assertRegex(stdout, expected_error)
+ popen = script_helper.spawn_python(os.path.join(tmp, "main.py"), cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
- popen = script_helper.spawn_python('-m', 'main', cwd=tmp)
- stdout, stderr = popen.communicate()
- self.assertRegex(stdout, expected_error)
+ popen = script_helper.spawn_python('-m', 'main', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
- # 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'")
+ # 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):
- with os_helper.temp_dir() as tmp:
- with open(os.path.join(tmp, "numpy.py"), "w", encoding='utf-8') as f:
- f.write("import numpy\nnumpy.array")
-
- expected_error = (
- rb"AttributeError: module 'numpy' has no attribute 'array' "
- rb"\(consider renaming '.*numpy.py' if it has the "
- rb"same name as a third-party module you intended to import\)\s+\Z"
+ script_errors = [
+ (
+ "import numpy\nnumpy.array",
+ rb"AttributeError: module 'numpy' has no attribute 'array'"
+ ),
+ (
+ "from numpy import array",
+ 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)
+
+ expected_error = error + (
+ rb" \(consider renaming '.*numpy.py' if it has the "
+ rb"same name as a library you intended to import\)\s+\Z"
+ )
- popen = script_helper.spawn_python(os.path.join(tmp, "numpy.py"))
- stdout, stderr = popen.communicate()
- self.assertRegex(stdout, expected_error)
+ popen = script_helper.spawn_python(os.path.join(tmp, "numpy.py"))
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
- popen = script_helper.spawn_python('-m', 'numpy', cwd=tmp)
- stdout, stderr = popen.communicate()
- self.assertRegex(stdout, expected_error)
+ popen = script_helper.spawn_python('-m', 'numpy', cwd=tmp)
+ stdout, stderr = popen.communicate()
+ self.assertRegex(stdout, expected_error)
- popen = script_helper.spawn_python('-c', 'import numpy', cwd=tmp)
- stdout, stderr = popen.communicate()
- 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):
with os_helper.temp_dir() as tmp:
expected_error = (
rb"AttributeError: module 'numpy' has no attribute 'attr'\s+\Z"
)
-
popen = script_helper.spawn_python('-c', 'import numpy; numpy.attr', cwd=tmp)
stdout, stderr = popen.communicate()
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):
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")
+
+ # Unhashable str subclass
with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
f.write("""
import fractions
except TypeError as 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)
stdout, stderr = popen.communicate()
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:
f.write("""
import fractions
except AttributeError 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.assertEqual(line, b"module 'fractions' has no attribute 'Fraction'")
+ with open(os.path.join(tmp, "main.py"), "w", encoding='utf-8') as f:
+ f.write("""
+import fractions
+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()
- self.assertEqual(
- stdout.splitlines(),
- [
- b"module 'fractions' has no attribute 'Fraction'",
- b"module 'fractions' has no attribute 'Fraction'",
- b"module 'fractions' has no attribute 'Fraction'",
- ],
- )
+ 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
popen = script_helper.spawn_python("main.py", cwd=tmp)
stdout, stderr = popen.communicate()
- self.assertEqual(
- stdout.splitlines(),
- [
- 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\)"
- )
+ lines = stdout.splitlines()
+ self.assertEqual(len(lines), 2)
+ for line in lines:
+ self.assertEqual(line, b"module 'fractions' has no attribute 'Fraction'")
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")
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)
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
_PyEval_ImportFrom(PyThreadState *tstate, PyObject *v, PyObject *name)
{
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) {
return x;
/* Issue #17636: in case this failed because of a circular relative
import, try to fallback on reading the module directly from
sys.modules. */
- if (PyObject_GetOptionalAttr(v, &_Py_ID(__name__), &pkgname) < 0) {
+ if (PyObject_GetOptionalAttr(v, &_Py_ID(__name__), &mod_name) < 0) {
return NULL;
}
- if (pkgname == NULL || !PyUnicode_Check(pkgname)) {
- Py_CLEAR(pkgname);
+ if (mod_name == NULL || !PyUnicode_Check(mod_name)) {
+ Py_CLEAR(mod_name);
goto error;
}
- fullmodname = PyUnicode_FromFormat("%U.%U", pkgname, name);
+ fullmodname = PyUnicode_FromFormat("%U.%U", mod_name, name);
if (fullmodname == NULL) {
- Py_DECREF(pkgname);
+ Py_DECREF(mod_name);
return NULL;
}
x = PyImport_GetModule(fullmodname);
if (x == NULL && !_PyErr_Occurred(tstate)) {
goto error;
}
- Py_DECREF(pkgname);
+ Py_DECREF(mod_name);
return x;
+
error:
- if (pkgname == NULL) {
- pkgname_or_unknown = PyUnicode_FromString("<unknown module name>");
- if (pkgname_or_unknown == NULL) {
+ if (mod_name == NULL) {
+ mod_name_or_unknown = PyUnicode_FromString("<unknown module name>");
+ if (mod_name_or_unknown == NULL) {
return NULL;
}
} 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;
- if (PyModule_Check(v)) {
- pkgpath = PyModule_GetFilenameObject(v);
- if (pkgpath == NULL) {
- if (!PyErr_ExceptionMatches(PyExc_SystemError)) {
- Py_DECREF(pkgname_or_unknown);
- return NULL;
+ origin = NULL;
+ if (PyObject_GetOptionalAttr(v, &_Py_ID(__spec__), &spec) < 0) {
+ Py_DECREF(mod_name_or_unknown);
+ return NULL;
+ }
+ if (spec == NULL) {
+ errmsg = PyUnicode_FromFormat(
+ "cannot import name %R from %R (unknown location)",
+ 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;
}
- // module filename missing
- _PyErr_Clear(tstate);
}
}
- if (pkgpath == NULL || !PyUnicode_Check(pkgpath)) {
- Py_CLEAR(pkgpath);
+
+ if (is_possibly_shadowing_stdlib) {
+ assert(origin);
errmsg = PyUnicode_FromFormat(
- "cannot import name %R from %R (unknown location)",
- name, pkgname_or_unknown
+ "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 {
- PyObject *spec;
- int rc = PyObject_GetOptionalAttr(v, &_Py_ID(__spec__), &spec);
- if (rc > 0) {
- rc = _PyModuleSpec_IsInitializing(spec);
- Py_DECREF(spec);
- }
+ int rc = _PyModuleSpec_IsInitializing(spec);
if (rc < 0) {
- Py_DECREF(pkgname_or_unknown);
- Py_DECREF(pkgpath);
- return NULL;
+ goto done;
+ }
+ 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);
- Py_DECREF(pkgname_or_unknown);
- Py_XDECREF(pkgpath);
+done_with_errmsg:
+ /* NULL checks for errmsg, mod_name, origin done by PyErr_SetImportError. */
+ _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 value;
}
-
-