From a7c4b5800b88f2ea86ee8a578d49a1ee842337d3 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Thu, 23 Apr 2015 03:36:46 +0200 Subject: [PATCH] Namespace packages work again. This time the same way as Python does it. --- jedi/evaluate/imports.py | 111 +++++++++++++++++--------------- jedi/evaluate/representation.py | 25 ++++++- 2 files changed, 83 insertions(+), 53 deletions(-) diff --git a/jedi/evaluate/imports.py b/jedi/evaluate/imports.py index bf424309..ebc252bc 100644 --- a/jedi/evaluate/imports.py +++ b/jedi/evaluate/imports.py @@ -389,39 +389,46 @@ class _Importer(object): except KeyError: pass - try: - 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 [] - # We can take the first element, because only the os special - # case yields multiple modules, which is not important for - # further imports. - base = bases[0] + 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 [] + # We can take the first element, because only the os special + # case yields multiple modules, which is not important for + # further imports. + base = 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 [str(i) for i in import_path] == ['os', 'path']: - return self._evaluator.find_types(base, 'path') + # 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 [str(i) for i in import_path] == ['os', 'path']: + return self._evaluator.find_types(base, 'path') - try: - paths = base.py__path__() - except AttributeError: - # The module is not a package. - _add_error(self._evaluator, import_path[-1]) - return [] - else: - 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. + try: + paths = base.py__path__(sys_path) + except AttributeError: + # The module is not a package. + _add_error(self._evaluator, import_path[-1]) + return [] + else: + 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. + try: module_file, module_path, is_pkg = \ find_module(import_parts[-1], [path]) - else: + break + except ImportError: + module_path = None + if module_path is None: + _add_error(self._evaluator, import_path[-1]) + return [] + else: + try: debug.dbg('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. @@ -431,30 +438,30 @@ class _Importer(object): find_module(import_parts[-1]) finally: sys.path = temp - except ImportError: - # The module is not a package. - _add_error(self._evaluator, import_path[-1]) - return [] - else: - source = None - if is_pkg: - # In this case, we don't have a file yet. Search for the - # __init__ file. - for suffix, _, _ in imp.get_suffixes(): - path = os.path.join(module_path, '__init__' + suffix) - if os.path.exists(path): - if suffix == '.py': - module_path = path - break - elif module_file: - source = module_file.read() - module_file.close() + except ImportError: + # The module is not a package. + _add_error(self._evaluator, import_path[-1]) + return [] - if module_file is None and not module_path.endswith('.py'): - module = compiled.load_module(module_path) - else: - module = _load_module(self._evaluator, module_path, source, - sys_path, module_name) + source = None + if is_pkg: + # In this case, we don't have a file yet. Search for the + # __init__ file. + for suffix, _, _ in imp.get_suffixes(): + path = os.path.join(module_path, '__init__' + suffix) + if os.path.exists(path): + if suffix == '.py': + module_path = path + break + elif module_file: + source = module_file.read() + module_file.close() + + if module_file is None and not module_path.endswith('.py'): + module = compiled.load_module(module_path) + else: + module = _load_module(self._evaluator, module_path, source, + sys_path, module_name) self._evaluator.modules[module_name] = module return [module] diff --git a/jedi/evaluate/representation.py b/jedi/evaluate/representation.py index a07d51b2..7074b1d6 100644 --- a/jedi/evaluate/representation.py +++ b/jedi/evaluate/representation.py @@ -786,11 +786,34 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, pr.Module, Wrapper)): @property def py__path__(self): """ + Not seen here, since it's a property. The callback actually uses a + variable, so use it like:: + + foo.py__path__(sys_path) + In case of a package, this returns Python's __path__ attribute, which is a list of paths (strings). Raises an AttributeError if the module is not a package. """ - def return_value(): + 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()