Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/astroid/interpreter/_import/util.py: 43%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

47 statements  

1# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html 

2# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE 

3# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt 

4 

5from __future__ import annotations 

6 

7import pathlib 

8import sys 

9from functools import lru_cache 

10from importlib._bootstrap_external import _NamespacePath # type: ignore[attr-defined] 

11from importlib.util import _find_spec_from_path # type: ignore[attr-defined] 

12 

13from astroid.const import IS_PYPY 

14 

15if sys.version_info >= (3, 11): 

16 from importlib.machinery import NamespaceLoader 

17else: 

18 from importlib._bootstrap_external import _NamespaceLoader as NamespaceLoader 

19 

20 

21@lru_cache(maxsize=4096) 

22def is_namespace(modname: str) -> bool: 

23 from astroid.modutils import ( # pylint: disable=import-outside-toplevel 

24 EXT_LIB_DIRS, 

25 STD_LIB_DIRS, 

26 ) 

27 

28 STD_AND_EXT_LIB_DIRS = STD_LIB_DIRS.union(EXT_LIB_DIRS) 

29 

30 if modname in sys.builtin_module_names: 

31 return False 

32 

33 found_spec = None 

34 

35 # find_spec() attempts to import parent packages when given dotted paths. 

36 # That's unacceptable here, so we fallback to _find_spec_from_path(), which does 

37 # not, but requires instead that each single parent ('astroid', 'nodes', etc.) 

38 # be specced from left to right. 

39 processed_components = [] 

40 last_submodule_search_locations: _NamespacePath | None = None 

41 for component in modname.split("."): 

42 processed_components.append(component) 

43 working_modname = ".".join(processed_components) 

44 try: 

45 # Both the modname and the path are built iteratively, with the 

46 # path (e.g. ['a', 'a/b', 'a/b/c']) lagging the modname by one 

47 found_spec = _find_spec_from_path( 

48 working_modname, path=last_submodule_search_locations 

49 ) 

50 except AttributeError: 

51 return False 

52 except ValueError: 

53 if modname == "__main__": 

54 return False 

55 try: 

56 # .pth files will be on sys.modules 

57 # __spec__ is set inconsistently on PyPy so we can't really on the heuristic here 

58 # See: https://foss.heptapod.net/pypy/pypy/-/issues/3736 

59 # Check first fragment of modname, e.g. "astroid", not "astroid.interpreter" 

60 # because of cffi's behavior 

61 # See: https://github.com/pylint-dev/astroid/issues/1776 

62 mod = sys.modules[processed_components[0]] 

63 return ( 

64 mod.__spec__ is None 

65 and getattr(mod, "__file__", None) is None 

66 and hasattr(mod, "__path__") 

67 and not IS_PYPY 

68 ) 

69 except KeyError: 

70 return False 

71 except AttributeError: 

72 # Workaround for "py" module 

73 # https://github.com/pytest-dev/apipkg/issues/13 

74 return False 

75 except KeyError: 

76 # Intermediate steps might raise KeyErrors 

77 # https://github.com/python/cpython/issues/93334 

78 # TODO: update if fixed in importlib 

79 # For tree a > b > c.py 

80 # >>> from importlib.machinery import PathFinder 

81 # >>> PathFinder.find_spec('a.b', ['a']) 

82 # KeyError: 'a' 

83 

84 # Repair last_submodule_search_locations 

85 if last_submodule_search_locations: 

86 # pylint: disable=unsubscriptable-object 

87 last_item = last_submodule_search_locations[-1] 

88 # e.g. for failure example above, add 'a/b' and keep going 

89 # so that find_spec('a.b.c', path=['a', 'a/b']) succeeds 

90 assumed_location = pathlib.Path(last_item) / component 

91 last_submodule_search_locations.append(str(assumed_location)) 

92 continue 

93 

94 # Update last_submodule_search_locations for next iteration 

95 if found_spec and found_spec.submodule_search_locations: 

96 # But immediately return False if we can detect we are in stdlib 

97 # or external lib (e.g site-packages) 

98 if any( 

99 any(location.startswith(lib_dir) for lib_dir in STD_AND_EXT_LIB_DIRS) 

100 for location in found_spec.submodule_search_locations 

101 ): 

102 return False 

103 last_submodule_search_locations = found_spec.submodule_search_locations 

104 

105 return ( 

106 found_spec is not None 

107 and found_spec.submodule_search_locations is not None 

108 and found_spec.origin is None 

109 and ( 

110 found_spec.loader is None or isinstance(found_spec.loader, NamespaceLoader) 

111 ) 

112 )