Format: text/x-python
⬇ Download File (7.9 KB)
   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