You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			231 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			231 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Python
		
	
import os
 | 
						|
from pathlib import Path
 | 
						|
from typing import Optional
 | 
						|
 | 
						|
from jedi.inference.cache import inference_state_method_cache
 | 
						|
from jedi.inference.names import AbstractNameDefinition, ModuleName
 | 
						|
from jedi.inference.filters import GlobalNameFilter, ParserTreeFilter, DictFilter, MergedFilter
 | 
						|
from jedi.inference import compiled
 | 
						|
from jedi.inference.base_value import TreeValue
 | 
						|
from jedi.inference.names import SubModuleName
 | 
						|
from jedi.inference.helpers import values_from_qualified_names
 | 
						|
from jedi.inference.compiled import create_simple_object
 | 
						|
from jedi.inference.base_value import ValueSet
 | 
						|
from jedi.inference.context import ModuleContext
 | 
						|
 | 
						|
 | 
						|
class _ModuleAttributeName(AbstractNameDefinition):
 | 
						|
    """
 | 
						|
    For module attributes like __file__, __str__ and so on.
 | 
						|
    """
 | 
						|
    api_type = 'instance'
 | 
						|
 | 
						|
    def __init__(self, parent_module, string_name, string_value=None):
 | 
						|
        self.parent_context = parent_module
 | 
						|
        self.string_name = string_name
 | 
						|
        self._string_value = string_value
 | 
						|
 | 
						|
    def infer(self):
 | 
						|
        if self._string_value is not None:
 | 
						|
            s = self._string_value
 | 
						|
            return ValueSet([
 | 
						|
                create_simple_object(self.parent_context.inference_state, s)
 | 
						|
            ])
 | 
						|
        return compiled.get_string_value_set(self.parent_context.inference_state)
 | 
						|
 | 
						|
 | 
						|
class SubModuleDictMixin:
 | 
						|
    @inference_state_method_cache()
 | 
						|
    def sub_modules_dict(self):
 | 
						|
        """
 | 
						|
        Lists modules in the directory of this module (if this module is a
 | 
						|
        package).
 | 
						|
        """
 | 
						|
        names = {}
 | 
						|
        if self.is_package():
 | 
						|
            mods = self.inference_state.compiled_subprocess.iter_module_names(
 | 
						|
                self.py__path__()
 | 
						|
            )
 | 
						|
            for name in mods:
 | 
						|
                # It's obviously a relative import to the current module.
 | 
						|
                names[name] = SubModuleName(self.as_context(), name)
 | 
						|
 | 
						|
        # In the case of an import like `from x.` we don't need to
 | 
						|
        # add all the variables, this is only about submodules.
 | 
						|
        return names
 | 
						|
 | 
						|
 | 
						|
class ModuleMixin(SubModuleDictMixin):
 | 
						|
    _module_name_class = ModuleName
 | 
						|
 | 
						|
    def get_filters(self, origin_scope=None):
 | 
						|
        yield MergedFilter(
 | 
						|
            ParserTreeFilter(
 | 
						|
                parent_context=self.as_context(),
 | 
						|
                origin_scope=origin_scope
 | 
						|
            ),
 | 
						|
            GlobalNameFilter(self.as_context()),
 | 
						|
        )
 | 
						|
        yield DictFilter(self.sub_modules_dict())
 | 
						|
        yield DictFilter(self._module_attributes_dict())
 | 
						|
        yield from self.iter_star_filters()
 | 
						|
 | 
						|
    def py__class__(self):
 | 
						|
        c, = values_from_qualified_names(self.inference_state, 'types', 'ModuleType')
 | 
						|
        return c
 | 
						|
 | 
						|
    def is_module(self):
 | 
						|
        return True
 | 
						|
 | 
						|
    def is_stub(self):
 | 
						|
        return False
 | 
						|
 | 
						|
    @property  # type: ignore[misc]
 | 
						|
    @inference_state_method_cache()
 | 
						|
    def name(self):
 | 
						|
        return self._module_name_class(self, self.string_names[-1])
 | 
						|
 | 
						|
    @inference_state_method_cache()
 | 
						|
    def _module_attributes_dict(self):
 | 
						|
        names = ['__package__', '__doc__', '__name__']
 | 
						|
        # All the additional module attributes are strings.
 | 
						|
        dct = dict((n, _ModuleAttributeName(self, n)) for n in names)
 | 
						|
        path = self.py__file__()
 | 
						|
        if path is not None:
 | 
						|
            dct['__file__'] = _ModuleAttributeName(self, '__file__', str(path))
 | 
						|
        return dct
 | 
						|
 | 
						|
    def iter_star_filters(self):
 | 
						|
        for star_module in self.star_imports():
 | 
						|
            f = next(star_module.get_filters(), None)
 | 
						|
            assert f is not None
 | 
						|
            yield f
 | 
						|
 | 
						|
    # I'm not sure if the star import cache is really that effective anymore
 | 
						|
    # with all the other really fast import caches. Recheck. Also we would need
 | 
						|
    # to push the star imports into InferenceState.module_cache, if we reenable this.
 | 
						|
    @inference_state_method_cache([])
 | 
						|
    def star_imports(self):
 | 
						|
        from jedi.inference.imports import Importer
 | 
						|
 | 
						|
        modules = []
 | 
						|
        module_context = self.as_context()
 | 
						|
        for i in self.tree_node.iter_imports():
 | 
						|
            if i.is_star_import():
 | 
						|
                new = Importer(
 | 
						|
                    self.inference_state,
 | 
						|
                    import_path=i.get_paths()[-1],
 | 
						|
                    module_context=module_context,
 | 
						|
                    level=i.level
 | 
						|
                ).follow()
 | 
						|
 | 
						|
                for module in new:
 | 
						|
                    if isinstance(module, ModuleValue):
 | 
						|
                        modules += module.star_imports()
 | 
						|
                modules += new
 | 
						|
        return modules
 | 
						|
 | 
						|
    def get_qualified_names(self):
 | 
						|
        """
 | 
						|
        A module doesn't have a qualified name, but it's important to note that
 | 
						|
        it's reachable and not `None`. With this information we can add
 | 
						|
        qualified names on top for all value children.
 | 
						|
        """
 | 
						|
        return ()
 | 
						|
 | 
						|
 | 
						|
