|
3 | 3 | # Copyright (c) 2021 Daniël van Noord <[email protected]>
|
4 | 4 | # Copyright (c) 2021 Neil Girdhar <[email protected]>
|
5 | 5 |
|
6 |
| -try: |
7 |
| - import pkg_resources |
8 |
| -except ImportError: |
9 |
| - pkg_resources = None # type: ignore[assignment] |
10 | 6 |
|
| 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 |
11 | 24 |
|
12 |
| -def is_namespace(modname): |
13 | 25 | 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 |
17 | 72 | )
|
0 commit comments