Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust is_namespace() to check ModuleSpec.loader #2410

Merged
merged 5 commits into from
Apr 30, 2024

Conversation

perrinjerome
Copy link
Contributor

@perrinjerome perrinjerome commented Apr 8, 2024

Only consider modules with submodule_search_locations namespaces.

Type of Changes

Type
βœ“ πŸ› Bug fix

Description

Make astroid.interpreter._import.util.is_namespace only consider modules using a loader set to NamespaceLoader or None as namespaces.
This fixes a problem that six.moves brain was not effective if six.moves was already imported.

Closes #2409

Copy link
Collaborator

@DanielNoord DanielNoord left a comment

Choose a reason for hiding this comment

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

@jacobtylerwalls what do you think of this PR? It seems like a correct approach right?

This fixes inference when six.moves is imported.

Closes pylint-dev#2409
@perrinjerome
Copy link
Contributor Author

I pushed force to change the commit message of this "option 1" and also the "option 2" from #2411 this was referencing a wrong issue.

@jacobtylerwalls
Copy link
Member

Yes, this looks promising, thank you!

@perrinjerome
Copy link
Contributor Author

This is a draft pull request because I was suggesting this and #2411 at the same time, to discuss two possible approaches. From the feedback, it seems that this option would be better, so I will close the other pull request and mark this one as ready for review.

@perrinjerome perrinjerome changed the title six: fix inference when six.moves is imported (option 1) six: fix inference when six.moves is imported Apr 14, 2024
@perrinjerome perrinjerome marked this pull request as ready for review April 14, 2024 14:45
DanielNoord
DanielNoord previously approved these changes Apr 15, 2024
Copy link
Collaborator

@DanielNoord DanielNoord left a comment

Choose a reason for hiding this comment

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

@jacobtylerwalls do you want to review this as well?

@jacobtylerwalls
Copy link
Member

Yes, on second thought, I would like to take a moment to consider this change in more detail. Reading PEP 451, I'm concerned that is not None is the demonstrated way to detect namespace packages, so I wouldn't mind the opportunity to look into it a little further.

@jacobtylerwalls jacobtylerwalls self-requested a review April 15, 2024 12:55
@perrinjerome
Copy link
Contributor Author

perrinjerome commented Apr 19, 2024

Thanks, it's good idea to check in more details, I am myself not convinced that this is the best way of detecting namespace packages, the example code you linked and also https://peps.python.org/pep-0451/#namespace-packages describe namespace packages as "a spec with loader set to None".

In the case of six.moves, the loader is not None:

(Pdb) import six.moves
(Pdb) six.moves.__spec__.loader
<six._SixMetaPathImporter object at 0x7f66230c99d0>

From this and from the PEP, it seems the condition should include loader is not None instead, something like this:

    return (
        found_spec is not None
        and found_spec.submodule_search_locations is not None
        and found_spec.loader is None
        and found_spec.origin is None
    )

but after trying a bit, in practice for namespace packages the loader is not None, it is importlib.machinery.NamespaceLoader ( and importlib._bootstrap_external._NamespaceLoader for python < 3.11 ). I did not research why this is different from the PEP.

Would it make sense to adjust the patch with something like this ? ( to be applied on top of this patch ):

diff --git a/astroid/interpreter/_import/util.py b/astroid/interpreter/_import/util.py
index cb947800..939f97d5 100644
--- a/astroid/interpreter/_import/util.py
+++ b/astroid/interpreter/_import/util.py
@@ -8,6 +8,11 @@ import pathlib
 import sys
 from functools import lru_cache
 from importlib._bootstrap_external import _NamespacePath
+
+if sys.version_info >= (3, 11):
+    from importlib.machinery import NamespaceLoader
+else:
+    from importlib._bootstrap_external import _NamespaceLoader as NamespaceLoader
 from importlib.util import _find_spec_from_path  # type: ignore[attr-defined]
 
 from astroid.const import IS_PYPY
