Import names are now always strings

This commit is contained in:
Dave Halter
2018-07-23 22:40:24 +02:00
parent 27ab4ba339
commit c88afb71c9
3 changed files with 54 additions and 44 deletions

View File

@@ -120,6 +120,8 @@ class Evaluator(object):
self.reset_recursion_limitations() self.reset_recursion_limitations()
self.allow_different_encoding = True self.allow_different_encoding = True
# Plugin API
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager
plugin_callbacks = plugin_manager.get_callbacks(self) plugin_callbacks = plugin_manager.get_callbacks(self)
self.execute = plugin_callbacks.decorate('execute', callback=execute) self.execute = plugin_callbacks.decorate('execute', callback=execute)

View File

@@ -152,7 +152,7 @@ def _add_error(context, name, message=None):
if message is None: if message is None:
name_str = str(name.value) if isinstance(name, tree.Name) else name name_str = str(name.value) if isinstance(name, tree.Name) else name
message = 'No module named ' + name_str message = 'No module named ' + name_str
if hasattr(name, 'parent'): if hasattr(name, 'parent') and context is not None:
analysis.add(context, 'import-error', name, message) analysis.add(context, 'import-error', name, message)
else: else:
debug.warning('ImportError without origin: ' + message) debug.warning('ImportError without origin: ' + message)
@@ -282,14 +282,26 @@ class Importer(object):
if not self.import_path: if not self.import_path:
return NO_CONTEXTS return NO_CONTEXTS
return self._evaluator.import_module( import_names = tuple(
self._evaluator, force_unicode(i.value if isinstance(i, tree.Name) else i)
self.import_path, for i in self.import_path
self.sys_path_with_modifications(),
partial(_add_error, self.module_context),
) )
try:
return self._evaluator.import_module(
self._evaluator,
import_names,
self.sys_path_with_modifications(),
)
except JediImportError as e:
# Try to look up the name that was responsible for the import
# error. Since this is a plugin API, we intentionally just use
# 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)
return NO_CONTEXTS
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.
if in_module is None: if in_module is None:
@@ -378,33 +390,33 @@ class Importer(object):
return names return names
def import_module(evaluator, import_path, sys_path, add_error_callback): class JediImportError(Exception):
def __init__(self, import_names):
self.import_names = import_names
def import_module(evaluator, import_names, sys_path):
""" """
This method is very similar to importlib's `_gcd_import`. This method is very similar to importlib's `_gcd_import`.
""" """
import_parts = [ if import_names[0] in settings.auto_import_modules:
force_unicode(i.value if isinstance(i, tree.Name) else i)
for i in import_path
]
if import_parts[0] in settings.auto_import_modules:
module = _load_module( module = _load_module(
evaluator, evaluator,
import_names=import_parts, import_names=import_names,
sys_path=sys_path, sys_path=sys_path,
) )
return ContextSet(module) return ContextSet(module)
module_name = '.'.join(import_parts) module_name = '.'.join(import_names)
try: try:
return ContextSet(evaluator.module_cache.get(module_name)) return ContextSet(evaluator.module_cache.get(module_name))
except KeyError: except KeyError:
pass pass
if len(import_path) > 1: if len(import_names) > 1:
# This is a recursive way of importing that works great with # This is a recursive way of importing that works great with
# the module cache. # the module cache.
bases = evaluator.import_module(evaluator, import_path[:-1], sys_path, add_error_callback) bases = evaluator.import_module(evaluator, import_names[:-1], sys_path)
if not bases: if not bases:
return NO_CONTEXTS return NO_CONTEXTS
# We can take the first element, because only the os special # We can take the first element, because only the os special
@@ -416,15 +428,14 @@ def import_module(evaluator, import_path, sys_path, add_error_callback):
# ``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_parts == ['os', 'path']: if import_names == ('os', 'path'):
return parent_module.py__getattribute__('path') return parent_module.py__getattribute__('path')
try: try:
method = parent_module.py__path__ method = parent_module.py__path__
except AttributeError: except AttributeError:
# The module is not a package. # The module is not a package.
add_error_callback(import_path[-1]) raise JediImportError(import_names)
return NO_CONTEXTS
else: else:
paths = method() paths = method()
debug.dbg('search_module %s in paths %s', module_name, paths) debug.dbg('search_module %s in paths %s', module_name, paths)
@@ -434,7 +445,7 @@ def import_module(evaluator, import_path, sys_path, add_error_callback):
if not isinstance(path, list): if not isinstance(path, list):
path = [path] path = [path]
code, module_path, is_pkg = evaluator.compiled_subprocess.get_module_info( code, module_path, is_pkg = evaluator.compiled_subprocess.get_module_info(
string=import_parts[-1], string=import_names[-1],
path=path, path=path,
full_name=module_name, full_name=module_name,
is_global_search=False, is_global_search=False,
@@ -442,26 +453,23 @@ def import_module(evaluator, import_path, sys_path, add_error_callback):
if module_path is not None: if module_path is not None:
break break
else: else:
add_error_callback(import_path[-1]) raise JediImportError(import_names)
return NO_CONTEXTS
else: else:
debug.dbg('global search_module %s', import_parts[-1]) debug.dbg('global search_module %s', import_names[-1])
# Override the sys.path. It works only good that way. # Override the sys.path. It works only good that way.
# Injecting the path directly into `find_module` did not work. # Injecting the path directly into `find_module` did not work.
code, module_path, is_pkg = evaluator.compiled_subprocess.get_module_info( code, module_path, is_pkg = evaluator.compiled_subprocess.get_module_info(
string=import_parts[-1], string=import_names[-1],
full_name=module_name, full_name=module_name,
sys_path=sys_path, sys_path=sys_path,
is_global_search=True, is_global_search=True,
) )
if module_path is None: if module_path is None:
# The module is not a package. raise JediImportError(import_names)
add_error_callback(import_path[-1])
return NO_CONTEXTS
module = _load_module( module = _load_module(
evaluator, module_path, code, sys_path, evaluator, module_path, code, sys_path,
import_names=import_parts, import_names=import_names,
safe_module_name=True, safe_module_name=True,
) )

View File

@@ -1,6 +1,7 @@
from parso.python.tree import Name from parso.python.tree import Name
from jedi.plugins.base import BasePlugin from jedi.plugins.base import BasePlugin
from jedi.evaluate.imports import JediImportError
class FlaskPlugin(BasePlugin): class FlaskPlugin(BasePlugin):
@@ -9,21 +10,20 @@ 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_path, *args, **kwargs): def wrapper(evaluator, import_names, *args, **kwargs):
import_parts = [ if len(import_names) > 2 and import_names[:2] == ('flask', 'ext'):
i.value if isinstance(i, Name) else i
for i in import_path
]
if len(import_path) > 2 and import_parts[:2] == ['flask', 'ext']:
# New style. # New style.
ipath = ('flask_' + str(import_parts[2]),) + import_path[3:] ipath = ('flask_' + str(import_names[2]),) + import_names[3:]
modules = callback(evaluator, ipath, *args, **kwargs) try:
if modules: return callback(evaluator, ipath, *args, **kwargs)
return modules except JediImportError:
else:
# Old style # Old style
return callback(evaluator, ('flaskext',) + import_path[2:], *args, **kwargs) return callback(
return callback(evaluator, import_path, *args, **kwargs) evaluator,
('flaskext',) + import_names[2:],
*args,
**kwargs
)
return callback(evaluator, import_names, *args, **kwargs)
return wrapper return wrapper