Skip to content

Commit 8f477fd

Browse files
authored
Use importlib instead of pkg_resources namespace package discovery (#1326)
1 parent 333437a commit 8f477fd

File tree

2 files changed

+64
-8
lines changed

2 files changed

+64
-8
lines changed

astroid/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import enum
22
import sys
33

4+
PY36 = sys.version_info[:2] == (3, 6)
45
PY38 = sys.version_info[:2] == (3, 8)
56
PY37_PLUS = sys.version_info >= (3, 7)
67
PY38_PLUS = sys.version_info >= (3, 8)

astroid/interpreter/_import/util.py

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,70 @@
33
# Copyright (c) 2021 Daniël van Noord <[email protected]>
44
# Copyright (c) 2021 Neil Girdhar <[email protected]>
55

6-
try:
7-
import pkg_resources
8-
except ImportError:
9-
pkg_resources = None # type: ignore[assignment]
106

7+
from importlib import abc, util
8+
9+
from astroid.const import PY36
10+
11+
12+
def _is_old_setuptools_namespace_package(modname: str) -> bool:
13+
"""Check for old types of setuptools namespace packages.
14+
15+
See https://setuptools.pypa.io/en/latest/pkg_resources.html and
16+
https://packaging.python.org/en/latest/guides/packaging-namespace-packages/
17+
18+
Because pkg_resources is slow to import we only do so if explicitly necessary.
19+
"""
20+
try:
21+
import pkg_resources # pylint: disable=import-outside-toplevel
22+
except ImportError:
23+
return False
1124

12-
def is_namespace(modname):
1325
return (
14-
pkg_resources is not None
15-
and hasattr(pkg_resources, "_namespace_packages")
16-
and modname in pkg_resources._namespace_packages
26+
hasattr(pkg_resources, "_namespace_packages")
27+
and modname in pkg_resources._namespace_packages # type: ignore[attr-defined]
28+
)
29+
30+
31+
def is_namespace(modname: str) -> bool:
32+
"""Determine whether we encounter a namespace package."""
33+
if PY36:
34+
# On Python 3.6 an AttributeError is raised when a package
35+
# is lacking a __path__ attribute and thus is not a
36+
# package.
37+
try:
38+
spec = util.find_spec(modname)
39+
except (AttributeError, ValueError):
40+
return _is_old_setuptools_namespace_package(modname)
41+
else:
42+
try:
43+
spec = util.find_spec(modname)
44+
except ValueError:
45+
return _is_old_setuptools_namespace_package(modname)
46+
47+
# If there is no spec or origin this is a namespace package
48+
# See: https://docs.python.org/3/library/importlib.html#importlib.machinery.ModuleSpec.origin
49+
# We assume builtin packages are never namespace
50+
if not spec or not spec.origin or spec.origin == "built-in":
51+
return False
52+
53+
# If there is no loader the package is namespace
54+
# See https://docs.python.org/3/library/importlib.html#importlib.abc.PathEntryFinder.find_loader
55+
if not spec.loader:
56+
return True
57+
# This checks for _frozen_importlib.FrozenImporter, which does not inherit from InspectLoader
58+
if hasattr(spec.loader, "_ORIGIN") and spec.loader._ORIGIN == "frozen":
59+
return False
60+
# Other loaders are namespace packages
61+
if not isinstance(spec.loader, abc.InspectLoader):
62+
return True
63+
64+
# Lastly we check if the package declares itself a namespace package
65+
try:
66+
source = spec.loader.get_source(spec.origin)
67+
# If the loader can't handle the spec, we're dealing with a namespace package
68+
except ImportError:
69+
return False
70+
return bool(
71+
source and "pkg_resources" in source and "declare_namespace(__name__)" in source
1772
)

0 commit comments

Comments
 (0)