astroid icon indicating copy to clipboard operation
astroid copied to clipboard

Fix setuptools>=64 import hooks

Open DanielNoord opened this issue 3 years ago • 4 comments

Steps

  • [x] For new features or bug fixes, add a ChangeLog entry describing what your PR does.
  • [x] Write a good description on what the PR does.

Description

This needs a test. Refs: https://github.com/PyCQA/pylint/issues/7306 and https://github.com/pypa/setuptools/issues/3518.

Type of Changes

Type
:bug: Bug fix

Related Issue

DanielNoord avatar Aug 23 '22 12:08 DanielNoord

@jacobtylerwalls This fixes the test that started failing after https://github.com/PyCQA/astroid/pull/1714

Would you mind taking a look at the PR and the linked issues/discussions? I'm hesitant to go forward, but this actually seems like it fixes some issues.

DanielNoord avatar Aug 23 '22 12:08 DanielNoord

Pull Request Test Coverage Report for Build 3280118627

  • 11 of 11 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.007%) to 92.279%

Totals Coverage Status
Change from base Build 3280113293: 0.007%
Covered Lines: 9872
Relevant Lines: 10698

💛 - Coveralls

coveralls avatar Aug 23 '22 12:08 coveralls

I'd like to provide input, but the fall is going to be very busy for me. I'm not sure when I'll have the time to take a look. I'm very hesitant on doing anything additional touching the import system for 2.15.

jacobtylerwalls avatar Aug 24 '22 01:08 jacobtylerwalls

I'd like to provide input, but the fall is going to be very busy for me. I'm not sure when I'll have the time to take a look. I'm very hesitant on doing anything additional touching the import system for 2.15.

Let's definitely not put this in 2.15. However, since this is going to affect myself as well when I update my local setuptools installation at some point I'd like to get this on astroid main at least. I'll try and see if I can get some more context in the meantime.

DanielNoord avatar Aug 24 '22 08:08 DanielNoord

@Pierre-Sassoulas Would you be okay with putting this in the next release? This will be necessary for pylint not to be incompatible with setuptools>=64 and I'd like to get feedback on it from users before everybody is on >=64.

DanielNoord avatar Jan 29 '23 19:01 DanielNoord

Sure but I think this need a rebase the changelog is not in 2.14.0

Yeah I will prepare the PR for final review then. Thanks!

DanielNoord avatar Jan 29 '23 19:01 DanielNoord

