1 import os 2 from pathlib import Path 3 from typing import Optional 4 5 from jedi.inference.cache import inference_state_method_cache 6 from jedi.inference.names import AbstractNameDefinition, ModuleName 7 from jedi.inference.filters import GlobalNameFilter, ParserTreeFilter, DictFilter, MergedFilter 8 from jedi.inference import compiled 9 from jedi.inference.base_value import TreeValue 10 from jedi.inference.names import SubModuleName 11 from jedi.inference.helpers import values_from_qualified_names 12 from jedi.inference.compiled import create_simple_object 13 from jedi.inference.base_value import ValueSet 14 from jedi.inference.context import ModuleContext 15 16 17 class _ModuleAttributeName(AbstractNameDefinition): 18 """ 19 For module attributes like __file__, __str__ and so on. 20 """ 21 api_type = 'instance' 22 23 def __init__(self, parent_module, string_name, string_value=None): 24 self.parent_context = parent_module 25 self.string_name = string_name 26 self._string_value = string_value 27 28 def infer(self): 29 if self._string_value is not None: 30 s = self._string_value 31 return ValueSet([ 32 create_simple_object(self.parent_context.inference_state, s) 33 ]) 34 return compiled.get_string_value_set(self.parent_context.inference_state) 35 36 37 class SubModuleDictMixin: 38 @inference_state_method_cache() 39 def sub_modules_dict(self): 40 """ 41 Lists modules in the directory of this module (if this module is a 42 package). 43 """ 44 names = {} 45 if self.is_package(): 46 mods = self.inference_state.compiled_subprocess.iter_module_names( 47 self.py__path__() 48 ) 49 for name in mods: 50 # It's obviously a relative import to the current module. 51 names[name] = SubModuleName(self.as_context(), name) 52 53 # In the case of an import like `from x.` we don't need to 54 # add all the variables, this is only about submodules. 55 return names 56 57 58 class ModuleMixin(SubModuleDictMixin): 59 _module_name_class = ModuleName 60 61 def get_filters(self, origin_scope=None): 62 yield MergedFilter( 63 ParserTreeFilter( 64 parent_context=self.as_context(), 65 origin_scope=origin_scope 66 ), 67 GlobalNameFilter(self.as_context()), 68 ) 69 yield DictFilter(self.sub_modules_dict()) 70 yield DictFilter(self._module_attributes_dict()) 71 yield from self.iter_star_filters() 72 73 def py__class__(self): 74 c, = values_from_qualified_names(self.inference_state, 'types', 'ModuleType') 75 return c 76 77 def is_module(self): 78 return True 79 80 def is_stub(self): 81 return False 82 83 @property # type: ignore[misc] 84 @inference_state_method_cache() 85 def name(self): 86 return self._module_name_class(self, self.string_names[-1]) 87 88 @inference_state_method_cache() 89 def _module_attributes_dict(self): 90 names = ['__package__', '__doc__', '__name__'] 91 # All the additional module attributes are strings. 92 dct = dict((n, _ModuleAttributeName(self, n)) for n in names) 93 path = self.py__file__() 94 if path is not None: 95 dct['__file__'] = _ModuleAttributeName(self, '__file__', str(path)) 96 return dct 97 98 def iter_star_filters(self): 99 for star_module in self.star_imports(): 100 f = next(star_module.get_filters(), None) 101 assert f is not None 102 yield f 103 104 # I'm not sure if the star import cache is really that effective anymore 105 # with all the other really fast import caches. Recheck. Also we would need 106 # to push the star imports into InferenceState.module_cache, if we reenable this. 107 @inference_state_method_cache([]) 108 def star_imports(self): 109 from jedi.inference.imports import Importer 110 111 modules = [] 112 module_context = self.as_context() 113 for i in self.tree_node.iter_imports(): 114 if i.is_star_import(): 115 new = Importer( 116 self.inference_state, 117 import_path=i.get_paths()[-1], 118 module_context=module_context, 119 level=i.level 120 ).follow() 121 122 for module in new: 123 if isinstance(module, ModuleValue): 124 modules += module.star_imports() 125 modules += new 126 return modules 127 128 def get_qualified_names(self): 129 """ 130 A module doesn't have a qualified name, but it's important to note that 131 it's reachable and not `None`. With this information we can add 132 qualified names on top for all value children. 133 """ 134 return () 135 136 137 class ModuleValue(ModuleMixin, TreeValue): 138 api_type = 'module' 139 140 def __init__(self, inference_state, module_node, code_lines, file_io=None, 141 string_names=None, is_package=False): 142 super().__init__( 143 inference_state, 144 parent_context=None, 145 tree_node=module_node 146 ) 147 self.file_io = file_io 148 if file_io is None: 149 self._path: Optional[Path] = None 150 else: 151 self._path = file_io.path 152 self.string_names = string_names # Optional[Tuple[str, ...]] 153 self.code_lines = code_lines 154 self._is_package = is_package 155 156 def is_stub(self): 157 if self._path is not None and self._path.suffix == '.pyi': 158 # Currently this is the way how we identify stubs when e.g. goto is 159 # used in them. This could be changed if stubs would be identified 160 # sooner and used as StubModuleValue. 161 return True 162 return super().is_stub() 163 164 def py__name__(self): 165 if self.string_names is None: 166 return None 167 return '.'.join(self.string_names) 168 169 def py__file__(self) -> Optional[Path]: 170 """ 171 In contrast to Python's __file__ can be None. 172 """ 173 if self._path is None: 174 return None 175 176 return self._path.absolute() 177 178 def is_package(self): 179 return self._is_package 180 181 def py__package__(self): 182 if self.string_names is None: 183 return [] 184 185 if self._is_package: 186 return self.string_names 187 return self.string_names[:-1] 188 189 def py__path__(self): 190 """ 191 In case of a package, this returns Python's __path__ attribute, which 192 is a list of paths (strings). 193 Returns None if the module is not a package. 194 """ 195 if not self._is_package: 196 return None 197 198 # A namespace package is typically auto generated and ~10 lines long. 199 first_few_lines = ''.join(self.code_lines[:50]) 200 # these are strings that need to be used for namespace packages, 201 # the first one is ``pkgutil``, the second ``pkg_resources``. 202 options = ('declare_namespace(__name__)', 'extend_path(__path__') 203 if options[0] in first_few_lines or options[1] in first_few_lines: 204 # It is a namespace, now try to find the rest of the 205 # modules on sys_path or whatever the search_path is. 206 paths = set() 207 for s in self.inference_state.get_sys_path(): 208 other = os.path.join(s, self.name.string_name) 209 if os.path.isdir(other): 210 paths.add(other) 211 if paths: 212 return list(paths) 213 # Nested namespace packages will not be supported. Nobody ever 214 # asked for it and in Python 3 they are there without using all the 215 # crap above. 216 217 # Default to the of this file. 218 file = self.py__file__() 219 assert file is not None # Shouldn't be a package in the first place. 220 return [os.path.dirname(file)] 221 222 def _as_context(self): 223 return ModuleContext(self) 224 225 def __repr__(self): 226 return "<%s: %s@%s-%s is_stub=%s>" % ( 227 self.__class__.__name__, self.py__name__(), 228 self.tree_node.start_pos[0], self.tree_node.end_pos[0], 229 self.is_stub() 230 ) 231