1
0
forked from VimPlug/jedi

Move the importing of modules out of compatibility

This commit is contained in:
Dave Halter
2020-07-02 10:51:49 +02:00
parent 5ab351dc8f
commit 0c1ba1b305
4 changed files with 132 additions and 133 deletions

View File

@@ -5,133 +5,8 @@ different Python versions.
import errno import errno
import sys import sys
import os import os
import warnings
import subprocess import subprocess
import pickle import pickle
import importlib
from zipimport import zipimporter
from jedi.file_io import KnownContentFileIO, ZipFileIO
def find_module(string, path=None, full_name=None, is_global_search=True):
"""
Provides information about a module.
This function isolates the differences in importing libraries introduced with
python 3.3 on; it gets a module name and optionally a path. It will return a
tuple containin an open file for the module (if not builtin), the filename
or the name of the module if it is a builtin one and a boolean indicating
if the module is contained in a package.
"""
spec = None
loader = None
for finder in sys.meta_path:
if is_global_search and finder != importlib.machinery.PathFinder:
p = None
else:
p = path
try:
find_spec = finder.find_spec
except AttributeError:
# These are old-school clases that still have a different API, just
# ignore those.
continue
spec = find_spec(string, p)
if spec is not None:
loader = spec.loader
if loader is None and not spec.has_location:
# This is a namespace package.
full_name = string if not path else full_name
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path)
return implicit_ns_info, True
break
return find_module_py33(string, path, loader)
def find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
loader = loader or importlib.machinery.PathFinder.find_module(string, path)
if loader is None and path is None: # Fallback to find builtins
try:
with warnings.catch_warnings(record=True):
# Mute "DeprecationWarning: Use importlib.util.find_spec()
# instead." While we should replace that in the future, it's
# probably good to wait until we deprecate Python 3.3, since
# it was added in Python 3.4 and find_loader hasn't been
# removed in 3.6.
loader = importlib.find_loader(string)
except ValueError as e:
# See #491. Importlib might raise a ValueError, to avoid this, we
# just raise an ImportError to fix the issue.
raise ImportError("Originally " + repr(e))
if loader is None:
raise ImportError("Couldn't find a loader for {}".format(string))
return _from_loader(loader, string)
def _from_loader(loader, string):
try:
is_package_method = loader.is_package
except AttributeError:
is_package = False
else:
is_package = is_package_method(string)
try:
get_filename = loader.get_filename
except AttributeError:
return None, is_package
else:
module_path = cast_path(get_filename(string))
# To avoid unicode and read bytes, "overwrite" loader.get_source if
# possible.
try:
f = type(loader).get_source
except AttributeError:
raise ImportError("get_source was not defined on loader")
if f is not importlib.machinery.SourceFileLoader.get_source:
# Unfortunately we are reading unicode here, not bytes.
# It seems hard to get bytes, because the zip importer
# logic just unpacks the zip file and returns a file descriptor
# that we cannot as easily access. Therefore we just read it as
# a string in the cases where get_source was overwritten.
code = loader.get_source(string)
else:
code = _get_source(loader, string)
if code is None:
return None, is_package
if isinstance(loader, zipimporter):
return ZipFileIO(module_path, code, cast_path(loader.archive)), is_package
return KnownContentFileIO(module_path, code), is_package
def _get_source(loader, fullname):
"""
This method is here as a replacement for SourceLoader.get_source. That
method returns unicode, but we prefer bytes.
"""
path = loader.get_filename(fullname)
try:
return loader.get_data(path)
except OSError:
raise ImportError('source not available through get_data()',
name=fullname)
class ImplicitNSInfo(object):
"""Stores information returned from an implicit namespace spec"""
def __init__(self, name, paths):
self.name = name
self.paths = paths
def cast_path(string): def cast_path(string):

View File