On closer inspection (seeing Jacob's comment) this feel like something a little risky if we want to release 2.16.0 soon and not do another beta release. Maybe we should do an rc release ?

Pierre-Sassoulas avatar Jan 29 '23 19:01 Pierre-Sassoulas

On closer inspection (seeing Jacob's comment) this feel like something a little risky if we want to release 2.16.0 soon and not do another beta release. Maybe we should do an rc release ?

Yeah I don't know. Based on the tests I think this should be fine, but we have been wrong before. If anything we can just quickly revert the change and release a patch astroid release? There is no code that builds upon this (both in astroid or pylint) so I don't foresee many merge issues when back porting.

DanielNoord avatar Jan 29 '23 20:01 DanielNoord

Codecov Report

Merging #1752 (6ea1a7d) into main (ac41d4e) will increase coverage by 0.00%. The diff coverage is 95.45%.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #1752   +/-   ##
=======================================
  Coverage   92.74%   92.74%           
=======================================
  Files          94       94           
  Lines       10943    10962   +19     
=======================================
+ Hits        10149    10167   +18     
- Misses        794      795    +1     
Flag Coverage Δ
linux 92.51% <95.45%> (+<0.01%) :arrow_up:
pypy 88.45% <72.72%> (-0.04%) :arrow_down:
windows 92.35% <95.45%> (+<0.01%) :arrow_up:

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
astroid/interpreter/_import/spec.py 97.35% <95.45%> (-0.30%) :arrow_down:

codecov[bot] avatar Jan 29 '23 20:01 codecov[bot]

I'm afraid 😄 Let's wait for a review by Jacob, forcing a namespace change last minute can ruin the effort we made with two beta releases and make our lives difficult (when we lived without this for months).

Pierre-Sassoulas avatar Jan 30 '23 09:01 Pierre-Sassoulas

Sorry for not prioritizing this yet. @DanielNoord would you be able to write a (short) summary of why you believe this is the correct approach? That would help me get into this. (There has been more discussion on the linked PyPA issue.)

jacobtylerwalls avatar Jan 30 '23 12:01 jacobtylerwalls

The idea is that we actually have a quite an advanced import system compared to other tools and are able to follow imports (somewhat successfully). However, when installing editable installs with pip a new type of _SPEC_FINDERS is returned. We don't have support for those but we can quite easily add them by telling our import system that we should handle them as normal (namespace) packages. I chose to use the __name__ attribute of the finders as we then don't build in dependencies on pip and setuptools etc. but can just infer the type of package from the finder's name.

The test case that now succeeds is notable as it shows that with this PR we actually are able to infer more imports than we previously could.

DanielNoord avatar Feb 04 '23 13:02 DanielNoord

Also, I noticed two failures in pylint with this change, including one crash and one astroid-error:

pytest -k private-import

Exception on node <ImportFrom l.33 at 0x1144f4790> in file '/Users/.../pylint/tests/functional/ext/private_import/private_import.py'
Traceback (most recent call last):
  File "/Users/.../pylint/pylint/utils/ast_walker.py", line 91, in walk
    callback(astroid)
  File "/Users/.../pylint/pylint/checkers/imports.py", line 562, in visit_importfrom
    self._add_imported_module(node, f"{imported_module.name}.{name}")
  File "/Users/.../pylint/pylint/checkers/imports.py", line 894, in _add_imported_module
    elif not astroid.modutils.is_standard_module(importedmodname):
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/.../astroid/astroid/modutils.py", line 528, in is_standard_module
    filename = file_from_modpath([modname])
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/.../astroid/astroid/modutils.py", line 334, in file_from_modpath
    return file_info_from_modpath(modpath, path, context_file).location
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/.../astroid/astroid/modutils.py", line 385, in file_info_from_modpath
    return _spec_from_modpath(modpath, path, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/.../astroid/astroid/modutils.py", line 595, in _spec_from_modpath
    found_spec = spec.find_spec(modpath, path)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/.../astroid/astroid/interpreter/_import/spec.py", line 423, in find_spec
    finder, spec = _find_spec_with_path(
                   ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/.../astroid/astroid/interpreter/_import/spec.py", line 372, in _find_spec_with_path
    spec = meta_finder.find_spec(modname, submodule_path)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/_pytest/assertion/rewrite.py", line 92, in find_spec
    if self._early_rewrite_bailout(name, state):
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/_pytest/assertion/rewrite.py", line 193, in _early_rewrite_bailout
    path = PurePath(*parts).with_suffix(".py")
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/pathlib.py", line 694, in with_suffix
    raise ValueError("%r has an empty name" % (self,))
ValueError: PurePosixPath('.') has an empty name

pytest -k non_init_parent_called

E       AssertionError: Wrong results for file "non_init_parent_called":
E       
E       Expected in testdata:
E          6: import-error
E         14: non-parent-init-called
E         22: no-member
E         27: no-member
E         50: no-member
E       
E       Unexpected in testdata:
E          1: astroid-error

jacobtylerwalls avatar Feb 04 '23 15:02 jacobtylerwalls

PurePosixPath

Feels like we should expand "." to the current working directory? 😓 That sounds like more issues..

We can switch that logic around? Only use the meta_finders in sys.meta_path that we know we support?

DanielNoord avatar Feb 04 '23 15:02 DanielNoord

Interesting that the failure is coming from pytest itself.

jacobtylerwalls avatar Feb 04 '23 15:02 jacobtylerwalls

Interesting that the failure is coming from pytest itself.

They probably have their own MetaPathFinder to handle all the fixture magic which we then try to use to locate an import. That's why I'm debating on moving the check for known MetaPathFinders to above line 372, in _find_spec_with_path. That should resolve this I think.

DanielNoord avatar Feb 04 '23 15:02 DanielNoord

That's why I'm debating on moving the check for known MetaPathFinders to above line 372, in _find_spec_with_path. That should resolve this I think.

I'd be curious to see the pylint test results: I worry about giving new priority to the builtin importers, if that's what you're suggesting:

>>> sys.meta_path
[<_distutils_hack.DistutilsMetaFinder object at 0x104452070>, <class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib_external.PathFinder'>]

jacobtylerwalls avatar Feb 04 '23 15:02 jacobtylerwalls

@jacobtylerwalls See my last change, this is what I meant with changing the order of checks. We still rely on our own 4 Finders initially, but if that doesn't work we start trying MetaPathFinders for which we now stuff works.

This means that we would need to add new support by hand, but it is much less error prone.

pylint test suite passed with https://github.com/PyCQA/astroid/pull/1752/commits/f889b5d6bb49278d9736b799172f19b3f09631df.

DanielNoord avatar Feb 05 '23 12:02 DanielNoord

If we agree that this is the way to go I'll add support for SixMetaPathImporter so the test case that I "unfailed" in this PR will pass again.

DanielNoord avatar Feb 05 '23 12:02 DanielNoord

Looks good, thanks! (A test would be nice.)

I think the six test serves as a good test for the general logic. We can now import and find that module where we previously couldn't as we support the _SixMetaPathImporter now. As for setuptools I don't think there is a good way to test it without introducing a dependency on it, which I don't really want.

I have tested this with https://github.com/jooola/pylint-import-error-bug to confirm that this branch does indeed fix the issue mentioned in the linked issue.

DanielNoord avatar Feb 05 '23 15:02 DanielNoord

Great!

I think the six test serves as a good test for the general logic. We can now import and find that module where we previously couldn't as we support the _SixMetaPathImporter now.

Indeed. (Sorry, I thought you had said you were about to revert the changes to that test.)

Yeah, that was unclear. My bad!

Thanks for the reviews all. I'm skipping the coverage check on this one!

DanielNoord avatar Feb 05 '23 20:02 DanielNoord