@@ -99,6 +104,8 @@ def is_namespace(modname: str) -> bool:
 
     return (
         found_spec is not None
-        and found_spec.submodule_search_locations
-        and found_spec.origin is None
+        and found_spec.submodule_search_locations is not None
+        and (
+            found_spec.loader is None or isinstance(found_spec.loader, NamespaceLoader)
+        )
     )

I have only tested this patch on python3.9 so far.

EDIT: also in the patch I have removed origin is None, there is no good reason for that, thinking twice, it was probably a mistake.

@perrinjerome
Copy link
Contributor Author

I did not research why this is different from the PEP.

I found https://bugs.python.org/issue35673 which mentions a "documentation bug".

@jacobtylerwalls
Copy link
Member

EDIT: also in the patch I have removed origin is None, there is no good reason for that, thinking twice, it was probably a mistake.

I was following the example here, which gives:

if spec.origin is None and spec.submodule_search_locations is not None:
    # namespace package

@jacobtylerwalls
Copy link
Member

@perrinjerome Alternatively, does this diff work for you? This looks like a mistake.

diff --git a/astroid/interpreter/_import/util.py b/astroid/interpreter/_import/util.py
index a8af9ec6..afead29f 100644
--- a/astroid/interpreter/_import/util.py
+++ b/astroid/interpreter/_import/util.py
@@ -32,7 +32,7 @@ def is_namespace(modname: str) -> bool:
     # not, but requires instead that each single parent ('astroid', 'nodes', etc.)
     # be specced from left to right.
     processed_components = []
-    last_submodule_search_locations: _NamespacePath | None = None
+    last_submodule_search_locations = []
     for component in modname.split("."):
         processed_components.append(component)
         working_modname = ".".join(processed_components)

@jacobtylerwalls jacobtylerwalls added this to the 3.2.0 milestone Apr 28, 2024
@jacobtylerwalls
Copy link
Member

Never mind, sorry, I ran your test and it doesn't pass with my suggestion.

Your latest suggested patch looks good to me--thanks for doing the research.

@perrinjerome
Copy link
Contributor Author

Thank you @jacobtylerwalls , I have updated the pull request with the patch we discussed.

@jacobtylerwalls jacobtylerwalls changed the title six: fix inference when six.moves is imported Adjust is_namespace() to check ModuleSpec.loader Apr 29, 2024
Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

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

Thanks!

Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

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

Ah, do you have any guidance on the failing pylint run? Did that expose a logic problem?

@perrinjerome
Copy link
Contributor Author

I think I have fixed the failing pylint test now, by reorganizing imports

Copy link

codecov bot commented Apr 30, 2024

Codecov Report

All modified and coverable lines are covered by tests βœ…

Project coverage is 92.76%. Comparing base (de942f3) to head (404e6c4).
Report is 10 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2410      +/-   ##
==========================================
+ Coverage   92.75%   92.76%   +0.01%     
==========================================
  Files          94       94              
  Lines       11088    11090       +2     
==========================================
+ Hits        10285    10288       +3     
+ Misses        803      802       -1     
Flag Coverage Ξ”
linux 92.57% <100.00%> (+0.01%) ⬆️
pypy 92.76% <100.00%> (+2.00%) ⬆️
windows 92.66% <100.00%> (+0.31%) ⬆️

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

Files Coverage Ξ”
astroid/interpreter/_import/util.py 91.48% <100.00%> (+0.58%) ⬆️

... and 1 file with indirect coverage changes

Copy link
Collaborator

@DanielNoord DanielNoord left a comment

Choose a reason for hiding this comment

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

@jacobtylerwalls this is good to go right?

Copy link
Member

@jacobtylerwalls jacobtylerwalls left a comment

Choose a reason for hiding this comment

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

Thanks, looks great!

@jacobtylerwalls jacobtylerwalls merged commit 4a8827d into pylint-dev:main Apr 30, 2024
20 checks passed
@perrinjerome
Copy link
Contributor Author

Thank you !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

six.moves brain is not effective when six.moves was already imported in interpreter running astroid
4 participants