Now it only supported for the import from normal way(only from sys.path). The custom way to import is uncertain.
Welcome to share your idea if you have. If there is a way to handle with it, I will feel good.
Now it only supported for the import from normal way(only from sys.path). The custom way to import is uncertain.
Welcome to share your idea if you have. If there is a way to handle with it, I will feel good.
There isnât a straightforward way to comprehensively handle arbitrary import hooks in the general case, which is why youâre not hearing a lot of enthusiasm from core devs for the idea.
âIn the face of ambiguity, refuse the temptation to guessâ definitely applies when it comes to comparing attempted imports to a full installation environment where all sorts of import system customisation might be going on (vs the more limited activity of comparing attempted imports to the relatively small and well known set of standard library module names).
Itâs a fine idea as a third party project though, as folks can consider whether its suitable for their personal development environments before decided whether they want to make it part of their development workflow.
(I personally wouldnât use it, as for me it fits in a niche that is already covered by VS Codeâs static code analysis, but I can see the potential utility in some other use cases)
So how to define the way in this project? It only scans all of the path in sys.path to get the reachable module. The other ways are ignore. But it is not only compare with the stdlib (although it first).
Thatâs up to you, based on your goals for the project. The point Iâm making is that thereâs no way of doing this that Iâd consider acceptable as a core feature. Third party projects have a lot more room for flexibility here (if you donât support import hooks, people who care can simply ignore your project).
If I say that I have an ambition that finally make the project as a new python feature, what can I do?
Come up with good answers to peopleâs questions. Unfortunately, youâre going to have to work on it, particularly if itâs important to you.
Work on your PyPI library and aim to have it handle package discovery in increasingly more complex environments.
What youâre proposing isnât fundamentally impossible, since static code analysers do this kind of thing when they complain they canât find an import target in the code theyâre analysing and offer suggestions for other lexically close names that are importable.
Instead, the concern is that solving the general case is sufficiently complex that it isnât something we want to maintain as part of the core import system (since thatâs already spectacularly complicated, weâre not exactly keen to incur significant additional complexity for the sake of improving error messages in a case that external development tools can already help with).
Static analysers also have the # noqa escape hatch available for situations where the code is doing something that the import system can handle but the static analyser canât. A runtime error doesnât have that additional level of configurability, so in trying to be more helpful, it could end up steering users in the wrong direction entirely - a bad hint can easily end up being worse than offering no hint at all.
In implementing the proposal as a third party package, there are a few potential outcomes:
Several days ago, I make an idea in the third party modules that improve and suggest name in ModuleNotFoundError(import xxx.yyy.zzz no longer just âNo module named âxxx.yyy.zzzââ, instead, use different message in different conditions such as âNo module named âxxxââ or âmodule âxxxâ has no child module âyyyââ, and give possible suggestion if it has).
Now with the module âfriendly_module_not_found_errorâ version 0.1.3 published, it also supports for custom import hook, but it needs a magic method â__find__â. It can return all of the top module that the hook can import if no argument given and all of the submodule of the module named given argument ânameâ.
Now I need for more feedback for it. Now the mainly changed in traceback here:
def _compute_suggestion_error(exc_value, tb, wrong_name):
if wrong_name is None or not isinstance(wrong_name, str):
return None
if isinstance(exc_value, AttributeError):
obj = exc_value.obj
try:
try:
d = dir(obj)
except TypeError: # Attributes are unsortable, e.g. int and str
d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
d = sorted([x for x in d if isinstance(x, str)])
hide_underscored = (wrong_name[:1] != '_')
if hide_underscored and tb is not None:
while tb.tb_next is not None:
tb = tb.tb_next
frame = tb.tb_frame
if 'self' in frame.f_locals and frame.f_locals['self'] is obj:
hide_underscored = False
if hide_underscored:
d = [x for x in d if x[:1] != '_']
except Exception:
return None
elif isinstance(exc_value, ImportError):
try:
mod = __import__(exc_value.name)
try:
d = dir(mod)
except TypeError: # Attributes are unsortable, e.g. int and str
d = list(mod.__dict__.keys())
d = sorted([x for x in d if isinstance(x, str)])
if wrong_name[:1] != '_':
d = [x for x in d if x[:1] != '_']
except Exception:
return _suggest_for_module(exc_value) # - return None
else:
assert isinstance(exc_value, NameError)
# find most recent frame
if tb is None:
return None
while tb.tb_next is not None:
tb = tb.tb_next
frame = tb.tb_frame
d = (
list(frame.f_locals)
+ list(frame.f_globals)
+ list(frame.f_builtins)
)
d = [x for x in d if isinstance(x, str)]
# Check first if we are in a method and the instance
# has the wrong name as attribute
if 'self' in frame.f_locals:
self = frame.f_locals['self']
try:
has_wrong_name = hasattr(self, wrong_name)
except Exception:
has_wrong_name = False
if has_wrong_name:
return f"self.{wrong_name}"
return _calculate_closed_name(wrong_name, d) # change to the standalone function
def _calculate_closed_name(wrong_name, d):
try:
d.sort()
except:
pass
try:
import _suggestions
return _suggestions._generate_suggestions(d, wrong_name)
except ImportError:
if len(d) > _MAX_CANDIDATE_ITEMS:
return None
wrong_name_len = len(wrong_name)
if wrong_name_len > _MAX_STRING_SIZE:
return None
best_distance = wrong_name_len
suggestion = None
d.sort()
for possible_name in d:
if possible_name == wrong_name:
# A missing attribute is "found". Don't suggest it (see GH-88821).
continue
# No more than 1/3 of the involved characters should need changed.
max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6
# Don't take matches we've already beaten.
max_distance = min(max_distance, best_distance - 1)
current_distance = _levenshtein_distance(wrong_name, possible_name, max_distance)
if current_distance > max_distance:
continue
if not suggestion or current_distance < best_distance:
suggestion = possible_name
best_distance = current_distance
return suggestion
def _suggest_for_module(exc_value):
import sys
import os
from importlib import machinery
def scan_dir(path):
"""
Return all of the packages in the path without import
containsïŒ
- .py file
- directory with "__init__.py"
- the .pyd/so file that has right ABI
"""
if not os.path.isdir(path):
return []
suffixes = machinery.EXTENSION_SUFFIXES
result = []
for name in os.listdir(path):
full_path = os.path.join(path, name)
# .py file
if name.endswith(".py") and os.path.isfile(full_path):
modname = name[:-3]
if modname.isidentifier():
result.append(modname)
# directory with "__init__.py"
elif os.path.isdir(full_path):
init_file = os.path.join(full_path, "__init__.py")
if os.path.isfile(init_file) and name.isidentifier():
result.append(name)
# the .pyd/so file that has right ABI
elif os.path.isfile(full_path):
for suf in suffixes:
if name.endswith(suf):
modname = name[:-len(suf)]
if modname.isidentifier():
result.append(modname)
break
return sorted(result)
def find_all_packages():
list_d = []
for hook in sys.meta_path:
try:
func = getattr(hook, "__find__", None)
if callable(func):
list_d.append(func())
except:
list_d.append([])
for i in sys.path:
if isinstance(i, str) and not i.endswith("idlelib"):
list_d.append(scan_dir(i))
list_d.append(sorted(sys.builtin_module_names))
return list_d
def compare_top_module(module_name):
result = _calculate_closed_name(module_name, sorted(sys.stdlib_module_names))
if result:
return result
other_result_list = []
for i in list_d:
result = _calculate_closed_name(module_name, i)
if result:
return result
def handle_hook_module(name, i):
"""
```
def __find__(self, name: str=None) -> List[str]:
return []
```
`__find__` method should return a list about the modules without import them.
when name is not None, it should return the submodule below it.
"""
for i in sys.meta_path:
try:
func = getattr(i, "__find__", None)
if callable(func):
list_d = sorted(func(name))
if i in list_d:
exc_value.msg += ", but it appear in the result from '__find__'. Is your code wrong?"
return i
result = _calculate_closed_name(i, list_d)
if result:
return result
except:
continue
def handle_hook_wrong_module(hook, wrong_name_list):
module_name = wrong_name_list[0]
for i in wrong_name_list[1:]:
exc_value.msg = f"module '{module_name}' has no child module '{i}'"
try:
func = getattr(hook, "__find__", None)
if callable(func):
list_d = sorted(func(module_name))
if i not in list_d:
return _calculate_closed_name(i, list_d)
except:
return
module_name += "." + i
exc_value.msg += ", but it appear in the final result from '__find__'. Is your code wrong?"
if not isinstance(exc_value, ModuleNotFoundError):
return
list_d = find_all_packages()
_module_name = exc_value.name
wrong_name_list = _module_name.split(".")
module_name = wrong_name_list[0]
if module_name in sys.modules:
for i in wrong_name_list[1:]:
original_module_name = module_name
module_name += "." + i
if module_name in sys.modules:
continue
exc_value.msg = f"module '{original_module_name}' has no child module '{i}'"
if hasattr(sys.modules[original_module_name], "__path__"):
d=[]
for ii in sys.modules[original_module_name].__path__:
d += scan_dir(ii)
wrong_name = i
return _calculate_closed_name(wrong_name, d)
else:
result = handle_hook_module(original_module_name, i)
if result:
if result == i:
return
return result
exc_value.msg += f"; '{original_module_name}' is not a package"
return
else:
if len(wrong_name_list) == 1 or module_name not in sum(list_d, []):
return compare_top_module(module_name)
else:
for i in sys.meta_path:
try:
func = getattr(i, "__find__", None)
if callable(func):
if module_name in func():
return handle_hook_wrong_module(i, wrong_name_list)
except:
continue
for i in sys.path:
if module_name in scan_dir(i):
module_path = f"{i}/{module_name}"
break
else:
return compare_top_module(module_name)
for i in wrong_name_list[1:]:
if not os.path.exists(module_path) or not os.path.isdir(module_path):
exc_value.msg = f"module '{module_name}' has no child module '{i}'; '{module_name}' is not a package"
return
original_module_name = module_name
module_name += "." + i
d = scan_dir(module_path)
if i not in scan_dir(module_path):
exc_value.msg = f"module '{original_module_name}' has no child module '{i}'"
return _calculate_closed_name(i, d)
module_path += f"/{i}"
With update the project from time to time, now the suggest for ModuleNotFoundError has been mature. Now the mainly code below:
def _compute_suggestion_error(exc_value, tb, wrong_name):
if wrong_name is None or not isinstance(wrong_name, str):
return None
if isinstance(exc_value, AttributeError):
obj = exc_value.obj
try:
try:
d = dir(obj)
except TypeError: # Attributes are unsortable, e.g. int and str
d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
d = sorted([x for x in d if isinstance(x, str)])
hide_underscored = (wrong_name[:1] != '_')
if hide_underscored and tb is not None:
while tb.tb_next is not None:
tb = tb.tb_next
frame = tb.tb_frame
if 'self' in frame.f_locals and frame.f_locals['self'] is obj:
hide_underscored = False
if hide_underscored:
d = [x for x in d if x[:1] != '_']
except Exception:
return None
elif isinstance(exc_value, ImportError):
try:
mod = __import__(exc_value.name)
try:
d = dir(mod)
except TypeError: # Attributes are unsortable, e.g. int and str
d = list(mod.__dict__.keys())
d = sorted([x for x in d if isinstance(x, str)])
if wrong_name[:1] != '_':
d = [x for x in d if x[:1] != '_']
except Exception:
return _suggest_for_module(exc_value)
else:
assert isinstance(exc_value, NameError)
# find most recent frame
if tb is None:
return None
while tb.tb_next is not None:
tb = tb.tb_next
frame = tb.tb_frame
d = (
list(frame.f_locals)
+ list(frame.f_globals)
+ list(frame.f_builtins)
)
d = [x for x in d if isinstance(x, str)]
# Check first if we are in a method and the instance
# has the wrong name as attribute
if 'self' in frame.f_locals:
self = frame.f_locals['self']
try:
has_wrong_name = hasattr(self, wrong_name)
except Exception:
has_wrong_name = False
if has_wrong_name:
return f"self.{wrong_name}"
return _calculate_closed_name(wrong_name, d)
def _calculate_closed_name(wrong_name, d):
try:
d.sort()
except:
pass
try:
import _suggestions
return _suggestions._generate_suggestions(d, wrong_name)
except ImportError:
if len(d) > _MAX_CANDIDATE_ITEMS:
return None
wrong_name_len = len(wrong_name)
if wrong_name_len > _MAX_STRING_SIZE:
return None
best_distance = wrong_name_len
suggestion = None
d.sort()
for possible_name in d:
if possible_name == wrong_name:
# A missing attribute is "found". Don't suggest it (see GH-88821).
continue
# No more than 1/3 of the involved characters should need changed.
max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6
# Don't take matches we've already beaten.
max_distance = min(max_distance, best_distance - 1)
current_distance = _levenshtein_distance(wrong_name, possible_name, max_distance)
if current_distance > max_distance:
continue
if not suggestion or current_distance < best_distance:
suggestion = possible_name
best_distance = current_distance
return suggestion
def _suggest_for_module(exc_value):
import sys
import os
from importlib import machinery
def scan_dir(path):
"""
Return all of the packages in the path without import
containsïŒ
- .py file
- directory with "__init__.py"
- the .pyd/so file that has right ABI
"""
if not os.path.isdir(path):
return []
suffixes = machinery.EXTENSION_SUFFIXES
result = []
for name in os.listdir(path):
full_path = os.path.join(path, name)
# .py file
if name.endswith(".py") and os.path.isfile(full_path):
modname = name[:-3]
if modname.isidentifier():
result.append(modname)
# directory with "__init__.py"
elif os.path.isdir(full_path):
init_file = os.path.join(full_path, "__init__.py")
if os.path.isfile(init_file) and name.isidentifier():
result.append(name)
# the .pyd/so file that has right ABI
elif os.path.isfile(full_path):
for suf in suffixes:
if name.endswith(suf):
modname = name[:-len(suf)]
if modname.isidentifier():
result.append(modname)
break
return sorted(result)
def find_all_packages():
return [scan_dir(i) if isinstance(i, str) and
not i.endswith("idlelib") else []
for i in sys.path] + [sorted(sys.builtin_module_names)]
def compare_top_module(module_name):
result = _calculate_closed_name(module_name, sorted(sys.stdlib_module_names))
if result:
return result
other_result_list = []
for i in list_d:
result = _calculate_closed_name(module_name, i)
if result:
other_result_list.append(result)
if other_result_list:
return other_result_list[0]
else:
return
def handle_wrong_module(module_name, path, child_module_list):
for i in child_module_list:
exc_value.msg = f"module '{module_name}' has no child module '{i}'"
if not os.path.exists(path) or not os.path.isdir(path):
exc_value.msg += f"; {module_name} is not a package"
return
list_d = scan_dir(path)
if i not in list_d:
return _calculate_closed_name(i, list_d)
path = os.path.join(path, i)
module_name += f".{i}"
if not isinstance(exc_value, ModuleNotFoundError):
return
list_d = find_all_packages()
_module_name = exc_value.name
wrong_name_list = _module_name.split(".")
module_name = wrong_name_list[0]
if module_name in sys.modules:
wrong_name_copy = wrong_name_list[1:]
for i in wrong_name_list[1:]:
original_module_name = module_name
module_name += "." + i
wrong_name_copy.pop(0)
if module_name in sys.modules:
continue
exc_value.msg = f"module '{original_module_name}' has no child module '{i}'"
if hasattr(sys.modules[original_module_name], "__path__"):
d=[]
for ii in sys.modules[original_module_name].__path__:
list_path = scan_dir(ii)
if i in list_path:
return handle_wrong_module(module_name, os.path.join(ii, i), wrong_name_copy)
d += list_path
wrong_name = i
return _calculate_closed_name(wrong_name, d)
else:
exc_value.msg += f"; '{original_module_name}' is not a package"
return
else:
if len(wrong_name_list) == 1 or module_name not in sum(list_d, []):
return compare_top_module(module_name)
else:
for i in sys.path:
if module_name in scan_dir(i):
module_path = f"{i}/{module_name}"
break
else:
return compare_top_module(module_name)
return handle_wrong_module(module_name, module_path, wrong_name_list[1:])
Is the version that able to PR to python?
This code will change the message of ModuleNotFoundError and make it more friendly: where the module name wrong and give the suggestion on it.
More detail here.
Does it address the issues raised above with custom import hooks? If not, Iâd say the answer is ânoâ.
Now the third-party version does, but it need a new method â__find__â that defined by users so I worry about that whether that is suitable for PR to python.
If it involves changes to the import machinery, I think it needs a PEP.
In fact. If it needs to support for custom import hooks, the PEP is needed. However, in my original plan, the supporting for custom import hooks is site-package-version only.
Here is the fake code in module âimportlib.abcâ if the suggestion should be given for that condition:
class MetaPathFinder(metaclass=abc.ABCMeta):
"""Abstract base class for import finders on sys.meta_path."""
# We don't define find_spec() here since that would break
# hasattr checks we do to support backward compatibility.
def invalidate_caches(self):
"""An optional method for clearing the finder's cache, if any.
This method is used by importlib.invalidate_caches().
"""
def __find__(self, name: str=None) -> list[str]:
"""An agreement for suggest the module name when ModuleNotFoundError raise
This method should return all of the submodules under the module given.
If name is None, it return all of the top modules.
This method shouldn't import any modules.
"""
return []
_register(MetaPathFinder, machinery.BuiltinImporter, machinery.FrozenImporter,
machinery.PathFinder, machinery.WindowsRegistryFinder)
The method â__find__â is the new method.
If the name of the method shouldnât be the dunder, the new name should be hardly found in various projects now, so that we can minimize the comflicts.
Now I think that if we can change the message and the attribute from âimportlibâ, the code might be better. However, the change will be too large because it changes the attribute name of âModuleNotFoundErrorâ.
Here is step 2 code (Now test in sitecustomize.py):
import importlib._bootstrap
import importlib._bootstrap_external
import _imp
import os
import sys
import traceback
def scan_dir(path):
"""
Return all of the packages in the path without import
containsïŒ
- .py file
- directory with "__init__.py"
- the .pyd/so file that has right ABI
"""
if not os.path.isdir(path):
return []
suffixes = _imp.extension_suffixes()
result = []
for name in os.listdir(path):
full_path = os.path.join(path, name)
# .py file
if name.endswith(".py") and os.path.isfile(full_path):
modname = name[:-3]
if modname.isidentifier():
result.append(modname)
# directory with "__init__.py"
elif os.path.isdir(full_path):
init_file = os.path.join(full_path, "__init__.py")
if os.path.isfile(init_file) and name.isidentifier():
result.append(name)
# the .pyd/so file that has right ABI
elif os.path.isfile(full_path):
for suf in suffixes:
if name.endswith(suf):
modname = name[:-len(suf)]
if modname.isidentifier():
result.append(modname)
break
return sorted(result)
_ERR_MSG_PREFIX = 'No module named '
_CHILD_ERR_MSG = 'module {!r} has no child module {!r}'
_ERR_MSG = _ERR_MSG_PREFIX + '{!r}'
def _find_and_load_unlocked(name, import_):
path = None
parent, _, child = name.rpartition('.')
parent_spec = None
if parent:
if parent not in sys.modules:
importlib._bootstrap._call_with_frames_removed(import_, parent)
# Crazy side-effects!
if name in sys.modules:
return sys.modules[name]
parent_module = sys.modules[parent]
try:
path = parent_module.__path__
except AttributeError:
msg = _CHILD_ERR_MSG.format(parent, child) + f'; {parent!r} is not a package'
raise ModuleNotFoundError(msg, name=child) from None
parent_spec = parent_module.__spec__
spec = importlib._bootstrap._find_spec(name, path)
if spec is None:
if not parent:
msg = f'{_ERR_MSG_PREFIX}{name!r}'
else:
msg = _CHILD_ERR_MSG.format(parent, child)
error = ModuleNotFoundError(msg, name=child)
suggest_list = []
for i in sys.meta_path:
func = getattr(i, '__find__', None)
if callable(func):
list_d = func(parent)
if list_d:
suggest_list.append(list_d)
if not parent:
for paths in sys.path:
suggest_list.append(scan_dir(paths))
else:
suggest_list.append(find_in_path(parent))
error._suggestion = suggest_list
raise error
else:
if parent_spec:
# Temporarily add child we are currently importing to parent's
# _uninitialized_submodules for circular import tracking.
parent_spec._uninitialized_submodules.append(child)
try:
module = importlib._bootstrap._load_unlocked(spec)
finally:
if parent_spec:
parent_spec._uninitialized_submodules.pop()
if parent:
# Set the module as an attribute on its parent.
parent_module = sys.modules[parent]
try:
setattr(parent_module, child, module)
except AttributeError:
msg = f"Cannot set an attribute on {parent!r} for child module {child!r}"
importlib._bootstrap._warnings.warn(msg, ImportWarning)
return module
importlib._bootstrap._find_and_load_unlocked = _find_and_load_unlocked
importlib._bootstrap.BuiltinImporter.__find__ = staticmethod(lambda name=None: (sorted(sys.builtin_module_names) if not name else []))
def find_in_path(name=None):
if not name:
return []
name_list = name.split(".")
for i in sys.path:
list_d = scan_dir(i)
if name_list[0] in list_d:
break
else:
return []
path = i
for j in name_list:
path = os.path.join(path, j)
if not os.path.isdir(path):
return []
if not os.path.exists(os.path.join(path, "__init__.py")):
return []
return scan_dir(path)
def _compute_suggestion_error(exc_value, tb, wrong_name):
if wrong_name is None or not isinstance(wrong_name, str):
return None
if isinstance(exc_value, AttributeError):
obj = exc_value.obj
try:
try:
d = dir(obj)
except TypeError: # Attributes are unsortable, e.g. int and str
d = list(obj.__class__.__dict__.keys()) + list(obj.__dict__.keys())
d = sorted([x for x in d if isinstance(x, str)])
hide_underscored = (wrong_name[:1] != '_')
if hide_underscored and tb is not None:
while tb.tb_next is not None:
tb = tb.tb_next
frame = tb.tb_frame
if 'self' in frame.f_locals and frame.f_locals['self'] is obj:
hide_underscored = False
if hide_underscored:
d = [x for x in d if x[:1] != '_']
except Exception:
return _handle_module(exc_value)
elif isinstance(exc_value, ImportError):
if isinstance(exc_value, ModuleNotFoundError):
return _handle_module(exc_value)
try:
mod = __import__(exc_value.name)
try:
d = dir(mod)
except TypeError: # Attributes are unsortable, e.g. int and str
d = list(mod.__dict__.keys())
d = sorted([x for x in d if isinstance(x, str)])
if wrong_name[:1] != '_':
d = [x for x in d if x[:1] != '_']
except Exception:
return None
else:
assert isinstance(exc_value, NameError)
# find most recent frame
if tb is None:
return None
while tb.tb_next is not None:
tb = tb.tb_next
frame = tb.tb_frame
d = (
list(frame.f_locals)
+ list(frame.f_globals)
+ list(frame.f_builtins)
)
d = [x for x in d if isinstance(x, str)]
# Check first if we are in a method and the instance
# has the wrong name as attribute
if 'self' in frame.f_locals:
self = frame.f_locals['self']
try:
has_wrong_name = hasattr(self, wrong_name)
except Exception:
has_wrong_name = False
if has_wrong_name:
return f"self.{wrong_name}"
return _calculate_closed_name(wrong_name, d)
def _handle_module(exc_value):
if not isinstance(exc_value, ModuleNotFoundError):
return
if len(exc_value.name) > traceback._MAX_STRING_SIZE:
return
all_result = []
for i in getattr(exc_value, "_suggestion", []):
if exc_value.name in i:
return exc_value.name
result = _calculate_closed_name(exc_value.name, i)
if result:
all_result.append(result)
return _calculate_closed_name(exc_value.name, sorted(all_result))
def _calculate_closed_name(wrong_name, d):
try:
import _suggestions
except ImportError:
pass
else:
return _suggestions._generate_suggestions(d, wrong_name)
# Compute closest match
if len(d) > traceback._MAX_CANDIDATE_ITEMS:
return None
wrong_name_len = len(wrong_name)
if wrong_name_len > traceback._MAX_STRING_SIZE:
return None
best_distance = wrong_name_len
suggestion = None
for possible_name in d:
if possible_name == wrong_name:
# A missing attribute is "found". Don't suggest it (see GH-88821).
continue
# No more than 1/3 of the involved characters should need changed.
max_distance = (len(possible_name) + wrong_name_len + 3) * _MOVE_COST // 6
# Don't take matches we've already beaten.
max_distance = min(max_distance, best_distance - 1)
current_distance = traceback._levenshtein_distance(wrong_name, possible_name, max_distance)
if current_distance > max_distance:
continue
if not suggestion or current_distance < best_distance:
suggestion = possible_name
best_distance = current_distance
return suggestion
traceback._compute_suggestion_error = _compute_suggestion_error
def new_init(self, exc_type, exc_value, exc_traceback, *, limit=None,
lookup_lines=True, capture_locals=False, compact=False,
max_group_width=15, max_group_depth=10, save_exc_type=True, _seen=None):
# NB: we need to accept exc_traceback, exc_value, exc_traceback to
# permit backwards compat with the existing API, otherwise we
# need stub thunk objects just to glue it together.
# Handle loops in __cause__ or __context__.
is_recursive_call = _seen is not None
if _seen is None:
_seen = set()
_seen.add(id(exc_value))
self.max_group_width = max_group_width
self.max_group_depth = max_group_depth
self.stack = traceback.StackSummary._extract_from_extended_frame_gen(
traceback._walk_tb_with_full_positions(exc_traceback),
limit=limit, lookup_lines=lookup_lines,
capture_locals=capture_locals)
self._exc_type = exc_type if save_exc_type else None
# Capture now to permit freeing resources: only complication is in the
# unofficial API _format_final_exc_line
self._str = traceback._safe_string(exc_value, 'exception')
try:
self.__notes__ = getattr(exc_value, '__notes__', None)
except Exception as e:
self.__notes__ = [
f'Ignored error getting __notes__: {_safe_string(e, '__notes__', repr)}']
self._is_syntax_error = False
self._have_exc_type = exc_type is not None
if exc_type is not None:
self.exc_type_qualname = exc_type.__qualname__
self.exc_type_module = exc_type.__module__
else:
self.exc_type_qualname = None
self.exc_type_module = None
if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially
self.filename = exc_value.filename
lno = exc_value.lineno
self.lineno = str(lno) if lno is not None else None
end_lno = exc_value.end_lineno
self.end_lineno = str(end_lno) if end_lno is not None else None
self.text = exc_value.text
self.offset = exc_value.offset
self.end_offset = exc_value.end_offset
self.msg = exc_value.msg
self._is_syntax_error = True
elif exc_type and issubclass(exc_type, ImportError) and \
getattr(exc_value, "name_from", None) is not None:
wrong_name = getattr(exc_value, "name_from", None)
suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
if suggestion:
self._str += f". Did you mean: '{suggestion}'?"
elif exc_type and issubclass(exc_type, ModuleNotFoundError) and \
getattr(exc_value, "name", None) and getattr(exc_value, "_suggestion", []):
wrong_name = getattr(exc_value, "name", None)
suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
if suggestion == wrong_name:
self._str += ", but it appear in the final result from '__find__'. Is your code wrong?"
elif suggestion:
self._str += f". Did you mean: '{suggestion}'?"
elif exc_type and issubclass(exc_type, (NameError, AttributeError)) and \
getattr(exc_value, "name", None) is not None:
wrong_name = getattr(exc_value, "name", None)
suggestion = _compute_suggestion_error(exc_value, exc_traceback, wrong_name)
if suggestion:
self._str += f". Did you mean: '{suggestion}'?"
if issubclass(exc_type, NameError):
wrong_name = getattr(exc_value, "name", None)
if wrong_name is not None and wrong_name in sys.stdlib_module_names:
if suggestion:
self._str += f" Or did you forget to import '{wrong_name}'?"
else:
self._str += f". Did you forget to import '{wrong_name}'?"
if lookup_lines:
self._load_lines()
self.__suppress_context__ = \
exc_value.__suppress_context__ if exc_value is not None else False
# Convert __cause__ and __context__ to `TracebackExceptions`s, use a
# queue to avoid recursion (only the top-level call gets _seen == None)
if not is_recursive_call:
queue = [(self, exc_value)]
while queue:
te, e = queue.pop()
if (e is not None and e.__cause__ is not None
and id(e.__cause__) not in _seen):
cause = traceback.TracebackException(
type(e.__cause__),
e.__cause__,
e.__cause__.__traceback__,
limit=limit,
lookup_lines=lookup_lines,
capture_locals=capture_locals,
max_group_width=max_group_width,
max_group_depth=max_group_depth,
_seen=_seen)
else:
cause = None
if compact:
need_context = (cause is None and
e is not None and
not e.__suppress_context__)
else:
need_context = True
if (e is not None and e.__context__ is not None
and need_context and id(e.__context__) not in _seen):
context = traceback.TracebackException(
type(e.__context__),
e.__context__,
e.__context__.__traceback__,
limit=limit,
lookup_lines=lookup_lines,
capture_locals=capture_locals,
max_group_width=max_group_width,
max_group_depth=max_group_depth,
_seen=_seen)
else:
context = None
if e is not None and isinstance(e, BaseExceptionGroup):
exceptions = []
for exc in e.exceptions:
texc = traceback.TracebackException(
type(exc),
exc,
exc.__traceback__,
limit=limit,
lookup_lines=lookup_lines,
capture_locals=capture_locals,
max_group_width=max_group_width,
max_group_depth=max_group_depth,
_seen=_seen)
exceptions.append(texc)
else:
exceptions = None
te.__cause__ = cause
te.__context__ = context
te.exceptions = exceptions
if cause:
queue.append((te.__cause__, e.__cause__))
if context:
queue.append((te.__context__, e.__context__))
if exceptions:
queue.extend(zip(te.exceptions, e.exceptions))
traceback.TracebackException.__init__ = new_init
It can pass the test at the module. However, it will change the ModuleNotFoundError object so it need a PEP
Iâm no longer sure I understand the purpose of this proposal. It seems like quite a lot of messy and potentially unreliable[1] code, and needs a change to the importer protocols. And this is all just to give a suggestion along the lines of âdid you mean Xâ when an import fails?
If we already had the information available, Iâd be supportive of letting the user know. But we donât. Also, this is the sort of thing where the user would encounter the issue, fix the problem, and never see it again, so thereâs not a lot of ongoing benefit here. And in any case, many IDEs offer tooltips and help to pick the right import name, so weâre duplicating what they do.
I personally donât think itâs worth it.
if the number of iterations that have been needed so far to cover various edge cases is anything to go by â©ïž
Well, I test for pycharm and vscode. When the code was inputed by yourself from letter to letter, they suggested for it. However, if the code was from ctrl+CV(the old code from online), they didnât. For example, the pycharm did it better only because it told you where is wrong in the module, the vscode wrosely told about the whole module name.
If there is a third party plugin please tell me.
The number of iteration is only by users. If they make the module name long and many submodule in the import, install too many modules or add too many paths in sys.path. However, they are abnormal behavior.
The normal module can provide the name that in subsubsubsubâŠmodule, user donât need to import the submodule like that. The storage space will forbidden user to install too many modules and before it the device has been nearly unusable. Python has its perfect packages manager system so that adding too many path to sys.path seems to be unnecessary.
Today I had tested for whether the IDE can suggest for that, but it became worse: they even nolonger analyses the fault in import in the old file. So that I think that I didnât see what you say that âmany IDEs offer tooltips and help to pick the right import nameâ .
To prove that it is wrong, I ask for ChatGPT to give me a code:
import asyncio
from web3 import AsyncWeb3
from web3.providers.async_rpc import AsyncHTTPProvider
async def main():
# ćć»șćŒæ„ Web3 ćźąæ·ç«Ż
w3 = AsyncWeb3(AsyncHTTPProvider("https://mainnet.infura.io/v3/YOUR_PROJECT_ID"))
# äŸćŠïŒè·ććœććșćé«ćșŠ
block_number = await w3.eth.block_number
print("Latest block:", block_number)
# è·ćæäžȘćșć俥æŻ
block = await w3.eth.get_block(block_number)
print("Block info:", block)
# è·ć莊æ·äœéą
balance = await w3.eth.get_balance("0x00000000219ab540356cBB839Cbe05303d7705Fa")
print("Balance:", w3.from_wei(balance, "ether"))
asyncio.run(main())
In fact the import is wrong: âmodule âweb3.providersâ has no child module âasync_rpcââ.
However, the pycharmâs suggestions here:

â Cannot find reference 'async_rpc' in '__init__.py' :3
â Unresolved reference 'AsyncHTTPProvider' :3
In module '__init__.py' create function async_rpc()
Install package async-rpc
Ignore all unresolved attributes of 'web3.providers'
Ignore unresolved reference 'web3.providers.async_rpc'
You call it âhelp to pick the right import nameâ?
In fact, some AI used the information that out-of-date so that what would be output may be wrong. What my do will cover the source from copied code.