-
-
Notifications
You must be signed in to change notification settings - Fork 299
Remove dependency on pkg_resources
from setuptools
#1536
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
Changes from 18 commits
22bca18
f53159e
0c536f1
52eb095
3175f0d
c8e2764
46a5d29
cbb47d9
4394f6d
ee77bfd
d116d52
2054a56
2697e10
c46e443
6d088ab
7aa7da4
fa60940
d0b90f4
f30e100
3dbe761
d92c48e
01b00a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,65 @@ | |
# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE | ||
# Copyright (c) https://github.com/PyCQA/astroid/blob/main/CONTRIBUTORS.txt | ||
|
||
try: | ||
import pkg_resources | ||
except ImportError: | ||
pkg_resources = None # type: ignore[assignment] | ||
|
||
|
||
def is_namespace(modname): | ||
return ( | ||
pkg_resources is not None | ||
and hasattr(pkg_resources, "_namespace_packages") | ||
and modname in pkg_resources._namespace_packages | ||
) | ||
from __future__ import annotations | ||
|
||
import pathlib | ||
from functools import lru_cache | ||
from importlib.machinery import BuiltinImporter | ||
from importlib.util import _find_spec_from_path | ||
|
||
|
||
def _is_setuptools_namespace(location: pathlib.Path) -> bool: | ||
try: | ||
with open(location / "__init__.py", "rb") as stream: | ||
data = stream.read(4096) | ||
except OSError: | ||
return False | ||
else: | ||
extend_path = b"pkgutil" in data and b"extend_path" in data | ||
declare_namespace = ( | ||
b"pkg_resources" in data and b"declare_namespace(__name__)" in data | ||
) | ||
return extend_path or declare_namespace | ||
|
||
|
||
@lru_cache(maxsize=4096) | ||
def is_namespace(modname: str) -> bool: | ||
found_spec = None | ||
|
||
# find_spec() attempts to import parent packages when given dotted paths. | ||
# That's unacceptable here, so we fallback to _find_spec_from_path(), which does | ||
# not, but requires instead that each single parent ('astroid', 'nodes', etc.) | ||
# be specced from left to right. | ||
processed_components = [] | ||
last_parent = None | ||
for component in modname.split("."): | ||
processed_components.append(component) | ||
working_modname = ".".join(processed_components) | ||
try: | ||
found_spec = _find_spec_from_path(working_modname, last_parent) | ||
DanielNoord marked this conversation as resolved.
Show resolved
Hide resolved
|
||
except ValueError: | ||
# executed .pth files may not have __spec__ | ||
return True | ||
last_parent = working_modname | ||
|
||
if found_spec is None: | ||
return False | ||
|
||
if found_spec.loader is BuiltinImporter: | ||
jacobtylerwalls marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# TODO(Py39): remove, since found_spec.origin will be "built-in" | ||
return False | ||
|
||
if found_spec.origin is None: | ||
return True | ||
|
||
if found_spec.submodule_search_locations is not None: | ||
for search_location in found_spec.submodule_search_locations: | ||
if any( | ||
_is_setuptools_namespace(directory) | ||
for directory in pathlib.Path(search_location).iterdir() | ||
if directory.is_dir() | ||
): | ||
return True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aaaand with better distinguishing between builtins and namespace packages, we ... don't even need this anymore? Highlighting in a comment in case we pop the hood on this again. |
||
|
||
return False |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,12 +10,11 @@ | |
from collections.abc import Iterator | ||
from contextlib import contextmanager | ||
|
||
import pkg_resources | ||
|
||
import astroid | ||
from astroid import manager, test_utils | ||
from astroid.const import IS_JYTHON | ||
from astroid.exceptions import AstroidBuildingError, AstroidImportError | ||
from astroid.interpreter._import import util | ||
from astroid.modutils import is_standard_module | ||
from astroid.nodes import Const | ||
from astroid.nodes.scoped_nodes import ClassDef | ||
|
@@ -111,6 +110,16 @@ def test_ast_from_namespace_pkgutil(self) -> None: | |
def test_ast_from_namespace_pkg_resources(self) -> None: | ||
self._test_ast_from_old_namespace_package_protocol("pkg_resources") | ||
|
||
def test_identify_old_namespace_package_protocol(self) -> None: | ||
# Like the above cases, this package follows the old namespace package protocol | ||
# astroid currently assumes such packages are in sys.modules, so import it | ||
# pylint: disable-next=import-outside-toplevel | ||
import tests.testdata.python3.data.path_pkg_resources_1.package.foo as _ # noqa | ||
|
||
self.assertTrue( | ||
util.is_namespace("tests.testdata.python3.data.path_pkg_resources_1") | ||
jacobtylerwalls marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
DanielNoord marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def test_implicit_namespace_package(self) -> None: | ||
data_dir = os.path.dirname(resources.find("data/namespace_pep_420")) | ||
contribute = os.path.join(data_dir, "contribute_to_namespace") | ||
|
@@ -131,7 +140,6 @@ def test_implicit_namespace_package(self) -> None: | |
def test_namespace_package_pth_support(self) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do these tests still fail without certain code? I thought these lines were needed to make these packages namespace packages in a hacky way. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, if you just make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't gotten around to testing it, but are we sure this still catches namespace packages defined in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The above zip contains a package as defined by that guideline. With the following diff: diff --git a/astroid/interpreter/_import/spec.py b/astroid/interpreter/_import/spec.py
index 7a1de6cdb..cc963d722 100644
--- a/astroid/interpreter/_import/spec.py
+++ b/astroid/interpreter/_import/spec.py
@@ -328,6 +328,9 @@ def _find_spec_with_path(search_path, modname, module_parts, processed, submodul
spec = finder.find_module(modname, module_parts, processed, submodule_path)
if spec is None:
continue
+ if spec.type == ModuleType.PY_NAMESPACE:
+ print(spec)
+ print(finder)
return finder, spec
raise ImportError(f"No module named {'.'.join(module_parts)}") And the following command:
ModuleSpec(name='myotherpackage', type=<ModuleType.PY_NAMESPACE: 10>, location=None, origin=None, submodule_search_locations=['/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/myotherpackage'])
<astroid.interpreter._import.spec.PathSpecFinder object at 0x1062e12d0>
ModuleSpec(name='mynamespace', type=<ModuleType.PY_NAMESPACE: 10>, location='', origin='namespace', submodule_search_locations=['/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace'])
<astroid.interpreter._import.spec.ExplicitNamespacePackageFinder object at 0x1062e17b0>
ModuleSpec(name='mynamespace', type=<ModuleType.PY_NAMESPACE: 10>, location='', origin='namespace', submodule_search_locations=['/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace'])
<astroid.interpreter._import.spec.ExplicitNamespacePackageFinder object at 0x1062e18d0>
ModuleSpec(name='mynamespace', type=<ModuleType.PY_NAMESPACE: 10>, location='', origin='namespace', submodule_search_locations=['/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace'])
<astroid.interpreter._import.spec.ExplicitNamespacePackageFinder object at 0x1062e1000> Whereas this PR returns: ModuleSpec(name='myotherpackage', type=<ModuleType.PY_NAMESPACE: 10>, location=None, origin=None, submodule_search_locations=['/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/myotherpackage'])
<astroid.interpreter._import.spec.PathSpecFinder object at 0x1053054b0> Without the ModuleSpec(name='myotherpackage', type=<ModuleType.PY_NAMESPACE: 10>, location=None, origin=None, submodule_search_locations=['/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/myotherpackage'])
<astroid.interpreter._import.spec.PathSpecFinder object at 0x10df415a0>
ModuleSpec(name='mynamespace', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df41570>
ModuleSpec(name='mynamespace', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df412a0>
ModuleSpec(name='subpackage_a', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace/subpackage_a', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df40340>
ModuleSpec(name='mynamespace', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df41c60>
ModuleSpec(name='mynamespace', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df41e40>
ModuleSpec(name='subpackage_a', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace/subpackage_a', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df40340>
ModuleSpec(name='mynamespace', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df417b0>
ModuleSpec(name='subpackage_a', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace/subpackage_a', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df41e40>
ModuleSpec(name='mynamespace', type=<ModuleType.PKG_DIRECTORY: 3>, location='/Users/daniel/DocumentenLaptop/Programming/Github/namespace_package/mynamespace', origin=None, submodule_search_locations=None)
<astroid.interpreter._import.spec.ImportlibFinder object at 0x10df41e70>
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for looking into this. I see the same output on I see % python3 -m pylint mynamespace --disable=all
ModuleSpec(name='mynamespace', type=<ModuleType.PKG_DIRECTORY: 3>, location='./mynamespace', origin=None, submodule_search_locations=None)
--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 0.00/10, +10.00)
% python3 -m pylint myotherpackage --disable=all
ModuleSpec(name='myotherpackage', type=<ModuleType.PY_NAMESPACE: 10>, location=None, origin=None, submodule_search_locations=['/Users/myuser/TestPackage/./myotherpackage'])
--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 0.00/10, +10.00) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is misread your question and thought you asked for my config. Well, there you have it 😄 I believe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OH. This is the part I wasn't expecting: python -m pip install -e . So the test case has to do with installing the package under site-packages. I wasn't expecting that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I guess that is required for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's possible it's the other way around. Installing it under site-packages with pip is enough to get astroid's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh hm, that might actually be true. I just assumed changed behaviour was a regression. I am not completely sure though. So I'll need to play around with this a little bit more to be sure. Should find time for this this week! |
||
pth = "foogle_fax-0.12.5-py2.7-nspkg.pth" | ||
site.addpackage(resources.RESOURCE_PATH, pth, []) | ||
pkg_resources._namespace_packages["foogle"] = [] | ||
|
||
jacobtylerwalls marked this conversation as resolved.
Show resolved
Hide resolved
|
||
try: | ||
module = self.manager.ast_from_module_name("foogle.fax") | ||
|
@@ -141,18 +149,14 @@ def test_namespace_package_pth_support(self) -> None: | |
with self.assertRaises(AstroidImportError): | ||
self.manager.ast_from_module_name("foogle.moogle") | ||
finally: | ||
del pkg_resources._namespace_packages["foogle"] | ||
sys.modules.pop("foogle") | ||
|
||
def test_nested_namespace_import(self) -> None: | ||
pth = "foogle_fax-0.12.5-py2.7-nspkg.pth" | ||
site.addpackage(resources.RESOURCE_PATH, pth, []) | ||
pkg_resources._namespace_packages["foogle"] = ["foogle.crank"] | ||
pkg_resources._namespace_packages["foogle.crank"] = [] | ||
try: | ||
self.manager.ast_from_module_name("foogle.crank") | ||
finally: | ||
del pkg_resources._namespace_packages["foogle"] | ||
sys.modules.pop("foogle") | ||
|
||
def test_namespace_and_file_mismatch(self) -> None: | ||
|
@@ -161,12 +165,10 @@ def test_namespace_and_file_mismatch(self) -> None: | |
self.assertEqual(ast.name, "unittest") | ||
pth = "foogle_fax-0.12.5-py2.7-nspkg.pth" | ||
site.addpackage(resources.RESOURCE_PATH, pth, []) | ||
pkg_resources._namespace_packages["foogle"] = [] | ||
try: | ||
with self.assertRaises(AstroidImportError): | ||
self.manager.ast_from_module_name("unittest.foogle.fax") | ||
finally: | ||
del pkg_resources._namespace_packages["foogle"] | ||
sys.modules.pop("foogle") | ||
|
||
def _test_ast_from_zip(self, archive: str) -> None: | ||
|
@@ -323,6 +325,7 @@ def test_clear_cache_clears_other_lru_caches(self) -> None: | |
lrus = ( | ||
astroid.nodes.node_classes.LookupMixIn.lookup, | ||
astroid.modutils._cache_normalize_path_, | ||
util.is_namespace, | ||
astroid.interpreter.objectmodel.ObjectModel.attributes, | ||
) | ||
|
||
|
@@ -332,6 +335,7 @@ def test_clear_cache_clears_other_lru_caches(self) -> None: | |
# Generate some hits and misses | ||
ClassDef().lookup("garbage") | ||
is_standard_module("unittest", std_path=["garbage_path"]) | ||
util.is_namespace("unittest") | ||
astroid.interpreter.objectmodel.ObjectModel().attributes() | ||
|
||
# Did the hits or misses actually happen? | ||
|
Uh oh!
There was an error while loading. Please reload this page.