1
0
forked from VimPlug/jedi

Refactor some of the import logic so it's possible to load typeshed modules

This commit is contained in:
Dave Halter
2018-07-24 01:19:09 +02:00
parent f72f3f3797
commit 1739ae44f0
3 changed files with 47 additions and 54 deletions

View File

@@ -12,7 +12,6 @@ This module also supports import autocompletion, which means to complete
statements like ``from datetim`` (cursor at the end would return ``datetime``). statements like ``from datetim`` (cursor at the end would return ``datetime``).
""" """
import os import os
from functools import partial
from parso.python import tree from parso.python import tree
from parso.tree import search_ancestor from parso.tree import search_ancestor
@@ -287,20 +286,22 @@ class Importer(object):
for i in self.import_path for i in self.import_path
) )
context_set = [None]
for i, name in enumerate(self.import_path):
try: try:
return self._evaluator.import_module( context_set = ContextSet.from_sets([
self._evaluator.import_module(
self._evaluator, self._evaluator,
import_names, import_names[:i+1],
module_context,
self.sys_path_with_modifications(), self.sys_path_with_modifications(),
) )
except JediImportError as e: for module_context in context_set
# Try to look up the name that was responsible for the import ])
# error. Since this is a plugin API, we intentionally just use except JediImportError:
# strings, because every plugin would need to unpack the names.
name = self.import_path[len(e.import_names) - 1]
_add_error(self.module_context, name) _add_error(self.module_context, name)
return NO_CONTEXTS return NO_CONTEXTS
return context_set
def _generate_name(self, name, in_module=None): def _generate_name(self, name, in_module=None):
# Create a pseudo import to be able to follow them. # Create a pseudo import to be able to follow them.
@@ -355,7 +356,8 @@ class Importer(object):
if context.api_type != 'module': # not a module if context.api_type != 'module': # not a module
continue continue
# namespace packages # namespace packages
if isinstance(context, ModuleContext) and context.py__file__().endswith('__init__.py'): if isinstance(context, ModuleContext) \
and context.py__file__().endswith('__init__.py'):
paths = context.py__path__() paths = context.py__path__()
names += self._get_module_names(paths, in_module=context) names += self._get_module_names(paths, in_module=context)
@@ -395,7 +397,7 @@ class JediImportError(Exception):
self.import_names = import_names self.import_names = import_names
def import_module(evaluator, import_names, sys_path): def import_module(evaluator, import_names, module_context, sys_path):
""" """
This method is very similar to importlib's `_gcd_import`. This method is very similar to importlib's `_gcd_import`.
""" """
@@ -413,19 +415,21 @@ def import_module(evaluator, import_names, sys_path):
except KeyError: except KeyError:
pass pass
if len(import_names) > 1: if module_context is None:
# This is a recursive way of importing that works great with debug.dbg('global search_module %s', import_names[-1])
# the module cache. # Override the sys.path. It works only good that way.
bases = evaluator.import_module(evaluator, import_names[:-1], sys_path) # Injecting the path directly into `find_module` did not work.
if not bases: code, module_path, is_pkg = evaluator.compiled_subprocess.get_module_info(
return NO_CONTEXTS string=import_names[-1],
# We can take the first element, because only the os special full_name=module_name,
# case yields multiple modules, which is not important for sys_path=sys_path,
# further imports. is_global_search=True,
parent_module = list(bases)[0] )
if module_path is None:
raise JediImportError(import_names)
else:
try: try:
method = parent_module.py__path__ method = module_context.py__path__
except AttributeError: except AttributeError:
# The module is not a package. # The module is not a package.
raise JediImportError(import_names) raise JediImportError(import_names)
@@ -447,18 +451,6 @@ def import_module(evaluator, import_names, sys_path):
break break
else: else:
raise JediImportError(import_names) raise JediImportError(import_names)
else:
debug.dbg('global search_module %s', import_names[-1])
# Override the sys.path. It works only good that way.
# Injecting the path directly into `find_module` did not work.
code, module_path, is_pkg = evaluator.compiled_subprocess.get_module_info(
string=import_names[-1],
full_name=module_name,
sys_path=sys_path,
is_global_search=True,
)
if module_path is None:
raise JediImportError(import_names)
module = _load_module( module = _load_module(
evaluator, module_path, code, sys_path, evaluator, module_path, code, sys_path,

View File

@@ -10,20 +10,21 @@ class FlaskPlugin(BasePlugin):
Handle "magic" Flask extension imports: Handle "magic" Flask extension imports:
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``. ``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
""" """
def wrapper(evaluator, import_names, *args, **kwargs): def wrapper(evaluator, import_names, module_context, sys_path):
if len(import_names) > 2 and import_names[:2] == ('flask', 'ext'): if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
# New style. # New style.
ipath = ('flask_' + str(import_names[2]),) + import_names[3:] ipath = ('flask_' + import_names[2]),
try: try:
return callback(evaluator, ipath, *args, **kwargs) return callback(evaluator, ipath, None, sys_path)
except JediImportError: except JediImportError:
# Old style context_set = callback(evaluator, ('flaskext',), None, sys_path)
# If context_set has no content a JediImportError is raised
# which should be caught anyway by the caller.
return callback( return callback(
evaluator, evaluator,
('flaskext',) + import_names[2:], ('flaskext', import_names[2]),
*args, next(iter(context_set)),
**kwargs sys_path
) )
return callback(evaluator, import_names, *args, **kwargs) return callback(evaluator, import_names, module_context, sys_path)
return wrapper return wrapper

View File

@@ -78,14 +78,14 @@ class StdlibPlugin(BasePlugin):
return wrapper return wrapper
def import_module(self, callback): def import_module(self, callback):
def wrapper(evaluator, import_names, sys_path): def wrapper(evaluator, import_names, module_context, sys_path):
# This is a huge exception, we follow a nested import # This is a huge exception, we follow a nested import
# ``os.path``, because it's a very important one in Python # ``os.path``, because it's a very important one in Python
# that is being achieved by messing with ``sys.modules`` in # that is being achieved by messing with ``sys.modules`` in
# ``os``. # ``os``.
if import_names == ('os', 'path'): if import_names == ('os', 'path'):
return callback(evaluator, ('os',), sys_path).py__getattribute__('path') return module_context.py__getattribute__('path')
return callback(evaluator, import_names, sys_path) return callback(evaluator, import_names, module_context, sys_path)
return wrapper return wrapper