Move import logic around a bit

This commit is contained in:
Dave Halter
2018-07-23 03:54:10 +02:00
parent 7711167052
commit 8a9202135b
3 changed files with 152 additions and 106 deletions

View File

@@ -12,6 +12,7 @@ This module also supports import autocompletion, which means to complete
statements like ``from datetim`` (cursor at the end would return ``datetime``).
"""
import os
from functools import partial
from parso.python import tree
from parso.tree import search_ancestor
@@ -21,7 +22,6 @@ from jedi._compatibility import (FileNotFoundError, ImplicitNSInfo,
force_unicode, unicode)
from jedi import debug
from jedi import settings
from jedi.common.utils import traverse_parents
from jedi.parser_utils import get_cached_code_lines
from jedi.evaluate import sys_path
from jedi.evaluate import helpers
@@ -282,114 +282,14 @@ class Importer(object):
if not self.import_path:
return NO_CONTEXTS
return self._do_import(self.import_path, self.sys_path_with_modifications())
return import_module(
self._evaluator,
self.import_path,
self.sys_path_with_modifications(),
partial(_add_error, self.module_context),
def _do_import(self, import_path, sys_path):
"""
This method is very similar to importlib's `_gcd_import`.
"""
import_parts = [
force_unicode(i.value if isinstance(i, tree.Name) else i)
for i in import_path
]
# Handle "magic" Flask extension imports:
# ``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
if len(import_path) > 2 and import_parts[:2] == ['flask', 'ext']:
# New style.
ipath = ('flask_' + str(import_parts[2]),) + import_path[3:]
modules = self._do_import(ipath, sys_path)
if modules:
return modules
else:
# Old style
return self._do_import(('flaskext',) + import_path[2:], sys_path)
if import_parts[0] in settings.auto_import_modules:
module = _load_module(
self._evaluator,
import_names=import_parts,
sys_path=sys_path,
)
return ContextSet(module)
module_name = '.'.join(import_parts)
try:
return ContextSet(self._evaluator.module_cache.get(module_name))
except KeyError:
pass
if len(import_path) > 1:
# This is a recursive way of importing that works great with
# the module cache.
bases = self._do_import(import_path[:-1], sys_path)
if not bases:
return NO_CONTEXTS
# We can take the first element, because only the os special
# case yields multiple modules, which is not important for
# further imports.
parent_module = list(bases)[0]
# This is a huge exception, we follow a nested import
# ``os.path``, because it's a very important one in Python
# that is being achieved by messing with ``sys.modules`` in
# ``os``.
if import_parts == ['os', 'path']:
return parent_module.py__getattribute__('path')
try:
method = parent_module.py__path__
except AttributeError:
# The module is not a package.
_add_error(self.module_context, import_path[-1])
return NO_CONTEXTS
else:
paths = method()
debug.dbg('search_module %s in paths %s', module_name, paths)
for path in paths:
# At the moment we are only using one path. So this is
# not important to be correct.
if not isinstance(path, list):
path = [path]
code, module_path, is_pkg = self._evaluator.compiled_subprocess.get_module_info(
string=import_parts[-1],
path=path,
full_name=module_name,
is_global_search=False,
)
if module_path is not None:
break
else:
_add_error(self.module_context, import_path[-1])
return NO_CONTEXTS
else:
debug.dbg('global search_module %s in %s', import_parts[-1], self.file_path)
# 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 = self._evaluator.compiled_subprocess.get_module_info(
string=import_parts[-1],
full_name=module_name,
sys_path=sys_path,
is_global_search=True,
)
if module_path is None:
# The module is not a package.
_add_error(self.module_context, import_path[-1])
return NO_CONTEXTS
module = _load_module(
self._evaluator, module_path, code, sys_path,
import_names=import_parts,
safe_module_name=True,
)
if module is None:
# The file might raise an ImportError e.g. and therefore not be
# importable.
return NO_CONTEXTS
return ContextSet(module)
def _generate_name(self, name, in_module=None):
# Create a pseudo import to be able to follow them.
if in_module is None:
@@ -478,6 +378,111 @@ class Importer(object):
return names
def import_module(evaluator, import_path, sys_path, add_error_callback):
"""
This method is very similar to importlib's `_gcd_import`.
"""
import_parts = [
force_unicode(i.value if isinstance(i, tree.Name) else i)
for i in import_path
]
if len(import_path) > 2 and import_parts[:2] == ['flask', 'ext']:
# New style.
ipath = ('flask_' + str(import_parts[2]),) + import_path[3:]
modules = import_module(evaluator, ipath, sys_path, add_error_callback)
if modules:
return modules
else:
# Old style
return import_module(evaluator, ('flaskext',) + import_path[2:], sys_path, add_error_callback)
if import_parts[0] in settings.auto_import_modules:
module = _load_module(
evaluator,
import_names=import_parts,
sys_path=sys_path,
)
return ContextSet(module)
module_name = '.'.join(import_parts)
try:
return ContextSet(evaluator.module_cache.get(module_name))
except KeyError:
pass
if len(import_path) > 1:
# This is a recursive way of importing that works great with
# the module cache.
bases = import_module(evaluator, import_path[:-1], sys_path, add_error_callback)
if not bases:
return NO_CONTEXTS
# We can take the first element, because only the os special
# case yields multiple modules, which is not important for
# further imports.
parent_module = list(bases)[0]
# This is a huge exception, we follow a nested import
# ``os.path``, because it's a very important one in Python
# that is being achieved by messing with ``sys.modules`` in
# ``os``.
if import_parts == ['os', 'path']:
return parent_module.py__getattribute__('path')
try:
method = parent_module.py__path__
except AttributeError:
# The module is not a package.
add_error_callback(import_path[-1])
return NO_CONTEXTS
else:
paths = method()
debug.dbg('search_module %s in paths %s', module_name, paths)
for path in paths:
# At the moment we are only using one path. So this is
# not important to be correct.
if not isinstance(path, list):
path = [path]
code, module_path, is_pkg = evaluator.compiled_subprocess.get_module_info(
string=import_parts[-1],
path=path,
full_name=module_name,
is_global_search=False,
)
if module_path is not None:
break
else:
add_error_callback(import_path[-1])
return NO_CONTEXTS
else:
debug.dbg('global search_module %s', import_parts[-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_parts[-1],
full_name=module_name,
sys_path=sys_path,
is_global_search=True,
)
if module_path is None:
# The module is not a package.
add_error_callback(import_path[-1])
return NO_CONTEXTS
module = _load_module(
evaluator, module_path, code, sys_path,
import_names=import_parts,
safe_module_name=True,
)
if module is None:
# The file might raise an ImportError e.g. and therefore not be
# importable.
return NO_CONTEXTS
return ContextSet(module)
def _load_module(evaluator, path=None, code=None, sys_path=None,
import_names=None, safe_module_name=False):
if import_names is None:

View File

@@ -11,3 +11,11 @@ class BasePlugin(object):
Decorates the execute(context, arguments) function.
"""
return callback
def import_module(callback):
"""
Decorates the
import_module(evaluator, import_path, sys_path, add_error_callback)
function.
"""
return callback

33
jedi/plugins/typeshed.py Normal file
View File

@@ -0,0 +1,33 @@
from jedi.plugins.base import BasePlugin
from jedi.evaluate.base_context import Context, ContextSet
class TypeshedPlugin(BasePlugin):
def foo():
return
class TypeshedProxy(object):
def __init__(self, parent_context, context, typeshed_context):
self.parent_context = parent_context
self._context = context
self._typeshed_context = typeshed_context
# We have to overwrite everything that has to do with trailers, name
# lookups and filters to make it possible to route name lookups towards
# compiled objects and the rest towards tree node contexts.
def py__getattribute__(self, *args, **kwargs):
context_results = self._context.py__getattribute__(
*args, **kwargs
)
typeshed_results = self._typeshed_context = py__getattribute__(
*args, **kwargs
)
print(context_results, typeshed_results)
return context_results
def __getattr__(self, name):
return getattr(self._context, name)
def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self.access_handle.get_repr())