| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592 |
- """
- :mod:`jedi.inference.imports` is here to resolve import statements and return
- the modules/classes/functions/whatever, which they stand for. However there's
- not any actual importing done. This module is about finding modules in the
- filesystem. This can be quite tricky sometimes, because Python imports are not
- always that simple.
- This module also supports import autocompletion, which means to complete
- statements like ``from datetim`` (cursor at the end would return ``datetime``).
- """
- import os
- from pathlib import Path
- from parso.python import tree
- from parso.tree import search_ancestor
- from jedi import debug
- from jedi import settings
- from jedi.file_io import FolderIO
- from jedi.parser_utils import get_cached_code_lines
- from jedi.inference import sys_path
- from jedi.inference import helpers
- from jedi.inference import compiled
- from jedi.inference import analysis
- from jedi.inference.utils import unite
- from jedi.inference.cache import inference_state_method_cache
- from jedi.inference.names import ImportName, SubModuleName
- from jedi.inference.base_value import ValueSet, NO_VALUES
- from jedi.inference.gradual.typeshed import import_module_decorator, \
- create_stub_module, parse_stub_module
- from jedi.inference.compiled.subprocess.functions import ImplicitNSInfo
- from jedi.plugins import plugin_manager
- class ModuleCache:
- def __init__(self):
- self._name_cache = {}
- def add(self, string_names, value_set):
- if string_names is not None:
- self._name_cache[string_names] = value_set
- def get(self, string_names):
- return self._name_cache.get(string_names)
- # This memoization is needed, because otherwise we will infinitely loop on
- # certain imports.
- @inference_state_method_cache(default=NO_VALUES)
- def infer_import(context, tree_name):
- module_context = context.get_root_context()
- from_import_name, import_path, level, values = \
- _prepare_infer_import(module_context, tree_name)
- if values:
- if from_import_name is not None:
- values = values.py__getattribute__(
- from_import_name,
- name_context=context,
- analysis_errors=False
- )
- if not values:
- path = import_path + (from_import_name,)
- importer = Importer(context.inference_state, path, module_context, level)
- values = importer.follow()
- debug.dbg('after import: %s', values)
- return values
- @inference_state_method_cache(default=[])
- def goto_import(context, tree_name):
- module_context = context.get_root_context()
- from_import_name, import_path, level, values = \
- _prepare_infer_import(module_context, tree_name)
- if not values:
- return []
- if from_import_name is not None:
- names = unite([
- c.goto(
- from_import_name,
- name_context=context,
- analysis_errors=False
- ) for c in values
- ])
- # Avoid recursion on the same names.
- if names and not any(n.tree_name is tree_name for n in names):
- return names
- path = import_path + (from_import_name,)
- importer = Importer(context.inference_state, path, module_context, level)
- values = importer.follow()
- return set(s.name for s in values)
- def _prepare_infer_import(module_context, tree_name):
- import_node = search_ancestor(tree_name, 'import_name', 'import_from')
- import_path = import_node.get_path_for_name(tree_name)
- from_import_name = None
- try:
- from_names = import_node.get_from_names()
- except AttributeError:
- # Is an import_name
- pass
- else:
- if len(from_names) + 1 == len(import_path):
- # We have to fetch the from_names part first and then check
- # if from_names exists in the modules.
- from_import_name = import_path[-1]
- import_path = from_names
- importer = Importer(module_context.inference_state, tuple(import_path),
- module_context, import_node.level)
- return from_import_name, tuple(import_path), import_node.level, importer.follow()
- def _add_error(value, name, message):
- if hasattr(name, 'parent') and value is not None:
- analysis.add(value, 'import-error', name, message)
- else:
- debug.warning('ImportError without origin: ' + message)
- def _level_to_base_import_path(project_path, directory, level):
- """
- In case the level is outside of the currently known package (something like
- import .....foo), we can still try our best to help the user for
- completions.
- """
- for i in range(level - 1):
- old = directory
- directory = os.path.dirname(directory)
- if old == directory:
- return None, None
- d = directory
- level_import_paths = []
- # Now that we are on the level that the user wants to be, calculate the
- # import path for it.
- while True:
- if d == project_path:
- return level_import_paths, d
- dir_name = os.path.basename(d)
- if dir_name:
- level_import_paths.insert(0, dir_name)
- d = os.path.dirname(d)
- else:
- return None, directory
- class Importer:
- def __init__(self, inference_state, import_path, module_context, level=0):
- """
- An implementation similar to ``__import__``. Use `follow`
- to actually follow the imports.
- *level* specifies whether to use absolute or relative imports. 0 (the
- default) means only perform absolute imports. Positive values for level
- indicate the number of parent directories to search relative to the
- directory of the module calling ``__import__()`` (see PEP 328 for the
- details).
- :param import_path: List of namespaces (strings or Names).
- """
- debug.speed('import %s %s' % (import_path, module_context))
- self._inference_state = inference_state
- self.level = level
- self._module_context = module_context
- self._fixed_sys_path = None
- self._infer_possible = True
- if level:
- base = module_context.get_value().py__package__()
- # We need to care for two cases, the first one is if it's a valid
- # Python import. This import has a properly defined module name
- # chain like `foo.bar.baz` and an import in baz is made for
- # `..lala.` It can then resolve to `foo.bar.lala`.
- # The else here is a heuristic for all other cases, if for example
- # in `foo` you search for `...bar`, it's obviously out of scope.
- # However since Jedi tries to just do it's best, we help the user
- # here, because he might have specified something wrong in his
- # project.
- if level <= len(base):
- # Here we basically rewrite the level to 0.
- base = tuple(base)
- if level > 1:
- base = base[:-level + 1]
- import_path = base + tuple(import_path)
- else:
- path = module_context.py__file__()
- project_path = self._inference_state.project.path
- import_path = list(import_path)
- if path is None:
- # If no path is defined, our best guess is that the current
- # file is edited by a user on the current working
- # directory. We need to add an initial path, because it
- # will get removed as the name of the current file.
- directory = project_path
- else:
- directory = os.path.dirname(path)
- base_import_path, base_directory = _level_to_base_import_path(
- project_path, directory, level,
- )
- if base_directory is None:
- # Everything is lost, the relative import does point
- # somewhere out of the filesystem.
- self._infer_possible = False
- else:
- self._fixed_sys_path = [base_directory]
- if base_import_path is None:
- if import_path:
- _add_error(
- module_context, import_path[0],
- message='Attempted relative import beyond top-level package.'
- )
- else:
- import_path = base_import_path + import_path
- self.import_path = import_path
- @property
- def _str_import_path(self):
- """Returns the import path as pure strings instead of `Name`."""
- return tuple(
- name.value if isinstance(name, tree.Name) else name
- for name in self.import_path
- )
- def _sys_path_with_modifications(self, is_completion):
- if self._fixed_sys_path is not None:
- return self._fixed_sys_path
- return (
- # For import completions we don't want to see init paths, but for
- # inference we want to show the user as much as possible.
- # See GH #1446.
- self._inference_state.get_sys_path(add_init_paths=not is_completion)
- + [
- str(p) for p
- in sys_path.check_sys_path_modifications(self._module_context)
- ]
- )
- def follow(self):
- if not self.import_path:
- if self._fixed_sys_path:
- # This is a bit of a special case, that maybe should be
- # revisited. If the project path is wrong or the user uses
- # relative imports the wrong way, we might end up here, where
- # the `fixed_sys_path == project.path` in that case we kind of
- # use the project.path.parent directory as our path. This is
- # usually not a problem, except if imports in other places are
- # using the same names. Example:
- #
- # foo/ < #1
- # - setup.py
- # - foo/ < #2
- # - __init__.py
- # - foo.py < #3
- #
- # If the top foo is our project folder and somebody uses
- # `from . import foo` in `setup.py`, it will resolve to foo #2,
- # which means that the import for foo.foo is cached as
- # `__init__.py` (#2) and not as `foo.py` (#3). This is usually
- # not an issue, because this case is probably pretty rare, but
- # might be an issue for some people.
- #
- # However for most normal cases where we work with different
- # file names, this code path hits where we basically change the
- # project path to an ancestor of project path.
- from jedi.inference.value.namespace import ImplicitNamespaceValue
- import_path = (os.path.basename(self._fixed_sys_path[0]),)
- ns = ImplicitNamespaceValue(
- self._inference_state,
- string_names=import_path,
- paths=self._fixed_sys_path,
- )
- return ValueSet({ns})
- return NO_VALUES
- if not self._infer_possible:
- return NO_VALUES
- # Check caches first
- from_cache = self._inference_state.stub_module_cache.get(self._str_import_path)
- if from_cache is not None:
- return ValueSet({from_cache})
- from_cache = self._inference_state.module_cache.get(self._str_import_path)
- if from_cache is not None:
- return from_cache
- sys_path = self._sys_path_with_modifications(is_completion=False)
- return import_module_by_names(
- self._inference_state, self.import_path, sys_path, self._module_context
- )
- def _get_module_names(self, search_path=None, in_module=None):
- """
- Get the names of all modules in the search_path. This means file names
- and not names defined in the files.
- """
- if search_path is None:
- sys_path = self._sys_path_with_modifications(is_completion=True)
- else:
- sys_path = search_path
- return list(iter_module_names(
- self._inference_state, self._module_context, sys_path,
- module_cls=ImportName if in_module is None else SubModuleName,
- add_builtin_modules=search_path is None and in_module is None,
- ))
- def completion_names(self, inference_state, only_modules=False):
- """
- :param only_modules: Indicates wheter it's possible to import a
- definition that is not defined in a module.
- """
- if not self._infer_possible:
- return []
- names = []
- if self.import_path:
- # flask
- if self._str_import_path == ('flask', 'ext'):
- # List Flask extensions like ``flask_foo``
- for mod in self._get_module_names():
- modname = mod.string_name
- if modname.startswith('flask_'):
- extname = modname[len('flask_'):]
- names.append(ImportName(self._module_context, extname))
- # Now the old style: ``flaskext.foo``
- for dir in self._sys_path_with_modifications(is_completion=True):
- flaskext = os.path.join(dir, 'flaskext')
- if os.path.isdir(flaskext):
- names += self._get_module_names([flaskext])
- values = self.follow()
- for value in values:
- # Non-modules are not completable.
- if value.api_type not in ('module', 'namespace'): # not a module
- continue
- if not value.is_compiled():
- # sub_modules_dict is not implemented for compiled modules.
- names += value.sub_modules_dict().values()
- if not only_modules:
- from jedi.inference.gradual.conversion import convert_values
- both_values = values | convert_values(values)
- for c in both_values:
- for filter in c.get_filters():
- names += filter.values()
- else:
- if self.level:
- # We only get here if the level cannot be properly calculated.
- names += self._get_module_names(self._fixed_sys_path)
- else:
- # This is just the list of global imports.
- names += self._get_module_names()
- return names
- def import_module_by_names(inference_state, import_names, sys_path=None,
- module_context=None, prefer_stubs=True):
- if sys_path is None:
- sys_path = inference_state.get_sys_path()
- str_import_names = tuple(
- i.value if isinstance(i, tree.Name) else i
- for i in import_names
- )
- value_set = [None]
- for i, name in enumerate(import_names):
- value_set = ValueSet.from_sets([
- import_module(
- inference_state,
- str_import_names[:i+1],
- parent_module_value,
- sys_path,
- prefer_stubs=prefer_stubs,
- ) for parent_module_value in value_set
- ])
- if not value_set:
- message = 'No module named ' + '.'.join(str_import_names)
- if module_context is not None:
- _add_error(module_context, name, message)
- else:
- debug.warning(message)
- return NO_VALUES
- return value_set
- @plugin_manager.decorate()
- @import_module_decorator
- def import_module(inference_state, import_names, parent_module_value, sys_path):
- """
- This method is very similar to importlib's `_gcd_import`.
- """
- if import_names[0] in settings.auto_import_modules:
- module = _load_builtin_module(inference_state, import_names, sys_path)
- if module is None:
- return NO_VALUES
- return ValueSet([module])
- module_name = '.'.join(import_names)
- if parent_module_value is None:
- # Override the sys.path. It works only good that way.
- # Injecting the path directly into `find_module` did not work.
- file_io_or_ns, is_pkg = inference_state.compiled_subprocess.get_module_info(
- string=import_names[-1],
- full_name=module_name,
- sys_path=sys_path,
- is_global_search=True,
- )
- if is_pkg is None:
- return NO_VALUES
- else:
- paths = parent_module_value.py__path__()
- if paths is None:
- # The module might not be a package.
- return NO_VALUES
- file_io_or_ns, is_pkg = inference_state.compiled_subprocess.get_module_info(
- string=import_names[-1],
- path=paths,
- full_name=module_name,
- is_global_search=False,
- )
- if is_pkg is None:
- return NO_VALUES
- if isinstance(file_io_or_ns, ImplicitNSInfo):
- from jedi.inference.value.namespace import ImplicitNamespaceValue
- module = ImplicitNamespaceValue(
- inference_state,
- string_names=tuple(file_io_or_ns.name.split('.')),
- paths=file_io_or_ns.paths,
- )
- elif file_io_or_ns is None:
- module = _load_builtin_module(inference_state, import_names, sys_path)
- if module is None:
- return NO_VALUES
- else:
- module = _load_python_module(
- inference_state, file_io_or_ns,
- import_names=import_names,
- is_package=is_pkg,
- )
- if parent_module_value is None:
- debug.dbg('global search_module %s: %s', import_names[-1], module)
- else:
- debug.dbg('search_module %s in paths %s: %s', module_name, paths, module)
- return ValueSet([module])
- def _load_python_module(inference_state, file_io,
- import_names=None, is_package=False):
- module_node = inference_state.parse(
- file_io=file_io,
- cache=True,
- diff_cache=settings.fast_parser,
- cache_path=settings.cache_directory,
- )
- from jedi.inference.value import ModuleValue
- return ModuleValue(
- inference_state, module_node,
- file_io=file_io,
- string_names=import_names,
- code_lines=get_cached_code_lines(inference_state.grammar, file_io.path),
- is_package=is_package,
- )
- def _load_builtin_module(inference_state, import_names=None, sys_path=None):
- project = inference_state.project
- if sys_path is None:
- sys_path = inference_state.get_sys_path()
- if not project._load_unsafe_extensions:
- safe_paths = project._get_base_sys_path(inference_state)
- sys_path = [p for p in sys_path if p in safe_paths]
- dotted_name = '.'.join(import_names)
- assert dotted_name is not None
- module = compiled.load_module(inference_state, dotted_name=dotted_name, sys_path=sys_path)
- if module is None:
- # The file might raise an ImportError e.g. and therefore not be
- # importable.
- return None
- return module
- def load_module_from_path(inference_state, file_io, import_names=None, is_package=None):
- """
- This should pretty much only be used for get_modules_containing_name. It's
- here to ensure that a random path is still properly loaded into the Jedi
- module structure.
- """
- path = Path(file_io.path)
- if import_names is None:
- e_sys_path = inference_state.get_sys_path()
- import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path)
- else:
- assert isinstance(is_package, bool)
- is_stub = path.suffix == '.pyi'
- if is_stub:
- folder_io = file_io.get_parent_folder()
- if folder_io.path.endswith('-stubs'):
- folder_io = FolderIO(folder_io.path[:-6])
- if path.name == '__init__.pyi':
- python_file_io = folder_io.get_file_io('__init__.py')
- else:
- python_file_io = folder_io.get_file_io(import_names[-1] + '.py')
- try:
- v = load_module_from_path(
- inference_state, python_file_io,
- import_names, is_package=is_package
- )
- values = ValueSet([v])
- except FileNotFoundError:
- values = NO_VALUES
- return create_stub_module(
- inference_state, inference_state.latest_grammar, values,
- parse_stub_module(inference_state, file_io), file_io, import_names
- )
- else:
- module = _load_python_module(
- inference_state, file_io,
- import_names=import_names,
- is_package=is_package,
- )
- inference_state.module_cache.add(import_names, ValueSet([module]))
- return module
- def load_namespace_from_path(inference_state, folder_io):
- import_names, is_package = sys_path.transform_path_to_dotted(
- inference_state.get_sys_path(),
- Path(folder_io.path)
- )
- from jedi.inference.value.namespace import ImplicitNamespaceValue
- return ImplicitNamespaceValue(inference_state, import_names, [folder_io.path])
- def follow_error_node_imports_if_possible(context, name):
- error_node = tree.search_ancestor(name, 'error_node')
- if error_node is not None:
- # Get the first command start of a started simple_stmt. The error
- # node is sometimes a small_stmt and sometimes a simple_stmt. Check
- # for ; leaves that start a new statements.
- start_index = 0
- for index, n in enumerate(error_node.children):
- if n.start_pos > name.start_pos:
- break
- if n == ';':
- start_index = index + 1
- nodes = error_node.children[start_index:]
- first_name = nodes[0].get_first_leaf().value
- # Make it possible to infer stuff like `import foo.` or
- # `from foo.bar`.
- if first_name in ('from', 'import'):
- is_import_from = first_name == 'from'
- level, names = helpers.parse_dotted_names(
- nodes,
- is_import_from=is_import_from,
- until_node=name,
- )
- return Importer(
- context.inference_state, names, context.get_root_context(), level).follow()
- return None
- def iter_module_names(inference_state, module_context, search_path,
- module_cls=ImportName, add_builtin_modules=True):
- """
- Get the names of all modules in the search_path. This means file names
- and not names defined in the files.
- """
- # add builtin module names
- if add_builtin_modules:
- for name in inference_state.compiled_subprocess.get_builtin_module_names():
- yield module_cls(module_context, name)
- for name in inference_state.compiled_subprocess.iter_module_names(search_path):
- yield module_cls(module_context, name)
|