258 lines
8.3 KiB
Python
258 lines
8.3 KiB
Python
import sys
|
|
import os
|
|
import inspect
|
|
import importlib
|
|
from pathlib import Path
|
|
from zipfile import ZipFile
|
|
from zipimport import zipimporter, ZipImportError
|
|
from importlib.machinery import all_suffixes
|
|
|
|
from jedi.inference.compiled import access
|
|
from jedi import debug
|
|
from jedi import parser_utils
|
|
from jedi.file_io import KnownContentFileIO, ZipFileIO
|
|
|
|
|
|
def get_sys_path():
|
|
return sys.path
|
|
|
|
|
|
def load_module(inference_state, **kwargs):
|
|
return access.load_module(inference_state, **kwargs)
|
|
|
|
|
|
def get_compiled_method_return(inference_state, id, attribute, *args, **kwargs):
|
|
handle = inference_state.compiled_subprocess.get_access_handle(id)
|
|
return getattr(handle.access, attribute)(*args, **kwargs)
|
|
|
|
|
|
def create_simple_object(inference_state, obj):
|
|
return access.create_access_path(inference_state, obj)
|
|
|
|
|
|
def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs):
|
|
"""
|
|
Returns Tuple[Union[NamespaceInfo, FileIO, None], Optional[bool]]
|
|
"""
|
|
if sys_path is not None:
|
|
sys.path, temp = sys_path, sys.path
|
|
try:
|
|
return _find_module(full_name=full_name, **kwargs)
|
|
except ImportError:
|
|
return None, None
|
|
finally:
|
|
if sys_path is not None:
|
|
sys.path = temp
|
|
|
|
|
|
def get_builtin_module_names(inference_state):
|
|
return sys.builtin_module_names
|
|
|
|
|
|
def _test_raise_error(inference_state, exception_type):
|
|
"""
|
|
Raise an error to simulate certain problems for unit tests.
|
|
"""
|
|
raise exception_type
|
|
|
|
|
|
def _test_print(inference_state, stderr=None, stdout=None):
|
|
"""
|
|
Force some prints in the subprocesses. This exists for unit tests.
|
|
"""
|
|
if stderr is not None:
|
|
print(stderr, file=sys.stderr)
|
|
sys.stderr.flush()
|
|
if stdout is not None:
|
|
print(stdout)
|
|
sys.stdout.flush()
|
|
|
|
|
|
def _get_init_path(directory_path):
|
|
"""
|
|
The __init__ file can be searched in a directory. If found return it, else
|
|
None.
|
|
"""
|
|
for suffix in all_suffixes():
|
|
path = os.path.join(directory_path, '__init__' + suffix)
|
|
if os.path.exists(path):
|
|
return path
|
|
return None
|
|
|
|
|
|
def safe_literal_eval(inference_state, value):
|
|
return parser_utils.safe_literal_eval(value)
|
|
|
|
|
|
def iter_module_names(*args, **kwargs):
|
|
return list(_iter_module_names(*args, **kwargs))
|
|
|
|
|
|
def _iter_module_names(inference_state, paths):
|
|
# Python modules/packages
|
|
for path in paths:
|
|
try:
|
|
dir_entries = ((entry.name, entry.is_dir()) for entry in os.scandir(path))
|
|
except OSError:
|
|
try:
|
|
zip_import_info = zipimporter(path)
|
|
# Unfortunately, there is no public way to access zipimporter's
|
|
# private _files member. We therefore have to use a
|
|
# custom function to iterate over the files.
|
|
dir_entries = _zip_list_subdirectory(
|
|
zip_import_info.archive, zip_import_info.prefix)
|
|
except ZipImportError:
|
|
# The file might not exist or reading it might lead to an error.
|
|
debug.warning("Not possible to list directory: %s", path)
|
|
continue
|
|
for name, is_dir in dir_entries:
|
|
# First Namespaces then modules/stubs
|
|
if is_dir:
|
|
# pycache is obviously not an interesting namespace. Also the
|
|
# name must be a valid identifier.
|
|
if name != '__pycache__' and name.isidentifier():
|
|
yield name
|
|
else:
|
|
if name.endswith('.pyi'): # Stub files
|
|
modname = name[:-4]
|
|
else:
|
|
modname = inspect.getmodulename(name)
|
|
|
|
if modname and '.' not in modname:
|
|
if modname != '__init__':
|
|
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:
|
|
if spec.origin == "frozen":
|
|
continue
|
|
|
|
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):
|
|
if not loader:
|
|
spec = importlib.machinery.PathFinder.find_spec(string, path)
|
|
if spec is not None:
|
|
loader = spec.loader
|
|
|
|
if loader is None and path is None: # Fallback to find builtins
|
|
try:
|
|
spec = importlib.util.find_spec(string)
|
|
if spec is not None:
|
|
loader = spec.loader
|
|
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 = 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, 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)
|
|
|
|
|
|
def _zip_list_subdirectory(zip_path, zip_subdir_path):
|
|
zip_file = ZipFile(zip_path)
|
|
zip_subdir_path = Path(zip_subdir_path)
|
|
zip_content_file_paths = zip_file.namelist()
|
|
for raw_file_name in zip_content_file_paths:
|
|
file_path = Path(raw_file_name)
|
|
if file_path.parent == zip_subdir_path:
|
|
file_path = file_path.relative_to(zip_subdir_path)
|
|
yield file_path.name, raw_file_name.endswith("/")
|
|
|
|
|
|
class ImplicitNSInfo:
|
|
"""Stores information returned from an implicit namespace spec"""
|
|
def __init__(self, name, paths):
|
|
self.name = name
|
|
self.paths = paths
|