Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -5456,5 +5456,17 @@ def test_suggestion_still_works_for_non_lazy_attributes(self):
self.assertNotIn(b"BAR_MODULE_LOADED", stdout)


class TestNoCrashInTracebackException(unittest.TestCase):
def test_module_not_found_error_with_bad_name(self):
exc = ModuleNotFoundError(name=NotImplemented)
try:
te = traceback.TracebackException.from_exception(exc)
except Exception as e:
self.fail(f"TracebackException raised unexpected exception: {e!r}")
else:
msg = "".join(te.format())
self.assertEqual(msg, "ModuleNotFoundError\n")


if __name__ == "__main__":
unittest.main()
37 changes: 20 additions & 17 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -1907,26 +1907,29 @@ def _find_incompatible_extension_module(module_name):
import importlib.machinery
import importlib.resources.readers

if not module_name or not importlib.machinery.EXTENSION_SUFFIXES:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should keep not module_name test.

if not isinstance(module_name, str) or not importlib.machinery.EXTENSION_SUFFIXES:
return

# We assume the last extension is untagged (eg. .so, .pyd)!
# tests.test_traceback.MiscTest.test_find_incompatible_extension_modules
# tests that assumption.
untagged_suffix = importlib.machinery.EXTENSION_SUFFIXES[-1]
# On Windows the debug tag is part of the module file stem, instead of the
# extension (eg. foo_d.pyd), so let's remove it and just look for .pyd.
if os.name == 'nt':
untagged_suffix = untagged_suffix.removeprefix('_d')

parent, _, child = module_name.rpartition('.')
if parent:
traversable = importlib.resources.files(parent)
else:
traversable = importlib.resources.readers.MultiplexedPath(
*map(pathlib.Path, filter(os.path.isdir, sys.path))
)
try:
untagged_suffix = importlib.machinery.EXTENSION_SUFFIXES[-1]
# On Windows the debug tag is part of the module file stem, instead of the
# extension (eg. foo_d.pyd), so let's remove it and just look for .pyd.
if os.name == 'nt':
untagged_suffix = untagged_suffix.removeprefix('_d')

parent, _, child = module_name.rpartition('.')
if parent:
traversable = importlib.resources.files(parent)
else:
traversable = importlib.resources.readers.MultiplexedPath(
*map(pathlib.Path, filter(os.path.isdir, sys.path))
)

for entry in traversable.iterdir():
if entry.name.startswith(child + '.') and entry.name.endswith(untagged_suffix):
return entry.name
for entry in traversable.iterdir():
if entry.name.startswith(child + '.') and entry.name.endswith(untagged_suffix):
return entry.name
except Exception:
return
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignoring silently exceptions sounds like a bad pattern. If you expect specific exceptions, use more precise except.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is impossible to use precise except. It is in handling an exception, where raising another exception will cause the crash.

Copy link
Copy Markdown
Member

@picnixz picnixz Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I sugegst that we let the exception propagate. If this code path fails it's something we want to know.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add type check and use try-except to fix traceback crash
Loading