@@ -2,12 +2,16 @@ import sys
import os import os
import re import re
import inspect import inspect
import importlib
import warnings
from zipimport import zipimporter
from importlib.machinery import all_suffixes from importlib.machinery import all_suffixes
from jedi._compatibility import find_module, cast_path from jedi._compatibility import cast_path
from jedi.inference.compiled import access from jedi.inference.compiled import access
from jedi import debug from jedi import debug
from jedi import parser_utils from jedi import parser_utils
from jedi.file_io import KnownContentFileIO, ZipFileIO
def get_sys_path(): def get_sys_path():
@@ -34,7 +38,7 @@ def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs):
if sys_path is not None: if sys_path is not None:
sys.path, temp = sys_path, sys.path sys.path, temp = sys_path, sys.path
try: try:
return find_module(full_name=full_name, **kwargs) return _find_module(full_name=full_name, **kwargs)
except ImportError: except ImportError:
return None, None return None, None
finally: finally:
@@ -112,3 +116,123 @@ def _iter_module_names(inference_state, paths):
if modname and '.' not in modname: if modname and '.' not in modname:
if modname != '__init__': if modname != '__init__':
yield modname yield modname
def _find_module(string, path=None, full_name=None, is_global_search=True):
"""
Provides information about a module.
This function isolates the differences in importing libraries introduced with
python 3.3 on; it gets a module name and optionally a path. It will return a
tuple containin an open file for the module (if not builtin), the filename
or the name of the module if it is a builtin one and a boolean indicating
if the module is contained in a package.
"""
spec = None
loader = None
for finder in sys.meta_path:
if is_global_search and finder != importlib.machinery.PathFinder:
p = None
else:
p = path
try:
find_spec = finder.find_spec
except AttributeError:
# These are old-school clases that still have a different API, just
# ignore those.
continue
spec = find_spec(string, p)
if spec is not None:
loader = spec.loader
if loader is None and not spec.has_location:
# This is a namespace package.
full_name = string if not path else full_name
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path)
return implicit_ns_info, True
break
return _find_module_py33(string, path, loader)
def _find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
loader = loader or importlib.machinery.PathFinder.find_module(string, path)
if loader is None and path is None: # Fallback to find builtins
try:
with warnings.catch_warnings(record=True):
# Mute "DeprecationWarning: Use importlib.util.find_spec()
# instead." While we should replace that in the future, it's
# probably good to wait until we deprecate Python 3.3, since
# it was added in Python 3.4 and find_loader hasn't been
# removed in 3.6.
loader = importlib.find_loader(string)
except ValueError as e:
# See #491. Importlib might raise a ValueError, to avoid this, we
# just raise an ImportError to fix the issue.
raise ImportError("Originally " + repr(e))
if loader is None:
raise ImportError("Couldn't find a loader for {}".format(string))
return _from_loader(loader, string)
def _from_loader(loader, string):
try:
is_package_method = loader.is_package
except AttributeError:
is_package = False
else:
is_package = is_package_method(string)
try:
get_filename = loader.get_filename
except AttributeError:
return None, is_package
else:
module_path = cast_path(get_filename(string))
# To avoid unicode and read bytes, "overwrite" loader.get_source if
# possible.
try:
f = type(loader).get_source
except AttributeError:
raise ImportError("get_source was not defined on loader")
if f is not importlib.machinery.SourceFileLoader.get_source:
# Unfortunately we are reading unicode here, not bytes.
# It seems hard to get bytes, because the zip importer
# logic just unpacks the zip file and returns a file descriptor
# that we cannot as easily access. Therefore we just read it as
# a string in the cases where get_source was overwritten.
code = loader.get_source(string)
else:
code = _get_source(loader, string)
if code is None:
return None, is_package
if isinstance(loader, zipimporter):
return ZipFileIO(module_path, code, cast_path(loader.archive)), is_package
return KnownContentFileIO(module_path, code), is_package
def _get_source(loader, fullname):
"""
This method is here as a replacement for SourceLoader.get_source. That
method returns unicode, but we prefer bytes.
"""
path = loader.get_filename(fullname)
try:
return loader.get_data(path)
except OSError:
raise ImportError('source not available through get_data()',
name=fullname)
class ImplicitNSInfo(object):
"""Stores information returned from an implicit namespace spec"""
def __init__(self, name, paths):
self.name = name
self.paths = paths

View File

@@ -13,7 +13,6 @@ import os
from parso.python import tree from parso.python import tree
from parso.tree import search_ancestor from parso.tree import search_ancestor
from jedi._compatibility import ImplicitNSInfo
from jedi import debug from jedi import debug
from jedi import settings from jedi import settings
from jedi.file_io import FolderIO from jedi.file_io import FolderIO
@@ -28,6 +27,7 @@ from jedi.inference.names import ImportName, SubModuleName
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.gradual.typeshed import import_module_decorator, \ from jedi.inference.gradual.typeshed import import_module_decorator, \
create_stub_module, parse_stub_module create_stub_module, parse_stub_module
from jedi.inference.compiled.subprocess.functions import ImplicitNSInfo
from jedi.plugins import plugin_manager from jedi.plugins import plugin_manager

View File

@@ -8,32 +8,32 @@ import os
import pytest import pytest
from jedi.file_io import FileIO from jedi.file_io import FileIO
from jedi._compatibility import find_module_py33, find_module
from jedi.inference import compiled from jedi.inference import compiled
from jedi.inference import imports from jedi.inference import imports
from jedi.api.project import Project from jedi.api.project import Project
from jedi.inference.gradual.conversion import _stub_to_python_value_set from jedi.inference.gradual.conversion import _stub_to_python_value_set
from jedi.inference.references import get_module_contexts_containing_name from jedi.inference.references import get_module_contexts_containing_name
from ..helpers import get_example_dir, test_dir, test_dir_project, root_dir from ..helpers import get_example_dir, test_dir, test_dir_project, root_dir
from jedi.inference.compiled.subprocess.functions import _find_module_py33, _find_module
THIS_DIR = os.path.dirname(__file__) THIS_DIR = os.path.dirname(__file__)
def test_find_module_basic(): def test_find_module_basic():
"""Needs to work like the old find_module.""" """Needs to work like the old find_module."""
assert find_module_py33('_io') == (None, False) assert _find_module_py33('_io') == (None, False)
with pytest.raises(ImportError): with pytest.raises(ImportError):
assert find_module_py33('_DOESNTEXIST_') == (None, None) assert _find_module_py33('_DOESNTEXIST_') == (None, None)
def test_find_module_package(): def test_find_module_package():
file_io, is_package = find_module('json') file_io, is_package = _find_module('json')
assert file_io.path.endswith(os.path.join('json', '__init__.py')) assert file_io.path.endswith(os.path.join('json', '__init__.py'))
assert is_package is True assert is_package is True
def test_find_module_not_package(): def test_find_module_not_package():
file_io, is_package = find_module('io') file_io, is_package = _find_module('io')
assert file_io.path.endswith('io.py') assert file_io.path.endswith('io.py')
assert is_package is False assert is_package is False