pschanely / CrossHair

An analysis tool for Python that blurs the line between testing and type systems.
Other
1.03k stars 49 forks source link

Better error message when a file can't be imported #202

Closed neighthan closed 1 year ago

neighthan commented 1 year ago

Problem

I'm just trying crosshair for the first time, but I got this error message (slightly edited) when I tried to watch my code directory:

I couldn't import a file.
.../python3.11/site-packages/crosshair/util.py:293:
|    try:
|        root_path, module_name = extract_module_from_file(filename)
|        with add_to_pypath(root_path):
>            return import_module(module_name)
|    except Exception as e:
|        raise ErrorDuringImport from e
|

cannot import name 'ClassName' from partially initialized module 'module.submodule' (most likely due to a circular import) (.../module/submodule.py)

When I went to from module.submodule import ClassName myself, it worked fine. After a little experimentation, I figured out which other import I needed to try to get the same circular import error that crosshair did.

Solution

It would be nice if crosshair would print out the whole stack trace, so it would be obvious what chain of imports caused the issue.

Alternatives

This isn't related to any major feature of crosshair, so it's obviously low priority. If we just keep things as-is, that's not a big deal; the user just has to play around with the imports, like I did, until they find the one which triggers the cyclic issue. But I also think that fixing this would probably only mean adjusting a couple of lines of code? I'm busy now, but if I end up continuing to use crosshair, I'll take a look at this later.

pschanely commented 1 year ago

First, APOLOGIES for the radio silence on this one. I just completely forgot about it. If you notice this happening anywhere else, do me a favor and ping me in the comments!

Indeed, for import errors specifically, tracebacks aren't displayed. There isn't really a good reason for this, so I've changed it in this commit. I'll wait to officially close out this issue until I cut the next release.

At that time, you should see something like this instead:

% cat module/submodule.py                         
from module.othersubmodule import OtherClassName

class ClassName:
    pass

% cat module/othersubmodule.py
from module.submodule import ClassName

class OtherClassName:
    pass

% python -m crosshair watch module/submodule.py
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  File "/Users/phillipschanely/proj/CrossHair/crosshair/util.py", line 293, in load_file
    return import_module(module_name)
  File "/Users/phillipschanely/proj/CrossHair/crosshair/util.py", line 279, in import_module
    result_module = importlib.import_module(module_name)
  File "/Users/phillipschanely/.pyenv/versions/3.10.4/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 883, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/Users/phillipschanely/proj/crosshair-testrepos/issue202/module/submodule.py", line 1, in <module>
    from module.othersubmodule import OtherClassName
  File "/Users/phillipschanely/proj/crosshair-testrepos/issue202/module/othersubmodule.py", line 1, in <module>
    from module.submodule import ClassName
  File "/Users/phillipschanely/proj/crosshair-testrepos/issue202/module/submodule.py", line 1, in <module>
    from module.othersubmodule import OtherClassName

I couldn't import a file.
/Users/phillipschanely/proj/CrossHair/crosshair/util.py:293:
|    try:
|        root_path, module_name = extract_module_from_file(filename)
|        with add_to_pypath(root_path):
>            return import_module(module_name)
|    except Exception as e:
|        raise ErrorDuringImport from e
|

cannot import name 'OtherClassName' from partially initialized module 'module.othersubmodule' (most likely due to a circular import) (/Users/phillipschanely/proj/crosshair-testrepos/issue202/module/othersubmodule.py)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------