forked from VimPlug/jedi
200 lines
6.7 KiB
Python
200 lines
6.7 KiB
Python
"""
|
|
This module is here to ensure compatibility of Windows/Linux/MacOS and
|
|
different Python versions.
|
|
"""
|
|
import errno
|
|
import sys
|
|
import os
|
|
import warnings
|
|
import subprocess
|
|
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 u(string, errors='strict'):
|
|
"""Cast to unicode DAMMIT!
|
|
Written because Python2 repr always implicitly casts to a string, so we
|
|
have to cast back to a unicode (and we now that we always deal with valid
|
|
unicode, because we check that in the beginning).
|
|
"""
|
|
if isinstance(string, bytes):
|
|
return str(string, encoding='UTF-8', errors=errors)
|
|
return string
|
|
|
|
|
|
def cast_path(obj):
|
|
"""
|
|
Take a bytes or str path and cast it to unicode.
|
|
|
|
Apparently it is perfectly fine to pass both byte and unicode objects into
|
|
the sys.path. This probably means that byte paths are normal at other
|
|
places as well.
|
|
|
|
Since this just really complicates everything and Python 2.7 will be EOL
|
|
soon anyway, just go with always strings.
|
|
"""
|
|
return u(obj, errors='replace')
|
|
|
|
|
|
def pickle_load(file):
|
|
try:
|
|
return pickle.load(file)
|
|
# Python on Windows don't throw EOF errors for pipes. So reraise them with
|
|
# the correct type, which is caught upwards.
|
|
except OSError:
|
|
if sys.platform == 'win32':
|
|
raise EOFError()
|
|
raise
|
|
|
|
|
|
def pickle_dump(data, file, protocol):
|
|
try:
|
|
pickle.dump(data, file, protocol)
|
|
# On Python 3.3 flush throws sometimes an error even though the writing
|
|
# operation should be completed.
|
|
file.flush()
|
|
# Python on Windows don't throw EPIPE errors for pipes. So reraise them with
|
|
# the correct type and error number.
|
|
except OSError:
|
|
if sys.platform == 'win32':
|
|
raise IOError(errno.EPIPE, "Broken pipe")
|
|
raise
|
|
|
|
|
|
class GeneralizedPopen(subprocess.Popen):
|
|
def __init__(self, *args, **kwargs):
|
|
if os.name == 'nt':
|
|
try:
|
|
# Was introduced in Python 3.7.
|
|
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
|
|
except AttributeError:
|
|
CREATE_NO_WINDOW = 0x08000000
|
|
kwargs['creationflags'] = CREATE_NO_WINDOW
|
|
# The child process doesn't need file descriptors except 0, 1, 2.
|
|
# This is unix only.
|
|
kwargs['close_fds'] = 'posix' in sys.builtin_module_names
|
|
super(GeneralizedPopen, self).__init__(*args, **kwargs)
|