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
11 changes: 11 additions & 0 deletions Lib/test/test_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import tempfile
import random
import string
import importlib.machinery
from test import support
import shutil
from test.support import (Error, captured_output, cpython_only, ALWAYS_EQ,
Expand Down Expand Up @@ -5194,6 +5195,16 @@ def test_windows_only_module_error(self):
else:
self.fail("ModuleNotFoundError was not raised")

def test_find_incompatible_extension_modules(self):
"""_find_incompatible_extension_modules assumes the last extension in
importlib.machinery.EXTENSION_SUFFIXES (defined in Python/dynload_*.c)
is untagged (eg. .so, .pyd).

This test exists to make sure that assumption is correct.
"""
if importlib.machinery.EXTENSION_SUFFIXES:
self.assertEqual(len(importlib.machinery.EXTENSION_SUFFIXES[-1].split('.')), 2)


class TestColorizedTraceback(unittest.TestCase):
maxDiff = None
Expand Down
31 changes: 31 additions & 0 deletions Lib/traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import collections.abc
import itertools
import linecache
import os
import sys
import textwrap
import types
Expand Down Expand Up @@ -1129,6 +1130,11 @@ def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None,
self._str += (". Site initialization is disabled, did you forget to "
+ "add the site-packages directory to sys.path "
+ "or to enable your virtual environment?")
elif abi_tag := _find_incompatible_extension_module(module_name):
self._str += (
". Although a module with this name was found for a "
f"different Python version ({abi_tag})."
)
else:
suggestion = _compute_suggestion_error(exc_value, exc_traceback, module_name)
if suggestion:
Expand Down Expand Up @@ -1880,3 +1886,28 @@ def _levenshtein_distance(a, b, max_cost):
# Everything in this row is too big, so bail early.
return max_cost + 1
return result


def _find_incompatible_extension_module(module_name):
import importlib.machinery
import importlib.resources.readers

if not module_name 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]

parent, _, child = module_name.rpartition('.')
if parent:
traversable = importlib.resources.files(parent)
else:
traversable = importlib.resources.readers.MultiplexedPath(
*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.removeprefix(child + '.').removesuffix(untagged_suffix)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :exc:`ModuleNotFoundError` hints when a module for a different ABI
exists.
Loading