class ModuleValue(ModuleMixin, TreeValue):
 | 
						|
    api_type = 'module'
 | 
						|
 | 
						|
    def __init__(self, inference_state, module_node, code_lines, file_io=None,
 | 
						|
                 string_names=None, is_package=False):
 | 
						|
        super().__init__(
 | 
						|
            inference_state,
 | 
						|
            parent_context=None,
 | 
						|
            tree_node=module_node
 | 
						|
        )
 | 
						|
        self.file_io = file_io
 | 
						|
        if file_io is None:
 | 
						|
            self._path: Optional[Path] = None
 | 
						|
        else:
 | 
						|
            self._path = file_io.path
 | 
						|
        self.string_names = string_names  # Optional[Tuple[str, ...]]
 | 
						|
        self.code_lines = code_lines
 | 
						|
        self._is_package = is_package
 | 
						|
 | 
						|
    def is_stub(self):
 | 
						|
        if self._path is not None and self._path.suffix == '.pyi':
 | 
						|
            # Currently this is the way how we identify stubs when e.g. goto is
 | 
						|
            # used in them. This could be changed if stubs would be identified
 | 
						|
            # sooner and used as StubModuleValue.
 | 
						|
            return True
 | 
						|
        return super().is_stub()
 | 
						|
 | 
						|
    def py__name__(self):
 | 
						|
        if self.string_names is None:
 | 
						|
            return None
 | 
						|
        return '.'.join(self.string_names)
 | 
						|
 | 
						|
    def py__file__(self) -> Optional[Path]:
 | 
						|
        """
 | 
						|
        In contrast to Python's __file__ can be None.
 | 
						|
        """
 | 
						|
        if self._path is None:
 | 
						|
            return None
 | 
						|
 | 
						|
        return self._path.absolute()
 | 
						|
 | 
						|
    def is_package(self):
 | 
						|
        return self._is_package
 | 
						|
 | 
						|
    def py__package__(self):
 | 
						|
        if self.string_names is None:
 | 
						|
            return []
 | 
						|
 | 
						|
        if self._is_package:
 | 
						|
            return self.string_names
 | 
						|
        return self.string_names[:-1]
 | 
						|
 | 
						|
    def py__path__(self):
 | 
						|
        """
 | 
						|
        In case of a package, this returns Python's __path__ attribute, which
 | 
						|
        is a list of paths (strings).
 | 
						|
        Returns None if the module is not a package.
 | 
						|
        """
 | 
						|
        if not self._is_package:
 | 
						|
            return None
 | 
						|
 | 
						|
        # A namespace package is typically auto generated and ~10 lines long.
 | 
						|
        first_few_lines = ''.join(self.code_lines[:50])
 | 
						|
        # these are strings that need to be used for namespace packages,
 | 
						|
        # the first one is ``pkgutil``, the second ``pkg_resources``.
 | 
						|
        options = ('declare_namespace(__name__)', 'extend_path(__path__')
 | 
						|
        if options[0] in first_few_lines or options[1] in first_few_lines:
 | 
						|
            # It is a namespace, now try to find the rest of the
 | 
						|
            # modules on sys_path or whatever the search_path is.
 | 
						|
            paths = set()
 | 
						|
            for s in self.inference_state.get_sys_path():
 | 
						|
                other = os.path.join(s, self.name.string_name)
 | 
						|
                if os.path.isdir(other):
 | 
						|
                    paths.add(other)
 | 
						|
            if paths:
 | 
						|
                return list(paths)
 | 
						|
            # Nested namespace packages will not be supported. Nobody ever
 | 
						|
            # asked for it and in Python 3 they are there without using all the
 | 
						|
            # crap above.
 | 
						|
 | 
						|
        # Default to the of this file.
 | 
						|
        file = self.py__file__()
 | 
						|
        assert file is not None  # Shouldn't be a package in the first place.
 | 
						|
        return [os.path.dirname(file)]
 | 
						|
 | 
						|
    def _as_context(self):
 | 
						|
        return ModuleContext(self)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return "<%s: %s@%s-%s is_stub=%s>" % (
 | 
						|
            self.__class__.__name__, self.py__name__(),
 | 
						|
            self.tree_node.start_pos[0], self.tree_node.end_pos[0],
 | 
						|
            self.is_stub()
 | 
						|
        )
 |