mirror of
https://github.com/davidhalter/jedi.git
synced 2026-05-25 01:38:36 +08:00
Fix nested namespace packages. At least now there's no error anymore. Fixes #743.
This commit is contained in:
@@ -278,20 +278,17 @@ class Importer(object):
|
|||||||
# We can take the first element, because only the os special
|
# We can take the first element, because only the os special
|
||||||
# case yields multiple modules, which is not important for
|
# case yields multiple modules, which is not important for
|
||||||
# further imports.
|
# further imports.
|
||||||
base = list(bases)[0]
|
parent_module = list(bases)[0]
|
||||||
|
|
||||||
# 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 [str(i) for i in import_path] == ['os', 'path']:
|
if [str(i) for i in import_path] == ['os', 'path']:
|
||||||
return self._evaluator.find_types(base, 'path')
|
return self._evaluator.find_types(parent_module, 'path')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# It's possible that by giving it always the sys path (and not
|
paths = parent_module.py__path__()
|
||||||
# the __path__ attribute of the parent, we get wrong results
|
|
||||||
# and nested namespace packages don't work. But I'm not sure.
|
|
||||||
paths = base.py__path__(sys_path)
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# The module is not a package.
|
# The module is not a package.
|
||||||
_add_error(self._evaluator, import_path[-1])
|
_add_error(self._evaluator, import_path[-1])
|
||||||
@@ -311,6 +308,7 @@ class Importer(object):
|
|||||||
_add_error(self._evaluator, import_path[-1])
|
_add_error(self._evaluator, import_path[-1])
|
||||||
return set()
|
return set()
|
||||||
else:
|
else:
|
||||||
|
parent_module = None
|
||||||
try:
|
try:
|
||||||
debug.dbg('search_module %s in %s', import_parts[-1], self.file_path)
|
debug.dbg('search_module %s in %s', import_parts[-1], self.file_path)
|
||||||
# Override the sys.path. It works only good that way.
|
# Override the sys.path. It works only good that way.
|
||||||
@@ -341,7 +339,7 @@ class Importer(object):
|
|||||||
if module_file is None and not module_path.endswith(('.py', '.zip', '.egg')):
|
if module_file is None and not module_path.endswith(('.py', '.zip', '.egg')):
|
||||||
module = compiled.load_module(self._evaluator, module_path)
|
module = compiled.load_module(self._evaluator, module_path)
|
||||||
else:
|
else:
|
||||||
module = _load_module(self._evaluator, module_path, source, sys_path)
|
module = _load_module(self._evaluator, module_path, source, sys_path, parent_module)
|
||||||
|
|
||||||
if module is None:
|
if module is None:
|
||||||
# The file might raise an ImportError e.g. and therefore not be
|
# The file might raise an ImportError e.g. and therefore not be
|
||||||
@@ -404,7 +402,7 @@ class Importer(object):
|
|||||||
|
|
||||||
# namespace packages
|
# namespace packages
|
||||||
if isinstance(scope, tree.Module) and scope.path.endswith('__init__.py'):
|
if isinstance(scope, tree.Module) and scope.path.endswith('__init__.py'):
|
||||||
paths = scope.py__path__(self.sys_path_with_modifications())
|
paths = scope.py__path__()
|
||||||
names += self._get_module_names(paths)
|
names += self._get_module_names(paths)
|
||||||
|
|
||||||
if only_modules:
|
if only_modules:
|
||||||
@@ -437,7 +435,7 @@ class Importer(object):
|
|||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
def _load_module(evaluator, path=None, source=None, sys_path=None):
|
def _load_module(evaluator, path=None, source=None, sys_path=None, parent_module=None):
|
||||||
def load(source):
|
def load(source):
|
||||||
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
|
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
|
||||||
if path is not None and path.endswith(('.py', '.zip', '.egg')) \
|
if path is not None and path.endswith(('.py', '.zip', '.egg')) \
|
||||||
@@ -450,7 +448,8 @@ def _load_module(evaluator, path=None, source=None, sys_path=None):
|
|||||||
p = path
|
p = path
|
||||||
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
|
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
|
||||||
save_parser(path, p)
|
save_parser(path, p)
|
||||||
return p.module
|
from jedi.evaluate.representation import ModuleWrapper
|
||||||
|
return ModuleWrapper(evaluator, p.module, parent_module)
|
||||||
|
|
||||||
if sys_path is None:
|
if sys_path is None:
|
||||||
sys_path = evaluator.sys_path
|
sys_path = evaluator.sys_path
|
||||||
|
|||||||
@@ -806,9 +806,10 @@ class GlobalName(helpers.FakeName):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
||||||
def __init__(self, evaluator, module):
|
def __init__(self, evaluator, module, parent_module=None):
|
||||||
self._evaluator = evaluator
|
self._evaluator = evaluator
|
||||||
self.base = self._module = module
|
self.base = self._module = module
|
||||||
|
self._parent_module = parent_module
|
||||||
|
|
||||||
def names_dicts(self, search_global):
|
def names_dicts(self, search_global):
|
||||||
yield self.base.names_dict
|
yield self.base.names_dict
|
||||||
@@ -854,6 +855,10 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
|||||||
return helpers.FakeName(unicode(self.base.name), self, (1, 0))
|
return helpers.FakeName(unicode(self.base.name), self, (1, 0))
|
||||||
|
|
||||||
def _get_init_directory(self):
|
def _get_init_directory(self):
|
||||||
|
"""
|
||||||
|
:return: The path to the directory of a package. None in case it's not
|
||||||
|
a package.
|
||||||
|
"""
|
||||||
for suffix, _, _ in imp.get_suffixes():
|
for suffix, _, _ in imp.get_suffixes():
|
||||||
ending = '__init__' + suffix
|
ending = '__init__' + suffix
|
||||||
py__file__ = self.py__file__()
|
py__file__ = self.py__file__()
|
||||||
@@ -884,6 +889,30 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
|||||||
else:
|
else:
|
||||||
return self.py__name__()
|
return self.py__name__()
|
||||||
|
|
||||||
|
def _py__path__(self):
|
||||||
|
if self._parent_module is None:
|
||||||
|
search_path = self._evaluator.sys_path
|
||||||
|
else:
|
||||||
|
search_path = self._parent_module.py__path__()
|
||||||
|
init_path = self.py__file__()
|
||||||
|
if os.path.basename(init_path) == '__init__.py':
|
||||||
|
with open(init_path, 'rb') as f:
|
||||||
|
content = common.source_to_unicode(f.read())
|
||||||
|
# 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 content or options[1] in content:
|
||||||
|
# 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 search_path:
|
||||||
|
other = os.path.join(s, unicode(self.name))
|
||||||
|
if os.path.isdir(other):
|
||||||
|
paths.add(other)
|
||||||
|
return list(paths)
|
||||||
|
# Default to this.
|
||||||
|
return [self._get_init_directory()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def py__path__(self):
|
def py__path__(self):
|
||||||
"""
|
"""
|
||||||
@@ -896,33 +925,12 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
|||||||
is a list of paths (strings).
|
is a list of paths (strings).
|
||||||
Raises an AttributeError if the module is not a package.
|
Raises an AttributeError if the module is not a package.
|
||||||
"""
|
"""
|
||||||
def return_value(search_path):
|
|
||||||
init_path = self.py__file__()
|
|
||||||
if os.path.basename(init_path) == '__init__.py':
|
|
||||||
|
|
||||||
with open(init_path, 'rb') as f:
|
|
||||||
content = common.source_to_unicode(f.read())
|
|
||||||
# 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 content or options[1] in content:
|
|
||||||
# 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 search_path:
|
|
||||||
other = os.path.join(s, unicode(self.name))
|
|
||||||
if os.path.isdir(other):
|
|
||||||
paths.add(other)
|
|
||||||
return list(paths)
|
|
||||||
# Default to this.
|
|
||||||
return [path]
|
|
||||||
|
|
||||||
path = self._get_init_directory()
|
path = self._get_init_directory()
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
raise AttributeError('Only packages have __path__ attributes.')
|
raise AttributeError('Only packages have __path__ attributes.')
|
||||||
else:
|
else:
|
||||||
return return_value
|
return self._py__path__
|
||||||
|
|
||||||
@memoize_default()
|
@memoize_default()
|
||||||
def _sub_modules_dict(self):
|
def _sub_modules_dict(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user