Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
43
venv/Lib/site-packages/jedi/__init__.py
Normal file
43
venv/Lib/site-packages/jedi/__init__.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
"""
|
||||
Jedi is a static analysis tool for Python that is typically used in
|
||||
IDEs/editors plugins. Jedi has a focus on autocompletion and goto
|
||||
functionality. Other features include refactoring, code search and finding
|
||||
references.
|
||||
|
||||
Jedi has a simple API to work with. There is a reference implementation as a
|
||||
`VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
|
||||
REPL is also possible, IPython uses it natively and for the CPython REPL you
|
||||
can install it. Jedi is well tested and bugs should be rare.
|
||||
|
||||
Here's a simple example of the autocompletion feature:
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''
|
||||
... import json
|
||||
... json.lo'''
|
||||
>>> script = jedi.Script(source, path='example.py')
|
||||
>>> script
|
||||
<Script: 'example.py' ...>
|
||||
>>> completions = script.complete(3, len('json.lo'))
|
||||
>>> completions
|
||||
[<Completion: load>, <Completion: loads>]
|
||||
>>> print(completions[0].complete)
|
||||
ad
|
||||
>>> print(completions[0].name)
|
||||
load
|
||||
"""
|
||||
|
||||
__version__ = '0.17.2'
|
||||
|
||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
||||
preload_module, names
|
||||
from jedi import settings
|
||||
from jedi.api.environment import find_virtualenvs, find_system_environments, \
|
||||
get_default_environment, InvalidPythonEnvironment, create_environment, \
|
||||
get_system_environment, InterpreterEnvironment
|
||||
from jedi.api.project import Project, get_default_project
|
||||
from jedi.api.exceptions import InternalError, RefactoringError
|
||||
|
||||
# Finally load the internal plugins. This is only internal.
|
||||
from jedi.plugins import registry
|
||||
del registry
|
72
venv/Lib/site-packages/jedi/__main__.py
Normal file
72
venv/Lib/site-packages/jedi/__main__.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
import sys
|
||||
from os.path import join, dirname, abspath, isdir
|
||||
|
||||
|
||||
def _start_linter():
|
||||
"""
|
||||
This is a pre-alpha API. You're not supposed to use it at all, except for
|
||||
testing. It will very likely change.
|
||||
"""
|
||||
import jedi
|
||||
|
||||
if '--debug' in sys.argv:
|
||||
jedi.set_debug_function()
|
||||
|
||||
for path in sys.argv[2:]:
|
||||
if path.startswith('--'):
|
||||
continue
|
||||
if isdir(path):
|
||||
import fnmatch
|
||||
import os
|
||||
|
||||
paths = []
|
||||
for root, dirnames, filenames in os.walk(path):
|
||||
for filename in fnmatch.filter(filenames, '*.py'):
|
||||
paths.append(os.path.join(root, filename))
|
||||
else:
|
||||
paths = [path]
|
||||
|
||||
try:
|
||||
for p in paths:
|
||||
for error in jedi.Script(path=p)._analysis():
|
||||
print(error)
|
||||
except Exception:
|
||||
if '--pdb' in sys.argv:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
import pdb
|
||||
pdb.post_mortem()
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
def _complete():
|
||||
import jedi
|
||||
import pdb
|
||||
|
||||
if '-d' in sys.argv:
|
||||
sys.argv.remove('-d')
|
||||
jedi.set_debug_function()
|
||||
|
||||
try:
|
||||
completions = jedi.Script(sys.argv[2]).complete()
|
||||
for c in completions:
|
||||
c.docstring()
|
||||
c.type
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
pdb.post_mortem()
|
||||
else:
|
||||
print(completions)
|
||||
|
||||
|
||||
if len(sys.argv) == 2 and sys.argv[1] == 'repl':
|
||||
# don't want to use __main__ only for repl yet, maybe we want to use it for
|
||||
# something else. So just use the keyword ``repl`` for now.
|
||||
print(join(dirname(abspath(__file__)), 'api', 'replstartup.py'))
|
||||
elif len(sys.argv) > 1 and sys.argv[1] == '_linter':
|
||||
_start_linter()
|
||||
elif len(sys.argv) > 1 and sys.argv[1] == '_complete':
|
||||
_complete()
|
||||
else:
|
||||
print('Command not implemented: %s' % sys.argv[1])
|
BIN
venv/Lib/site-packages/jedi/__pycache__/__init__.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/jedi/__pycache__/__init__.cpython-36.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/jedi/__pycache__/__main__.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/jedi/__pycache__/__main__.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/jedi/__pycache__/cache.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/jedi/__pycache__/cache.cpython-36.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/jedi/__pycache__/common.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/jedi/__pycache__/common.cpython-36.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/jedi/__pycache__/debug.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/jedi/__pycache__/debug.cpython-36.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/jedi/__pycache__/file_io.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/jedi/__pycache__/file_io.cpython-36.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
venv/Lib/site-packages/jedi/__pycache__/settings.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/jedi/__pycache__/settings.cpython-36.pyc
Normal file
Binary file not shown.
BIN
venv/Lib/site-packages/jedi/__pycache__/utils.cpython-36.pyc
Normal file
BIN
venv/Lib/site-packages/jedi/__pycache__/utils.cpython-36.pyc
Normal file
Binary file not shown.
631
venv/Lib/site-packages/jedi/_compatibility.py
Normal file
631
venv/Lib/site-packages/jedi/_compatibility.py
Normal file
|
@ -0,0 +1,631 @@
|
|||
"""
|
||||
To ensure compatibility from Python ``2.7`` - ``3.x``, a module has been
|
||||
created. Clearly there is huge need to use conforming syntax.
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import atexit
|
||||
import errno
|
||||
import functools
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import pkgutil
|
||||
import warnings
|
||||
import subprocess
|
||||
import weakref
|
||||
try:
|
||||
import importlib
|
||||
except ImportError:
|
||||
pass
|
||||
from zipimport import zipimporter
|
||||
|
||||
from jedi.file_io import KnownContentFileIO, ZipFileIO
|
||||
|
||||
is_py3 = sys.version_info[0] >= 3
|
||||
is_py35 = is_py3 and sys.version_info[1] >= 5
|
||||
py_version = int(str(sys.version_info[0]) + str(sys.version_info[1]))
|
||||
|
||||
|
||||
if sys.version_info[:2] < (3, 5):
|
||||
"""
|
||||
A super-minimal shim around listdir that behave like
|
||||
scandir for the information we need.
|
||||
"""
|
||||
class _DirEntry:
|
||||
|
||||
def __init__(self, name, basepath):
|
||||
self.name = name
|
||||
self.basepath = basepath
|
||||
|
||||
def is_dir(self):
|
||||
path_for_name = os.path.join(self.basepath, self.name)
|
||||
return os.path.isdir(path_for_name)
|
||||
|
||||
def scandir(dir):
|
||||
return [_DirEntry(name, dir) for name in os.listdir(dir)]
|
||||
else:
|
||||
from os import scandir
|
||||
|
||||
|
||||
class DummyFile(object):
|
||||
def __init__(self, loader, string):
|
||||
self.loader = loader
|
||||
self.string = string
|
||||
|
||||
def read(self):
|
||||
return self.loader.get_source(self.string)
|
||||
|
||||
def close(self):
|
||||
del self.loader
|
||||
|
||||
|
||||
def find_module_py34(string, path=None, full_name=None, is_global_search=True):
|
||||
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 is_py3 and 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)
|
||||
|
||||
|
||||
def find_module_pre_py3(string, path=None, full_name=None, is_global_search=True):
|
||||
# This import is here, because in other places it will raise a
|
||||
# DeprecationWarning.
|
||||
import imp
|
||||
try:
|
||||
module_file, module_path, description = imp.find_module(string, path)
|
||||
module_type = description[2]
|
||||
is_package = module_type is imp.PKG_DIRECTORY
|
||||
if is_package:
|
||||
# In Python 2 directory package imports are returned as folder
|
||||
# paths, not __init__.py paths.
|
||||
p = os.path.join(module_path, '__init__.py')
|
||||
try:
|
||||
module_file = open(p)
|
||||
module_path = p
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
elif module_type != imp.PY_SOURCE:
|
||||
if module_file is not None:
|
||||
module_file.close()
|
||||
module_file = None
|
||||
|
||||
if module_file is None:
|
||||
return None, is_package
|
||||
|
||||
with module_file:
|
||||
code = module_file.read()
|
||||
return KnownContentFileIO(cast_path(module_path), code), is_package
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if path is None:
|
||||
path = sys.path
|
||||
for item in path:
|
||||
loader = pkgutil.get_importer(item)
|
||||
if loader:
|
||||
loader = loader.find_module(string)
|
||||
if loader is not None:
|
||||
return _from_loader(loader, string)
|
||||
raise ImportError("No module named {}".format(string))
|
||||
|
||||
|
||||
find_module = find_module_py34 if is_py3 else find_module_pre_py3
|
||||
find_module.__doc__ = """
|
||||
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.
|
||||
"""
|
||||
|
||||
|
||||
class ImplicitNSInfo(object):
|
||||
"""Stores information returned from an implicit namespace spec"""
|
||||
def __init__(self, name, paths):
|
||||
self.name = name
|
||||
self.paths = paths
|
||||
|
||||
|
||||
if is_py3:
|
||||
all_suffixes = importlib.machinery.all_suffixes
|
||||
else:
|
||||
def all_suffixes():
|
||||
# Is deprecated and raises a warning in Python 3.6.
|
||||
import imp
|
||||
return [suffix for suffix, _, _ in imp.get_suffixes()]
|
||||
|
||||
|
||||
# unicode function
|
||||
try:
|
||||
unicode = unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
# re-raise function
|
||||
if is_py3:
|
||||
def reraise(exception, traceback):
|
||||
raise exception.with_traceback(traceback)
|
||||
else:
|
||||
eval(compile("""
|
||||
def reraise(exception, traceback):
|
||||
raise exception, None, traceback
|
||||
""", 'blub', 'exec'))
|
||||
|
||||
reraise.__doc__ = """
|
||||
Re-raise `exception` with a `traceback` object.
|
||||
|
||||
Usage::
|
||||
|
||||
reraise(Exception, sys.exc_info()[2])
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def use_metaclass(meta, *bases):
|
||||
""" Create a class with a metaclass. """
|
||||
if not bases:
|
||||
bases = (object,)
|
||||
return meta("Py2CompatibilityMetaClass", bases, {})
|
||||
|
||||
|
||||
try:
|
||||
encoding = sys.stdout.encoding
|
||||
if encoding is None:
|
||||
encoding = 'utf-8'
|
||||
except AttributeError:
|
||||
encoding = 'ascii'
|
||||
|
||||
|
||||
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 unicode(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 force_unicode(obj):
|
||||
# Intentionally don't mix those two up, because those two code paths might
|
||||
# be different in the future (maybe windows?).
|
||||
return cast_path(obj)
|
||||
|
||||
|
||||
try:
|
||||
import builtins # module name in python 3
|
||||
except ImportError:
|
||||
import __builtin__ as builtins # noqa: F401
|
||||
|
||||
|
||||
import ast # noqa: F401
|
||||
|
||||
|
||||
def literal_eval(string):
|
||||
return ast.literal_eval(string)
|
||||
|
||||
|
||||
try:
|
||||
from itertools import zip_longest
|
||||
except ImportError:
|
||||
from itertools import izip_longest as zip_longest # Python 2 # noqa: F401
|
||||
|
||||
try:
|
||||
FileNotFoundError = FileNotFoundError
|
||||
except NameError:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
try:
|
||||
IsADirectoryError = IsADirectoryError
|
||||
except NameError:
|
||||
IsADirectoryError = IOError
|
||||
|
||||
try:
|
||||
PermissionError = PermissionError
|
||||
except NameError:
|
||||
PermissionError = IOError
|
||||
|
||||
try:
|
||||
NotADirectoryError = NotADirectoryError
|
||||
except NameError:
|
||||
class NotADirectoryError(Exception):
|
||||
# Don't implement this for Python 2 anymore.
|
||||
pass
|
||||
|
||||
|
||||
def no_unicode_pprint(dct):
|
||||
"""
|
||||
Python 2/3 dict __repr__ may be different, because of unicode differens
|
||||
(with or without a `u` prefix). Normally in doctests we could use `pprint`
|
||||
to sort dicts and check for equality, but here we have to write a separate
|
||||
function to do that.
|
||||
"""
|
||||
import pprint
|
||||
s = pprint.pformat(dct)
|
||||
print(re.sub("u'", "'", s))
|
||||
|
||||
|
||||
def utf8_repr(func):
|
||||
"""
|
||||
``__repr__`` methods in Python 2 don't allow unicode objects to be
|
||||
returned. Therefore cast them to utf-8 bytes in this decorator.
|
||||
"""
|
||||
def wrapper(self):
|
||||
result = func(self)
|
||||
if isinstance(result, unicode):
|
||||
return result.encode('utf-8')
|
||||
else:
|
||||
return result
|
||||
|
||||
if is_py3:
|
||||
return func
|
||||
else:
|
||||
return wrapper
|
||||
|
||||
|
||||
if is_py3:
|
||||
import queue
|
||||
else:
|
||||
import Queue as queue # noqa: F401
|
||||
|
||||
try:
|
||||
# Attempt to load the C implementation of pickle on Python 2 as it is way
|
||||
# faster.
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
|
||||
|
||||
def pickle_load(file):
|
||||
try:
|
||||
if is_py3:
|
||||
return pickle.load(file, encoding='bytes')
|
||||
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 _python2_dct_keys_to_unicode(data):
|
||||
"""
|
||||
Python 2 stores object __dict__ entries as bytes, not unicode, correct it
|
||||
here. Python 2 can deal with both, Python 3 expects unicode.
|
||||
"""
|
||||
if isinstance(data, tuple):
|
||||
return tuple(_python2_dct_keys_to_unicode(x) for x in data)
|
||||
elif isinstance(data, list):
|
||||
return list(_python2_dct_keys_to_unicode(x) for x in data)
|
||||
elif hasattr(data, '__dict__') and type(data.__dict__) == dict:
|
||||
data.__dict__ = {unicode(k): v for k, v in data.__dict__.items()}
|
||||
return data
|
||||
|
||||
|
||||
def pickle_dump(data, file, protocol):
|
||||
try:
|
||||
if not is_py3:
|
||||
data = _python2_dct_keys_to_unicode(data)
|
||||
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
|
||||
|
||||
|
||||
# Determine the highest protocol version compatible for a given list of Python
|
||||
# versions.
|
||||
def highest_pickle_protocol(python_versions):
|
||||
protocol = 4
|
||||
for version in python_versions:
|
||||
if version[0] == 2:
|
||||
# The minimum protocol version for the versions of Python that we
|
||||
# support (2.7 and 3.3+) is 2.
|
||||
return 2
|
||||
if version[1] < 4:
|
||||
protocol = 3
|
||||
return protocol
|
||||
|
||||
|
||||
try:
|
||||
from inspect import Parameter
|
||||
except ImportError:
|
||||
class Parameter(object):
|
||||
POSITIONAL_ONLY = object()
|
||||
POSITIONAL_OR_KEYWORD = object()
|
||||
VAR_POSITIONAL = object()
|
||||
KEYWORD_ONLY = object()
|
||||
VAR_KEYWORD = object()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# shutil.which is not available on Python 2.7.
|
||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||
"""Given a command, mode, and a PATH string, return the path which
|
||||
conforms to the given mode on the PATH, or None if there is no such
|
||||
file.
|
||||
|
||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
||||
of os.environ.get("PATH"), or can be overridden with a custom search
|
||||
path.
|
||||
|
||||
"""
|
||||
# Check that a given file can be accessed with the correct mode.
|
||||
# Additionally check that `file` is not a directory, as on Windows
|
||||
# directories pass the os.access check.
|
||||
def _access_check(fn, mode):
|
||||
return (os.path.exists(fn) and os.access(fn, mode)
|
||||
and not os.path.isdir(fn))
|
||||
|
||||
# If we're given a path with a directory part, look it up directly rather
|
||||
# than referring to PATH directories. This includes checking relative to the
|
||||
# current directory, e.g. ./script
|
||||
if os.path.dirname(cmd):
|
||||
if _access_check(cmd, mode):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
if path is None:
|
||||
path = os.environ.get("PATH", os.defpath)
|
||||
if not path:
|
||||
return None
|
||||
path = path.split(os.pathsep)
|
||||
|
||||
if sys.platform == "win32":
|
||||
# The current directory takes precedence on Windows.
|
||||
if os.curdir not in path:
|
||||
path.insert(0, os.curdir)
|
||||
|
||||
# PATHEXT is necessary to check on Windows.
|
||||
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
||||
# See if the given file matches any of the expected path extensions.
|
||||
# This will allow us to short circuit when given "python.exe".
|
||||
# If it does match, only test that one, otherwise we have to try
|
||||
# others.
|
||||
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
||||
files = [cmd]
|
||||
else:
|
||||
files = [cmd + ext for ext in pathext]
|
||||
else:
|
||||
# On other platforms you don't have things like PATHEXT to tell you
|
||||
# what file suffixes are executable, so just pass on cmd as-is.
|
||||
files = [cmd]
|
||||
|
||||
seen = set()
|
||||
for dir in path:
|
||||
normdir = os.path.normcase(dir)
|
||||
if normdir not in seen:
|
||||
seen.add(normdir)
|
||||
for thefile in files:
|
||||
name = os.path.join(dir, thefile)
|
||||
if _access_check(name, mode):
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
if not is_py3:
|
||||
# Simplified backport of Python 3 weakref.finalize:
|
||||
# https://github.com/python/cpython/blob/ded4737989316653469763230036b04513cb62b3/Lib/weakref.py#L502-L662
|
||||
class finalize(object):
|
||||
"""Class for finalization of weakrefable objects.
|
||||
|
||||
finalize(obj, func, *args, **kwargs) returns a callable finalizer
|
||||
object which will be called when obj is garbage collected. The
|
||||
first time the finalizer is called it evaluates func(*arg, **kwargs)
|
||||
and returns the result. After this the finalizer is dead, and
|
||||
calling it just returns None.
|
||||
|
||||
When the program exits any remaining finalizers will be run.
|
||||
"""
|
||||
|
||||
# Finalizer objects don't have any state of their own.
|
||||
# This ensures that they cannot be part of a ref-cycle.
|
||||
__slots__ = ()
|
||||
_registry = {}
|
||||
|
||||
def __init__(self, obj, func, *args, **kwargs):
|
||||
info = functools.partial(func, *args, **kwargs)
|
||||
info.weakref = weakref.ref(obj, self)
|
||||
self._registry[self] = info
|
||||
|
||||
# To me it's an absolute mystery why in Python 2 we need _=None. It
|
||||
# makes really no sense since it's never really called. Then again it
|
||||
# might be called by Python 2.7 itself, but weakref.finalize is not
|
||||
# documented in Python 2 and therefore shouldn't be randomly called.
|
||||
# We never call this stuff with a parameter and therefore this
|
||||
# parameter should not be needed. But it is. ~dave
|
||||
def __call__(self, _=None):
|
||||
"""Return func(*args, **kwargs) if alive."""
|
||||
info = self._registry.pop(self, None)
|
||||
if info:
|
||||
return info()
|
||||
|
||||
@classmethod
|
||||
def _exitfunc(cls):
|
||||
if not cls._registry:
|
||||
return
|
||||
for finalizer in list(cls._registry):
|
||||
try:
|
||||
finalizer()
|
||||
except Exception:
|
||||
sys.excepthook(*sys.exc_info())
|
||||
assert finalizer not in cls._registry
|
||||
|
||||
atexit.register(finalize._exitfunc)
|
||||
weakref.finalize = finalize
|
||||
|
||||
|
||||
if is_py3 and sys.version_info[1] > 5:
|
||||
from inspect import unwrap
|
||||
else:
|
||||
# Only Python >=3.6 does properly limit the amount of unwraps. This is very
|
||||
# relevant in the case of unittest.mock.patch.
|
||||
# Below is the implementation of Python 3.7.
|
||||
def unwrap(func, stop=None):
|
||||
"""Get the object wrapped by *func*.
|
||||
|
||||
Follows the chain of :attr:`__wrapped__` attributes returning the last
|
||||
object in the chain.
|
||||
|
||||
*stop* is an optional callback accepting an object in the wrapper chain
|
||||
as its sole argument that allows the unwrapping to be terminated early if
|
||||
the callback returns a true value. If the callback never returns a true
|
||||
value, the last object in the chain is returned as usual. For example,
|
||||
:func:`signature` uses this to stop unwrapping if any object in the
|
||||
chain has a ``__signature__`` attribute defined.
|
||||
|
||||
:exc:`ValueError` is raised if a cycle is encountered.
|
||||
|
||||
"""
|
||||
if stop is None:
|
||||
def _is_wrapper(f):
|
||||
return hasattr(f, '__wrapped__')
|
||||
else:
|
||||
def _is_wrapper(f):
|
||||
return hasattr(f, '__wrapped__') and not stop(f)
|
||||
f = func # remember the original func for error reporting
|
||||
# Memoise by id to tolerate non-hashable objects, but store objects to
|
||||
# ensure they aren't destroyed, which would allow their IDs to be reused.
|
||||
memo = {id(f): f}
|
||||
recursion_limit = sys.getrecursionlimit()
|
||||
while _is_wrapper(func):
|
||||
func = func.__wrapped__
|
||||
id_func = id(func)
|
||||
if (id_func in memo) or (len(memo) >= recursion_limit):
|
||||
raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
|
||||
memo[id_func] = func
|
||||
return func
|
917
venv/Lib/site-packages/jedi/api/__init__.py
Normal file
917
venv/Lib/site-packages/jedi/api/__init__.py
Normal file
|
@ -0,0 +1,917 @@
|
|||
"""
|
||||
The API basically only provides one class. You can create a :class:`Script` and
|
||||
use its methods.
|
||||
|
||||
Additionally you can add a debug function with :func:`set_debug_function`.
|
||||
Alternatively, if you don't need a custom function and are happy with printing
|
||||
debug messages to stdout, simply call :func:`set_debug_function` without
|
||||
arguments.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import warnings
|
||||
from functools import wraps
|
||||
|
||||
import parso
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import force_unicode, cast_path, is_py3
|
||||
from jedi.parser_utils import get_executable_nodes
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi import cache
|
||||
from jedi.file_io import KnownContentFileIO
|
||||
from jedi.api import classes
|
||||
from jedi.api import interpreter
|
||||
from jedi.api import helpers
|
||||
from jedi.api.helpers import validate_line_column
|
||||
from jedi.api.completion import Completion, search_in_module
|
||||
from jedi.api.keywords import KeywordName
|
||||
from jedi.api.environment import InterpreterEnvironment
|
||||
from jedi.api.project import get_default_project, Project
|
||||
from jedi.api.errors import parso_to_jedi_errors
|
||||
from jedi.api import refactoring
|
||||
from jedi.api.refactoring.extract import extract_function, extract_variable
|
||||
from jedi.inference import InferenceState
|
||||
from jedi.inference import imports
|
||||
from jedi.inference.references import find_references
|
||||
from jedi.inference.arguments import try_iter_content
|
||||
from jedi.inference.helpers import infer_call_of_leaf
|
||||
from jedi.inference.sys_path import transform_path_to_dotted
|
||||
from jedi.inference.syntax_tree import tree_name_to_values
|
||||
from jedi.inference.value import ModuleValue
|
||||
from jedi.inference.base_value import ValueSet
|
||||
from jedi.inference.value.iterable import unpack_tuple_to_dict
|
||||
from jedi.inference.gradual.conversion import convert_names, convert_values
|
||||
from jedi.inference.gradual.utils import load_proper_stub_module
|
||||
from jedi.inference.utils import to_list
|
||||
|
||||
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
|
||||
# can remove some "maximum recursion depth" errors.
|
||||
sys.setrecursionlimit(3000)
|
||||
|
||||
|
||||
def _no_python2_support(func):
|
||||
# TODO remove when removing Python 2/3.5
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self._inference_state.grammar.version_info < (3, 6) or sys.version_info < (3, 6):
|
||||
raise NotImplementedError(
|
||||
"No support for refactorings/search on Python 2/3.5"
|
||||
)
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
class Script(object):
|
||||
"""
|
||||
A Script is the base for completions, goto or whatever you want to do with
|
||||
Jedi. The counter part of this class is :class:`Interpreter`, which works
|
||||
with actual dictionaries and can work with a REPL. This class
|
||||
should be used when a user edits code in an editor.
|
||||
|
||||
You can either use the ``code`` parameter or ``path`` to read a file.
|
||||
Usually you're going to want to use both of them (in an editor).
|
||||
|
||||
The Script's ``sys.path`` is very customizable:
|
||||
|
||||
- If `project` is provided with a ``sys_path``, that is going to be used.
|
||||
- If `environment` is provided, its ``sys.path`` will be used
|
||||
(see :func:`Environment.get_sys_path <jedi.api.environment.Environment.get_sys_path>`);
|
||||
- Otherwise ``sys.path`` will match that of the default environment of
|
||||
Jedi, which typically matches the sys path that was used at the time
|
||||
when Jedi was imported.
|
||||
|
||||
Most methods have a ``line`` and a ``column`` parameter. Lines in Jedi are
|
||||
always 1-based and columns are always zero based. To avoid repetition they
|
||||
are not always documented. You can omit both line and column. Jedi will
|
||||
then just do whatever action you are calling at the end of the file. If you
|
||||
provide only the line, just will complete at the end of that line.
|
||||
|
||||
.. warning:: By default :attr:`jedi.settings.fast_parser` is enabled, which means
|
||||
that parso reuses modules (i.e. they are not immutable). With this setting
|
||||
Jedi is **not thread safe** and it is also not safe to use multiple
|
||||
:class:`.Script` instances and its definitions at the same time.
|
||||
|
||||
If you are a normal plugin developer this should not be an issue. It is
|
||||
an issue for people that do more complex stuff with Jedi.
|
||||
|
||||
This is purely a performance optimization and works pretty well for all
|
||||
typical usages, however consider to turn the setting off if it causes
|
||||
you problems. See also
|
||||
`this discussion <https://github.com/davidhalter/jedi/issues/1240>`_.
|
||||
|
||||
:param code: The source code of the current file, separated by newlines.
|
||||
:type code: str
|
||||
:param line: Deprecated, please use it directly on e.g. ``.complete``
|
||||
:type line: int
|
||||
:param column: Deprecated, please use it directly on e.g. ``.complete``
|
||||
:type column: int
|
||||
:param path: The path of the file in the file system, or ``''`` if
|
||||
it hasn't been saved yet.
|
||||
:type path: str or None
|
||||
:param encoding: Deprecated, cast to unicode yourself. The encoding of
|
||||
``code``, if it is not a ``unicode`` object (default ``'utf-8'``).
|
||||
:type encoding: str
|
||||
:param sys_path: Deprecated, use the project parameter.
|
||||
:type sys_path: typing.List[str]
|
||||
:param Environment environment: Provide a predefined :ref:`Environment <environments>`
|
||||
to work with a specific Python version or virtualenv.
|
||||
:param Project project: Provide a :class:`.Project` to make sure finding
|
||||
references works well, because the right folder is searched. There are
|
||||
also ways to modify the sys path and other things.
|
||||
"""
|
||||
def __init__(self, code=None, line=None, column=None, path=None,
|
||||
encoding=None, sys_path=None, environment=None,
|
||||
project=None, source=None):
|
||||
self._orig_path = path
|
||||
# An empty path (also empty string) should always result in no path.
|
||||
self.path = os.path.abspath(path) if path else None
|
||||
|
||||
if encoding is None:
|
||||
encoding = 'utf-8'
|
||||
else:
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.17.0. You should cast to valid "
|
||||
"unicode yourself, especially if you are not using utf-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
if line is not None:
|
||||
warnings.warn(
|
||||
"Providing the line is now done in the functions themselves "
|
||||
"like `Script(...).complete(line, column)`",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
if column is not None:
|
||||
warnings.warn(
|
||||
"Providing the column is now done in the functions themselves "
|
||||
"like `Script(...).complete(line, column)`",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
if source is not None:
|
||||
code = source
|
||||
warnings.warn(
|
||||
"Use the code keyword argument instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
if code is None:
|
||||
# TODO add a better warning than the traceback!
|
||||
with open(path, 'rb') as f:
|
||||
code = f.read()
|
||||
|
||||
if sys_path is not None and not is_py3:
|
||||
sys_path = list(map(force_unicode, sys_path))
|
||||
|
||||
if project is None:
|
||||
# Load the Python grammar of the current interpreter.
|
||||
project = get_default_project(
|
||||
os.path.dirname(self.path) if path else None
|
||||
)
|
||||
# TODO deprecate and remove sys_path from the Script API.
|
||||
if sys_path is not None:
|
||||
project._sys_path = sys_path
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.17.0. Use the project API instead, "
|
||||
"which means Script(project=Project(dir, sys_path=sys_path)) instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
|
||||
self._inference_state = InferenceState(
|
||||
project, environment=environment, script_path=self.path
|
||||
)
|
||||
debug.speed('init')
|
||||
self._module_node, code = self._inference_state.parse_and_get_code(
|
||||
code=code,
|
||||
path=self.path,
|
||||
encoding=encoding,
|
||||
use_latest_grammar=path and path.endswith('.pyi'),
|
||||
cache=False, # No disk cache, because the current script often changes.
|
||||
diff_cache=settings.fast_parser,
|
||||
cache_path=settings.cache_directory,
|
||||
)
|
||||
debug.speed('parsed')
|
||||
self._code_lines = parso.split_lines(code, keepends=True)
|
||||
self._code = code
|
||||
self._pos = line, column
|
||||
|
||||
cache.clear_time_caches()
|
||||
debug.reset_time()
|
||||
|
||||
# Cache the module, this is mostly useful for testing, since this shouldn't
|
||||
# be called multiple times.
|
||||
@cache.memoize_method
|
||||
def _get_module(self):
|
||||
names = None
|
||||
is_package = False
|
||||
if self.path is not None:
|
||||
import_names, is_p = transform_path_to_dotted(
|
||||
self._inference_state.get_sys_path(add_parent_paths=False),
|
||||
self.path
|
||||
)
|
||||
if import_names is not None:
|
||||
names = import_names
|
||||
is_package = is_p
|
||||
|
||||
if self.path is None:
|
||||
file_io = None
|
||||
else:
|
||||
file_io = KnownContentFileIO(cast_path(self.path), self._code)
|
||||
if self.path is not None and self.path.endswith('.pyi'):
|
||||
# We are in a stub file. Try to load the stub properly.
|
||||
stub_module = load_proper_stub_module(
|
||||
self._inference_state,
|
||||
file_io,
|
||||
names,
|
||||
self._module_node
|
||||
)
|
||||
if stub_module is not None:
|
||||
return stub_module
|
||||
|
||||
if names is None:
|
||||
names = ('__main__',)
|
||||
|
||||
module = ModuleValue(
|
||||
self._inference_state, self._module_node,
|
||||
file_io=file_io,
|
||||
string_names=names,
|
||||
code_lines=self._code_lines,
|
||||
is_package=is_package,
|
||||
)
|
||||
if names[0] not in ('builtins', '__builtin__', 'typing'):
|
||||
# These modules are essential for Jedi, so don't overwrite them.
|
||||
self._inference_state.module_cache.add(names, ValueSet([module]))
|
||||
return module
|
||||
|
||||
def _get_module_context(self):
|
||||
return self._get_module().as_context()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s %r>' % (
|
||||
self.__class__.__name__,
|
||||
repr(self._orig_path),
|
||||
self._inference_state.environment,
|
||||
)
|
||||
|
||||
@validate_line_column
|
||||
def complete(self, line=None, column=None, **kwargs):
|
||||
"""
|
||||
Completes objects under the cursor.
|
||||
|
||||
Those objects contain information about the completions, more than just
|
||||
names.
|
||||
|
||||
:param fuzzy: Default False. Will return fuzzy completions, which means
|
||||
that e.g. ``ooa`` will match ``foobar``.
|
||||
:return: Completion objects, sorted by name. Normal names appear
|
||||
before "private" names that start with ``_`` and those appear
|
||||
before magic methods and name mangled names that start with ``__``.
|
||||
:rtype: list of :class:`.Completion`
|
||||
"""
|
||||
return self._complete(line, column, **kwargs)
|
||||
|
||||
def _complete(self, line, column, fuzzy=False): # Python 2...
|
||||
with debug.increase_indent_cm('complete'):
|
||||
completion = Completion(
|
||||
self._inference_state, self._get_module_context(), self._code_lines,
|
||||
(line, column), self.get_signatures, fuzzy=fuzzy,
|
||||
)
|
||||
return completion.complete()
|
||||
|
||||
def completions(self, fuzzy=False):
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.16.0. Use Script(...).complete instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return self.complete(*self._pos, fuzzy=fuzzy)
|
||||
|
||||
@validate_line_column
|
||||
def infer(self, line=None, column=None, **kwargs):
|
||||
"""
|
||||
Return the definitions of under the cursor. It is basically a wrapper
|
||||
around Jedi's type inference.
|
||||
|
||||
This method follows complicated paths and returns the end, not the
|
||||
first definition. The big difference between :meth:`goto` and
|
||||
:meth:`infer` is that :meth:`goto` doesn't
|
||||
follow imports and statements. Multiple objects may be returned,
|
||||
because depending on an option you can have two different versions of a
|
||||
function.
|
||||
|
||||
:param only_stubs: Only return stubs for this method.
|
||||
:param prefer_stubs: Prefer stubs to Python objects for this method.
|
||||
:rtype: list of :class:`.Name`
|
||||
"""
|
||||
with debug.increase_indent_cm('infer'):
|
||||
return self._infer(line, column, **kwargs)
|
||||
|
||||
def goto_definitions(self, **kwargs):
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.16.0. Use Script(...).infer instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return self.infer(*self._pos, **kwargs)
|
||||
|
||||
def _infer(self, line, column, only_stubs=False, prefer_stubs=False):
|
||||
pos = line, column
|
||||
leaf = self._module_node.get_name_of_position(pos)
|
||||
if leaf is None:
|
||||
leaf = self._module_node.get_leaf_for_position(pos)
|
||||
if leaf is None or leaf.type == 'string':
|
||||
return []
|
||||
|
||||
context = self._get_module_context().create_context(leaf)
|
||||
|
||||
values = helpers.infer(self._inference_state, context, leaf)
|
||||
values = convert_values(
|
||||
values,
|
||||
only_stubs=only_stubs,
|
||||
prefer_stubs=prefer_stubs,
|
||||
)
|
||||
|
||||
defs = [classes.Name(self._inference_state, c.name) for c in values]
|
||||
# The additional set here allows the definitions to become unique in an
|
||||
# API sense. In the internals we want to separate more things than in
|
||||
# the API.
|
||||
return helpers.sorted_definitions(set(defs))
|
||||
|
||||
def goto_assignments(self, follow_imports=False, follow_builtin_imports=False, **kwargs):
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.16.0. Use Script(...).goto instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return self.goto(*self._pos,
|
||||
follow_imports=follow_imports,
|
||||
follow_builtin_imports=follow_builtin_imports,
|
||||
**kwargs)
|
||||
|
||||
@validate_line_column
|
||||
def goto(self, line=None, column=None, **kwargs):
|
||||
"""
|
||||
Goes to the name that defined the object under the cursor. Optionally
|
||||
you can follow imports.
|
||||
Multiple objects may be returned, depending on an if you can have two
|
||||
different versions of a function.
|
||||
|
||||
:param follow_imports: The method will follow imports.
|
||||
:param follow_builtin_imports: If ``follow_imports`` is True will try
|
||||
to look up names in builtins (i.e. compiled or extension modules).
|
||||
:param only_stubs: Only return stubs for this method.
|
||||
:param prefer_stubs: Prefer stubs to Python objects for this method.
|
||||
:rtype: list of :class:`.Name`
|
||||
"""
|
||||
with debug.increase_indent_cm('goto'):
|
||||
return self._goto(line, column, **kwargs)
|
||||
|
||||
def _goto(self, line, column, follow_imports=False, follow_builtin_imports=False,
|
||||
only_stubs=False, prefer_stubs=False):
|
||||
tree_name = self._module_node.get_name_of_position((line, column))
|
||||
if tree_name is None:
|
||||
# Without a name we really just want to jump to the result e.g.
|
||||
# executed by `foo()`, if we the cursor is after `)`.
|
||||
return self.infer(line, column, only_stubs=only_stubs, prefer_stubs=prefer_stubs)
|
||||
name = self._get_module_context().create_name(tree_name)
|
||||
|
||||
# Make it possible to goto the super class function/attribute
|
||||
# definitions, when they are overwritten.
|
||||
names = []
|
||||
if name.tree_name.is_definition() and name.parent_context.is_class():
|
||||
class_node = name.parent_context.tree_node
|
||||
class_value = self._get_module_context().create_value(class_node)
|
||||
mro = class_value.py__mro__()
|
||||
next(mro) # Ignore the first entry, because it's the class itself.
|
||||
for cls in mro:
|
||||
names = cls.goto(tree_name.value)
|
||||
if names:
|
||||
break
|
||||
|
||||
if not names:
|
||||
names = list(name.goto())
|
||||
|
||||
if follow_imports:
|
||||
names = helpers.filter_follow_imports(names, follow_builtin_imports)
|
||||
names = convert_names(
|
||||
names,
|
||||
only_stubs=only_stubs,
|
||||
prefer_stubs=prefer_stubs,
|
||||
)
|
||||
|
||||
defs = [classes.Name(self._inference_state, d) for d in set(names)]
|
||||
# Avoid duplicates
|
||||
return list(set(helpers.sorted_definitions(defs)))
|
||||
|
||||
@_no_python2_support
|
||||
def search(self, string, **kwargs):
|
||||
"""
|
||||
Searches a name in the current file. For a description of how the
|
||||
search string should look like, please have a look at
|
||||
:meth:`.Project.search`.
|
||||
|
||||
:param bool all_scopes: Default False; searches not only for
|
||||
definitions on the top level of a module level, but also in
|
||||
functions and classes.
|
||||
:yields: :class:`.Name`
|
||||
"""
|
||||
return self._search(string, **kwargs) # Python 2 ...
|
||||
|
||||
def _search(self, string, all_scopes=False):
|
||||
return self._search_func(string, all_scopes=all_scopes)
|
||||
|
||||
@to_list
|
||||
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
|
||||
names = self._names(all_scopes=all_scopes)
|
||||
wanted_type, wanted_names = helpers.split_search_string(string)
|
||||
return search_in_module(
|
||||
self._inference_state,
|
||||
self._get_module_context(),
|
||||
names=names,
|
||||
wanted_type=wanted_type,
|
||||
wanted_names=wanted_names,
|
||||
complete=complete,
|
||||
fuzzy=fuzzy,
|
||||
)
|
||||
|
||||
def complete_search(self, string, **kwargs):
|
||||
"""
|
||||
Like :meth:`.Script.search`, but completes that string. If you want to
|
||||
have all possible definitions in a file you can also provide an empty
|
||||
string.
|
||||
|
||||
:param bool all_scopes: Default False; searches not only for
|
||||
definitions on the top level of a module level, but also in
|
||||
functions and classes.
|
||||
:param fuzzy: Default False. Will return fuzzy completions, which means
|
||||
that e.g. ``ooa`` will match ``foobar``.
|
||||
:yields: :class:`.Completion`
|
||||
"""
|
||||
return self._search_func(string, complete=True, **kwargs)
|
||||
|
||||
@validate_line_column
|
||||
def help(self, line=None, column=None):
|
||||
"""
|
||||
Used to display a help window to users. Uses :meth:`.Script.goto` and
|
||||
returns additional definitions for keywords and operators.
|
||||
|
||||
Typically you will want to display :meth:`.BaseName.docstring` to the
|
||||
user for all the returned definitions.
|
||||
|
||||
The additional definitions are ``Name(...).type == 'keyword'``.
|
||||
These definitions do not have a lot of value apart from their docstring
|
||||
attribute, which contains the output of Python's :func:`help` function.
|
||||
|
||||
:rtype: list of :class:`.Name`
|
||||
"""
|
||||
definitions = self.goto(line, column, follow_imports=True)
|
||||
if definitions:
|
||||
return definitions
|
||||
leaf = self._module_node.get_leaf_for_position((line, column))
|
||||
if leaf is not None and leaf.type in ('keyword', 'operator', 'error_leaf'):
|
||||
def need_pydoc():
|
||||
if leaf.value in ('(', ')', '[', ']'):
|
||||
if leaf.parent.type == 'trailer':
|
||||
return False
|
||||
if leaf.parent.type == 'atom':
|
||||
return False
|
||||
grammar = self._inference_state.grammar
|
||||
# This parso stuff is not public, but since I control it, this
|
||||
# is fine :-) ~dave
|
||||
reserved = grammar._pgen_grammar.reserved_syntax_strings.keys()
|
||||
return leaf.value in reserved
|
||||
|
||||
if need_pydoc():
|
||||
name = KeywordName(self._inference_state, leaf.value)
|
||||
return [classes.Name(self._inference_state, name)]
|
||||
return []
|
||||
|
||||
def usages(self, **kwargs):
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.16.0. Use Script(...).get_references instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return self.get_references(*self._pos, **kwargs)
|
||||
|
||||
@validate_line_column
|
||||
def get_references(self, line=None, column=None, **kwargs):
|
||||
"""
|
||||
Lists all references of a variable in a project. Since this can be
|
||||
quite hard to do for Jedi, if it is too complicated, Jedi will stop
|
||||
searching.
|
||||
|
||||
:param include_builtins: Default ``True``. If ``False``, checks if a reference
|
||||
is a builtin (e.g. ``sys``) and in that case does not return it.
|
||||
:param scope: Default ``'project'``. If ``'file'``, include references in
|
||||
the current module only.
|
||||
:rtype: list of :class:`.Name`
|
||||
"""
|
||||
|
||||
def _references(include_builtins=True, scope='project'):
|
||||
if scope not in ('project', 'file'):
|
||||
raise ValueError('Only the scopes "file" and "project" are allowed')
|
||||
tree_name = self._module_node.get_name_of_position((line, column))
|
||||
if tree_name is None:
|
||||
# Must be syntax
|
||||
return []
|
||||
|
||||
names = find_references(self._get_module_context(), tree_name, scope == 'file')
|
||||
|
||||
definitions = [classes.Name(self._inference_state, n) for n in names]
|
||||
if not include_builtins or scope == 'file':
|
||||
definitions = [d for d in definitions if not d.in_builtin_module()]
|
||||
return helpers.sorted_definitions(definitions)
|
||||
return _references(**kwargs)
|
||||
|
||||
def call_signatures(self):
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.16.0. Use Script(...).get_signatures instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return self.get_signatures(*self._pos)
|
||||
|
||||
@validate_line_column
|
||||
def get_signatures(self, line=None, column=None):
|
||||
"""
|
||||
Return the function object of the call under the cursor.
|
||||
|
||||
E.g. if the cursor is here::
|
||||
|
||||
abs(# <-- cursor is here
|
||||
|
||||
This would return the ``abs`` function. On the other hand::
|
||||
|
||||
abs()# <-- cursor is here
|
||||
|
||||
This would return an empty list..
|
||||
|
||||
:rtype: list of :class:`.Signature`
|
||||
"""
|
||||
pos = line, column
|
||||
call_details = helpers.get_signature_details(self._module_node, pos)
|
||||
if call_details is None:
|
||||
return []
|
||||
|
||||
context = self._get_module_context().create_context(call_details.bracket_leaf)
|
||||
definitions = helpers.cache_signatures(
|
||||
self._inference_state,
|
||||
context,
|
||||
call_details.bracket_leaf,
|
||||
self._code_lines,
|
||||
pos
|
||||
)
|
||||
debug.speed('func_call followed')
|
||||
|
||||
# TODO here we use stubs instead of the actual values. We should use
|
||||
# the signatures from stubs, but the actual values, probably?!
|
||||
return [classes.Signature(self._inference_state, signature, call_details)
|
||||
for signature in definitions.get_signatures()]
|
||||
|
||||
@validate_line_column
|
||||
def get_context(self, line=None, column=None):
|
||||
"""
|
||||
Returns the scope context under the cursor. This basically means the
|
||||
function, class or module where the cursor is at.
|
||||
|
||||
:rtype: :class:`.Name`
|
||||
"""
|
||||
pos = (line, column)
|
||||
leaf = self._module_node.get_leaf_for_position(pos, include_prefixes=True)
|
||||
if leaf.start_pos > pos or leaf.type == 'endmarker':
|
||||
previous_leaf = leaf.get_previous_leaf()
|
||||
if previous_leaf is not None:
|
||||
leaf = previous_leaf
|
||||
|
||||
module_context = self._get_module_context()
|
||||
|
||||
n = tree.search_ancestor(leaf, 'funcdef', 'classdef')
|
||||
if n is not None and n.start_pos < pos <= n.children[-1].start_pos:
|
||||
# This is a bit of a special case. The context of a function/class
|
||||
# name/param/keyword is always it's parent context, not the
|
||||
# function itself. Catch all the cases here where we are before the
|
||||
# suite object, but still in the function.
|
||||
context = module_context.create_value(n).as_context()
|
||||
else:
|
||||
context = module_context.create_context(leaf)
|
||||
|
||||
while context.name is None:
|
||||
context = context.parent_context # comprehensions
|
||||
|
||||
definition = classes.Name(self._inference_state, context.name)
|
||||
while definition.type != 'module':
|
||||
name = definition._name # TODO private access
|
||||
tree_name = name.tree_name
|
||||
if tree_name is not None: # Happens with lambdas.
|
||||
scope = tree_name.get_definition()
|
||||
if scope.start_pos[1] < column:
|
||||
break
|
||||
definition = definition.parent()
|
||||
return definition
|
||||
|
||||
def _analysis(self):
|
||||
self._inference_state.is_analysis = True
|
||||
self._inference_state.analysis_modules = [self._module_node]
|
||||
module = self._get_module_context()
|
||||
try:
|
||||
for node in get_executable_nodes(self._module_node):
|
||||
context = module.create_context(node)
|
||||
if node.type in ('funcdef', 'classdef'):
|
||||
# Resolve the decorators.
|
||||
tree_name_to_values(self._inference_state, context, node.children[1])
|
||||
elif isinstance(node, tree.Import):
|
||||
import_names = set(node.get_defined_names())
|
||||
if node.is_nested():
|
||||
import_names |= set(path[-1] for path in node.get_paths())
|
||||
for n in import_names:
|
||||
imports.infer_import(context, n)
|
||||
elif node.type == 'expr_stmt':
|
||||
types = context.infer_node(node)
|
||||
for testlist in node.children[:-1:2]:
|
||||
# Iterate tuples.
|
||||
unpack_tuple_to_dict(context, types, testlist)
|
||||
else:
|
||||
if node.type == 'name':
|
||||
defs = self._inference_state.infer(context, node)
|
||||
else:
|
||||
defs = infer_call_of_leaf(context, node)
|
||||
try_iter_content(defs)
|
||||
self._inference_state.reset_recursion_limitations()
|
||||
|
||||
ana = [a for a in self._inference_state.analysis if self.path == a.path]
|
||||
return sorted(set(ana), key=lambda x: x.line)
|
||||
finally:
|
||||
self._inference_state.is_analysis = False
|
||||
|
||||
def get_names(self, **kwargs):
|
||||
"""
|
||||
Returns names defined in the current file.
|
||||
|
||||
:param all_scopes: If True lists the names of all scopes instead of
|
||||
only the module namespace.
|
||||
:param definitions: If True lists the names that have been defined by a
|
||||
class, function or a statement (``a = b`` returns ``a``).
|
||||
:param references: If True lists all the names that are not listed by
|
||||
``definitions=True``. E.g. ``a = b`` returns ``b``.
|
||||
:rtype: list of :class:`.Name`
|
||||
"""
|
||||
names = self._names(**kwargs)
|
||||
return [classes.Name(self._inference_state, n) for n in names]
|
||||
|
||||
def get_syntax_errors(self):
|
||||
"""
|
||||
Lists all syntax errors in the current file.
|
||||
|
||||
:rtype: list of :class:`.SyntaxError`
|
||||
"""
|
||||
return parso_to_jedi_errors(self._inference_state.grammar, self._module_node)
|
||||
|
||||
def _names(self, all_scopes=False, definitions=True, references=False):
|
||||
# Set line/column to a random position, because they don't matter.
|
||||
module_context = self._get_module_context()
|
||||
defs = [
|
||||
module_context.create_name(name)
|
||||
for name in helpers.get_module_names(
|
||||
self._module_node,
|
||||
all_scopes=all_scopes,
|
||||
definitions=definitions,
|
||||
references=references,
|
||||
)
|
||||
]
|
||||
return sorted(defs, key=lambda x: x.start_pos)
|
||||
|
||||
@_no_python2_support
|
||||
def rename(self, line=None, column=None, **kwargs):
|
||||
"""
|
||||
Renames all references of the variable under the cursor.
|
||||
|
||||
:param new_name: The variable under the cursor will be renamed to this
|
||||
string.
|
||||
:raises: :exc:`.RefactoringError`
|
||||
:rtype: :class:`.Refactoring`
|
||||
"""
|
||||
return self._rename(line, column, **kwargs)
|
||||
|
||||
def _rename(self, line, column, new_name): # Python 2...
|
||||
definitions = self.get_references(line, column, include_builtins=False)
|
||||
return refactoring.rename(self._inference_state, definitions, new_name)
|
||||
|
||||
@_no_python2_support
|
||||
def extract_variable(self, line, column, **kwargs):
|
||||
"""
|
||||
Moves an expression to a new statemenet.
|
||||
|
||||
For example if you have the cursor on ``foo`` and provide a
|
||||
``new_name`` called ``bar``::
|
||||
|
||||
foo = 3.1
|
||||
x = int(foo + 1)
|
||||
|
||||
the code above will become::
|
||||
|
||||
foo = 3.1
|
||||
bar = foo + 1
|
||||
x = int(bar)
|
||||
|
||||
:param new_name: The expression under the cursor will be renamed to
|
||||
this string.
|
||||
:param int until_line: The the selection range ends at this line, when
|
||||
omitted, Jedi will be clever and try to define the range itself.
|
||||
:param int until_column: The the selection range ends at this column, when
|
||||
omitted, Jedi will be clever and try to define the range itself.
|
||||
:raises: :exc:`.RefactoringError`
|
||||
:rtype: :class:`.Refactoring`
|
||||
"""
|
||||
return self._extract_variable(line, column, **kwargs) # Python 2...
|
||||
|
||||
@validate_line_column
|
||||
def _extract_variable(self, line, column, new_name, until_line=None, until_column=None):
|
||||
if until_line is None and until_column is None:
|
||||
until_pos = None
|
||||
else:
|
||||
if until_line is None:
|
||||
until_line = line
|
||||
if until_column is None:
|
||||
until_column = len(self._code_lines[until_line - 1])
|
||||
until_pos = until_line, until_column
|
||||
return extract_variable(
|
||||
self._inference_state, self.path, self._module_node,
|
||||
new_name, (line, column), until_pos
|
||||
)
|
||||
|
||||
@_no_python2_support
|
||||
def extract_function(self, line, column, **kwargs):
|
||||
"""
|
||||
Moves an expression to a new function.
|
||||
|
||||
For example if you have the cursor on ``foo`` and provide a
|
||||
``new_name`` called ``bar``::
|
||||
|
||||
global_var = 3
|
||||
|
||||
def x():
|
||||
foo = 3.1
|
||||
x = int(foo + 1 + global_var)
|
||||
|
||||
the code above will become::
|
||||
|
||||
global_var = 3
|
||||
|
||||
def bar(foo):
|
||||
return int(foo + 1 + global_var)
|
||||
|
||||
def x():
|
||||
foo = 3.1
|
||||
x = bar(foo)
|
||||
|
||||
:param new_name: The expression under the cursor will be replaced with
|
||||
a function with this name.
|
||||
:param int until_line: The the selection range ends at this line, when
|
||||
omitted, Jedi will be clever and try to define the range itself.
|
||||
:param int until_column: The the selection range ends at this column, when
|
||||
omitted, Jedi will be clever and try to define the range itself.
|
||||
:raises: :exc:`.RefactoringError`
|
||||
:rtype: :class:`.Refactoring`
|
||||
"""
|
||||
return self._extract_function(line, column, **kwargs) # Python 2...
|
||||
|
||||
@validate_line_column
|
||||
def _extract_function(self, line, column, new_name, until_line=None, until_column=None):
|
||||
if until_line is None and until_column is None:
|
||||
until_pos = None
|
||||
else:
|
||||
if until_line is None:
|
||||
until_line = line
|
||||
if until_column is None:
|
||||
until_column = len(self._code_lines[until_line - 1])
|
||||
until_pos = until_line, until_column
|
||||
return extract_function(
|
||||
self._inference_state, self.path, self._get_module_context(),
|
||||
new_name, (line, column), until_pos
|
||||
)
|
||||
|
||||
@_no_python2_support
|
||||
def inline(self, line=None, column=None):
|
||||
"""
|
||||
Inlines a variable under the cursor. This is basically the opposite of
|
||||
extracting a variable. For example with the cursor on bar::
|
||||
|
||||
foo = 3.1
|
||||
bar = foo + 1
|
||||
x = int(bar)
|
||||
|
||||
the code above will become::
|
||||
|
||||
foo = 3.1
|
||||
x = int(foo + 1)
|
||||
|
||||
:raises: :exc:`.RefactoringError`
|
||||
:rtype: :class:`.Refactoring`
|
||||
"""
|
||||
names = [d._name for d in self.get_references(line, column, include_builtins=True)]
|
||||
return refactoring.inline(self._inference_state, names)
|
||||
|
||||
|
||||
class Interpreter(Script):
|
||||
"""
|
||||
Jedi's API for Python REPLs.
|
||||
|
||||
Implements all of the methods that are present in :class:`.Script` as well.
|
||||
|
||||
In addition to completions that normal REPL completion does like
|
||||
``str.upper``, Jedi also supports code completion based on static code
|
||||
analysis. For example Jedi will complete ``str().upper``.
|
||||
|
||||
>>> from os.path import join
|
||||
>>> namespace = locals()
|
||||
>>> script = Interpreter('join("").up', [namespace])
|
||||
>>> print(script.complete()[0].name)
|
||||
upper
|
||||
|
||||
All keyword arguments are same as the arguments for :class:`.Script`.
|
||||
|
||||
:param str code: Code to parse.
|
||||
:type namespaces: typing.List[dict]
|
||||
:param namespaces: A list of namespace dictionaries such as the one
|
||||
returned by :func:`globals` and :func:`locals`.
|
||||
"""
|
||||
_allow_descriptor_getattr_default = True
|
||||
|
||||
def __init__(self, code, namespaces, **kwds):
|
||||
try:
|
||||
namespaces = [dict(n) for n in namespaces]
|
||||
except Exception:
|
||||
raise TypeError("namespaces must be a non-empty list of dicts.")
|
||||
|
||||
environment = kwds.get('environment', None)
|
||||
if environment is None:
|
||||
environment = InterpreterEnvironment()
|
||||
else:
|
||||
if not isinstance(environment, InterpreterEnvironment):
|
||||
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
|
||||
|
||||
super(Interpreter, self).__init__(code, environment=environment,
|
||||
project=Project(os.getcwd()), **kwds)
|
||||
self.namespaces = namespaces
|
||||
self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default
|
||||
|
||||
@cache.memoize_method
|
||||
def _get_module_context(self):
|
||||
tree_module_value = ModuleValue(
|
||||
self._inference_state, self._module_node,
|
||||
file_io=KnownContentFileIO(self.path, self._code),
|
||||
string_names=('__main__',),
|
||||
code_lines=self._code_lines,
|
||||
)
|
||||
return interpreter.MixedModuleContext(
|
||||
tree_module_value,
|
||||
self.namespaces,
|
||||
)
|
||||
|
||||
|
||||
def names(source=None, path=None, encoding='utf-8', all_scopes=False,
|
||||
definitions=True, references=False, environment=None):
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.16.0. Use Script(...).get_names instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
|
||||
return Script(source, path=path, encoding=encoding).get_names(
|
||||
all_scopes=all_scopes,
|
||||
definitions=definitions,
|
||||
references=references,
|
||||
)
|
||||
|
||||
|
||||
def preload_module(*modules):
|
||||
"""
|
||||
Preloading modules tells Jedi to load a module now, instead of lazy parsing
|
||||
of modules. This can be useful for IDEs, to control which modules to load
|
||||
on startup.
|
||||
|
||||
:param modules: different module names, list of string.
|
||||
"""
|
||||
for m in modules:
|
||||
s = "import %s as x; x." % m
|
||||
Script(s, path=None).complete(1, len(s))
|
||||
|
||||
|
||||
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
|
||||
notices=True, speed=True):
|
||||
"""
|
||||
Define a callback debug function to get all the debug messages.
|
||||
|
||||
If you don't specify any arguments, debug messages will be printed to stdout.
|
||||
|
||||
:param func_cb: The callback function for debug messages.
|
||||
"""
|
||||
debug.debug_function = func_cb
|
||||
debug.enable_warning = warnings
|
||||
debug.enable_notice = notices
|
||||
debug.enable_speed = speed
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
925
venv/Lib/site-packages/jedi/api/classes.py
Normal file
925
venv/Lib/site-packages/jedi/api/classes.py
Normal file
|
@ -0,0 +1,925 @@
|
|||
"""
|
||||
There are a couple of classes documented in here:
|
||||
|
||||
- :class:`.BaseName` as an abstact base class for almost everything.
|
||||
- :class:`.Name` used in a lot of places
|
||||
- :class:`.Completion` for completions
|
||||
- :class:`.BaseSignature` as a base class for signatures
|
||||
- :class:`.Signature` for :meth:`.Script.get_signatures` only
|
||||
- :class:`.ParamName` used for parameters of signatures
|
||||
- :class:`.Refactoring` for refactorings
|
||||
- :class:`.SyntaxError` for :meth:`.Script.get_syntax_errors` only
|
||||
|
||||
These classes are the much biggest part of the API, because they contain
|
||||
the interesting information about all operations.
|
||||
"""
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from parso.python.tree import search_ancestor
|
||||
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi.inference.utils import unite
|
||||
from jedi.cache import memoize_method
|
||||
from jedi.inference import imports
|
||||
from jedi.inference.imports import ImportName
|
||||
from jedi.inference.compiled.mixed import MixedName
|
||||
from jedi.inference.gradual.typeshed import StubModuleValue
|
||||
from jedi.inference.gradual.conversion import convert_names, convert_values
|
||||
from jedi.inference.base_value import ValueSet
|
||||
from jedi.api.keywords import KeywordName
|
||||
from jedi.api import completion_cache
|
||||
from jedi.api.helpers import filter_follow_imports
|
||||
|
||||
|
||||
def _sort_names_by_start_pos(names):
|
||||
return sorted(names, key=lambda s: s.start_pos or (0, 0))
|
||||
|
||||
|
||||
def defined_names(inference_state, context):
|
||||
"""
|
||||
List sub-definitions (e.g., methods in class).
|
||||
|
||||
:type scope: Scope
|
||||
:rtype: list of Name
|
||||
"""
|
||||
filter = next(context.get_filters())
|
||||
names = [name for name in filter.values()]
|
||||
return [Name(inference_state, n) for n in _sort_names_by_start_pos(names)]
|
||||
|
||||
|
||||
def _values_to_definitions(values):
|
||||
return [Name(c.inference_state, c.name) for c in values]
|
||||
|
||||
|
||||
class BaseName(object):
|
||||
"""
|
||||
The base class for all definitions, completions and signatures.
|
||||
"""
|
||||
_mapping = {
|
||||
'posixpath': 'os.path',
|
||||
'riscospath': 'os.path',
|
||||
'ntpath': 'os.path',
|
||||
'os2emxpath': 'os.path',
|
||||
'macpath': 'os.path',
|
||||
'genericpath': 'os.path',
|
||||
'posix': 'os',
|
||||
'_io': 'io',
|
||||
'_functools': 'functools',
|
||||
'_collections': 'collections',
|
||||
'_socket': 'socket',
|
||||
'_sqlite3': 'sqlite3',
|
||||
'__builtin__': 'builtins',
|
||||
}
|
||||
|
||||
_tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in {
|
||||
'argparse._ActionsContainer': 'argparse.ArgumentParser',
|
||||
}.items())
|
||||
|
||||
def __init__(self, inference_state, name):
|
||||
self._inference_state = inference_state
|
||||
self._name = name
|
||||
"""
|
||||
An instance of :class:`parso.python.tree.Name` subclass.
|
||||
"""
|
||||
self.is_keyword = isinstance(self._name, KeywordName)
|
||||
|
||||
@memoize_method
|
||||
def _get_module_context(self):
|
||||
# This can take a while to complete, because in the worst case of
|
||||
# imports (consider `import a` completions), we need to load all
|
||||
# modules starting with a first.
|
||||
return self._name.get_root_context()
|
||||
|
||||
@property
|
||||
def module_path(self):
|
||||
"""
|
||||
Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``
|
||||
|
||||
:rtype: str or None
|
||||
"""
|
||||
module = self._get_module_context()
|
||||
if module.is_stub() or not module.is_compiled():
|
||||
# Compiled modules should not return a module path even if they
|
||||
# have one.
|
||||
return self._get_module_context().py__file__()
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Name of variable/function/class/module.
|
||||
|
||||
For example, for ``x = None`` it returns ``'x'``.
|
||||
|
||||
:rtype: str or None
|
||||
"""
|
||||
return self._name.get_public_name()
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
The type of the definition.
|
||||
|
||||
Here is an example of the value of this attribute. Let's consider
|
||||
the following source. As what is in ``variable`` is unambiguous
|
||||
to Jedi, :meth:`jedi.Script.infer` should return a list of
|
||||
definition for ``sys``, ``f``, ``C`` and ``x``.
|
||||
|
||||
>>> from jedi._compatibility import no_unicode_pprint
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... import keyword
|
||||
...
|
||||
... class C:
|
||||
... pass
|
||||
...
|
||||
... class D:
|
||||
... pass
|
||||
...
|
||||
... x = D()
|
||||
...
|
||||
... def f():
|
||||
... pass
|
||||
...
|
||||
... for variable in [keyword, f, C, x]:
|
||||
... variable'''
|
||||
|
||||
>>> script = Script(source)
|
||||
>>> defs = script.infer()
|
||||
|
||||
Before showing what is in ``defs``, let's sort it by :attr:`line`
|
||||
so that it is easy to relate the result to the source code.
|
||||
|
||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
|
||||
[<Name full_name='keyword', description='module keyword'>,
|
||||
<Name full_name='__main__.C', description='class C'>,
|
||||
<Name full_name='__main__.D', description='instance D'>,
|
||||
<Name full_name='__main__.f', description='def f'>]
|
||||
|
||||
Finally, here is what you can get from :attr:`type`:
|
||||
|
||||
>>> defs = [str(d.type) for d in defs] # It's unicode and in Py2 has u before it.
|
||||
>>> defs[0]
|
||||
'module'
|
||||
>>> defs[1]
|
||||
'class'
|
||||
>>> defs[2]
|
||||
'instance'
|
||||
>>> defs[3]
|
||||
'function'
|
||||
|
||||
Valid values for type are ``module``, ``class``, ``instance``, ``function``,
|
||||
``param``, ``path``, ``keyword`` and ``statement``.
|
||||
|
||||
"""
|
||||
tree_name = self._name.tree_name
|
||||
resolve = False
|
||||
if tree_name is not None:
|
||||
# TODO move this to their respective names.
|
||||
definition = tree_name.get_definition()
|
||||
if definition is not None and definition.type == 'import_from' and \
|
||||
tree_name.is_definition():
|
||||
resolve = True
|
||||
|
||||
if isinstance(self._name, imports.SubModuleName) or resolve:
|
||||
for value in self._name.infer():
|
||||
return value.api_type
|
||||
return self._name.api_type
|
||||
|
||||
@property
|
||||
def module_name(self):
|
||||
"""
|
||||
The module name, a bit similar to what ``__name__`` is in a random
|
||||
Python module.
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = 'import json'
|
||||
>>> script = Script(source, path='example.py')
|
||||
>>> d = script.infer()[0]
|
||||
>>> print(d.module_name) # doctest: +ELLIPSIS
|
||||
json
|
||||
"""
|
||||
return self._get_module_context().py__name__()
|
||||
|
||||
def in_builtin_module(self):
|
||||
"""
|
||||
Returns True, if this is a builtin module.
|
||||
"""
|
||||
value = self._get_module_context().get_value()
|
||||
if isinstance(value, StubModuleValue):
|
||||
return any(v.is_compiled() for v in value.non_stub_value_set)
|
||||
return value.is_compiled()
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
"""The line where the definition occurs (starting with 1)."""
|
||||
start_pos = self._name.start_pos
|
||||
if start_pos is None:
|
||||
return None
|
||||
return start_pos[0]
|
||||
|
||||
@property
|
||||
def column(self):
|
||||
"""The column where the definition occurs (starting with 0)."""
|
||||
start_pos = self._name.start_pos
|
||||
if start_pos is None:
|
||||
return None
|
||||
return start_pos[1]
|
||||
|
||||
def get_definition_start_position(self):
|
||||
"""
|
||||
The (row, column) of the start of the definition range. Rows start with
|
||||
1, columns start with 0.
|
||||
|
||||
:rtype: Optional[Tuple[int, int]]
|
||||
"""
|
||||
if self._name.tree_name is None:
|
||||
return None
|
||||
definition = self._name.tree_name.get_definition()
|
||||
if definition is None:
|
||||
return self._name.start_pos
|
||||
return definition.start_pos
|
||||
|
||||
def get_definition_end_position(self):
|
||||
"""
|
||||
The (row, column) of the end of the definition range. Rows start with
|
||||
1, columns start with 0.
|
||||
|
||||
:rtype: Optional[Tuple[int, int]]
|
||||
"""
|
||||
if self._name.tree_name is None:
|
||||
return None
|
||||
definition = self._name.tree_name.get_definition()
|
||||
if definition is None:
|
||||
return self._name.tree_name.end_pos
|
||||
if self.type in ("function", "class"):
|
||||
last_leaf = definition.get_last_leaf()
|
||||
if last_leaf.type == "newline":
|
||||
return last_leaf.get_previous_leaf().end_pos
|
||||
return last_leaf.end_pos
|
||||
return definition.end_pos
|
||||
|
||||
def docstring(self, raw=False, fast=True):
|
||||
r"""
|
||||
Return a document string for this completion object.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''\
|
||||
... def f(a, b=1):
|
||||
... "Document for function f."
|
||||
... '''
|
||||
>>> script = Script(source, path='example.py')
|
||||
>>> doc = script.infer(1, len('def f'))[0].docstring()
|
||||
>>> print(doc)
|
||||
f(a, b=1)
|
||||
<BLANKLINE>
|
||||
Document for function f.
|
||||
|
||||
Notice that useful extra information is added to the actual
|
||||
docstring, e.g. function signatures are prepended to their docstrings.
|
||||
If you need the actual docstring, use ``raw=True`` instead.
|
||||
|
||||
>>> print(script.infer(1, len('def f'))[0].docstring(raw=True))
|
||||
Document for function f.
|
||||
|
||||
:param fast: Don't follow imports that are only one level deep like
|
||||
``import foo``, but follow ``from foo import bar``. This makes
|
||||
sense for speed reasons. Completing `import a` is slow if you use
|
||||
the ``foo.docstring(fast=False)`` on every object, because it
|
||||
parses all libraries starting with ``a``.
|
||||
"""
|
||||
if isinstance(self._name, ImportName) and fast:
|
||||
return ''
|
||||
doc = self._get_docstring()
|
||||
if raw:
|
||||
return doc
|
||||
|
||||
signature_text = self._get_docstring_signature()
|
||||
if signature_text and doc:
|
||||
return signature_text + '\n\n' + doc
|
||||
else:
|
||||
return signature_text + doc
|
||||
|
||||
def _get_docstring(self):
|
||||
return self._name.py__doc__()
|
||||
|
||||
def _get_docstring_signature(self):
|
||||
return '\n'.join(
|
||||
signature.to_string()
|
||||
for signature in self._get_signatures(for_docstring=True)
|
||||
)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""
|
||||
A description of the :class:`.Name` object, which is heavily used
|
||||
in testing. e.g. for ``isinstance`` it returns ``def isinstance``.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi._compatibility import no_unicode_pprint
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... def f():
|
||||
... pass
|
||||
...
|
||||
... class C:
|
||||
... pass
|
||||
...
|
||||
... variable = f if random.choice([0,1]) else C'''
|
||||
>>> script = Script(source) # line is maximum by default
|
||||
>>> defs = script.infer(column=3)
|
||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
|
||||
[<Name full_name='__main__.f', description='def f'>,
|
||||
<Name full_name='__main__.C', description='class C'>]
|
||||
>>> str(defs[0].description) # strip literals in python2
|
||||
'def f'
|
||||
>>> str(defs[1].description)
|
||||
'class C'
|
||||
|
||||
"""
|
||||
typ = self.type
|
||||
tree_name = self._name.tree_name
|
||||
if typ == 'param':
|
||||
return typ + ' ' + self._name.to_string()
|
||||
if typ in ('function', 'class', 'module', 'instance') or tree_name is None:
|
||||
if typ == 'function':
|
||||
# For the description we want a short and a pythonic way.
|
||||
typ = 'def'
|
||||
return typ + ' ' + self._name.get_public_name()
|
||||
|
||||
definition = tree_name.get_definition(include_setitem=True) or tree_name
|
||||
# Remove the prefix, because that's not what we want for get_code
|
||||
# here.
|
||||
txt = definition.get_code(include_prefix=False)
|
||||
# Delete comments:
|
||||
txt = re.sub(r'#[^\n]+\n', ' ', txt)
|
||||
# Delete multi spaces/newlines
|
||||
txt = re.sub(r'\s+', ' ', txt).strip()
|
||||
return txt
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""
|
||||
Dot-separated path of this object.
|
||||
|
||||
It is in the form of ``<module>[.<submodule>[...]][.<object>]``.
|
||||
It is useful when you want to look up Python manual of the
|
||||
object at hand.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... import os
|
||||
... os.path.join'''
|
||||
>>> script = Script(source, path='example.py')
|
||||
>>> print(script.infer(3, len('os.path.join'))[0].full_name)
|
||||
os.path.join
|
||||
|
||||
Notice that it returns ``'os.path.join'`` instead of (for example)
|
||||
``'posixpath.join'``. This is not correct, since the modules name would
|
||||
be ``<module 'posixpath' ...>```. However most users find the latter
|
||||
more practical.
|
||||
"""
|
||||
if not self._name.is_value_name:
|
||||
return None
|
||||
|
||||
names = self._name.get_qualified_names(include_module_names=True)
|
||||
if names is None:
|
||||
return None
|
||||
|
||||
names = list(names)
|
||||
try:
|
||||
names[0] = self._mapping[names[0]]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return '.'.join(names)
|
||||
|
||||
def is_stub(self):
|
||||
"""
|
||||
Returns True if the current name is defined in a stub file.
|
||||
"""
|
||||
if not self._name.is_value_name:
|
||||
return False
|
||||
|
||||
return self._name.get_root_context().is_stub()
|
||||
|
||||
def is_side_effect(self):
|
||||
"""
|
||||
Checks if a name is defined as ``self.foo = 3``. In case of self, this
|
||||
function would return False, for foo it would return True.
|
||||
"""
|
||||
tree_name = self._name.tree_name
|
||||
if tree_name is None:
|
||||
return False
|
||||
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
|
||||
|
||||
def goto(self, **kwargs):
|
||||
"""
|
||||
Like :meth:`.Script.goto` (also supports the same params), but does it
|
||||
for the current name. This is typically useful if you are using
|
||||
something like :meth:`.Script.get_names()`.
|
||||
|
||||
:param follow_imports: The goto call will follow imports.
|
||||
:param follow_builtin_imports: If follow_imports is True will try to
|
||||
look up names in builtins (i.e. compiled or extension modules).
|
||||
:param only_stubs: Only return stubs for this goto call.
|
||||
:param prefer_stubs: Prefer stubs to Python objects for this goto call.
|
||||
:rtype: list of :class:`Name`
|
||||
"""
|
||||
with debug.increase_indent_cm('goto for %s' % self._name):
|
||||
return self._goto(**kwargs)
|
||||
|
||||
def goto_assignments(self, **kwargs): # Python 2...
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.16.0. Use .goto.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return self.goto(**kwargs)
|
||||
|
||||
def _goto(self, follow_imports=False, follow_builtin_imports=False,
|
||||
only_stubs=False, prefer_stubs=False):
|
||||
|
||||
if not self._name.is_value_name:
|
||||
return []
|
||||
|
||||
names = self._name.goto()
|
||||
if follow_imports:
|
||||
names = filter_follow_imports(names, follow_builtin_imports)
|
||||
names = convert_names(
|
||||
names,
|
||||
only_stubs=only_stubs,
|
||||
prefer_stubs=prefer_stubs,
|
||||
)
|
||||
return [self if n == self._name else Name(self._inference_state, n)
|
||||
for n in names]
|
||||
|
||||
def infer(self, **kwargs): # Python 2...
|
||||
"""
|
||||
Like :meth:`.Script.infer`, it can be useful to understand which type
|
||||
the current name has.
|
||||
|
||||
Return the actual definitions. I strongly recommend not using it for
|
||||
your completions, because it might slow down |jedi|. If you want to
|
||||
read only a few objects (<=20), it might be useful, especially to get
|
||||
the original docstrings. The basic problem of this function is that it
|
||||
follows all results. This means with 1000 completions (e.g. numpy),
|
||||
it's just very, very slow.
|
||||
|
||||
:param only_stubs: Only return stubs for this goto call.
|
||||
:param prefer_stubs: Prefer stubs to Python objects for this type
|
||||
inference call.
|
||||
:rtype: list of :class:`Name`
|
||||
"""
|
||||
with debug.increase_indent_cm('infer for %s' % self._name):
|
||||
return self._infer(**kwargs)
|
||||
|
||||
def _infer(self, only_stubs=False, prefer_stubs=False):
|
||||
assert not (only_stubs and prefer_stubs)
|
||||
|
||||
if not self._name.is_value_name:
|
||||
return []
|
||||
|
||||
# First we need to make sure that we have stub names (if possible) that
|
||||
# we can follow. If we don't do that, we can end up with the inferred
|
||||
# results of Python objects instead of stubs.
|
||||
names = convert_names([self._name], prefer_stubs=True)
|
||||
values = convert_values(
|
||||
ValueSet.from_sets(n.infer() for n in names),
|
||||
only_stubs=only_stubs,
|
||||
prefer_stubs=prefer_stubs,
|
||||
)
|
||||
resulting_names = [c.name for c in values]
|
||||
return [self if n == self._name else Name(self._inference_state, n)
|
||||
for n in resulting_names]
|
||||
|
||||
@property
|
||||
@memoize_method
|
||||
def params(self):
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.16.0. Use get_signatures()[...].params",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
# Only return the first one. There might be multiple one, especially
|
||||
# with overloading.
|
||||
for signature in self._get_signatures():
|
||||
return [
|
||||
Name(self._inference_state, n)
|
||||
for n in signature.get_param_names(resolve_stars=True)
|
||||
]
|
||||
|
||||
if self.type == 'function' or self.type == 'class':
|
||||
# Fallback, if no signatures were defined (which is probably by
|
||||
# itself a bug).
|
||||
return []
|
||||
raise AttributeError('There are no params defined on this.')
|
||||
|
||||
def parent(self):
|
||||
"""
|
||||
Returns the parent scope of this identifier.
|
||||
|
||||
:rtype: Name
|
||||
"""
|
||||
if not self._name.is_value_name:
|
||||
return None
|
||||
|
||||
if self.type in ('function', 'class', 'param') and self._name.tree_name is not None:
|
||||
# Since the parent_context doesn't really match what the user
|
||||
# thinks of that the parent is here, we do these cases separately.
|
||||
# The reason for this is the following:
|
||||
# - class: Nested classes parent_context is always the
|
||||
# parent_context of the most outer one.
|
||||
# - function: Functions in classes have the module as
|
||||
# parent_context.
|
||||
# - param: The parent_context of a param is not its function but
|
||||
# e.g. the outer class or module.
|
||||
cls_or_func_node = self._name.tree_name.get_definition()
|
||||
parent = search_ancestor(cls_or_func_node, 'funcdef', 'classdef', 'file_input')
|
||||
context = self._get_module_context().create_value(parent).as_context()
|
||||
else:
|
||||
context = self._name.parent_context
|
||||
|
||||
if context is None:
|
||||
return None
|
||||
while context.name is None:
|
||||
# Happens for comprehension contexts
|
||||
context = context.parent_context
|
||||
|
||||
return Name(self._inference_state, context.name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %sname=%r, description=%r>" % (
|
||||
self.__class__.__name__,
|
||||
'full_' if self.full_name else '',
|
||||
self.full_name or self.name,
|
||||
self.description,
|
||||
)
|
||||
|
||||
def get_line_code(self, before=0, after=0):
|
||||
"""
|
||||
Returns the line of code where this object was defined.
|
||||
|
||||
:param before: Add n lines before the current line to the output.
|
||||
:param after: Add n lines after the current line to the output.
|
||||
|
||||
:return str: Returns the line(s) of code or an empty string if it's a
|
||||
builtin.
|
||||
"""
|
||||
if not self._name.is_value_name:
|
||||
return ''
|
||||
|
||||
lines = self._name.get_root_context().code_lines
|
||||
if lines is None:
|
||||
# Probably a builtin module, just ignore in that case.
|
||||
return ''
|
||||
|
||||
index = self._name.start_pos[0] - 1
|
||||
start_index = max(index - before, 0)
|
||||
return ''.join(lines[start_index:index + after + 1])
|
||||
|
||||
def _get_signatures(self, for_docstring=False):
|
||||
if for_docstring and self._name.api_type == 'statement' and not self.is_stub():
|
||||
# For docstrings we don't resolve signatures if they are simple
|
||||
# statements and not stubs. This is a speed optimization.
|
||||
return []
|
||||
|
||||
if isinstance(self._name, MixedName):
|
||||
# While this would eventually happen anyway, it's basically just a
|
||||
# shortcut to not infer anything tree related, because it's really
|
||||
# not necessary.
|
||||
return self._name.infer_compiled_value().get_signatures()
|
||||
|
||||
names = convert_names([self._name], prefer_stubs=True)
|
||||
return [sig for name in names for sig in name.infer().get_signatures()]
|
||||
|
||||
def get_signatures(self):
|
||||
"""
|
||||
Returns all potential signatures for a function or a class. Multiple
|
||||
signatures are typical if you use Python stubs with ``@overload``.
|
||||
|
||||
:rtype: list of :class:`BaseSignature`
|
||||
"""
|
||||
return [
|
||||
BaseSignature(self._inference_state, s)
|
||||
for s in self._get_signatures()
|
||||
]
|
||||
|
||||
def execute(self):
|
||||
"""
|
||||
Uses type inference to "execute" this identifier and returns the
|
||||
executed objects.
|
||||
|
||||
:rtype: list of :class:`Name`
|
||||
"""
|
||||
return _values_to_definitions(self._name.infer().execute_with_values())
|
||||
|
||||
def get_type_hint(self):
|
||||
"""
|
||||
Returns type hints like ``Iterable[int]`` or ``Union[int, str]``.
|
||||
|
||||
This method might be quite slow, especially for functions. The problem
|
||||
is finding executions for those functions to return something like
|
||||
``Callable[[int, str], str]``.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self._name.infer().get_type_hint()
|
||||
|
||||
|
||||
class Completion(BaseName):
|
||||
"""
|
||||
``Completion`` objects are returned from :meth:`.Script.complete`. They
|
||||
provide additional information about a completion.
|
||||
"""
|
||||
def __init__(self, inference_state, name, stack, like_name_length,
|
||||
is_fuzzy, cached_name=None):
|
||||
super(Completion, self).__init__(inference_state, name)
|
||||
|
||||
self._like_name_length = like_name_length
|
||||
self._stack = stack
|
||||
self._is_fuzzy = is_fuzzy
|
||||
self._cached_name = cached_name
|
||||
|
||||
# Completion objects with the same Completion name (which means
|
||||
# duplicate items in the completion)
|
||||
self._same_name_completions = []
|
||||
|
||||
def _complete(self, like_name):
|
||||
append = ''
|
||||
if settings.add_bracket_after_function \
|
||||
and self.type == 'function':
|
||||
append = '('
|
||||
|
||||
name = self._name.get_public_name()
|
||||
if like_name:
|
||||
name = name[self._like_name_length:]
|
||||
return name + append
|
||||
|
||||
@property
|
||||
def complete(self):
|
||||
"""
|
||||
Only works with non-fuzzy completions. Returns None if fuzzy
|
||||
completions are used.
|
||||
|
||||
Return the rest of the word, e.g. completing ``isinstance``::
|
||||
|
||||
isinstan# <-- Cursor is here
|
||||
|
||||
would return the string 'ce'. It also adds additional stuff, depending
|
||||
on your ``settings.py``.
|
||||
|
||||
Assuming the following function definition::
|
||||
|
||||
def foo(param=0):
|
||||
pass
|
||||
|
||||
completing ``foo(par`` would give a ``Completion`` which ``complete``
|
||||
would be ``am=``.
|
||||
"""
|
||||
if self._is_fuzzy:
|
||||
return None
|
||||
return self._complete(True)
|
||||
|
||||
@property
|
||||
def name_with_symbols(self):
|
||||
"""
|
||||
Similar to :attr:`.name`, but like :attr:`.name` returns also the
|
||||
symbols, for example assuming the following function definition::
|
||||
|
||||
def foo(param=0):
|
||||
pass
|
||||
|
||||
completing ``foo(`` would give a ``Completion`` which
|
||||
``name_with_symbols`` would be "param=".
|
||||
|
||||
"""
|
||||
return self._complete(False)
|
||||
|
||||
def docstring(self, raw=False, fast=True):
|
||||
"""
|
||||
Documented under :meth:`BaseName.docstring`.
|
||||
"""
|
||||
if self._like_name_length >= 3:
|
||||
# In this case we can just resolve the like name, because we
|
||||
# wouldn't load like > 100 Python modules anymore.
|
||||
fast = False
|
||||
|
||||
return super(Completion, self).docstring(raw=raw, fast=fast)
|
||||
|
||||
def _get_docstring(self):
|
||||
if self._cached_name is not None:
|
||||
return completion_cache.get_docstring(
|
||||
self._cached_name,
|
||||
self._name.get_public_name(),
|
||||
lambda: self._get_cache()
|
||||
)
|
||||
return super(Completion, self)._get_docstring()
|
||||
|
||||
def _get_docstring_signature(self):
|
||||
if self._cached_name is not None:
|
||||
return completion_cache.get_docstring_signature(
|
||||
self._cached_name,
|
||||
self._name.get_public_name(),
|
||||
lambda: self._get_cache()
|
||||
)
|
||||
return super(Completion, self)._get_docstring_signature()
|
||||
|
||||
def _get_cache(self):
|
||||
return (
|
||||
super(Completion, self).type,
|
||||
super(Completion, self)._get_docstring_signature(),
|
||||
super(Completion, self)._get_docstring(),
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
Documented under :meth:`BaseName.type`.
|
||||
"""
|
||||
# Purely a speed optimization.
|
||||
if self._cached_name is not None:
|
||||
return completion_cache.get_type(
|
||||
self._cached_name,
|
||||
self._name.get_public_name(),
|
||||
lambda: self._get_cache()
|
||||
)
|
||||
|
||||
return super(Completion, self).type
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self._name.get_public_name())
|
||||
|
||||
|
||||
class Name(BaseName):
|
||||
"""
|
||||
*Name* objects are returned from many different APIs including
|
||||
:meth:`.Script.goto` or :meth:`.Script.infer`.
|
||||
"""
|
||||
def __init__(self, inference_state, definition):
|
||||
super(Name, self).__init__(inference_state, definition)
|
||||
|
||||
@property
|
||||
def desc_with_module(self):
|
||||
warnings.warn(
|
||||
"Deprecated since version 0.17.0. No replacement for now, maybe .full_name helps",
|
||||
DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
return "%s:%s" % (self.module_name, self.description)
|
||||
|
||||
@memoize_method
|
||||
def defined_names(self):
|
||||
"""
|
||||
List sub-definitions (e.g., methods in class).
|
||||
|
||||
:rtype: list of :class:`Name`
|
||||
"""
|
||||
defs = self._name.infer()
|
||||
return sorted(
|
||||
unite(defined_names(self._inference_state, d.as_context()) for d in defs),
|
||||
key=lambda s: s._name.start_pos or (0, 0)
|
||||
)
|
||||
|
||||
def is_definition(self):
|
||||
"""
|
||||
Returns True, if defined as a name in a statement, function or class.
|
||||
Returns False, if it's a reference to such a definition.
|
||||
"""
|
||||
if self._name.tree_name is None:
|
||||
return True
|
||||
else:
|
||||
return self._name.tree_name.is_definition()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._name.start_pos == other._name.start_pos \
|
||||
and self.module_path == other.module_path \
|
||||
and self.name == other.name \
|
||||
and self._inference_state == other._inference_state
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._name.start_pos, self.module_path, self.name, self._inference_state))
|
||||
|
||||
|
||||
class BaseSignature(Name):
|
||||
"""
|
||||
These signatures are returned by :meth:`BaseName.get_signatures`
|
||||
calls.
|
||||
"""
|
||||
def __init__(self, inference_state, signature):
|
||||
super(BaseSignature, self).__init__(inference_state, signature.name)
|
||||
self._signature = signature
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
"""
|
||||
Returns definitions for all parameters that a signature defines.
|
||||
This includes stuff like ``*args`` and ``**kwargs``.
|
||||
|
||||
:rtype: list of :class:`.ParamName`
|
||||
"""
|
||||
return [ParamName(self._inference_state, n)
|
||||
for n in self._signature.get_param_names(resolve_stars=True)]
|
||||
|
||||
def to_string(self):
|
||||
"""
|
||||
Returns a text representation of the signature. This could for example
|
||||
look like ``foo(bar, baz: int, **kwargs)``.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self._signature.to_string()
|
||||
|
||||
|
||||
class Signature(BaseSignature):
|
||||
"""
|
||||
A full signature object is the return value of
|
||||
:meth:`.Script.get_signatures`.
|
||||
"""
|
||||
def __init__(self, inference_state, signature, call_details):
|
||||
super(Signature, self).__init__(inference_state, signature)
|
||||
self._call_details = call_details
|
||||
self._signature = signature
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
"""
|
||||
Returns the param index of the current cursor position.
|
||||
Returns None if the index cannot be found in the curent call.
|
||||
|
||||
:rtype: int
|
||||
"""
|
||||
return self._call_details.calculate_index(
|
||||
self._signature.get_param_names(resolve_stars=True)
|
||||
)
|
||||
|
||||
@property
|
||||
def bracket_start(self):
|
||||
"""
|
||||
Returns a line/column tuple of the bracket that is responsible for the
|
||||
last function call. The first line is 1 and the first column 0.
|
||||
|
||||
:rtype: int, int
|
||||
"""
|
||||
return self._call_details.bracket_leaf.start_pos
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: index=%r %s>' % (
|
||||
type(self).__name__,
|
||||
self.index,
|
||||
self._signature.to_string(),
|
||||
)
|
||||
|
||||
|
||||
class ParamName(Name):
|
||||
def infer_default(self):
|
||||
"""
|
||||
Returns default values like the ``1`` of ``def foo(x=1):``.
|
||||
|
||||
:rtype: list of :class:`.Name`
|
||||
"""
|
||||
return _values_to_definitions(self._name.infer_default())
|
||||
|
||||
def infer_annotation(self, **kwargs):
|
||||
"""
|
||||
:param execute_annotation: Default True; If False, values are not
|
||||
executed and classes are returned instead of instances.
|
||||
:rtype: list of :class:`.Name`
|
||||
"""
|
||||
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
|
||||
|
||||
def to_string(self):
|
||||
"""
|
||||
Returns a simple representation of a param, like
|
||||
``f: Callable[..., Any]``.
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
return self._name.to_string()
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
"""
|
||||
Returns an enum instance of :mod:`inspect`'s ``Parameter`` enum.
|
||||
|
||||
:rtype: :py:attr:`inspect.Parameter.kind`
|
||||
"""
|
||||
if sys.version_info < (3, 5):
|
||||
raise NotImplementedError(
|
||||
'Python 2 is end-of-life, the new feature is not available for it'
|
||||
)
|
||||
return self._name.get_kind()
|
665
venv/Lib/site-packages/jedi/api/completion.py
Normal file
665
venv/Lib/site-packages/jedi/api/completion.py
Normal file
|
@ -0,0 +1,665 @@
|
|||
import re
|
||||
from textwrap import dedent
|
||||
|
||||
from parso.python.token import PythonTokenTypes
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor, Leaf
|
||||
from parso import split_lines
|
||||
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.api import classes
|
||||
from jedi.api import helpers
|
||||
from jedi.api import keywords
|
||||
from jedi.api.strings import complete_dict
|
||||
from jedi.api.file_name import complete_file_name
|
||||
from jedi.inference import imports
|
||||
from jedi.inference.base_value import ValueSet
|
||||
from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names
|
||||
from jedi.inference.context import get_global_filters
|
||||
from jedi.inference.value import TreeInstance, ModuleValue
|
||||
from jedi.inference.names import ParamNameWrapper, SubModuleName
|
||||
from jedi.inference.gradual.conversion import convert_values, convert_names
|
||||
from jedi.parser_utils import cut_value_at_position
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
class ParamNameWithEquals(ParamNameWrapper):
|
||||
def get_public_name(self):
|
||||
return self.string_name + '='
|
||||
|
||||
|
||||
def _get_signature_param_names(signatures, positional_count, used_kwargs):
|
||||
# Add named params
|
||||
for call_sig in signatures:
|
||||
for i, p in enumerate(call_sig.params):
|
||||
# Allow protected access, because it's a public API.
|
||||
# TODO reconsider with Python 2 drop
|
||||
kind = p._name.get_kind()
|
||||
if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD:
|
||||
continue
|
||||
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \
|
||||
and p.name not in used_kwargs:
|
||||
yield ParamNameWithEquals(p._name)
|
||||
|
||||
|
||||
def _must_be_kwarg(signatures, positional_count, used_kwargs):
|
||||
if used_kwargs:
|
||||
return True
|
||||
|
||||
must_be_kwarg = True
|
||||
for signature in signatures:
|
||||
for i, p in enumerate(signature.params):
|
||||
# TODO reconsider with Python 2 drop
|
||||
kind = p._name.get_kind()
|
||||
if kind is Parameter.VAR_POSITIONAL:
|
||||
# In case there were not already kwargs, the next param can
|
||||
# always be a normal argument.
|
||||
return False
|
||||
|
||||
if i >= positional_count and kind in (Parameter.POSITIONAL_OR_KEYWORD,
|
||||
Parameter.POSITIONAL_ONLY):
|
||||
must_be_kwarg = False
|
||||
break
|
||||
if not must_be_kwarg:
|
||||
break
|
||||
return must_be_kwarg
|
||||
|
||||
|
||||
def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name):
|
||||
comp_dct = set()
|
||||
if settings.case_insensitive_completion:
|
||||
like_name = like_name.lower()
|
||||
for name in completion_names:
|
||||
string = name.string_name
|
||||
if settings.case_insensitive_completion:
|
||||
string = string.lower()
|
||||
if helpers.match(string, like_name, fuzzy=fuzzy):
|
||||
new = classes.Completion(
|
||||
inference_state,
|
||||
name,
|
||||
stack,
|
||||
len(like_name),
|
||||
is_fuzzy=fuzzy,
|
||||
cached_name=cached_name,
|
||||
)
|
||||
k = (new.name, new.complete) # key
|
||||
if k not in comp_dct:
|
||||
comp_dct.add(k)
|
||||
tree_name = name.tree_name
|
||||
if tree_name is not None:
|
||||
definition = tree_name.get_definition()
|
||||
if definition is not None and definition.type == 'del_stmt':
|
||||
continue
|
||||
yield new
|
||||
|
||||
|
||||
def _remove_duplicates(completions, other_completions):
|
||||
names = {d.name for d in other_completions}
|
||||
return [c for c in completions if c.name not in names]
|
||||
|
||||
|
||||
def get_user_context(module_context, position):
|
||||
"""
|
||||
Returns the scope in which the user resides. This includes flows.
|
||||
"""
|
||||
leaf = module_context.tree_node.get_leaf_for_position(position, include_prefixes=True)
|
||||
return module_context.create_context(leaf)
|
||||
|
||||
|
||||
def get_flow_scope_node(module_node, position):
|
||||
node = module_node.get_leaf_for_position(position, include_prefixes=True)
|
||||
while not isinstance(node, (tree.Scope, tree.Flow)):
|
||||
node = node.parent
|
||||
|
||||
return node
|
||||
|
||||
|
||||
@plugin_manager.decorate()
|
||||
def complete_param_names(context, function_name, decorator_nodes):
|
||||
# Basically there's no way to do param completion. The plugins are
|
||||
# responsible for this.
|
||||
return []
|
||||
|
||||
|
||||
class Completion:
|
||||
def __init__(self, inference_state, module_context, code_lines, position,
|
||||
signatures_callback, fuzzy=False):
|
||||
self._inference_state = inference_state
|
||||
self._module_context = module_context
|
||||
self._module_node = module_context.tree_node
|
||||
self._code_lines = code_lines
|
||||
|
||||
# The first step of completions is to get the name
|
||||
self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
|
||||
# The actual cursor position is not what we need to calculate
|
||||
# everything. We want the start of the name we're on.
|
||||
self._original_position = position
|
||||
self._signatures_callback = signatures_callback
|
||||
|
||||
self._fuzzy = fuzzy
|
||||
|
||||
def complete(self):
|
||||
leaf = self._module_node.get_leaf_for_position(
|
||||
self._original_position,
|
||||
include_prefixes=True
|
||||
)
|
||||
string, start_leaf, quote = _extract_string_while_in_string(leaf, self._original_position)
|
||||
|
||||
prefixed_completions = complete_dict(
|
||||
self._module_context,
|
||||
self._code_lines,
|
||||
start_leaf or leaf,
|
||||
self._original_position,
|
||||
None if string is None else quote + string,
|
||||
fuzzy=self._fuzzy,
|
||||
)
|
||||
|
||||
if string is not None and not prefixed_completions:
|
||||
prefixed_completions = list(complete_file_name(
|
||||
self._inference_state, self._module_context, start_leaf, quote, string,
|
||||
self._like_name, self._signatures_callback,
|
||||
self._code_lines, self._original_position,
|
||||
self._fuzzy
|
||||
))
|
||||
if string is not None:
|
||||
if not prefixed_completions and '\n' in string:
|
||||
# Complete only multi line strings
|
||||
prefixed_completions = self._complete_in_string(start_leaf, string)
|
||||
return prefixed_completions
|
||||
|
||||
cached_name, completion_names = self._complete_python(leaf)
|
||||
|
||||
completions = list(filter_names(self._inference_state, completion_names,
|
||||
self.stack, self._like_name,
|
||||
self._fuzzy, cached_name=cached_name))
|
||||
|
||||
return (
|
||||
# Removing duplicates mostly to remove False/True/None duplicates.
|
||||
_remove_duplicates(prefixed_completions, completions)
|
||||
+ sorted(completions, key=lambda x: (x.name.startswith('__'),
|
||||
x.name.startswith('_'),
|
||||
x.name.lower()))
|
||||
)
|
||||
|
||||
def _complete_python(self, leaf):
|
||||
"""
|
||||
Analyzes the current context of a completion and decides what to
|
||||
return.
|
||||
|
||||
Technically this works by generating a parser stack and analysing the
|
||||
current stack for possible grammar nodes.
|
||||
|
||||
Possible enhancements:
|
||||
- global/nonlocal search global
|
||||
- yield from / raise from <- could be only exceptions/generators
|
||||
- In args: */**: no completion
|
||||
- In params (also lambda): no completion before =
|
||||
"""
|
||||
|
||||
grammar = self._inference_state.grammar
|
||||
self.stack = stack = None
|
||||
self._position = (
|
||||
self._original_position[0],
|
||||
self._original_position[1] - len(self._like_name)
|
||||
)
|
||||
cached_name = None
|
||||
|
||||
try:
|
||||
self.stack = stack = helpers.get_stack_at_position(
|
||||
grammar, self._code_lines, leaf, self._position
|
||||
)
|
||||
except helpers.OnErrorLeaf as e:
|
||||
value = e.error_leaf.value
|
||||
if value == '.':
|
||||
# After ErrorLeaf's that are dots, we will not do any
|
||||
# completions since this probably just confuses the user.
|
||||
return cached_name, []
|
||||
|
||||
# If we don't have a value, just use global completion.
|
||||
return cached_name, self._complete_global_scope()
|
||||
|
||||
allowed_transitions = \
|
||||
list(stack._allowed_transition_names_and_token_types())
|
||||
|
||||
if 'if' in allowed_transitions:
|
||||
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
|
||||
previous_leaf = leaf.get_previous_leaf()
|
||||
|
||||
indent = self._position[1]
|
||||
if not (leaf.start_pos <= self._position <= leaf.end_pos):
|
||||
indent = leaf.start_pos[1]
|
||||
|
||||
if previous_leaf is not None:
|
||||
stmt = previous_leaf
|
||||
while True:
|
||||
stmt = search_ancestor(
|
||||
stmt, 'if_stmt', 'for_stmt', 'while_stmt', 'try_stmt',
|
||||
'error_node',
|
||||
)
|
||||
if stmt is None:
|
||||
break
|
||||
|
||||
type_ = stmt.type
|
||||
if type_ == 'error_node':
|
||||
first = stmt.children[0]
|
||||
if isinstance(first, Leaf):
|
||||
type_ = first.value + '_stmt'
|
||||
# Compare indents
|
||||
if stmt.start_pos[1] == indent:
|
||||
if type_ == 'if_stmt':
|
||||
allowed_transitions += ['elif', 'else']
|
||||
elif type_ == 'try_stmt':
|
||||
allowed_transitions += ['except', 'finally', 'else']
|
||||
elif type_ == 'for_stmt':
|
||||
allowed_transitions.append('else')
|
||||
|
||||
completion_names = []
|
||||
|
||||
kwargs_only = False
|
||||
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
|
||||
PythonTokenTypes.INDENT)):
|
||||
# This means that we actually have to do type inference.
|
||||
|
||||
nonterminals = [stack_node.nonterminal for stack_node in stack]
|
||||
|
||||
nodes = _gather_nodes(stack)
|
||||
if nodes and nodes[-1] in ('as', 'def', 'class'):
|
||||
# No completions for ``with x as foo`` and ``import x as foo``.
|
||||
# Also true for defining names as a class or function.
|
||||
return cached_name, list(self._complete_inherited(is_function=True))
|
||||
elif "import_stmt" in nonterminals:
|
||||
level, names = parse_dotted_names(nodes, "import_from" in nonterminals)
|
||||
|
||||
only_modules = not ("import_from" in nonterminals and 'import' in nodes)
|
||||
completion_names += self._get_importer_names(
|
||||
names,
|
||||
level,
|
||||
only_modules=only_modules,
|
||||
)
|
||||
elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
|
||||
dot = self._module_node.get_leaf_for_position(self._position)
|
||||
cached_name, n = self._complete_trailer(dot.get_previous_leaf())
|
||||
completion_names += n
|
||||
elif self._is_parameter_completion():
|
||||
completion_names += self._complete_params(leaf)
|
||||
else:
|
||||
# Apparently this looks like it's good enough to filter most cases
|
||||
# so that signature completions don't randomly appear.
|
||||
# To understand why this works, three things are important:
|
||||
# 1. trailer with a `,` in it is either a subscript or an arglist.
|
||||
# 2. If there's no `,`, it's at the start and only signatures start
|
||||
# with `(`. Other trailers could start with `.` or `[`.
|
||||
# 3. Decorators are very primitive and have an optional `(` with
|
||||
# optional arglist in them.
|
||||
if nodes[-1] in ['(', ','] \
|
||||
and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
|
||||
signatures = self._signatures_callback(*self._position)
|
||||
if signatures:
|
||||
call_details = signatures[0]._call_details
|
||||
used_kwargs = list(call_details.iter_used_keyword_arguments())
|
||||
positional_count = call_details.count_positional_arguments()
|
||||
|
||||
completion_names += _get_signature_param_names(
|
||||
signatures,
|
||||
positional_count,
|
||||
used_kwargs,
|
||||
)
|
||||
|
||||
kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs)
|
||||
|
||||
if not kwargs_only:
|
||||
completion_names += self._complete_global_scope()
|
||||
completion_names += self._complete_inherited(is_function=False)
|
||||
|
||||
if not kwargs_only:
|
||||
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
|
||||
completion_names += self._complete_keywords(
|
||||
allowed_transitions,
|
||||
only_values=not (not current_line or current_line[-1] in ' \t.;'
|
||||
and current_line[-3:] != '...')
|
||||
)
|
||||
|
||||
return cached_name, completion_names
|
||||
|
||||
def _is_parameter_completion(self):
|
||||
tos = self.stack[-1]
|
||||
if tos.nonterminal == 'lambdef' and len(tos.nodes) == 1:
|
||||
# We are at the position `lambda `, where basically the next node
|
||||
# is a param.
|
||||
return True
|
||||
if tos.nonterminal in 'parameters':
|
||||
# Basically we are at the position `foo(`, there's nothing there
|
||||
# yet, so we have no `typedargslist`.
|
||||
return True
|
||||
# var args is for lambdas and typed args for normal functions
|
||||
return tos.nonterminal in ('typedargslist', 'varargslist') and tos.nodes[-1] == ','
|
||||
|
||||
def _complete_params(self, leaf):
|
||||
stack_node = self.stack[-2]
|
||||
if stack_node.nonterminal == 'parameters':
|
||||
stack_node = self.stack[-3]
|
||||
if stack_node.nonterminal == 'funcdef':
|
||||
context = get_user_context(self._module_context, self._position)
|
||||
node = search_ancestor(leaf, 'error_node', 'funcdef')
|
||||
if node is not None:
|
||||
if node.type == 'error_node':
|
||||
n = node.children[0]
|
||||
if n.type == 'decorators':
|
||||
decorators = n.children
|
||||
elif n.type == 'decorator':
|
||||
decorators = [n]
|
||||
else:
|
||||
decorators = []
|
||||
else:
|
||||
decorators = node.get_decorators()
|
||||
function_name = stack_node.nodes[1]
|
||||
|
||||
return complete_param_names(context, function_name.value, decorators)
|
||||
return []
|
||||
|
||||
def _complete_keywords(self, allowed_transitions, only_values):
|
||||
for k in allowed_transitions:
|
||||
if isinstance(k, str) and k.isalpha():
|
||||
if not only_values or k in ('True', 'False', 'None'):
|
||||
yield keywords.KeywordName(self._inference_state, k)
|
||||
|
||||
def _complete_global_scope(self):
|
||||
context = get_user_context(self._module_context, self._position)
|
||||
debug.dbg('global completion scope: %s', context)
|
||||
flow_scope_node = get_flow_scope_node(self._module_node, self._position)
|
||||
filters = get_global_filters(
|
||||
context,
|
||||
self._position,
|
||||
flow_scope_node
|
||||
)
|
||||
completion_names = []
|
||||
for filter in filters:
|
||||
completion_names += filter.values()
|
||||
return completion_names
|
||||
|
||||
def _complete_trailer(self, previous_leaf):
|
||||
inferred_context = self._module_context.create_context(previous_leaf)
|
||||
values = infer_call_of_leaf(inferred_context, previous_leaf)
|
||||
debug.dbg('trailer completion values: %s', values, color='MAGENTA')
|
||||
|
||||
# The cached name simply exists to make speed optimizations for certain
|
||||
# modules.
|
||||
cached_name = None
|
||||
if len(values) == 1:
|
||||
v, = values
|
||||
if v.is_module():
|
||||
if len(v.string_names) == 1:
|
||||
module_name = v.string_names[0]
|
||||
if module_name in ('numpy', 'tensorflow', 'matplotlib', 'pandas'):
|
||||
cached_name = module_name
|
||||
|
||||
return cached_name, self._complete_trailer_for_values(values)
|
||||
|
||||
def _complete_trailer_for_values(self, values):
|
||||
user_context = get_user_context(self._module_context, self._position)
|
||||
|
||||
return complete_trailer(user_context, values)
|
||||
|
||||
def _get_importer_names(self, names, level=0, only_modules=True):
|
||||
names = [n.value for n in names]
|
||||
i = imports.Importer(self._inference_state, names, self._module_context, level)
|
||||
return i.completion_names(self._inference_state, only_modules=only_modules)
|
||||
|
||||
def _complete_inherited(self, is_function=True):
|
||||
"""
|
||||
Autocomplete inherited methods when overriding in child class.
|
||||
"""
|
||||
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
|
||||
cls = tree.search_ancestor(leaf, 'classdef')
|
||||
if cls is None:
|
||||
return
|
||||
|
||||
# Complete the methods that are defined in the super classes.
|
||||
class_value = self._module_context.create_value(cls)
|
||||
|
||||
if cls.start_pos[1] >= leaf.start_pos[1]:
|
||||
return
|
||||
|
||||
filters = class_value.get_filters(is_instance=True)
|
||||
# The first dict is the dictionary of class itself.
|
||||
next(filters)
|
||||
for filter in filters:
|
||||
for name in filter.values():
|
||||
# TODO we should probably check here for properties
|
||||
if (name.api_type == 'function') == is_function:
|
||||
yield name
|
||||
|
||||
def _complete_in_string(self, start_leaf, string):
|
||||
"""
|
||||
To make it possible for people to have completions in doctests or
|
||||
generally in "Python" code in docstrings, we use the following
|
||||
heuristic:
|
||||
|
||||
- Having an indented block of code
|
||||
- Having some doctest code that starts with `>>>`
|
||||
- Having backticks that doesn't have whitespace inside it
|
||||
"""
|
||||
def iter_relevant_lines(lines):
|
||||
include_next_line = False
|
||||
for l in code_lines:
|
||||
if include_next_line or l.startswith('>>>') or l.startswith(' '):
|
||||
yield re.sub(r'^( *>>> ?| +)', '', l)
|
||||
else:
|
||||
yield None
|
||||
|
||||
include_next_line = bool(re.match(' *>>>', l))
|
||||
|
||||
string = dedent(string)
|
||||
code_lines = split_lines(string, keepends=True)
|
||||
relevant_code_lines = list(iter_relevant_lines(code_lines))
|
||||
if relevant_code_lines[-1] is not None:
|
||||
# Some code lines might be None, therefore get rid of that.
|
||||
relevant_code_lines = ['\n' if c is None else c for c in relevant_code_lines]
|
||||
return self._complete_code_lines(relevant_code_lines)
|
||||
match = re.search(r'`([^`\s]+)', code_lines[-1])
|
||||
if match:
|
||||
return self._complete_code_lines([match.group(1)])
|
||||
return []
|
||||
|
||||
def _complete_code_lines(self, code_lines):
|
||||
module_node = self._inference_state.grammar.parse(''.join(code_lines))
|
||||
module_value = ModuleValue(
|
||||
self._inference_state,
|
||||
module_node,
|
||||
code_lines=code_lines,
|
||||
)
|
||||
module_value.parent_context = self._module_context
|
||||
return Completion(
|
||||
self._inference_state,
|
||||
module_value.as_context(),
|
||||
code_lines=code_lines,
|
||||
position=module_node.end_pos,
|
||||
signatures_callback=lambda *args, **kwargs: [],
|
||||
fuzzy=self._fuzzy
|
||||
).complete()
|
||||
|
||||
|
||||
def _gather_nodes(stack):
|
||||
nodes = []
|
||||
for stack_node in stack:
|
||||
if stack_node.dfa.from_rule == 'small_stmt':
|
||||
nodes = []
|
||||
else:
|
||||
nodes += stack_node.nodes
|
||||
return nodes
|
||||
|
||||
|
||||
_string_start = re.compile(r'^\w*(\'{3}|"{3}|\'|")')
|
||||
|
||||
|
||||
def _extract_string_while_in_string(leaf, position):
|
||||
def return_part_of_leaf(leaf):
|
||||
kwargs = {}
|
||||
if leaf.line == position[0]:
|
||||
kwargs['endpos'] = position[1] - leaf.column
|
||||
match = _string_start.match(leaf.value, **kwargs)
|
||||
if not match:
|
||||
return None, None, None
|
||||
start = match.group(0)
|
||||
if leaf.line == position[0] and position[1] < leaf.column + match.end():
|
||||
return None, None, None
|
||||
return cut_value_at_position(leaf, position)[match.end():], leaf, start
|
||||
|
||||
if position < leaf.start_pos:
|
||||
return None, None, None
|
||||
|
||||
if leaf.type == 'string':
|
||||
return return_part_of_leaf(leaf)
|
||||
|
||||
leaves = []
|
||||
while leaf is not None:
|
||||
if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value):
|
||||
if len(leaf.value) > 1:
|
||||
return return_part_of_leaf(leaf)
|
||||
prefix_leaf = None
|
||||
if not leaf.prefix:
|
||||
prefix_leaf = leaf.get_previous_leaf()
|
||||
if prefix_leaf is None or prefix_leaf.type != 'name' \
|
||||
or not all(c in 'rubf' for c in prefix_leaf.value.lower()):
|
||||
prefix_leaf = None
|
||||
|
||||
return (
|
||||
''.join(cut_value_at_position(l, position) for l in leaves),
|
||||
prefix_leaf or leaf,
|
||||
('' if prefix_leaf is None else prefix_leaf.value)
|
||||
+ cut_value_at_position(leaf, position),
|
||||
)
|
||||
if leaf.line != position[0]:
|
||||
# Multi line strings are always simple error leaves and contain the
|
||||
# whole string, single line error leaves are atherefore important
|
||||
# now and since the line is different, it's not really a single
|
||||
# line string anymore.
|
||||
break
|
||||
leaves.insert(0, leaf)
|
||||
leaf = leaf.get_previous_leaf()
|
||||
return None, None, None
|
||||
|
||||
|
||||
def complete_trailer(user_context, values):
|
||||
completion_names = []
|
||||
for value in values:
|
||||
for filter in value.get_filters(origin_scope=user_context.tree_node):
|
||||
completion_names += filter.values()
|
||||
|
||||
if not value.is_stub() and isinstance(value, TreeInstance):
|
||||
completion_names += _complete_getattr(user_context, value)
|
||||
|
||||
python_values = convert_values(values)
|
||||
for c in python_values:
|
||||
if c not in values:
|
||||
for filter in c.get_filters(origin_scope=user_context.tree_node):
|
||||
completion_names += filter.values()
|
||||
return completion_names
|
||||
|
||||
|
||||
def _complete_getattr(user_context, instance):
|
||||
"""
|
||||
A heuristic to make completion for proxy objects work. This is not
|
||||
intended to work in all cases. It works exactly in this case:
|
||||
|
||||
def __getattr__(self, name):
|
||||
...
|
||||
return getattr(any_object, name)
|
||||
|
||||
It is important that the return contains getattr directly, otherwise it
|
||||
won't work anymore. It's really just a stupid heuristic. It will not
|
||||
work if you write e.g. `return (getatr(o, name))`, because of the
|
||||
additional parentheses. It will also not work if you move the getattr
|
||||
to some other place that is not the return statement itself.
|
||||
|
||||
It is intentional that it doesn't work in all cases. Generally it's
|
||||
really hard to do even this case (as you can see below). Most people
|
||||
will write it like this anyway and the other ones, well they are just
|
||||
out of luck I guess :) ~dave.
|
||||
"""
|
||||
names = (instance.get_function_slot_names(u'__getattr__')
|
||||
or instance.get_function_slot_names(u'__getattribute__'))
|
||||
functions = ValueSet.from_sets(
|
||||
name.infer()
|
||||
for name in names
|
||||
)
|
||||
for func in functions:
|
||||
tree_node = func.tree_node
|
||||
if tree_node is None or tree_node.type != 'funcdef':
|
||||
continue
|
||||
|
||||
for return_stmt in tree_node.iter_return_stmts():
|
||||
# Basically until the next comment we just try to find out if a
|
||||
# return statement looks exactly like `return getattr(x, name)`.
|
||||
if return_stmt.type != 'return_stmt':
|
||||
continue
|
||||
atom_expr = return_stmt.children[1]
|
||||
if atom_expr.type != 'atom_expr':
|
||||
continue
|
||||
atom = atom_expr.children[0]
|
||||
trailer = atom_expr.children[1]
|
||||
if len(atom_expr.children) != 2 or atom.type != 'name' \
|
||||
or atom.value != 'getattr':
|
||||
continue
|
||||
arglist = trailer.children[1]
|
||||
if arglist.type != 'arglist' or len(arglist.children) < 3:
|
||||
continue
|
||||
context = func.as_context()
|
||||
object_node = arglist.children[0]
|
||||
|
||||
# Make sure it's a param: foo in __getattr__(self, foo)
|
||||
name_node = arglist.children[2]
|
||||
name_list = context.goto(name_node, name_node.start_pos)
|
||||
if not any(n.api_type == 'param' for n in name_list):
|
||||
continue
|
||||
|
||||
# Now that we know that these are most probably completion
|
||||
# objects, we just infer the object and return them as
|
||||
# completions.
|
||||
objects = context.infer_node(object_node)
|
||||
return complete_trailer(user_context, objects)
|
||||
return []
|
||||
|
||||
|
||||
def search_in_module(inference_state, module_context, names, wanted_names,
|
||||
wanted_type, complete=False, fuzzy=False,
|
||||
ignore_imports=False, convert=False):
|
||||
for s in wanted_names[:-1]:
|
||||
new_names = []
|
||||
for n in names:
|
||||
if s == n.string_name:
|
||||
if n.tree_name is not None and n.api_type == 'module' \
|
||||
and ignore_imports:
|
||||
continue
|
||||
new_names += complete_trailer(
|
||||
module_context,
|
||||
n.infer()
|
||||
)
|
||||
debug.dbg('dot lookup on search %s from %s', new_names, names[:10])
|
||||
names = new_names
|
||||
|
||||
last_name = wanted_names[-1].lower()
|
||||
for n in names:
|
||||
string = n.string_name.lower()
|
||||
if complete and helpers.match(string, last_name, fuzzy=fuzzy) \
|
||||
or not complete and string == last_name:
|
||||
if isinstance(n, SubModuleName):
|
||||
names = [v.name for v in n.infer()]
|
||||
else:
|
||||
names = [n]
|
||||
if convert:
|
||||
names = convert_names(names)
|
||||
for n2 in names:
|
||||
if complete:
|
||||
def_ = classes.Completion(
|
||||
inference_state, n2,
|
||||
stack=None,
|
||||
like_name_length=len(last_name),
|
||||
is_fuzzy=fuzzy,
|
||||
)
|
||||
else:
|
||||
def_ = classes.Name(inference_state, n2)
|
||||
if not wanted_type or wanted_type == def_.type:
|
||||
yield def_
|
25
venv/Lib/site-packages/jedi/api/completion_cache.py
Normal file
25
venv/Lib/site-packages/jedi/api/completion_cache.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
_cache = {}
|
||||
|
||||
|
||||
def save_entry(module_name, name, cache):
|
||||
try:
|
||||
module_cache = _cache[module_name]
|
||||
except KeyError:
|
||||
module_cache = _cache[module_name] = {}
|
||||
module_cache[name] = cache
|
||||
|
||||
|
||||
def _create_get_from_cache(number):
|
||||
def _get_from_cache(module_name, name, get_cache_values):
|
||||
try:
|
||||
return _cache[module_name][name][number]
|
||||
except KeyError:
|
||||
v = get_cache_values()
|
||||
save_entry(module_name, name, v)
|
||||
return v[number]
|
||||
return _get_from_cache
|
||||
|
||||
|
||||
get_type = _create_get_from_cache(0)
|
||||
get_docstring_signature = _create_get_from_cache(1)
|
||||
get_docstring = _create_get_from_cache(2)
|
489
venv/Lib/site-packages/jedi/api/environment.py
Normal file
489
venv/Lib/site-packages/jedi/api/environment.py
Normal file
|
@ -0,0 +1,489 @@
|
|||
"""
|
||||
Environments are a way to activate different Python versions or Virtualenvs for
|
||||
static analysis. The Python binary in that environment is going to be executed.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import filecmp
|
||||
from collections import namedtuple
|
||||
|
||||
from jedi._compatibility import highest_pickle_protocol, which
|
||||
from jedi.cache import memoize_method, time_cache
|
||||
from jedi.inference.compiled.subprocess import CompiledSubprocess, \
|
||||
InferenceStateSameProcess, InferenceStateSubprocess
|
||||
|
||||
import parso
|
||||
|
||||
_VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
|
||||
_SUPPORTED_PYTHONS = ['3.8', '3.7', '3.6', '3.5', '2.7']
|
||||
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
|
||||
_CONDA_VAR = 'CONDA_PREFIX'
|
||||
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
|
||||
|
||||
|
||||
class InvalidPythonEnvironment(Exception):
|
||||
"""
|
||||
If you see this exception, the Python executable or Virtualenv you have
|
||||
been trying to use is probably not a correct Python version.
|
||||
"""
|
||||
|
||||
|
||||
class _BaseEnvironment(object):
|
||||
@memoize_method
|
||||
def get_grammar(self):
|
||||
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
|
||||
return parso.load_grammar(version=version_string)
|
||||
|
||||
@property
|
||||
def _sha256(self):
|
||||
try:
|
||||
return self._hash
|
||||
except AttributeError:
|
||||
self._hash = _calculate_sha256_for_file(self.executable)
|
||||
return self._hash
|
||||
|
||||
|
||||
def _get_info():
|
||||
return (
|
||||
sys.executable,
|
||||
sys.prefix,
|
||||
sys.version_info[:3],
|
||||
)
|
||||
|
||||
|
||||
class Environment(_BaseEnvironment):
|
||||
"""
|
||||
This class is supposed to be created by internal Jedi architecture. You
|
||||
should not create it directly. Please use create_environment or the other
|
||||
functions instead. It is then returned by that function.
|
||||
"""
|
||||
_subprocess = None
|
||||
|
||||
def __init__(self, executable, env_vars=None):
|
||||
self._start_executable = executable
|
||||
self._env_vars = env_vars
|
||||
# Initialize the environment
|
||||
self._get_subprocess()
|
||||
|
||||
def _get_subprocess(self):
|
||||
if self._subprocess is not None and not self._subprocess.is_crashed:
|
||||
return self._subprocess
|
||||
|
||||
try:
|
||||
self._subprocess = CompiledSubprocess(self._start_executable,
|
||||
env_vars=self._env_vars)
|
||||
info = self._subprocess._send(None, _get_info)
|
||||
except Exception as exc:
|
||||
raise InvalidPythonEnvironment(
|
||||
"Could not get version information for %r: %r" % (
|
||||
self._start_executable,
|
||||
exc))
|
||||
|
||||
# Since it could change and might not be the same(?) as the one given,
|
||||
# set it here.
|
||||
self.executable = info[0]
|
||||
"""
|
||||
The Python executable, matches ``sys.executable``.
|
||||
"""
|
||||
self.path = info[1]
|
||||
"""
|
||||
The path to an environment, matches ``sys.prefix``.
|
||||
"""
|
||||
self.version_info = _VersionInfo(*info[2])
|
||||
"""
|
||||
Like :data:`sys.version_info`: a tuple to show the current
|
||||
Environment's Python version.
|
||||
"""
|
||||
|
||||
# py2 sends bytes via pickle apparently?!
|
||||
if self.version_info.major == 2:
|
||||
self.executable = self.executable.decode()
|
||||
self.path = self.path.decode()
|
||||
|
||||
# Adjust pickle protocol according to host and client version.
|
||||
self._subprocess._pickle_protocol = highest_pickle_protocol([
|
||||
sys.version_info, self.version_info])
|
||||
|
||||
return self._subprocess
|
||||
|
||||
def __repr__(self):
|
||||
version = '.'.join(str(i) for i in self.version_info)
|
||||
return '<%s: %s in %s>' % (self.__class__.__name__, version, self.path)
|
||||
|
||||
def get_inference_state_subprocess(self, inference_state):
|
||||
return InferenceStateSubprocess(inference_state, self._get_subprocess())
|
||||
|
||||
@memoize_method
|
||||
def get_sys_path(self):
|
||||
"""
|
||||
The sys path for this environment. Does not include potential
|
||||
modifications from e.g. appending to :data:`sys.path`.
|
||||
|
||||
:returns: list of str
|
||||
"""
|
||||
# It's pretty much impossible to generate the sys path without actually
|
||||
# executing Python. The sys path (when starting with -S) itself depends
|
||||
# on how the Python version was compiled (ENV variables).
|
||||
# If you omit -S when starting Python (normal case), additionally
|
||||
# site.py gets executed.
|
||||
return self._get_subprocess().get_sys_path()
|
||||
|
||||
|
||||
class _SameEnvironmentMixin(object):
|
||||
def __init__(self):
|
||||
self._start_executable = self.executable = sys.executable
|
||||
self.path = sys.prefix
|
||||
self.version_info = _VersionInfo(*sys.version_info[:3])
|
||||
self._env_vars = None
|
||||
|
||||
|
||||
class SameEnvironment(_SameEnvironmentMixin, Environment):
|
||||
pass
|
||||
|
||||
|
||||
class InterpreterEnvironment(_SameEnvironmentMixin, _BaseEnvironment):
|
||||
def get_inference_state_subprocess(self, inference_state):
|
||||
return InferenceStateSameProcess(inference_state)
|
||||
|
||||
def get_sys_path(self):
|
||||
return sys.path
|
||||
|
||||
|
||||
def _get_virtual_env_from_var(env_var='VIRTUAL_ENV'):
|
||||
"""Get virtualenv environment from VIRTUAL_ENV environment variable.
|
||||
|
||||
It uses `safe=False` with ``create_environment``, because the environment
|
||||
variable is considered to be safe / controlled by the user solely.
|
||||
"""
|
||||
var = os.environ.get(env_var)
|
||||
if var:
|
||||
# Under macOS in some cases - notably when using Pipenv - the
|
||||
# sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
|
||||
# /path/to/env so we need to fully resolve the paths in order to
|
||||
# compare them.
|
||||
if os.path.realpath(var) == os.path.realpath(sys.prefix):
|
||||
return _try_get_same_env()
|
||||
|
||||
try:
|
||||
return create_environment(var, safe=False)
|
||||
except InvalidPythonEnvironment:
|
||||
pass
|
||||
|
||||
|
||||
def _calculate_sha256_for_file(path):
|
||||
sha256 = hashlib.sha256()
|
||||
with open(path, 'rb') as f:
|
||||
for block in iter(lambda: f.read(filecmp.BUFSIZE), b''):
|
||||
sha256.update(block)
|
||||
return sha256.hexdigest()
|
||||
|
||||
|
||||
def get_default_environment():
|
||||
"""
|
||||
Tries to return an active Virtualenv or conda environment.
|
||||
If there is no VIRTUAL_ENV variable or no CONDA_PREFIX variable set
|
||||
set it will return the latest Python version installed on the system. This
|
||||
makes it possible to use as many new Python features as possible when using
|
||||
autocompletion and other functionality.
|
||||
|
||||
:returns: :class:`.Environment`
|
||||
"""
|
||||
virtual_env = _get_virtual_env_from_var()
|
||||
if virtual_env is not None:
|
||||
return virtual_env
|
||||
|
||||
conda_env = _get_virtual_env_from_var(_CONDA_VAR)
|
||||
if conda_env is not None:
|
||||
return conda_env
|
||||
|
||||
return _try_get_same_env()
|
||||
|
||||
|
||||
def _try_get_same_env():
|
||||
env = SameEnvironment()
|
||||
if not os.path.basename(env.executable).lower().startswith('python'):
|
||||
# This tries to counter issues with embedding. In some cases (e.g.
|
||||
# VIM's Python Mac/Windows, sys.executable is /foo/bar/vim. This
|
||||
# happens, because for Mac a function called `_NSGetExecutablePath` is
|
||||
# used and for Windows `GetModuleFileNameW`. These are both platform
|
||||
# specific functions. For all other systems sys.executable should be
|
||||
# alright. However here we try to generalize:
|
||||
#
|
||||
# 1. Check if the executable looks like python (heuristic)
|
||||
# 2. In case it's not try to find the executable
|
||||
# 3. In case we don't find it use an interpreter environment.
|
||||
#
|
||||
# The last option will always work, but leads to potential crashes of
|
||||
# Jedi - which is ok, because it happens very rarely and even less,
|
||||
# because the code below should work for most cases.
|
||||
if os.name == 'nt':
|
||||
# The first case would be a virtualenv and the second a normal
|
||||
# Python installation.
|
||||
checks = (r'Scripts\python.exe', 'python.exe')
|
||||
else:
|
||||
# For unix it looks like Python is always in a bin folder.
|
||||
checks = (
|
||||
'bin/python%s.%s' % (sys.version_info[0], sys.version[1]),
|
||||
'bin/python%s' % (sys.version_info[0]),
|
||||
'bin/python',
|
||||
)
|
||||
for check in checks:
|
||||
guess = os.path.join(sys.exec_prefix, check)
|
||||
if os.path.isfile(guess):
|
||||
# Bingo - We think we have our Python.
|
||||
return Environment(guess)
|
||||
# It looks like there is no reasonable Python to be found.
|
||||
return InterpreterEnvironment()
|
||||
# If no virtualenv is found, use the environment we're already
|
||||
# using.
|
||||
return env
|
||||
|
||||
|
||||
def get_cached_default_environment():
|
||||
var = os.environ.get('VIRTUAL_ENV') or os.environ.get(_CONDA_VAR)
|
||||
environment = _get_cached_default_environment()
|
||||
|
||||
# Under macOS in some cases - notably when using Pipenv - the
|
||||
# sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
|
||||
# /path/to/env so we need to fully resolve the paths in order to
|
||||
# compare them.
|
||||
if var and os.path.realpath(var) != os.path.realpath(environment.path):
|
||||
_get_cached_default_environment.clear_cache()
|
||||
return _get_cached_default_environment()
|
||||
return environment
|
||||
|
||||
|
||||
@time_cache(seconds=10 * 60) # 10 Minutes
|
||||
def _get_cached_default_environment():
|
||||
try:
|
||||
return get_default_environment()
|
||||
except InvalidPythonEnvironment:
|
||||
# It's possible that `sys.executable` is wrong. Typically happens
|
||||
# when Jedi is used in an executable that embeds Python. For further
|
||||
# information, have a look at:
|
||||
# https://github.com/davidhalter/jedi/issues/1531
|
||||
return InterpreterEnvironment()
|
||||
|
||||
|
||||
def find_virtualenvs(paths=None, **kwargs):
|
||||
"""
|
||||
:param paths: A list of paths in your file system to be scanned for
|
||||
Virtualenvs. It will search in these paths and potentially execute the
|
||||
Python binaries.
|
||||
:param safe: Default True. In case this is False, it will allow this
|
||||
function to execute potential `python` environments. An attacker might
|
||||
be able to drop an executable in a path this function is searching by
|
||||
default. If the executable has not been installed by root, it will not
|
||||
be executed.
|
||||
:param use_environment_vars: Default True. If True, the VIRTUAL_ENV
|
||||
variable will be checked if it contains a valid VirtualEnv.
|
||||
CONDA_PREFIX will be checked to see if it contains a valid conda
|
||||
environment.
|
||||
|
||||
:yields: :class:`.Environment`
|
||||
"""
|
||||
def py27_comp(paths=None, safe=True, use_environment_vars=True):
|
||||
if paths is None:
|
||||
paths = []
|
||||
|
||||
_used_paths = set()
|
||||
|
||||
if use_environment_vars:
|
||||
# Using this variable should be safe, because attackers might be
|
||||
# able to drop files (via git) but not environment variables.
|
||||
virtual_env = _get_virtual_env_from_var()
|
||||
if virtual_env is not None:
|
||||
yield virtual_env
|
||||
_used_paths.add(virtual_env.path)
|
||||
|
||||
conda_env = _get_virtual_env_from_var(_CONDA_VAR)
|
||||
if conda_env is not None:
|
||||
yield conda_env
|
||||
_used_paths.add(conda_env.path)
|
||||
|
||||
for directory in paths:
|
||||
if not os.path.isdir(directory):
|
||||
continue
|
||||
|
||||
directory = os.path.abspath(directory)
|
||||
for path in os.listdir(directory):
|
||||
path = os.path.join(directory, path)
|
||||
if path in _used_paths:
|
||||
# A path shouldn't be inferred twice.
|
||||
continue
|
||||
_used_paths.add(path)
|
||||
|
||||
try:
|
||||
executable = _get_executable_path(path, safe=safe)
|
||||
yield Environment(executable)
|
||||
except InvalidPythonEnvironment:
|
||||
pass
|
||||
|
||||
return py27_comp(paths, **kwargs)
|
||||
|
||||
|
||||
def find_system_environments(**kwargs):
|
||||
"""
|
||||
Ignores virtualenvs and returns the Python versions that were installed on
|
||||
your system. This might return nothing, if you're running Python e.g. from
|
||||
a portable version.
|
||||
|
||||
The environments are sorted from latest to oldest Python version.
|
||||
|
||||
:yields: :class:`.Environment`
|
||||
"""
|
||||
for version_string in _SUPPORTED_PYTHONS:
|
||||
try:
|
||||
yield get_system_environment(version_string, **kwargs)
|
||||
except InvalidPythonEnvironment:
|
||||
pass
|
||||
|
||||
|
||||
# TODO: this function should probably return a list of environments since
|
||||
# multiple Python installations can be found on a system for the same version.
|
||||
def get_system_environment(version, **kwargs):
|
||||
"""
|
||||
Return the first Python environment found for a string of the form 'X.Y'
|
||||
where X and Y are the major and minor versions of Python.
|
||||
|
||||
:raises: :exc:`.InvalidPythonEnvironment`
|
||||
:returns: :class:`.Environment`
|
||||
"""
|
||||
exe = which('python' + version)
|
||||
if exe:
|
||||
if exe == sys.executable:
|
||||
return SameEnvironment()
|
||||
return Environment(exe)
|
||||
|
||||
if os.name == 'nt':
|
||||
for exe in _get_executables_from_windows_registry(version):
|
||||
try:
|
||||
return Environment(exe, **kwargs)
|
||||
except InvalidPythonEnvironment:
|
||||
pass
|
||||
raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
|
||||
|
||||
|
||||
def create_environment(path, safe=True, **kwargs):
|
||||
"""
|
||||
Make it possible to manually create an Environment object by specifying a
|
||||
Virtualenv path or an executable path and optional environment variables.
|
||||
|
||||
:raises: :exc:`.InvalidPythonEnvironment`
|
||||
:returns: :class:`.Environment`
|
||||
|
||||
TODO: make env_vars a kwarg when Python 2 is dropped. For now, preserve API
|
||||
"""
|
||||
return _create_environment(path, safe, **kwargs)
|
||||
|
||||
|
||||
def _create_environment(path, safe=True, env_vars=None):
|
||||
if os.path.isfile(path):
|
||||
_assert_safe(path, safe)
|
||||
return Environment(path, env_vars=env_vars)
|
||||
return Environment(_get_executable_path(path, safe=safe), env_vars=env_vars)
|
||||
|
||||
|
||||
def _get_executable_path(path, safe=True):
|
||||
"""
|
||||
Returns None if it's not actually a virtual env.
|
||||
"""
|
||||
|
||||
if os.name == 'nt':
|
||||
python = os.path.join(path, 'Scripts', 'python.exe')
|
||||
else:
|
||||
python = os.path.join(path, 'bin', 'python')
|
||||
if not os.path.exists(python):
|
||||
raise InvalidPythonEnvironment("%s seems to be missing." % python)
|
||||
|
||||
_assert_safe(python, safe)
|
||||
return python
|
||||
|
||||
|
||||
def _get_executables_from_windows_registry(version):
|
||||
# The winreg module is named _winreg on Python 2.
|
||||
try:
|
||||
import winreg
|
||||
except ImportError:
|
||||
import _winreg as winreg
|
||||
|
||||
# TODO: support Python Anaconda.
|
||||
sub_keys = [
|
||||
r'SOFTWARE\Python\PythonCore\{version}\InstallPath',
|
||||
r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath',
|
||||
r'SOFTWARE\Python\PythonCore\{version}-32\InstallPath',
|
||||
r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath'
|
||||
]
|
||||
for root_key in [winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE]:
|
||||
for sub_key in sub_keys:
|
||||
sub_key = sub_key.format(version=version)
|
||||
try:
|
||||
with winreg.OpenKey(root_key, sub_key) as key:
|
||||
prefix = winreg.QueryValueEx(key, '')[0]
|
||||
exe = os.path.join(prefix, 'python.exe')
|
||||
if os.path.isfile(exe):
|
||||
yield exe
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
|
||||
def _assert_safe(executable_path, safe):
|
||||
if safe and not _is_safe(executable_path):
|
||||
raise InvalidPythonEnvironment(
|
||||
"The python binary is potentially unsafe.")
|
||||
|
||||
|
||||
def _is_safe(executable_path):
|
||||
# Resolve sym links. A venv typically is a symlink to a known Python
|
||||
# binary. Only virtualenvs copy symlinks around.
|
||||
real_path = os.path.realpath(executable_path)
|
||||
|
||||
if _is_unix_safe_simple(real_path):
|
||||
return True
|
||||
|
||||
# Just check the list of known Python versions. If it's not in there,
|
||||
# it's likely an attacker or some Python that was not properly
|
||||
# installed in the system.
|
||||
for environment in find_system_environments():
|
||||
if environment.executable == real_path:
|
||||
return True
|
||||
|
||||
# If the versions don't match, just compare the binary files. If we
|
||||
# don't do that, only venvs will be working and not virtualenvs.
|
||||
# venvs are symlinks while virtualenvs are actual copies of the
|
||||
# Python files.
|
||||
# This still means that if the system Python is updated and the
|
||||
# virtualenv's Python is not (which is probably never going to get
|
||||
# upgraded), it will not work with Jedi. IMO that's fine, because
|
||||
# people should just be using venv. ~ dave
|
||||
if environment._sha256 == _calculate_sha256_for_file(real_path):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _is_unix_safe_simple(real_path):
|
||||
if _is_unix_admin():
|
||||
# In case we are root, just be conservative and
|
||||
# only execute known paths.
|
||||
return any(real_path.startswith(p) for p in _SAFE_PATHS)
|
||||
|
||||
uid = os.stat(real_path).st_uid
|
||||
# The interpreter needs to be owned by root. This means that it wasn't
|
||||
# written by a user and therefore attacking Jedi is not as simple.
|
||||
# The attack could look like the following:
|
||||
# 1. A user clones a repository.
|
||||
# 2. The repository has an innocent looking folder called foobar. jedi
|
||||
# searches for the folder and executes foobar/bin/python --version if
|
||||
# there's also a foobar/bin/activate.
|
||||
# 3. The attacker has gained code execution, since he controls
|
||||
# foobar/bin/python.
|
||||
return uid == 0
|
||||
|
||||
|
||||
def _is_unix_admin():
|
||||
try:
|
||||
return os.getuid() == 0
|
||||
except AttributeError:
|
||||
return False # Windows
|
46
venv/Lib/site-packages/jedi/api/errors.py
Normal file
46
venv/Lib/site-packages/jedi/api/errors.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
"""
|
||||
This file is about errors in Python files and not about exception handling in
|
||||
Jedi.
|
||||
"""
|
||||
|
||||
|
||||
def parso_to_jedi_errors(grammar, module_node):
|
||||
return [SyntaxError(e) for e in grammar.iter_errors(module_node)]
|
||||
|
||||
|
||||
class SyntaxError(object):
|
||||
"""
|
||||
Syntax errors are generated by :meth:`.Script.get_syntax_errors`.
|
||||
"""
|
||||
def __init__(self, parso_error):
|
||||
self._parso_error = parso_error
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
"""The line where the error starts (starting with 1)."""
|
||||
return self._parso_error.start_pos[0]
|
||||
|
||||
@property
|
||||
def column(self):
|
||||
"""The column where the error starts (starting with 0)."""
|
||||
return self._parso_error.start_pos[1]
|
||||
|
||||
@property
|
||||
def until_line(self):
|
||||
"""The line where the error ends (starting with 1)."""
|
||||
return self._parso_error.end_pos[0]
|
||||
|
||||
@property
|
||||
def until_column(self):
|
||||
"""The column where the error ends (starting with 0)."""
|
||||
return self._parso_error.end_pos[1]
|
||||
|
||||
def get_message(self):
|
||||
return self._parso_error.message
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s from=%s to=%s>' % (
|
||||
self.__class__.__name__,
|
||||
self._parso_error.start_pos,
|
||||
self._parso_error.end_pos,
|
||||
)
|
31
venv/Lib/site-packages/jedi/api/exceptions.py
Normal file
31
venv/Lib/site-packages/jedi/api/exceptions.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
class _JediError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InternalError(_JediError):
|
||||
"""
|
||||
This error might happen a subprocess is crashing. The reason for this is
|
||||
usually broken C code in third party libraries. This is not a very common
|
||||
thing and it is safe to use Jedi again. However using the same calls might
|
||||
result in the same error again.
|
||||
"""
|
||||
|
||||
|
||||
class WrongVersion(_JediError):
|
||||
"""
|
||||
This error is reserved for the future, shouldn't really be happening at the
|
||||
moment.
|
||||
"""
|
||||
|
||||
|
||||
class RefactoringError(_JediError):
|
||||
"""
|
||||
Refactorings can fail for various reasons. So if you work with refactorings
|
||||
like :meth:`.Script.rename`, :meth:`.Script.inline`,
|
||||
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`, make
|
||||
sure to catch these. The descriptions in the errors are ususally valuable
|
||||
for end users.
|
||||
|
||||
A typical ``RefactoringError`` would tell the user that inlining is not
|
||||
possible if no name is under the cursor.
|
||||
"""
|
156
venv/Lib/site-packages/jedi/api/file_name.py
Normal file
156
venv/Lib/site-packages/jedi/api/file_name.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
import os
|
||||
|
||||
from jedi._compatibility import FileNotFoundError, force_unicode, scandir
|
||||
from jedi.api import classes
|
||||
from jedi.api.strings import StringName, get_quote_ending
|
||||
from jedi.api.helpers import match
|
||||
from jedi.inference.helpers import get_str_or_none
|
||||
|
||||
|
||||
class PathName(StringName):
|
||||
api_type = u'path'
|
||||
|
||||
|
||||
def complete_file_name(inference_state, module_context, start_leaf, quote, string,
|
||||
like_name, signatures_callback, code_lines, position, fuzzy):
|
||||
# First we want to find out what can actually be changed as a name.
|
||||
like_name_length = len(os.path.basename(string))
|
||||
|
||||
addition = _get_string_additions(module_context, start_leaf)
|
||||
if string.startswith('~'):
|
||||
string = os.path.expanduser(string)
|
||||
if addition is None:
|
||||
return
|
||||
string = addition + string
|
||||
|
||||
# Here we use basename again, because if strings are added like
|
||||
# `'foo' + 'bar`, it should complete to `foobar/`.
|
||||
must_start_with = os.path.basename(string)
|
||||
string = os.path.dirname(string)
|
||||
|
||||
sigs = signatures_callback(*position)
|
||||
is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs)
|
||||
if is_in_os_path_join:
|
||||
to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start)
|
||||
if to_be_added is None:
|
||||
is_in_os_path_join = False
|
||||
else:
|
||||
string = to_be_added + string
|
||||
base_path = os.path.join(inference_state.project.path, string)
|
||||
try:
|
||||
listed = sorted(scandir(base_path), key=lambda e: e.name)
|
||||
# OSError: [Errno 36] File name too long: '...'
|
||||
except (FileNotFoundError, OSError):
|
||||
return
|
||||
quote_ending = get_quote_ending(quote, code_lines, position)
|
||||
for entry in listed:
|
||||
name = entry.name
|
||||
if match(name, must_start_with, fuzzy=fuzzy):
|
||||
if is_in_os_path_join or not entry.is_dir():
|
||||
name += quote_ending
|
||||
else:
|
||||
name += os.path.sep
|
||||
|
||||
yield classes.Completion(
|
||||
inference_state,
|
||||
PathName(inference_state, name[len(must_start_with) - like_name_length:]),
|
||||
stack=None,
|
||||
like_name_length=like_name_length,
|
||||
is_fuzzy=fuzzy,
|
||||
)
|
||||
|
||||
|
||||
def _get_string_additions(module_context, start_leaf):
|
||||
def iterate_nodes():
|
||||
node = addition.parent
|
||||
was_addition = True
|
||||
for child_node in reversed(node.children[:node.children.index(addition)]):
|
||||
if was_addition:
|
||||
was_addition = False
|
||||
yield child_node
|
||||
continue
|
||||
|
||||
if child_node != '+':
|
||||
break
|
||||
was_addition = True
|
||||
|
||||
addition = start_leaf.get_previous_leaf()
|
||||
if addition != '+':
|
||||
return ''
|
||||
context = module_context.create_context(start_leaf)
|
||||
return _add_strings(context, reversed(list(iterate_nodes())))
|
||||
|
||||
|
||||
def _add_strings(context, nodes, add_slash=False):
|
||||
string = ''
|
||||
first = True
|
||||
for child_node in nodes:
|
||||
values = context.infer_node(child_node)
|
||||
if len(values) != 1:
|
||||
return None
|
||||
c, = values
|
||||
s = get_str_or_none(c)
|
||||
if s is None:
|
||||
return None
|
||||
if not first and add_slash:
|
||||
string += os.path.sep
|
||||
string += force_unicode(s)
|
||||
first = False
|
||||
return string
|
||||
|
||||
|
||||
def _add_os_path_join(module_context, start_leaf, bracket_start):
|
||||
def check(maybe_bracket, nodes):
|
||||
if maybe_bracket.start_pos != bracket_start:
|
||||
return None
|
||||
|
||||
if not nodes:
|
||||
return ''
|
||||
context = module_context.create_context(nodes[0])
|
||||
return _add_strings(context, nodes, add_slash=True) or ''
|
||||
|
||||
if start_leaf.type == 'error_leaf':
|
||||
# Unfinished string literal, like `join('`
|
||||
value_node = start_leaf.parent
|
||||
index = value_node.children.index(start_leaf)
|
||||
if index > 0:
|
||||
error_node = value_node.children[index - 1]
|
||||
if error_node.type == 'error_node' and len(error_node.children) >= 2:
|
||||
index = -2
|
||||
if error_node.children[-1].type == 'arglist':
|
||||
arglist_nodes = error_node.children[-1].children
|
||||
index -= 1
|
||||
else:
|
||||
arglist_nodes = []
|
||||
|
||||
return check(error_node.children[index + 1], arglist_nodes[::2])
|
||||
return None
|
||||
|
||||
# Maybe an arglist or some weird error case. Therefore checked below.
|
||||
searched_node_child = start_leaf
|
||||
while searched_node_child.parent is not None \
|
||||
and searched_node_child.parent.type not in ('arglist', 'trailer', 'error_node'):
|
||||
searched_node_child = searched_node_child.parent
|
||||
|
||||
if searched_node_child.get_first_leaf() is not start_leaf:
|
||||
return None
|
||||
searched_node = searched_node_child.parent
|
||||
if searched_node is None:
|
||||
return None
|
||||
|
||||
index = searched_node.children.index(searched_node_child)
|
||||
arglist_nodes = searched_node.children[:index]
|
||||
if searched_node.type == 'arglist':
|
||||
trailer = searched_node.parent
|
||||
if trailer.type == 'error_node':
|
||||
trailer_index = trailer.children.index(searched_node)
|
||||
assert trailer_index >= 2
|
||||
assert trailer.children[trailer_index - 1] == '('
|
||||
return check(trailer.children[trailer_index - 1], arglist_nodes[::2])
|
||||
elif trailer.type == 'trailer':
|
||||
return check(trailer.children[0], arglist_nodes[::2])
|
||||
elif searched_node.type == 'trailer':
|
||||
return check(searched_node.children[0], [])
|
||||
elif searched_node.type == 'error_node':
|
||||
# Stuff like `join(""`
|
||||
return check(arglist_nodes[-1], [])
|
522
venv/Lib/site-packages/jedi/api/helpers.py
Normal file
522
venv/Lib/site-packages/jedi/api/helpers.py
Normal file
|
@ -0,0 +1,522 @@
|
|||
"""
|
||||
Helpers for the API
|
||||
"""
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from textwrap import dedent
|
||||
from itertools import chain
|
||||
from functools import wraps
|
||||
|
||||
from parso.python.parser import Parser
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import u, Parameter
|
||||
from jedi.inference.base_value import NO_VALUES
|
||||
from jedi.inference.syntax_tree import infer_atom
|
||||
from jedi.inference.helpers import infer_call_of_leaf
|
||||
from jedi.inference.compiled import get_string_value_set
|
||||
from jedi.cache import signature_time_cache, memoize_method
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
||||
|
||||
|
||||
def _start_match(string, like_name):
|
||||
return string.startswith(like_name)
|
||||
|
||||
|
||||
def _fuzzy_match(string, like_name):
|
||||
if len(like_name) <= 1:
|
||||
return like_name in string
|
||||
pos = string.find(like_name[0])
|
||||
if pos >= 0:
|
||||
return _fuzzy_match(string[pos + 1:], like_name[1:])
|
||||
return False
|
||||
|
||||
|
||||
def match(string, like_name, fuzzy=False):
|
||||
if fuzzy:
|
||||
return _fuzzy_match(string, like_name)
|
||||
else:
|
||||
return _start_match(string, like_name)
|
||||
|
||||
|
||||
def sorted_definitions(defs):
|
||||
# Note: `or ''` below is required because `module_path` could be
|
||||
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0, x.name))
|
||||
|
||||
|
||||
def get_on_completion_name(module_node, lines, position):
|
||||
leaf = module_node.get_leaf_for_position(position)
|
||||
if leaf is None or leaf.type in ('string', 'error_leaf'):
|
||||
# Completions inside strings are a bit special, we need to parse the
|
||||
# string. The same is true for comments and error_leafs.
|
||||
line = lines[position[0] - 1]
|
||||
# The first step of completions is to get the name
|
||||
return re.search(r'(?!\d)\w+$|$', line[:position[1]]).group(0)
|
||||
elif leaf.type not in ('name', 'keyword'):
|
||||
return ''
|
||||
|
||||
return leaf.value[:position[1] - leaf.start_pos[1]]
|
||||
|
||||
|
||||
def _get_code(code_lines, start_pos, end_pos):
|
||||
# Get relevant lines.
|
||||
lines = code_lines[start_pos[0] - 1:end_pos[0]]
|
||||
# Remove the parts at the end of the line.
|
||||
lines[-1] = lines[-1][:end_pos[1]]
|
||||
# Remove first line indentation.
|
||||
lines[0] = lines[0][start_pos[1]:]
|
||||
return ''.join(lines)
|
||||
|
||||
|
||||
class OnErrorLeaf(Exception):
|
||||
@property
|
||||
def error_leaf(self):
|
||||
return self.args[0]
|
||||
|
||||
|
||||
def _get_code_for_stack(code_lines, leaf, position):
|
||||
# It might happen that we're on whitespace or on a comment. This means
|
||||
# that we would not get the right leaf.
|
||||
if leaf.start_pos >= position:
|
||||
# If we're not on a comment simply get the previous leaf and proceed.
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return u('') # At the beginning of the file.
|
||||
|
||||
is_after_newline = leaf.type == 'newline'
|
||||
while leaf.type == 'newline':
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return u('')
|
||||
|
||||
if leaf.type == 'error_leaf' or leaf.type == 'string':
|
||||
if leaf.start_pos[0] < position[0]:
|
||||
# On a different line, we just begin anew.
|
||||
return u('')
|
||||
|
||||
# Error leafs cannot be parsed, completion in strings is also
|
||||
# impossible.
|
||||
raise OnErrorLeaf(leaf)
|
||||
else:
|
||||
user_stmt = leaf
|
||||
while True:
|
||||
if user_stmt.parent.type in ('file_input', 'suite', 'simple_stmt'):
|
||||
break
|
||||
user_stmt = user_stmt.parent
|
||||
|
||||
if is_after_newline:
|
||||
if user_stmt.start_pos[1] > position[1]:
|
||||
# This means that it's actually a dedent and that means that we
|
||||
# start without value (part of a suite).
|
||||
return u('')
|
||||
|
||||
# This is basically getting the relevant lines.
|
||||
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
|
||||
|
||||
|
||||
def get_stack_at_position(grammar, code_lines, leaf, pos):
|
||||
"""
|
||||
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
|
||||
"""
|
||||
class EndMarkerReached(Exception):
|
||||
pass
|
||||
|
||||
def tokenize_without_endmarker(code):
|
||||
# TODO This is for now not an official parso API that exists purely
|
||||
# for Jedi.
|
||||
tokens = grammar._tokenize(code)
|
||||
for token in tokens:
|
||||
if token.string == safeword:
|
||||
raise EndMarkerReached()
|
||||
elif token.prefix.endswith(safeword):
|
||||
# This happens with comments.
|
||||
raise EndMarkerReached()
|
||||
elif token.string.endswith(safeword):
|
||||
yield token # Probably an f-string literal that was not finished.
|
||||
raise EndMarkerReached()
|
||||
else:
|
||||
yield token
|
||||
|
||||
# The code might be indedented, just remove it.
|
||||
code = dedent(_get_code_for_stack(code_lines, leaf, pos))
|
||||
# We use a word to tell Jedi when we have reached the start of the
|
||||
# completion.
|
||||
# Use Z as a prefix because it's not part of a number suffix.
|
||||
safeword = 'ZZZ_USER_WANTS_TO_COMPLETE_HERE_WITH_JEDI'
|
||||
code = code + ' ' + safeword
|
||||
|
||||
p = Parser(grammar._pgen_grammar, error_recovery=True)
|
||||
try:
|
||||
p.parse(tokens=tokenize_without_endmarker(code))
|
||||
except EndMarkerReached:
|
||||
return p.stack
|
||||
raise SystemError(
|
||||
"This really shouldn't happen. There's a bug in Jedi:\n%s"
|
||||
% list(tokenize_without_endmarker(code))
|
||||
)
|
||||
|
||||
|
||||
def infer(inference_state, context, leaf):
|
||||
if leaf.type == 'name':
|
||||
return inference_state.infer(context, leaf)
|
||||
|
||||
parent = leaf.parent
|
||||
definitions = NO_VALUES
|
||||
if parent.type == 'atom':
|
||||
# e.g. `(a + b)`
|
||||
definitions = context.infer_node(leaf.parent)
|
||||
elif parent.type == 'trailer':
|
||||
# e.g. `a()`
|
||||
definitions = infer_call_of_leaf(context, leaf)
|
||||
elif isinstance(leaf, tree.Literal):
|
||||
# e.g. `"foo"` or `1.0`
|
||||
return infer_atom(context, leaf)
|
||||
elif leaf.type in ('fstring_string', 'fstring_start', 'fstring_end'):
|
||||
return get_string_value_set(inference_state)
|
||||
return definitions
|
||||
|
||||
|
||||
def filter_follow_imports(names, follow_builtin_imports=False):
|
||||
for name in names:
|
||||
if name.is_import():
|
||||
new_names = list(filter_follow_imports(
|
||||
name.goto(),
|
||||
follow_builtin_imports=follow_builtin_imports,
|
||||
))
|
||||
found_builtin = False
|
||||
if follow_builtin_imports:
|
||||
for new_name in new_names:
|
||||
if new_name.start_pos is None:
|
||||
found_builtin = True
|
||||
|
||||
if found_builtin:
|
||||
yield name
|
||||
else:
|
||||
for new_name in new_names:
|
||||
yield new_name
|
||||
else:
|
||||
yield name
|
||||
|
||||
|
||||
class CallDetails(object):
|
||||
def __init__(self, bracket_leaf, children, position):
|
||||
['bracket_leaf', 'call_index', 'keyword_name_str']
|
||||
self.bracket_leaf = bracket_leaf
|
||||
self._children = children
|
||||
self._position = position
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
return _get_index_and_key(self._children, self._position)[0]
|
||||
|
||||
@property
|
||||
def keyword_name_str(self):
|
||||
return _get_index_and_key(self._children, self._position)[1]
|
||||
|
||||
@memoize_method
|
||||
def _list_arguments(self):
|
||||
return list(_iter_arguments(self._children, self._position))
|
||||
|
||||
def calculate_index(self, param_names):
|
||||
positional_count = 0
|
||||
used_names = set()
|
||||
star_count = -1
|
||||
args = self._list_arguments()
|
||||
if not args:
|
||||
if param_names:
|
||||
return 0
|
||||
else:
|
||||
return None
|
||||
|
||||
is_kwarg = False
|
||||
for i, (star_count, key_start, had_equal) in enumerate(args):
|
||||
is_kwarg |= had_equal | (star_count == 2)
|
||||
if star_count:
|
||||
pass # For now do nothing, we don't know what's in there here.
|
||||
else:
|
||||
if i + 1 != len(args): # Not last
|
||||
if had_equal:
|
||||
used_names.add(key_start)
|
||||
else:
|
||||
positional_count += 1
|
||||
|
||||
for i, param_name in enumerate(param_names):
|
||||
kind = param_name.get_kind()
|
||||
|
||||
if not is_kwarg:
|
||||
if kind == Parameter.VAR_POSITIONAL:
|
||||
return i
|
||||
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
|
||||
if i == positional_count:
|
||||
return i
|
||||
|
||||
if key_start is not None and not star_count == 1 or star_count == 2:
|
||||
if param_name.string_name not in used_names \
|
||||
and (kind == Parameter.KEYWORD_ONLY
|
||||
or kind == Parameter.POSITIONAL_OR_KEYWORD
|
||||
and positional_count <= i):
|
||||
if star_count:
|
||||
return i
|
||||
if had_equal:
|
||||
if param_name.string_name == key_start:
|
||||
return i
|
||||
else:
|
||||
if param_name.string_name.startswith(key_start):
|
||||
return i
|
||||
|
||||
if kind == Parameter.VAR_KEYWORD:
|
||||
return i
|
||||
return None
|
||||
|
||||
def iter_used_keyword_arguments(self):
|
||||
for star_count, key_start, had_equal in list(self._list_arguments()):
|
||||
if had_equal and key_start:
|
||||
yield key_start
|
||||
|
||||
def count_positional_arguments(self):
|
||||
count = 0
|
||||
for star_count, key_start, had_equal in self._list_arguments()[:-1]:
|
||||
if star_count:
|
||||
break
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def _iter_arguments(nodes, position):
|
||||
def remove_after_pos(name):
|
||||
if name.type != 'name':
|
||||
return None
|
||||
return name.value[:position[1] - name.start_pos[1]]
|
||||
|
||||
# Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
|
||||
nodes_before = [c for c in nodes if c.start_pos < position]
|
||||
if nodes_before[-1].type == 'arglist':
|
||||
for x in _iter_arguments(nodes_before[-1].children, position):
|
||||
yield x # Python 2 :(
|
||||
return
|
||||
|
||||
previous_node_yielded = False
|
||||
stars_seen = 0
|
||||
for i, node in enumerate(nodes_before):
|
||||
if node.type == 'argument':
|
||||
previous_node_yielded = True
|
||||
first = node.children[0]
|
||||
second = node.children[1]
|
||||
if second == '=':
|
||||
if second.start_pos < position:
|
||||
yield 0, first.value, True
|
||||
else:
|
||||
yield 0, remove_after_pos(first), False
|
||||
elif first in ('*', '**'):
|
||||
yield len(first.value), remove_after_pos(second), False
|
||||
else:
|
||||
# Must be a Comprehension
|
||||
first_leaf = node.get_first_leaf()
|
||||
if first_leaf.type == 'name' and first_leaf.start_pos >= position:
|
||||
yield 0, remove_after_pos(first_leaf), False
|
||||
else:
|
||||
yield 0, None, False
|
||||
stars_seen = 0
|
||||
elif node.type in ('testlist', 'testlist_star_expr'): # testlist is Python 2
|
||||
for n in node.children[::2]:
|
||||
if n.type == 'star_expr':
|
||||
stars_seen = 1
|
||||
n = n.children[1]
|
||||
yield stars_seen, remove_after_pos(n), False
|
||||
stars_seen = 0
|
||||
# The count of children is even if there's a comma at the end.
|
||||
previous_node_yielded = bool(len(node.children) % 2)
|
||||
elif isinstance(node, tree.PythonLeaf) and node.value == ',':
|
||||
if not previous_node_yielded:
|
||||
yield stars_seen, '', False
|
||||
stars_seen = 0
|
||||
previous_node_yielded = False
|
||||
elif isinstance(node, tree.PythonLeaf) and node.value in ('*', '**'):
|
||||
stars_seen = len(node.value)
|
||||
elif node == '=' and nodes_before[-1]:
|
||||
previous_node_yielded = True
|
||||
before = nodes_before[i - 1]
|
||||
if before.type == 'name':
|
||||
yield 0, before.value, True
|
||||
else:
|
||||
yield 0, None, False
|
||||
# Just ignore the star that is probably a syntax error.
|
||||
stars_seen = 0
|
||||
|
||||
if not previous_node_yielded:
|
||||
if nodes_before[-1].type == 'name':
|
||||
yield stars_seen, remove_after_pos(nodes_before[-1]), False
|
||||
else:
|
||||
yield stars_seen, '', False
|
||||
|
||||
|
||||
def _get_index_and_key(nodes, position):
|
||||
"""
|
||||
Returns the amount of commas and the keyword argument string.
|
||||
"""
|
||||
nodes_before = [c for c in nodes if c.start_pos < position]
|
||||
if nodes_before[-1].type == 'arglist':
|
||||
return _get_index_and_key(nodes_before[-1].children, position)
|
||||
|
||||
key_str = None
|
||||
|
||||
last = nodes_before[-1]
|
||||
if last.type == 'argument' and last.children[1] == '=' \
|
||||
and last.children[1].end_pos <= position:
|
||||
# Checked if the argument
|
||||
key_str = last.children[0].value
|
||||
elif last == '=':
|
||||
key_str = nodes_before[-2].value
|
||||
|
||||
return nodes_before.count(','), key_str
|
||||
|
||||
|
||||
def _get_signature_details_from_error_node(node, additional_children, position):
|
||||
for index, element in reversed(list(enumerate(node.children))):
|
||||
# `index > 0` means that it's a trailer and not an atom.
|
||||
if element == '(' and element.end_pos <= position and index > 0:
|
||||
# It's an error node, we don't want to match too much, just
|
||||
# until the parentheses is enough.
|
||||
children = node.children[index:]
|
||||
name = element.get_previous_leaf()
|
||||
if name is None:
|
||||
continue
|
||||
if name.type == 'name' or name.parent.type in ('trailer', 'atom'):
|
||||
return CallDetails(element, children + additional_children, position)
|
||||
|
||||
|
||||
def get_signature_details(module, position):
|
||||
leaf = module.get_leaf_for_position(position, include_prefixes=True)
|
||||
# It's easier to deal with the previous token than the next one in this
|
||||
# case.
|
||||
if leaf.start_pos >= position:
|
||||
# Whitespace / comments after the leaf count towards the previous leaf.
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
|
||||
# Now that we know where we are in the syntax tree, we start to look at
|
||||
# parents for possible function definitions.
|
||||
node = leaf.parent
|
||||
while node is not None:
|
||||
if node.type in ('funcdef', 'classdef', 'decorated', 'async_stmt'):
|
||||
# Don't show signatures if there's stuff before it that just
|
||||
# makes it feel strange to have a signature.
|
||||
return None
|
||||
|
||||
additional_children = []
|
||||
for n in reversed(node.children):
|
||||
if n.start_pos < position:
|
||||
if n.type == 'error_node':
|
||||
result = _get_signature_details_from_error_node(
|
||||
n, additional_children, position
|
||||
)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
additional_children[0:0] = n.children
|
||||
continue
|
||||
additional_children.insert(0, n)
|
||||
|
||||
# Find a valid trailer
|
||||
if node.type == 'trailer' and node.children[0] == '(' \
|
||||
or node.type == 'decorator' and node.children[2] == '(':
|
||||
# Additionally we have to check that an ending parenthesis isn't
|
||||
# interpreted wrong. There are two cases:
|
||||
# 1. Cursor before paren -> The current signature is good
|
||||
# 2. Cursor after paren -> We need to skip the current signature
|
||||
if not (leaf is node.children[-1] and position >= leaf.end_pos):
|
||||
leaf = node.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
return CallDetails(
|
||||
node.children[0] if node.type == 'trailer' else node.children[2],
|
||||
node.children,
|
||||
position
|
||||
)
|
||||
|
||||
node = node.parent
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@signature_time_cache("call_signatures_validity")
|
||||
def cache_signatures(inference_state, context, bracket_leaf, code_lines, user_pos):
|
||||
"""This function calculates the cache key."""
|
||||
line_index = user_pos[0] - 1
|
||||
|
||||
before_cursor = code_lines[line_index][:user_pos[1]]
|
||||
other_lines = code_lines[bracket_leaf.start_pos[0]:line_index]
|
||||
whole = ''.join(other_lines + [before_cursor])
|
||||
before_bracket = re.match(r'.*\(', whole, re.DOTALL)
|
||||
|
||||
module_path = context.get_root_context().py__file__()
|
||||
if module_path is None:
|
||||
yield None # Don't cache!
|
||||
else:
|
||||
yield (module_path, before_bracket, bracket_leaf.start_pos)
|
||||
yield infer(
|
||||
inference_state,
|
||||
context,
|
||||
bracket_leaf.get_previous_leaf(),
|
||||
)
|
||||
|
||||
|
||||
def validate_line_column(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, line=None, column=None, *args, **kwargs):
|
||||
line = max(len(self._code_lines), 1) if line is None else line
|
||||
if not (0 < line <= len(self._code_lines)):
|
||||
raise ValueError('`line` parameter is not in a valid range.')
|
||||
|
||||
line_string = self._code_lines[line - 1]
|
||||
line_len = len(line_string)
|
||||
if line_string.endswith('\r\n'):
|
||||
line_len -= 2
|
||||
elif line_string.endswith('\n'):
|
||||
line_len -= 1
|
||||
|
||||
column = line_len if column is None else column
|
||||
if not (0 <= column <= line_len):
|
||||
raise ValueError('`column` parameter (%d) is not in a valid range '
|
||||
'(0-%d) for line %d (%r).' % (
|
||||
column, line_len, line, line_string))
|
||||
return func(self, line, column, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_module_names(module, all_scopes, definitions=True, references=False):
|
||||
"""
|
||||
Returns a dictionary with name parts as keys and their call paths as
|
||||
values.
|
||||
"""
|
||||
def def_ref_filter(name):
|
||||
is_def = name.is_definition()
|
||||
return definitions and is_def or references and not is_def
|
||||
|
||||
names = list(chain.from_iterable(module.get_used_names().values()))
|
||||
if not all_scopes:
|
||||
# We have to filter all the names that don't have the module as a
|
||||
# parent_scope. There's None as a parent, because nodes in the module
|
||||
# node have the parent module and not suite as all the others.
|
||||
# Therefore it's important to catch that case.
|
||||
|
||||
def is_module_scope_name(name):
|
||||
parent_scope = get_parent_scope(name)
|
||||
# async functions have an extra wrapper. Strip it.
|
||||
if parent_scope and parent_scope.type == 'async_stmt':
|
||||
parent_scope = parent_scope.parent
|
||||
return parent_scope in (module, None)
|
||||
|
||||
names = [n for n in names if is_module_scope_name(n)]
|
||||
return filter(def_ref_filter, names)
|
||||
|
||||
|
||||
def split_search_string(name):
|
||||
type, _, dotted_names = name.rpartition(' ')
|
||||
if type == 'def':
|
||||
type = 'function'
|
||||
return type, dotted_names.split('.')
|
75
venv/Lib/site-packages/jedi/api/interpreter.py
Normal file
75
venv/Lib/site-packages/jedi/api/interpreter.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
"""
|
||||
TODO Some parts of this module are still not well documented.
|
||||
"""
|
||||
|
||||
from jedi.inference import compiled
|
||||
from jedi.inference.base_value import ValueSet
|
||||
from jedi.inference.filters import ParserTreeFilter, MergedFilter
|
||||
from jedi.inference.names import TreeNameDefinition
|
||||
from jedi.inference.compiled import mixed
|
||||
from jedi.inference.compiled.access import create_access_path
|
||||
from jedi.inference.context import ModuleContext
|
||||
|
||||
|
||||
def _create(inference_state, obj):
|
||||
return compiled.create_from_access_path(
|
||||
inference_state, create_access_path(inference_state, obj)
|
||||
)
|
||||
|
||||
|
||||
class NamespaceObject(object):
|
||||
def __init__(self, dct):
|
||||
self.__dict__ = dct
|
||||
|
||||
|
||||
class MixedTreeName(TreeNameDefinition):
|
||||
def infer(self):
|
||||
"""
|
||||
In IPython notebook it is typical that some parts of the code that is
|
||||
provided was already executed. In that case if something is not properly
|
||||
inferred, it should still infer from the variables it already knows.
|
||||
"""
|
||||
inferred = super(MixedTreeName, self).infer()
|
||||
if not inferred:
|
||||
for compiled_value in self.parent_context.mixed_values:
|
||||
for f in compiled_value.get_filters():
|
||||
values = ValueSet.from_sets(
|
||||
n.infer() for n in f.get(self.string_name)
|
||||
)
|
||||
if values:
|
||||
return values
|
||||
return inferred
|
||||
|
||||
|
||||
class MixedParserTreeFilter(ParserTreeFilter):
|
||||
name_class = MixedTreeName
|
||||
|
||||
|
||||
class MixedModuleContext(ModuleContext):
|
||||
def __init__(self, tree_module_value, namespaces):
|
||||
super(MixedModuleContext, self).__init__(tree_module_value)
|
||||
self.mixed_values = [
|
||||
self._get_mixed_object(
|
||||
_create(self.inference_state, NamespaceObject(n))
|
||||
) for n in namespaces
|
||||
]
|
||||
|
||||
def _get_mixed_object(self, compiled_value):
|
||||
return mixed.MixedObject(
|
||||
compiled_value=compiled_value,
|
||||
tree_value=self._value
|
||||
)
|
||||
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
yield MergedFilter(
|
||||
MixedParserTreeFilter(
|
||||
parent_context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
),
|
||||
self.get_global_filter(),
|
||||
)
|
||||
|
||||
for mixed_object in self.mixed_values:
|
||||
for filter in mixed_object.get_filters(until_position, origin_scope):
|
||||
yield filter
|
57
venv/Lib/site-packages/jedi/api/keywords.py
Normal file
57
venv/Lib/site-packages/jedi/api/keywords.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
import pydoc
|
||||
|
||||
from jedi.inference.utils import ignored
|
||||
from jedi.inference.names import AbstractArbitraryName
|
||||
|
||||
try:
|
||||
from pydoc_data import topics as pydoc_topics
|
||||
except ImportError:
|
||||
# Python 2
|
||||
try:
|
||||
import pydoc_topics
|
||||
except ImportError:
|
||||
# This is for Python 3 embeddable version, which dont have
|
||||
# pydoc_data module in its file python3x.zip.
|
||||
pydoc_topics = None
|
||||
|
||||
|
||||
class KeywordName(AbstractArbitraryName):
|
||||
api_type = u'keyword'
|
||||
|
||||
def py__doc__(self):
|
||||
return imitate_pydoc(self.string_name)
|
||||
|
||||
|
||||
def imitate_pydoc(string):
|
||||
"""
|
||||
It's not possible to get the pydoc's without starting the annoying pager
|
||||
stuff.
|
||||
"""
|
||||
if pydoc_topics is None:
|
||||
return ''
|
||||
|
||||
# str needed because of possible unicode stuff in py2k (pydoc doesn't work
|
||||
# with unicode strings)
|
||||
string = str(string)
|
||||
h = pydoc.help
|
||||
with ignored(KeyError):
|
||||
# try to access symbols
|
||||
string = h.symbols[string]
|
||||
string, _, related = string.partition(' ')
|
||||
|
||||
def get_target(s):
|
||||
return h.topics.get(s, h.keywords.get(s))
|
||||
|
||||
while isinstance(string, str):
|
||||
string = get_target(string)
|
||||
|
||||
try:
|
||||
# is a tuple now
|
||||
label, related = string
|
||||
except TypeError:
|
||||
return ''
|
||||
|
||||
try:
|
||||
return pydoc_topics.topics[label].strip() if pydoc_topics else ''
|
||||
except KeyError:
|
||||
return ''
|
427
venv/Lib/site-packages/jedi/api/project.py
Normal file
427
venv/Lib/site-packages/jedi/api/project.py
Normal file
|
@ -0,0 +1,427 @@
|
|||
"""
|
||||
Projects are a way to handle Python projects within Jedi. For simpler plugins
|
||||
you might not want to deal with projects, but if you want to give the user more
|
||||
flexibility to define sys paths and Python interpreters for a project,
|
||||
:class:`.Project` is the perfect way to allow for that.
|
||||
|
||||
Projects can be saved to disk and loaded again, to allow project definitions to
|
||||
be used across repositories.
|
||||
"""
|
||||
import os
|
||||
import errno
|
||||
import json
|
||||
import sys
|
||||
|
||||
from jedi._compatibility import FileNotFoundError, PermissionError, \
|
||||
IsADirectoryError, NotADirectoryError
|
||||
from jedi import debug
|
||||
from jedi.api.environment import get_cached_default_environment, create_environment
|
||||
from jedi.api.exceptions import WrongVersion
|
||||
from jedi.api.completion import search_in_module
|
||||
from jedi.api.helpers import split_search_string, get_module_names
|
||||
from jedi._compatibility import force_unicode
|
||||
from jedi.inference.imports import load_module_from_path, \
|
||||
load_namespace_from_path, iter_module_names
|
||||
from jedi.inference.sys_path import discover_buildout_paths
|
||||
from jedi.inference.cache import inference_state_as_method_param_cache
|
||||
from jedi.inference.references import recurse_find_python_folders_and_files, search_in_file_ios
|
||||
from jedi.file_io import FolderIO
|
||||
from jedi.common import traverse_parents
|
||||
|
||||
_CONFIG_FOLDER = '.jedi'
|
||||
_CONTAINS_POTENTIAL_PROJECT = \
|
||||
'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in', 'pyproject.toml'
|
||||
|
||||
_SERIALIZER_VERSION = 1
|
||||
|
||||
|
||||
def _try_to_skip_duplicates(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
found_tree_nodes = []
|
||||
found_modules = []
|
||||
for definition in func(*args, **kwargs):
|
||||
tree_node = definition._name.tree_name
|
||||
if tree_node is not None and tree_node in found_tree_nodes:
|
||||
continue
|
||||
if definition.type == 'module' and definition.module_path is not None:
|
||||
if definition.module_path in found_modules:
|
||||
continue
|
||||
found_modules.append(definition.module_path)
|
||||
yield definition
|
||||
found_tree_nodes.append(tree_node)
|
||||
return wrapper
|
||||
|
||||
|
||||
def _remove_duplicates_from_path(path):
|
||||
used = set()
|
||||
for p in path:
|
||||
if p in used:
|
||||
continue
|
||||
used.add(p)
|
||||
yield p
|
||||
|
||||
|
||||
def _force_unicode_list(lst):
|
||||
return list(map(force_unicode, lst))
|
||||
|
||||
|
||||
class Project(object):
|
||||
"""
|
||||
Projects are a simple way to manage Python folders and define how Jedi does
|
||||
import resolution. It is mostly used as a parameter to :class:`.Script`.
|
||||
Additionally there are functions to search a whole project.
|
||||
"""
|
||||
_environment = None
|
||||
|
||||
@staticmethod
|
||||
def _get_config_folder_path(base_path):
|
||||
return os.path.join(base_path, _CONFIG_FOLDER)
|
||||
|
||||
@staticmethod
|
||||
def _get_json_path(base_path):
|
||||
return os.path.join(Project._get_config_folder_path(base_path), 'project.json')
|
||||
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
"""
|
||||
Loads a project from a specific path. You should not provide the path
|
||||
to ``.jedi/project.json``, but rather the path to the project folder.
|
||||
|
||||
:param path: The path of the directory you want to use as a project.
|
||||
"""
|
||||
with open(cls._get_json_path(path)) as f:
|
||||
version, data = json.load(f)
|
||||
|
||||
if version == 1:
|
||||
return cls(**data)
|
||||
else:
|
||||
raise WrongVersion(
|
||||
"The Jedi version of this project seems newer than what we can handle."
|
||||
)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Saves the project configuration in the project in ``.jedi/project.json``.
|
||||
"""
|
||||
data = dict(self.__dict__)
|
||||
data.pop('_environment', None)
|
||||
data.pop('_django', None) # TODO make django setting public?
|
||||
data = {k.lstrip('_'): v for k, v in data.items()}
|
||||
|
||||
# TODO when dropping Python 2 use pathlib.Path.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
os.makedirs(self._get_config_folder_path(self._path))
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
with open(self._get_json_path(self._path), 'w') as f:
|
||||
return json.dump((_SERIALIZER_VERSION, data), f)
|
||||
|
||||
def __init__(self, path, **kwargs):
|
||||
"""
|
||||
:param path: The base path for this project.
|
||||
:param environment_path: The Python executable path, typically the path
|
||||
of a virtual environment.
|
||||
:param load_unsafe_extensions: Default False, Loads extensions that are not in the
|
||||
sys path and in the local directories. With this option enabled,
|
||||
this is potentially unsafe if you clone a git repository and
|
||||
analyze it's code, because those compiled extensions will be
|
||||
important and therefore have execution privileges.
|
||||
:param sys_path: list of str. You can override the sys path if you
|
||||
want. By default the ``sys.path.`` is generated by the
|
||||
environment (virtualenvs, etc).
|
||||
:param added_sys_path: list of str. Adds these paths at the end of the
|
||||
sys path.
|
||||
:param smart_sys_path: If this is enabled (default), adds paths from
|
||||
local directories. Otherwise you will have to rely on your packages
|
||||
being properly configured on the ``sys.path``.
|
||||
"""
|
||||
def py2_comp(path, environment_path=None, load_unsafe_extensions=False,
|
||||
sys_path=None, added_sys_path=(), smart_sys_path=True):
|
||||
self._path = os.path.abspath(path)
|
||||
|
||||
self._environment_path = environment_path
|
||||
self._sys_path = sys_path
|
||||
self._smart_sys_path = smart_sys_path
|
||||
self._load_unsafe_extensions = load_unsafe_extensions
|
||||
self._django = False
|
||||
self.added_sys_path = list(added_sys_path)
|
||||
"""The sys path that is going to be added at the end of the """
|
||||
|
||||
py2_comp(path, **kwargs)
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
The base path for this project.
|
||||
"""
|
||||
return self._path
|
||||
|
||||
@inference_state_as_method_param_cache()
|
||||
def _get_base_sys_path(self, inference_state):
|
||||
# The sys path has not been set explicitly.
|
||||
sys_path = list(inference_state.environment.get_sys_path())
|
||||
try:
|
||||
sys_path.remove('')
|
||||
except ValueError:
|
||||
pass
|
||||
return sys_path
|
||||
|
||||
@inference_state_as_method_param_cache()
|
||||
def _get_sys_path(self, inference_state, add_parent_paths=True, add_init_paths=False):
|
||||
"""
|
||||
Keep this method private for all users of jedi. However internally this
|
||||
one is used like a public method.
|
||||
"""
|
||||
suffixed = list(self.added_sys_path)
|
||||
prefixed = []
|
||||
|
||||
if self._sys_path is None:
|
||||
sys_path = list(self._get_base_sys_path(inference_state))
|
||||
else:
|
||||
sys_path = list(self._sys_path)
|
||||
|
||||
if self._smart_sys_path:
|
||||
prefixed.append(self._path)
|
||||
|
||||
if inference_state.script_path is not None:
|
||||
suffixed += discover_buildout_paths(inference_state, inference_state.script_path)
|
||||
|
||||
if add_parent_paths:
|
||||
# Collect directories in upward search by:
|
||||
# 1. Skipping directories with __init__.py
|
||||
# 2. Stopping immediately when above self._path
|
||||
traversed = []
|
||||
for parent_path in traverse_parents(inference_state.script_path):
|
||||
if parent_path == self._path or not parent_path.startswith(self._path):
|
||||
break
|
||||
if not add_init_paths \
|
||||
and os.path.isfile(os.path.join(parent_path, "__init__.py")):
|
||||
continue
|
||||
traversed.append(parent_path)
|
||||
|
||||
# AFAIK some libraries have imports like `foo.foo.bar`, which
|
||||
# leads to the conclusion to by default prefer longer paths
|
||||
# rather than shorter ones by default.
|
||||
suffixed += reversed(traversed)
|
||||
|
||||
if self._django:
|
||||
prefixed.append(self._path)
|
||||
|
||||
path = prefixed + sys_path + suffixed
|
||||
return list(_force_unicode_list(_remove_duplicates_from_path(path)))
|
||||
|
||||
def get_environment(self):
|
||||
if self._environment is None:
|
||||
if self._environment_path is not None:
|
||||
self._environment = create_environment(self._environment_path, safe=False)
|
||||
else:
|
||||
self._environment = get_cached_default_environment()
|
||||
return self._environment
|
||||
|
||||
def search(self, string, **kwargs):
|
||||
"""
|
||||
Searches a name in the whole project. If the project is very big,
|
||||
at some point Jedi will stop searching. However it's also very much
|
||||
recommended to not exhaust the generator. Just display the first ten
|
||||
results to the user.
|
||||
|
||||
There are currently three different search patterns:
|
||||
|
||||
- ``foo`` to search for a definition foo in any file or a file called
|
||||
``foo.py`` or ``foo.pyi``.
|
||||
- ``foo.bar`` to search for the ``foo`` and then an attribute ``bar``
|
||||
in it.
|
||||
- ``class foo.bar.Bar`` or ``def foo.bar.baz`` to search for a specific
|
||||
API type.
|
||||
|
||||
:param bool all_scopes: Default False; searches not only for
|
||||
definitions on the top level of a module level, but also in
|
||||
functions and classes.
|
||||
:yields: :class:`.Name`
|
||||
"""
|
||||
return self._search(string, **kwargs)
|
||||
|
||||
def complete_search(self, string, **kwargs):
|
||||
"""
|
||||
Like :meth:`.Script.search`, but completes that string. An empty string
|
||||
lists all definitions in a project, so be careful with that.
|
||||
|
||||
:param bool all_scopes: Default False; searches not only for
|
||||
definitions on the top level of a module level, but also in
|
||||
functions and classes.
|
||||
:yields: :class:`.Completion`
|
||||
"""
|
||||
return self._search_func(string, complete=True, **kwargs)
|
||||
|
||||
def _search(self, string, all_scopes=False): # Python 2..
|
||||
return self._search_func(string, all_scopes=all_scopes)
|
||||
|
||||
@_try_to_skip_duplicates
|
||||
def _search_func(self, string, complete=False, all_scopes=False):
|
||||
# Using a Script is they easiest way to get an empty module context.
|
||||
from jedi import Script
|
||||
s = Script('', project=self)
|
||||
inference_state = s._inference_state
|
||||
empty_module_context = s._get_module_context()
|
||||
|
||||
if inference_state.grammar.version_info < (3, 6) or sys.version_info < (3, 6):
|
||||
raise NotImplementedError(
|
||||
"No support for refactorings/search on Python 2/3.5"
|
||||
)
|
||||
debug.dbg('Search for string %s, complete=%s', string, complete)
|
||||
wanted_type, wanted_names = split_search_string(string)
|
||||
name = wanted_names[0]
|
||||
stub_folder_name = name + '-stubs'
|
||||
|
||||
ios = recurse_find_python_folders_and_files(FolderIO(self._path))
|
||||
file_ios = []
|
||||
|
||||
# 1. Search for modules in the current project
|
||||
for folder_io, file_io in ios:
|
||||
if file_io is None:
|
||||
file_name = folder_io.get_base_name()
|
||||
if file_name == name or file_name == stub_folder_name:
|
||||
f = folder_io.get_file_io('__init__.py')
|
||||
try:
|
||||
m = load_module_from_path(inference_state, f).as_context()
|
||||
except FileNotFoundError:
|
||||
f = folder_io.get_file_io('__init__.pyi')
|
||||
try:
|
||||
m = load_module_from_path(inference_state, f).as_context()
|
||||
except FileNotFoundError:
|
||||
m = load_namespace_from_path(inference_state, folder_io).as_context()
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
file_ios.append(file_io)
|
||||
file_name = os.path.basename(file_io.path)
|
||||
if file_name in (name + '.py', name + '.pyi'):
|
||||
m = load_module_from_path(inference_state, file_io).as_context()
|
||||
else:
|
||||
continue
|
||||
|
||||
debug.dbg('Search of a specific module %s', m)
|
||||
for x in search_in_module(
|
||||
inference_state,
|
||||
m,
|
||||
names=[m.name],
|
||||
wanted_type=wanted_type,
|
||||
wanted_names=wanted_names,
|
||||
complete=complete,
|
||||
convert=True,
|
||||
ignore_imports=True,
|
||||
):
|
||||
yield x # Python 2...
|
||||
|
||||
# 2. Search for identifiers in the project.
|
||||
for module_context in search_in_file_ios(inference_state, file_ios, name):
|
||||
names = get_module_names(module_context.tree_node, all_scopes=all_scopes)
|
||||
names = [module_context.create_name(n) for n in names]
|
||||
names = _remove_imports(names)
|
||||
for x in search_in_module(
|
||||
inference_state,
|
||||
module_context,
|
||||
names=names,
|
||||
wanted_type=wanted_type,
|
||||
wanted_names=wanted_names,
|
||||
complete=complete,
|
||||
ignore_imports=True,
|
||||
):
|
||||
yield x # Python 2...
|
||||
|
||||
# 3. Search for modules on sys.path
|
||||
sys_path = [
|
||||
p for p in self._get_sys_path(inference_state)
|
||||
# Exclude folders that are handled by recursing of the Python
|
||||
# folders.
|
||||
if not p.startswith(self._path)
|
||||
]
|
||||
names = list(iter_module_names(inference_state, empty_module_context, sys_path))
|
||||
for x in search_in_module(
|
||||
inference_state,
|
||||
empty_module_context,
|
||||
names=names,
|
||||
wanted_type=wanted_type,
|
||||
wanted_names=wanted_names,
|
||||
complete=complete,
|
||||
convert=True,
|
||||
):
|
||||
yield x # Python 2...
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._path)
|
||||
|
||||
|
||||
def _is_potential_project(path):
|
||||
for name in _CONTAINS_POTENTIAL_PROJECT:
|
||||
if os.path.exists(os.path.join(path, name)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _is_django_path(directory):
|
||||
""" Detects the path of the very well known Django library (if used) """
|
||||
try:
|
||||
with open(os.path.join(directory, 'manage.py'), 'rb') as f:
|
||||
return b"DJANGO_SETTINGS_MODULE" in f.read()
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||
return False
|
||||
|
||||
|
||||
def get_default_project(path=None):
|
||||
"""
|
||||
If a project is not defined by the user, Jedi tries to define a project by
|
||||
itself as well as possible. Jedi traverses folders until it finds one of
|
||||
the following:
|
||||
|
||||
1. A ``.jedi/config.json``
|
||||
2. One of the following files: ``setup.py``, ``.git``, ``.hg``,
|
||||
``requirements.txt`` and ``MANIFEST.in``.
|
||||
"""
|
||||
if path is None:
|
||||
path = os.getcwd()
|
||||
|
||||
check = os.path.realpath(path)
|
||||
probable_path = None
|
||||
first_no_init_file = None
|
||||
for dir in traverse_parents(check, include_current=True):
|
||||
try:
|
||||
return Project.load(dir)
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||
pass
|
||||
except NotADirectoryError:
|
||||
continue
|
||||
|
||||
if first_no_init_file is None:
|
||||
if os.path.exists(os.path.join(dir, '__init__.py')):
|
||||
# In the case that a __init__.py exists, it's in 99% just a
|
||||
# Python package and the project sits at least one level above.
|
||||
continue
|
||||
else:
|
||||
first_no_init_file = dir
|
||||
|
||||
if _is_django_path(dir):
|
||||
project = Project(dir)
|
||||
project._django = True
|
||||
return project
|
||||
|
||||
if probable_path is None and _is_potential_project(dir):
|
||||
probable_path = dir
|
||||
|
||||
if probable_path is not None:
|
||||
# TODO search for setup.py etc
|
||||
return Project(probable_path)
|
||||
|
||||
if first_no_init_file is not None:
|
||||
return Project(first_no_init_file)
|
||||
|
||||
curdir = path if os.path.isdir(path) else os.path.dirname(path)
|
||||
return Project(curdir)
|
||||
|
||||
|
||||
def _remove_imports(names):
|
||||
return [
|
||||
n for n in names
|
||||
if n.tree_name is None or n.api_type != 'module'
|
||||
]
|
249
venv/Lib/site-packages/jedi/api/refactoring/__init__.py
Normal file
249
venv/Lib/site-packages/jedi/api/refactoring/__init__.py
Normal file
|
@ -0,0 +1,249 @@
|
|||
from os.path import dirname, basename, join, relpath
|
||||
import os
|
||||
import re
|
||||
import difflib
|
||||
|
||||
from parso import split_lines
|
||||
|
||||
from jedi.api.exceptions import RefactoringError
|
||||
|
||||
EXPRESSION_PARTS = (
|
||||
'or_test and_test not_test comparison '
|
||||
'expr xor_expr and_expr shift_expr arith_expr term factor power atom_expr'
|
||||
).split()
|
||||
|
||||
|
||||
class ChangedFile(object):
|
||||
def __init__(self, inference_state, from_path, to_path,
|
||||
module_node, node_to_str_map):
|
||||
self._inference_state = inference_state
|
||||
self._from_path = from_path
|
||||
self._to_path = to_path
|
||||
self._module_node = module_node
|
||||
self._node_to_str_map = node_to_str_map
|
||||
|
||||
def get_diff(self):
|
||||
old_lines = split_lines(self._module_node.get_code(), keepends=True)
|
||||
new_lines = split_lines(self.get_new_code(), keepends=True)
|
||||
|
||||
# Add a newline at the end if it's missing. Otherwise the diff will be
|
||||
# very weird. A `diff -u file1 file2` would show the string:
|
||||
#
|
||||
# \ No newline at end of file
|
||||
#
|
||||
# This is not necessary IMO, because Jedi does not really play with
|
||||
# newlines and the ending newline does not really matter in Python
|
||||
# files. ~dave
|
||||
if old_lines[-1] != '':
|
||||
old_lines[-1] += '\n'
|
||||
if new_lines[-1] != '':
|
||||
new_lines[-1] += '\n'
|
||||
|
||||
project_path = self._inference_state.project.path
|
||||
if self._from_path is None:
|
||||
from_p = ''
|
||||
else:
|
||||
from_p = relpath(self._from_path, project_path)
|
||||
if self._to_path is None:
|
||||
to_p = ''
|
||||
else:
|
||||
to_p = relpath(self._to_path, project_path)
|
||||
diff = difflib.unified_diff(
|
||||
old_lines, new_lines,
|
||||
fromfile=from_p,
|
||||
tofile=to_p,
|
||||
)
|
||||
# Apparently there's a space at the end of the diff - for whatever
|
||||
# reason.
|
||||
return ''.join(diff).rstrip(' ')
|
||||
|
||||
def get_new_code(self):
|
||||
return self._inference_state.grammar.refactor(self._module_node, self._node_to_str_map)
|
||||
|
||||
def apply(self):
|
||||
if self._from_path is None:
|
||||
raise RefactoringError(
|
||||
'Cannot apply a refactoring on a Script with path=None'
|
||||
)
|
||||
|
||||
with open(self._from_path, 'w', newline='') as f:
|
||||
f.write(self.get_new_code())
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._from_path)
|
||||
|
||||
|
||||
class Refactoring(object):
|
||||
def __init__(self, inference_state, file_to_node_changes, renames=()):
|
||||
self._inference_state = inference_state
|
||||
self._renames = renames
|
||||
self._file_to_node_changes = file_to_node_changes
|
||||
|
||||
def get_changed_files(self):
|
||||
"""
|
||||
Returns a path to ``ChangedFile`` map.
|
||||
"""
|
||||
def calculate_to_path(p):
|
||||
if p is None:
|
||||
return p
|
||||
for from_, to in renames:
|
||||
if p.startswith(from_):
|
||||
p = to + p[len(from_):]
|
||||
return p
|
||||
|
||||
renames = self.get_renames()
|
||||
return {
|
||||
path: ChangedFile(
|
||||
self._inference_state,
|
||||
from_path=path,
|
||||
to_path=calculate_to_path(path),
|
||||
module_node=next(iter(map_)).get_root_node(),
|
||||
node_to_str_map=map_
|
||||
) for path, map_ in sorted(self._file_to_node_changes.items())
|
||||
}
|
||||
|
||||
def get_renames(self):
|
||||
"""
|
||||
Files can be renamed in a refactoring.
|
||||
|
||||
Returns ``Iterable[Tuple[str, str]]``.
|
||||
"""
|
||||
return sorted(self._renames)
|
||||
|
||||
def get_diff(self):
|
||||
text = ''
|
||||
project_path = self._inference_state.project.path
|
||||
for from_, to in self.get_renames():
|
||||
text += 'rename from %s\nrename to %s\n' \
|
||||
% (relpath(from_, project_path), relpath(to, project_path))
|
||||
|
||||
return text + ''.join(f.get_diff() for f in self.get_changed_files().values())
|
||||
|
||||
def apply(self):
|
||||
"""
|
||||
Applies the whole refactoring to the files, which includes renames.
|
||||
"""
|
||||
for f in self.get_changed_files().values():
|
||||
f.apply()
|
||||
|
||||
for old, new in self.get_renames():
|
||||
os.rename(old, new)
|
||||
|
||||
|
||||
def _calculate_rename(path, new_name):
|
||||
name = basename(path)
|
||||
dir_ = dirname(path)
|
||||
if name in ('__init__.py', '__init__.pyi'):
|
||||
parent_dir = dirname(dir_)
|
||||
return dir_, join(parent_dir, new_name)
|
||||
ending = re.search(r'\.pyi?$', name).group(0)
|
||||
return path, join(dir_, new_name + ending)
|
||||
|
||||
|
||||
def rename(inference_state, definitions, new_name):
|
||||
file_renames = set()
|
||||
file_tree_name_map = {}
|
||||
|
||||
if not definitions:
|
||||
raise RefactoringError("There is no name under the cursor")
|
||||
|
||||
for d in definitions:
|
||||
tree_name = d._name.tree_name
|
||||
if d.type == 'module' and tree_name is None:
|
||||
file_renames.add(_calculate_rename(d.module_path, new_name))
|
||||
else:
|
||||
# This private access is ok in a way. It's not public to
|
||||
# protect Jedi users from seeing it.
|
||||
if tree_name is not None:
|
||||
fmap = file_tree_name_map.setdefault(d.module_path, {})
|
||||
fmap[tree_name] = tree_name.prefix + new_name
|
||||
return Refactoring(inference_state, file_tree_name_map, file_renames)
|
||||
|
||||
|
||||
def inline(inference_state, names):
|
||||
if not names:
|
||||
raise RefactoringError("There is no name under the cursor")
|
||||
if any(n.api_type == 'module' for n in names):
|
||||
raise RefactoringError("Cannot inline imports or modules")
|
||||
if any(n.tree_name is None for n in names):
|
||||
raise RefactoringError("Cannot inline builtins/extensions")
|
||||
|
||||
definitions = [n for n in names if n.tree_name.is_definition()]
|
||||
if len(definitions) == 0:
|
||||
raise RefactoringError("No definition found to inline")
|
||||
if len(definitions) > 1:
|
||||
raise RefactoringError("Cannot inline a name with multiple definitions")
|
||||
if len(names) == 1:
|
||||
raise RefactoringError("There are no references to this name")
|
||||
|
||||
tree_name = definitions[0].tree_name
|
||||
|
||||
expr_stmt = tree_name.get_definition()
|
||||
if expr_stmt.type != 'expr_stmt':
|
||||
type_ = dict(
|
||||
funcdef='function',
|
||||
classdef='class',
|
||||
).get(expr_stmt.type, expr_stmt.type)
|
||||
raise RefactoringError("Cannot inline a %s" % type_)
|
||||
|
||||
if len(expr_stmt.get_defined_names(include_setitem=True)) > 1:
|
||||
raise RefactoringError("Cannot inline a statement with multiple definitions")
|
||||
first_child = expr_stmt.children[1]
|
||||
if first_child.type == 'annassign' and len(first_child.children) == 4:
|
||||
first_child = first_child.children[2]
|
||||
if first_child != '=':
|
||||
if first_child.type == 'annassign':
|
||||
raise RefactoringError(
|
||||
'Cannot inline a statement that is defined by an annotation'
|
||||
)
|
||||
else:
|
||||
raise RefactoringError(
|
||||
'Cannot inline a statement with "%s"'
|
||||
% first_child.get_code(include_prefix=False)
|
||||
)
|
||||
|
||||
rhs = expr_stmt.get_rhs()
|
||||
replace_code = rhs.get_code(include_prefix=False)
|
||||
|
||||
references = [n for n in names if not n.tree_name.is_definition()]
|
||||
file_to_node_changes = {}
|
||||
for name in references:
|
||||
tree_name = name.tree_name
|
||||
path = name.get_root_context().py__file__()
|
||||
s = replace_code
|
||||
if rhs.type == 'testlist_star_expr' \
|
||||
or tree_name.parent.type in EXPRESSION_PARTS \
|
||||
or tree_name.parent.type == 'trailer' \
|
||||
and tree_name.parent.get_next_sibling() is not None:
|
||||
s = '(' + replace_code + ')'
|
||||
|
||||
of_path = file_to_node_changes.setdefault(path, {})
|
||||
|
||||
n = tree_name
|
||||
prefix = n.prefix
|
||||
par = n.parent
|
||||
if par.type == 'trailer' and par.children[0] == '.':
|
||||
prefix = par.parent.children[0].prefix
|
||||
n = par
|
||||
for some_node in par.parent.children[:par.parent.children.index(par)]:
|
||||
of_path[some_node] = ''
|
||||
of_path[n] = prefix + s
|
||||
|
||||
path = definitions[0].get_root_context().py__file__()
|
||||
changes = file_to_node_changes.setdefault(path, {})
|
||||
changes[expr_stmt] = _remove_indent_of_prefix(expr_stmt.get_first_leaf().prefix)
|
||||
next_leaf = expr_stmt.get_next_leaf()
|
||||
|
||||
# Most of the time we have to remove the newline at the end of the
|
||||
# statement, but if there's a comment we might not need to.
|
||||
if next_leaf.prefix.strip(' \t') == '' \
|
||||
and (next_leaf.type == 'newline' or next_leaf == ';'):
|
||||
changes[next_leaf] = ''
|
||||
return Refactoring(inference_state, file_to_node_changes)
|
||||
|
||||
|
||||
def _remove_indent_of_prefix(prefix):
|
||||
r"""
|
||||
Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
|
||||
"""
|
||||
return ''.join(split_lines(prefix, keepends=True)[:-1])
|
Binary file not shown.
Binary file not shown.
387
venv/Lib/site-packages/jedi/api/refactoring/extract.py
Normal file
387
venv/Lib/site-packages/jedi/api/refactoring/extract.py
Normal file
|
@ -0,0 +1,387 @@
|
|||
from textwrap import dedent
|
||||
|
||||
from parso import split_lines
|
||||
|
||||
from jedi import debug
|
||||
from jedi.api.exceptions import RefactoringError
|
||||
from jedi.api.refactoring import Refactoring, EXPRESSION_PARTS
|
||||
from jedi.common import indent_block
|
||||
from jedi.parser_utils import function_is_classmethod, function_is_staticmethod
|
||||
|
||||
|
||||
_DEFINITION_SCOPES = ('suite', 'file_input')
|
||||
_VARIABLE_EXCTRACTABLE = EXPRESSION_PARTS + \
|
||||
('atom testlist_star_expr testlist test lambdef lambdef_nocond '
|
||||
'keyword name number string fstring').split()
|
||||
|
||||
|
||||
def extract_variable(inference_state, path, module_node, name, pos, until_pos):
|
||||
nodes = _find_nodes(module_node, pos, until_pos)
|
||||
debug.dbg('Extracting nodes: %s', nodes)
|
||||
|
||||
is_expression, message = _is_expression_with_error(nodes)
|
||||
if not is_expression:
|
||||
raise RefactoringError(message)
|
||||
|
||||
generated_code = name + ' = ' + _expression_nodes_to_string(nodes)
|
||||
file_to_node_changes = {path: _replace(nodes, name, generated_code, pos)}
|
||||
return Refactoring(inference_state, file_to_node_changes)
|
||||
|
||||
|
||||
def _is_expression_with_error(nodes):
|
||||
"""
|
||||
Returns a tuple (is_expression, error_string).
|
||||
"""
|
||||
if any(node.type == 'name' and node.is_definition() for node in nodes):
|
||||
return False, 'Cannot extract a name that defines something'
|
||||
|
||||
if nodes[0].type not in _VARIABLE_EXCTRACTABLE:
|
||||
return False, 'Cannot extract a "%s"' % nodes[0].type
|
||||
return True, ''
|
||||
|
||||
|
||||
def _find_nodes(module_node, pos, until_pos):
|
||||
"""
|
||||
Looks up a module and tries to find the appropriate amount of nodes that
|
||||
are in there.
|
||||
"""
|
||||
start_node = module_node.get_leaf_for_position(pos, include_prefixes=True)
|
||||
|
||||
if until_pos is None:
|
||||
if start_node.type == 'operator':
|
||||
next_leaf = start_node.get_next_leaf()
|
||||
if next_leaf is not None and next_leaf.start_pos == pos:
|
||||
start_node = next_leaf
|
||||
|
||||
if _is_not_extractable_syntax(start_node):
|
||||
start_node = start_node.parent
|
||||
|
||||
if start_node.parent.type == 'trailer':
|
||||
start_node = start_node.parent.parent
|
||||
while start_node.parent.type in EXPRESSION_PARTS:
|
||||
start_node = start_node.parent
|
||||
|
||||
nodes = [start_node]
|
||||
else:
|
||||
# Get the next leaf if we are at the end of a leaf
|
||||
if start_node.end_pos == pos:
|
||||
next_leaf = start_node.get_next_leaf()
|
||||
if next_leaf is not None:
|
||||
start_node = next_leaf
|
||||
|
||||
# Some syntax is not exactable, just use its parent
|
||||
if _is_not_extractable_syntax(start_node):
|
||||
start_node = start_node.parent
|
||||
|
||||
# Find the end
|
||||
end_leaf = module_node.get_leaf_for_position(until_pos, include_prefixes=True)
|
||||
if end_leaf.start_pos > until_pos:
|
||||
end_leaf = end_leaf.get_previous_leaf()
|
||||
if end_leaf is None:
|
||||
raise RefactoringError('Cannot extract anything from that')
|
||||
|
||||
parent_node = start_node
|
||||
while parent_node.end_pos < end_leaf.end_pos:
|
||||
parent_node = parent_node.parent
|
||||
|
||||
nodes = _remove_unwanted_expression_nodes(parent_node, pos, until_pos)
|
||||
|
||||
# If the user marks just a return statement, we return the expression
|
||||
# instead of the whole statement, because the user obviously wants to
|
||||
# extract that part.
|
||||
if len(nodes) == 1 and start_node.type in ('return_stmt', 'yield_expr'):
|
||||
return [nodes[0].children[1]]
|
||||
return nodes
|
||||
|
||||
|
||||
def _replace(nodes, expression_replacement, extracted, pos,
|
||||
insert_before_leaf=None, remaining_prefix=None):
|
||||
# Now try to replace the nodes found with a variable and move the code
|
||||
# before the current statement.
|
||||
definition = _get_parent_definition(nodes[0])
|
||||
if insert_before_leaf is None:
|
||||
insert_before_leaf = definition.get_first_leaf()
|
||||
first_node_leaf = nodes[0].get_first_leaf()
|
||||
|
||||
lines = split_lines(insert_before_leaf.prefix, keepends=True)
|
||||
if first_node_leaf is insert_before_leaf:
|
||||
if remaining_prefix is not None:
|
||||
# The remaining prefix has already been calculated.
|
||||
lines[:-1] = remaining_prefix
|
||||
lines[-1:-1] = [indent_block(extracted, lines[-1]) + '\n']
|
||||
extracted_prefix = ''.join(lines)
|
||||
|
||||
replacement_dct = {}
|
||||
if first_node_leaf is insert_before_leaf:
|
||||
replacement_dct[nodes[0]] = extracted_prefix + expression_replacement
|
||||
else:
|
||||
if remaining_prefix is None:
|
||||
p = first_node_leaf.prefix
|
||||
else:
|
||||
p = remaining_prefix + _get_indentation(nodes[0])
|
||||
replacement_dct[nodes[0]] = p + expression_replacement
|
||||
replacement_dct[insert_before_leaf] = extracted_prefix + insert_before_leaf.value
|
||||
|
||||
for node in nodes[1:]:
|
||||
replacement_dct[node] = ''
|
||||
return replacement_dct
|
||||
|
||||
|
||||
def _expression_nodes_to_string(nodes):
|
||||
return ''.join(n.get_code(include_prefix=i != 0) for i, n in enumerate(nodes))
|
||||
|
||||
|
||||
def _suite_nodes_to_string(nodes, pos):
|
||||
n = nodes[0]
|
||||
prefix, part_of_code = _split_prefix_at(n.get_first_leaf(), pos[0] - 1)
|
||||
code = part_of_code + n.get_code(include_prefix=False) \
|
||||
+ ''.join(n.get_code() for n in nodes[1:])
|
||||
return prefix, code
|
||||
|
||||
|
||||
def _split_prefix_at(leaf, until_line):
|
||||
"""
|
||||
Returns a tuple of the leaf's prefix, split at the until_line
|
||||
position.
|
||||
"""
|
||||
# second means the second returned part
|
||||
second_line_count = leaf.start_pos[0] - until_line
|
||||
lines = split_lines(leaf.prefix, keepends=True)
|
||||
return ''.join(lines[:-second_line_count]), ''.join(lines[-second_line_count:])
|
||||
|
||||
|
||||
def _get_indentation(node):
|
||||
return split_lines(node.get_first_leaf().prefix)[-1]
|
||||
|
||||
|
||||
def _get_parent_definition(node):
|
||||
"""
|
||||
Returns the statement where a node is defined.
|
||||
"""
|
||||
while node is not None:
|
||||
if node.parent.type in _DEFINITION_SCOPES:
|
||||
return node
|
||||
node = node.parent
|
||||
raise NotImplementedError('We should never even get here')
|
||||
|
||||
|
||||
def _remove_unwanted_expression_nodes(parent_node, pos, until_pos):
|
||||
"""
|
||||
This function makes it so for `1 * 2 + 3` you can extract `2 + 3`, even
|
||||
though it is not part of the expression.
|
||||
"""
|
||||
typ = parent_node.type
|
||||
is_suite_part = typ in ('suite', 'file_input')
|
||||
if typ in EXPRESSION_PARTS or is_suite_part:
|
||||
nodes = parent_node.children
|
||||
for i, n in enumerate(nodes):
|
||||
if n.end_pos > pos:
|
||||
start_index = i
|
||||
if n.type == 'operator':
|
||||
start_index -= 1
|
||||
break
|
||||
for i, n in reversed(list(enumerate(nodes))):
|
||||
if n.start_pos < until_pos:
|
||||
end_index = i
|
||||
if n.type == 'operator':
|
||||
end_index += 1
|
||||
|
||||
# Something like `not foo or bar` should not be cut after not
|
||||
for n2 in nodes[i:]:
|
||||
if _is_not_extractable_syntax(n2):
|
||||
end_index += 1
|
||||
else:
|
||||
break
|
||||
break
|
||||
nodes = nodes[start_index:end_index + 1]
|
||||
if not is_suite_part:
|
||||
nodes[0:1] = _remove_unwanted_expression_nodes(nodes[0], pos, until_pos)
|
||||
nodes[-1:] = _remove_unwanted_expression_nodes(nodes[-1], pos, until_pos)
|
||||
return nodes
|
||||
return [parent_node]
|
||||
|
||||
|
||||
def _is_not_extractable_syntax(node):
|
||||
return node.type == 'operator' \
|
||||
or node.type == 'keyword' and node.value not in ('None', 'True', 'False')
|
||||
|
||||
|
||||
def extract_function(inference_state, path, module_context, name, pos, until_pos):
|
||||
nodes = _find_nodes(module_context.tree_node, pos, until_pos)
|
||||
assert len(nodes)
|
||||
|
||||
is_expression, _ = _is_expression_with_error(nodes)
|
||||
context = module_context.create_context(nodes[0])
|
||||
is_bound_method = context.is_bound_method()
|
||||
params, return_variables = list(_find_inputs_and_outputs(module_context, context, nodes))
|
||||
|
||||
# Find variables
|
||||
# Is a class method / method
|
||||
if context.is_module():
|
||||
insert_before_leaf = None # Leaf will be determined later
|
||||
else:
|
||||
node = _get_code_insertion_node(context.tree_node, is_bound_method)
|
||||
insert_before_leaf = node.get_first_leaf()
|
||||
if is_expression:
|
||||
code_block = 'return ' + _expression_nodes_to_string(nodes) + '\n'
|
||||
remaining_prefix = None
|
||||
has_ending_return_stmt = False
|
||||
else:
|
||||
has_ending_return_stmt = _is_node_ending_return_stmt(nodes[-1])
|
||||
if not has_ending_return_stmt:
|
||||
# Find the actually used variables (of the defined ones). If none are
|
||||
# used (e.g. if the range covers the whole function), return the last
|
||||
# defined variable.
|
||||
return_variables = list(_find_needed_output_variables(
|
||||
context,
|
||||
nodes[0].parent,
|
||||
nodes[-1].end_pos,
|
||||
return_variables
|
||||
)) or [return_variables[-1]] if return_variables else []
|
||||
|
||||
remaining_prefix, code_block = _suite_nodes_to_string(nodes, pos)
|
||||
after_leaf = nodes[-1].get_next_leaf()
|
||||
first, second = _split_prefix_at(after_leaf, until_pos[0])
|
||||
code_block += first
|
||||
|
||||
code_block = dedent(code_block)
|
||||
if not has_ending_return_stmt:
|
||||
output_var_str = ', '.join(return_variables)
|
||||
code_block += 'return ' + output_var_str + '\n'
|
||||
|
||||
# Check if we have to raise RefactoringError
|
||||
_check_for_non_extractables(nodes[:-1] if has_ending_return_stmt else nodes)
|
||||
|
||||
decorator = ''
|
||||
self_param = None
|
||||
if is_bound_method:
|
||||
if not function_is_staticmethod(context.tree_node):
|
||||
function_param_names = context.get_value().get_param_names()
|
||||
if len(function_param_names):
|
||||
self_param = function_param_names[0].string_name
|
||||
params = [p for p in params if p != self_param]
|
||||
|
||||
if function_is_classmethod(context.tree_node):
|
||||
decorator = '@classmethod\n'
|
||||
else:
|
||||
code_block += '\n'
|
||||
|
||||
function_code = '%sdef %s(%s):\n%s' % (
|
||||
decorator,
|
||||
name,
|
||||
', '.join(params if self_param is None else [self_param] + params),
|
||||
indent_block(code_block)
|
||||
)
|
||||
|
||||
function_call = '%s(%s)' % (
|
||||
('' if self_param is None else self_param + '.') + name,
|
||||
', '.join(params)
|
||||
)
|
||||
if is_expression:
|
||||
replacement = function_call
|
||||
else:
|
||||
if has_ending_return_stmt:
|
||||
replacement = 'return ' + function_call + '\n'
|
||||
else:
|
||||
replacement = output_var_str + ' = ' + function_call + '\n'
|
||||
|
||||
replacement_dct = _replace(nodes, replacement, function_code, pos,
|
||||
insert_before_leaf, remaining_prefix)
|
||||
if not is_expression:
|
||||
replacement_dct[after_leaf] = second + after_leaf.value
|
||||
file_to_node_changes = {path: replacement_dct}
|
||||
return Refactoring(inference_state, file_to_node_changes)
|
||||
|
||||
|
||||
def _check_for_non_extractables(nodes):
|
||||
for n in nodes:
|
||||
try:
|
||||
children = n.children
|
||||
except AttributeError:
|
||||
if n.value == 'return':
|
||||
raise RefactoringError(
|
||||
'Can only extract return statements if they are at the end.')
|
||||
if n.value == 'yield':
|
||||
raise RefactoringError('Cannot extract yield statements.')
|
||||
else:
|
||||
_check_for_non_extractables(children)
|
||||
|
||||
|
||||
def _is_name_input(module_context, names, first, last):
|
||||
for name in names:
|
||||
if name.api_type == 'param' or not name.parent_context.is_module():
|
||||
if name.get_root_context() is not module_context:
|
||||
return True
|
||||
if name.start_pos is None or not (first <= name.start_pos < last):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _find_inputs_and_outputs(module_context, context, nodes):
|
||||
first = nodes[0].start_pos
|
||||
last = nodes[-1].end_pos
|
||||
|
||||
inputs = []
|
||||
outputs = []
|
||||
for name in _find_non_global_names(nodes):
|
||||
if name.is_definition():
|
||||
if name not in outputs:
|
||||
outputs.append(name.value)
|
||||
else:
|
||||
if name.value not in inputs:
|
||||
name_definitions = context.goto(name, name.start_pos)
|
||||
if not name_definitions \
|
||||
or _is_name_input(module_context, name_definitions, first, last):
|
||||
inputs.append(name.value)
|
||||
|
||||
# Check if outputs are really needed:
|
||||
return inputs, outputs
|
||||
|
||||
|
||||
def _find_non_global_names(nodes):
|
||||
for node in nodes:
|
||||
try:
|
||||
children = node.children
|
||||
except AttributeError:
|
||||
if node.type == 'name':
|
||||
yield node
|
||||
else:
|
||||
# We only want to check foo in foo.bar
|
||||
if node.type == 'trailer' and node.children[0] == '.':
|
||||
continue
|
||||
|
||||
for x in _find_non_global_names(children): # Python 2...
|
||||
yield x
|
||||
|
||||
|
||||
def _get_code_insertion_node(node, is_bound_method):
|
||||
if not is_bound_method or function_is_staticmethod(node):
|
||||
while node.parent.type != 'file_input':
|
||||
node = node.parent
|
||||
|
||||
while node.parent.type in ('async_funcdef', 'decorated', 'async_stmt'):
|
||||
node = node.parent
|
||||
return node
|
||||
|
||||
|
||||
def _find_needed_output_variables(context, search_node, at_least_pos, return_variables):
|
||||
"""
|
||||
Searches everything after at_least_pos in a node and checks if any of the
|
||||
return_variables are used in there and returns those.
|
||||
"""
|
||||
for node in search_node.children:
|
||||
if node.start_pos < at_least_pos:
|
||||
continue
|
||||
|
||||
return_variables = set(return_variables)
|
||||
for name in _find_non_global_names([node]):
|
||||
if not name.is_definition() and name.value in return_variables:
|
||||
return_variables.remove(name.value)
|
||||
yield name.value
|
||||
|
||||
|
||||
def _is_node_ending_return_stmt(node):
|
||||
t = node.type
|
||||
if t == 'simple_stmt':
|
||||
return _is_node_ending_return_stmt(node.children[0])
|
||||
return t == 'return_stmt'
|
29
venv/Lib/site-packages/jedi/api/replstartup.py
Normal file
29
venv/Lib/site-packages/jedi/api/replstartup.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
"""
|
||||
To use Jedi completion in Python interpreter, add the following in your shell
|
||||
setup (e.g., ``.bashrc``). This works only on Linux/Mac, because readline is
|
||||
not available on Windows. If you still want Jedi autocompletion in your REPL,
|
||||
just use IPython instead::
|
||||
|
||||
export PYTHONSTARTUP="$(python -m jedi repl)"
|
||||
|
||||
Then you will be able to use Jedi completer in your Python interpreter::
|
||||
|
||||
$ python
|
||||
Python 2.7.2+ (default, Jul 20 2012, 22:15:08)
|
||||
[GCC 4.6.1] on linux2
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import os
|
||||
>>> os.path.join('a', 'b').split().in<TAB> # doctest: +SKIP
|
||||
..dex ..sert
|
||||
|
||||
"""
|
||||
import jedi.utils
|
||||
from jedi import __version__ as __jedi_version__
|
||||
|
||||
print('REPL completion using Jedi %s' % __jedi_version__)
|
||||
jedi.utils.setup_readline(fuzzy=False)
|
||||
|
||||
del jedi
|
||||
|
||||
# Note: try not to do many things here, as it will contaminate global
|
||||
# namespace of the interpreter.
|
109
venv/Lib/site-packages/jedi/api/strings.py
Normal file
109
venv/Lib/site-packages/jedi/api/strings.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
"""
|
||||
This module is here for string completions. This means mostly stuff where
|
||||
strings are returned, like `foo = dict(bar=3); foo["ba` would complete to
|
||||
`"bar"]`.
|
||||
|
||||
It however does the same for numbers. The difference between string completions
|
||||
and other completions is mostly that this module doesn't return defined
|
||||
names in a module, but pretty much an arbitrary string.
|
||||
"""
|
||||
import re
|
||||
|
||||
from jedi._compatibility import unicode
|
||||
from jedi.inference.names import AbstractArbitraryName
|
||||
from jedi.inference.helpers import infer_call_of_leaf
|
||||
from jedi.api.classes import Completion
|
||||
from jedi.parser_utils import cut_value_at_position
|
||||
|
||||
_sentinel = object()
|
||||
|
||||
|
||||
class StringName(AbstractArbitraryName):
|
||||
api_type = u'string'
|
||||
is_value_name = False
|
||||
|
||||
|
||||
def complete_dict(module_context, code_lines, leaf, position, string, fuzzy):
|
||||
bracket_leaf = leaf
|
||||
if bracket_leaf != '[':
|
||||
bracket_leaf = leaf.get_previous_leaf()
|
||||
|
||||
cut_end_quote = ''
|
||||
if string:
|
||||
cut_end_quote = get_quote_ending(string, code_lines, position, invert_result=True)
|
||||
|
||||
if bracket_leaf == '[':
|
||||
if string is None and leaf is not bracket_leaf:
|
||||
string = cut_value_at_position(leaf, position)
|
||||
|
||||
context = module_context.create_context(bracket_leaf)
|
||||
before_bracket_leaf = bracket_leaf.get_previous_leaf()
|
||||
if before_bracket_leaf.type in ('atom', 'trailer', 'name'):
|
||||
values = infer_call_of_leaf(context, before_bracket_leaf)
|
||||
return list(_completions_for_dicts(
|
||||
module_context.inference_state,
|
||||
values,
|
||||
'' if string is None else string,
|
||||
cut_end_quote,
|
||||
fuzzy=fuzzy,
|
||||
))
|
||||
return []
|
||||
|
||||
|
||||
def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote, fuzzy):
|
||||
for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)):
|
||||
dict_key_str = _create_repr_string(literal_string, dict_key)
|
||||
if dict_key_str.startswith(literal_string):
|
||||
name = StringName(inference_state, dict_key_str[:-len(cut_end_quote) or None])
|
||||
yield Completion(
|
||||
inference_state,
|
||||
name,
|
||||
stack=None,
|
||||
like_name_length=len(literal_string),
|
||||
is_fuzzy=fuzzy
|
||||
)
|
||||
|
||||
|
||||
def _create_repr_string(literal_string, dict_key):
|
||||
if not isinstance(dict_key, (unicode, bytes)) or not literal_string:
|
||||
return repr(dict_key)
|
||||
|
||||
r = repr(dict_key)
|
||||
prefix, quote = _get_string_prefix_and_quote(literal_string)
|
||||
if quote is None:
|
||||
return r
|
||||
if quote == r[0]:
|
||||
return prefix + r
|
||||
return prefix + quote + r[1:-1] + quote
|
||||
|
||||
|
||||
def _get_python_keys(dicts):
|
||||
for dct in dicts:
|
||||
if dct.array_type == 'dict':
|
||||
for key in dct.get_key_values():
|
||||
dict_key = key.get_safe_value(default=_sentinel)
|
||||
if dict_key is not _sentinel:
|
||||
yield dict_key
|
||||
|
||||
|
||||
def _get_string_prefix_and_quote(string):
|
||||
match = re.match(r'(\w*)("""|\'{3}|"|\')', string)
|
||||
if match is None:
|
||||
return None, None
|
||||
return match.group(1), match.group(2)
|
||||
|
||||
|
||||
def _matches_quote_at_position(code_lines, quote, position):
|
||||
string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
|
||||
return string == quote
|
||||
|
||||
|
||||
def get_quote_ending(string, code_lines, position, invert_result=False):
|
||||
_, quote = _get_string_prefix_and_quote(string)
|
||||
if quote is None:
|
||||
return ''
|
||||
|
||||
# Add a quote only if it's not already there.
|
||||
if _matches_quote_at_position(code_lines, quote, position) != invert_result:
|
||||
return ''
|
||||
return quote
|
114
venv/Lib/site-packages/jedi/cache.py
Normal file
114
venv/Lib/site-packages/jedi/cache.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
"""
|
||||
This caching is very important for speed and memory optimizations. There's
|
||||
nothing really spectacular, just some decorators. The following cache types are
|
||||
available:
|
||||
|
||||
- ``time_cache`` can be used to cache something for just a limited time span,
|
||||
which can be useful if there's user interaction and the user cannot react
|
||||
faster than a certain time.
|
||||
|
||||
This module is one of the reasons why |jedi| is not thread-safe. As you can see
|
||||
there are global variables, which are holding the cache information. Some of
|
||||
these variables are being cleaned after every API usage.
|
||||
"""
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
from jedi import settings
|
||||
from parso.cache import parser_cache
|
||||
|
||||
_time_caches = {}
|
||||
|
||||
|
||||
def clear_time_caches(delete_all=False):
|
||||
""" Jedi caches many things, that should be completed after each completion
|
||||
finishes.
|
||||
|
||||
:param delete_all: Deletes also the cache that is normally not deleted,
|
||||
like parser cache, which is important for faster parsing.
|
||||
"""
|
||||
global _time_caches
|
||||
|
||||
if delete_all:
|
||||
for cache in _time_caches.values():
|
||||
cache.clear()
|
||||
parser_cache.clear()
|
||||
else:
|
||||
# normally just kill the expired entries, not all
|
||||
for tc in _time_caches.values():
|
||||
# check time_cache for expired entries
|
||||
for key, (t, value) in list(tc.items()):
|
||||
if t < time.time():
|
||||
# delete expired entries
|
||||
del tc[key]
|
||||
|
||||
|
||||
def signature_time_cache(time_add_setting):
|
||||
"""
|
||||
This decorator works as follows: Call it with a setting and after that
|
||||
use the function with a callable that returns the key.
|
||||
But: This function is only called if the key is not available. After a
|
||||
certain amount of time (`time_add_setting`) the cache is invalid.
|
||||
|
||||
If the given key is None, the function will not be cached.
|
||||
"""
|
||||
def _temp(key_func):
|
||||
dct = {}
|
||||
_time_caches[time_add_setting] = dct
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
generator = key_func(*args, **kwargs)
|
||||
key = next(generator)
|
||||
try:
|
||||
expiry, value = dct[key]
|
||||
if expiry > time.time():
|
||||
return value
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
value = next(generator)
|
||||
time_add = getattr(settings, time_add_setting)
|
||||
if key is not None:
|
||||
dct[key] = time.time() + time_add, value
|
||||
return value
|
||||
return wrapper
|
||||
return _temp
|
||||
|
||||
|
||||
def time_cache(seconds):
|
||||
def decorator(func):
|
||||
cache = {}
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
key = (args, frozenset(kwargs.items()))
|
||||
try:
|
||||
created, result = cache[key]
|
||||
if time.time() < created + seconds:
|
||||
return result
|
||||
except KeyError:
|
||||
pass
|
||||
result = func(*args, **kwargs)
|
||||
cache[key] = time.time(), result
|
||||
return result
|
||||
|
||||
wrapper.clear_cache = lambda: cache.clear()
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def memoize_method(method):
|
||||
"""A normal memoize function."""
|
||||
@wraps(method)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
cache_dict = self.__dict__.setdefault('_memoize_method_dct', {})
|
||||
dct = cache_dict.setdefault(method, {})
|
||||
key = (args, frozenset(kwargs.items()))
|
||||
try:
|
||||
return dct[key]
|
||||
except KeyError:
|
||||
result = method(self, *args, **kwargs)
|
||||
dct[key] = result
|
||||
return result
|
||||
return wrapper
|
36
venv/Lib/site-packages/jedi/common.py
Normal file
36
venv/Lib/site-packages/jedi/common.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
def traverse_parents(path, include_current=False):
|
||||
if not include_current:
|
||||
path = os.path.dirname(path)
|
||||
|
||||
previous = None
|
||||
while previous != path:
|
||||
yield path
|
||||
previous = path
|
||||
path = os.path.dirname(path)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def monkeypatch(obj, attribute_name, new_value):
|
||||
"""
|
||||
Like pytest's monkeypatch, but as a value manager.
|
||||
"""
|
||||
old_value = getattr(obj, attribute_name)
|
||||
try:
|
||||
setattr(obj, attribute_name, new_value)
|
||||
yield
|
||||
finally:
|
||||
setattr(obj, attribute_name, old_value)
|
||||
|
||||
|
||||
def indent_block(text, indention=' '):
|
||||
"""This function indents a text block with a default of four spaces."""
|
||||
temp = ''
|
||||
while text and text[-1] == '\n':
|
||||
temp += text[-1]
|
||||
text = text[:-1]
|
||||
lines = text.split('\n')
|
||||
return '\n'.join(map(lambda s: indention + s, lines)) + temp
|
143
venv/Lib/site-packages/jedi/debug.py
Normal file
143
venv/Lib/site-packages/jedi/debug.py
Normal file
|
@ -0,0 +1,143 @@
|
|||
import os
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
|
||||
from jedi._compatibility import encoding, is_py3, u
|
||||
|
||||
_inited = False
|
||||
|
||||
|
||||
def _lazy_colorama_init():
|
||||
"""
|
||||
Lazily init colorama if necessary, not to screw up stdout if debugging is
|
||||
not enabled.
|
||||
|
||||
This version of the function does nothing.
|
||||
"""
|
||||
|
||||
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
# Does not work on Windows, as pyreadline and colorama interfere
|
||||
raise ImportError
|
||||
else:
|
||||
# Use colorama for nicer console output.
|
||||
from colorama import Fore, init
|
||||
from colorama import initialise
|
||||
|
||||
def _lazy_colorama_init(): # noqa: F811
|
||||
"""
|
||||
Lazily init colorama if necessary, not to screw up stdout is
|
||||
debug not enabled.
|
||||
|
||||
This version of the function does init colorama.
|
||||
"""
|
||||
global _inited
|
||||
if not _inited:
|
||||
# pytest resets the stream at the end - causes troubles. Since
|
||||
# after every output the stream is reset automatically we don't
|
||||
# need this.
|
||||
initialise.atexit_done = True
|
||||
try:
|
||||
init(strip=False)
|
||||
except Exception:
|
||||
# Colorama fails with initializing under vim and is buggy in
|
||||
# version 0.3.6.
|
||||
pass
|
||||
_inited = True
|
||||
|
||||
except ImportError:
|
||||
class Fore(object):
|
||||
RED = ''
|
||||
GREEN = ''
|
||||
YELLOW = ''
|
||||
MAGENTA = ''
|
||||
RESET = ''
|
||||
BLUE = ''
|
||||
|
||||
NOTICE = object()
|
||||
WARNING = object()
|
||||
SPEED = object()
|
||||
|
||||
enable_speed = False
|
||||
enable_warning = False
|
||||
enable_notice = False
|
||||
|
||||
# callback, interface: level, str
|
||||
debug_function = None
|
||||
_debug_indent = 0
|
||||
_start_time = time.time()
|
||||
|
||||
|
||||
def reset_time():
|
||||
global _start_time, _debug_indent
|
||||
_start_time = time.time()
|
||||
_debug_indent = 0
|
||||
|
||||
|
||||
def increase_indent(func):
|
||||
"""Decorator for makin """
|
||||
def wrapper(*args, **kwargs):
|
||||
with increase_indent_cm():
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
@contextmanager
|
||||
def increase_indent_cm(title=None, color='MAGENTA'):
|
||||
global _debug_indent
|
||||
if title:
|
||||
dbg('Start: ' + title, color=color)
|
||||
_debug_indent += 1
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_debug_indent -= 1
|
||||
if title:
|
||||
dbg('End: ' + title, color=color)
|
||||
|
||||
|
||||
def dbg(message, *args, **kwargs):
|
||||
""" Looks at the stack, to see if a debug message should be printed. """
|
||||
# Python 2 compatibility, because it doesn't understand default args
|
||||
color = kwargs.pop('color', 'GREEN')
|
||||
assert color
|
||||
|
||||
if debug_function and enable_notice:
|
||||
i = ' ' * _debug_indent
|
||||
_lazy_colorama_init()
|
||||
debug_function(color, i + 'dbg: ' + message % tuple(u(repr(a)) for a in args))
|
||||
|
||||
|
||||
def warning(message, *args, **kwargs):
|
||||
format = kwargs.pop('format', True)
|
||||
assert not kwargs
|
||||
|
||||
if debug_function and enable_warning:
|
||||
i = ' ' * _debug_indent
|
||||
if format:
|
||||
message = message % tuple(u(repr(a)) for a in args)
|
||||
debug_function('RED', i + 'warning: ' + message)
|
||||
|
||||
|
||||
def speed(name):
|
||||
if debug_function and enable_speed:
|
||||
now = time.time()
|
||||
i = ' ' * _debug_indent
|
||||
debug_function('YELLOW', i + 'speed: ' + '%s %s' % (name, now - _start_time))
|
||||
|
||||
|
||||
def print_to_stdout(color, str_out):
|
||||
"""
|
||||
The default debug function that prints to standard out.
|
||||
|
||||
:param str color: A string that is an attribute of ``colorama.Fore``.
|
||||
"""
|
||||
col = getattr(Fore, color)
|
||||
_lazy_colorama_init()
|
||||
if not is_py3:
|
||||
str_out = str_out.encode(encoding, 'replace')
|
||||
print(col + str_out + Fore.RESET)
|
||||
|
||||
|
||||
# debug_function = print_to_stdout
|
83
venv/Lib/site-packages/jedi/file_io.py
Normal file
83
venv/Lib/site-packages/jedi/file_io.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
import os
|
||||
|
||||
from parso import file_io
|
||||
|
||||
|
||||
class AbstractFolderIO(object):
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
def get_base_name(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def list(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_file_io(self, name):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_parent_folder(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.path)
|
||||
|
||||
|
||||
class FolderIO(AbstractFolderIO):
|
||||
def get_base_name(self):
|
||||
return os.path.basename(self.path)
|
||||
|
||||
def list(self):
|
||||
return os.listdir(self.path)
|
||||
|
||||
def get_file_io(self, name):
|
||||
return FileIO(os.path.join(self.path, name))
|
||||
|
||||
def get_parent_folder(self):
|
||||
return FolderIO(os.path.dirname(self.path))
|
||||
|
||||
def walk(self):
|
||||
for root, dirs, files in os.walk(self.path):
|
||||
root_folder_io = FolderIO(root)
|
||||
original_folder_ios = [FolderIO(os.path.join(root, d)) for d in dirs]
|
||||
modified_folder_ios = list(original_folder_ios)
|
||||
yield (
|
||||
root_folder_io,
|
||||
modified_folder_ios,
|
||||
[FileIO(os.path.join(root, f)) for f in files],
|
||||
)
|
||||
modified_iterator = iter(reversed(modified_folder_ios))
|
||||
current = next(modified_iterator, None)
|
||||
i = len(original_folder_ios)
|
||||
for folder_io in reversed(original_folder_ios):
|
||||
i -= 1 # Basically enumerate but reversed
|
||||
if current is folder_io:
|
||||
current = next(modified_iterator, None)
|
||||
else:
|
||||
del dirs[i]
|
||||
|
||||
|
||||
class FileIOFolderMixin(object):
|
||||
def get_parent_folder(self):
|
||||
return FolderIO(os.path.dirname(self.path))
|
||||
|
||||
|
||||
class ZipFileIO(file_io.KnownContentFileIO, FileIOFolderMixin):
|
||||
"""For .zip and .egg archives"""
|
||||
def __init__(self, path, code, zip_path):
|
||||
super(ZipFileIO, self).__init__(path, code)
|
||||
self._zip_path = zip_path
|
||||
|
||||
def get_last_modified(self):
|
||||
try:
|
||||
return os.path.getmtime(self._zip_path)
|
||||
except OSError: # Python 3 would probably only need FileNotFoundError
|
||||
return None
|
||||
|
||||
|
||||
class FileIO(file_io.FileIO, FileIOFolderMixin):
|
||||
pass
|
||||
|
||||
|
||||
class KnownContentFileIO(file_io.KnownContentFileIO, FileIOFolderMixin):
|
||||
pass
|
197
venv/Lib/site-packages/jedi/inference/__init__.py
Normal file
197
venv/Lib/site-packages/jedi/inference/__init__.py
Normal file
|
@ -0,0 +1,197 @@
|
|||
"""
|
||||
Type inference of Python code in |jedi| is based on three assumptions:
|
||||
|
||||
* The code uses as least side effects as possible. Jedi understands certain
|
||||
list/tuple/set modifications, but there's no guarantee that Jedi detects
|
||||
everything (list.append in different modules for example).
|
||||
* No magic is being used:
|
||||
|
||||
- metaclasses
|
||||
- ``setattr()`` / ``__import__()``
|
||||
- writing to ``globals()``, ``locals()``, ``object.__dict__``
|
||||
* The programmer is not a total dick, e.g. like `this
|
||||
<https://github.com/davidhalter/jedi/issues/24>`_ :-)
|
||||
|
||||
The actual algorithm is based on a principle I call lazy type inference. That
|
||||
said, the typical entry point for static analysis is calling
|
||||
``infer_expr_stmt``. There's separate logic for autocompletion in the API, the
|
||||
inference_state is all about inferring an expression.
|
||||
|
||||
TODO this paragraph is not what jedi does anymore, it's similar, but not the
|
||||
same.
|
||||
|
||||
Now you need to understand what follows after ``infer_expr_stmt``. Let's
|
||||
make an example::
|
||||
|
||||
import datetime
|
||||
datetime.date.toda# <-- cursor here
|
||||
|
||||
First of all, this module doesn't care about completion. It really just cares
|
||||
about ``datetime.date``. At the end of the procedure ``infer_expr_stmt`` will
|
||||
return the ``date`` class.
|
||||
|
||||
To *visualize* this (simplified):
|
||||
|
||||
- ``InferenceState.infer_expr_stmt`` doesn't do much, because there's no assignment.
|
||||
- ``Context.infer_node`` cares for resolving the dotted path
|
||||
- ``InferenceState.find_types`` searches for global definitions of datetime, which
|
||||
it finds in the definition of an import, by scanning the syntax tree.
|
||||
- Using the import logic, the datetime module is found.
|
||||
- Now ``find_types`` is called again by ``infer_node`` to find ``date``
|
||||
inside the datetime module.
|
||||
|
||||
Now what would happen if we wanted ``datetime.date.foo.bar``? Two more
|
||||
calls to ``find_types``. However the second call would be ignored, because the
|
||||
first one would return nothing (there's no foo attribute in ``date``).
|
||||
|
||||
What if the import would contain another ``ExprStmt`` like this::
|
||||
|
||||
from foo import bar
|
||||
Date = bar.baz
|
||||
|
||||
Well... You get it. Just another ``infer_expr_stmt`` recursion. It's really
|
||||
easy. Python can obviously get way more complicated then this. To understand
|
||||
tuple assignments, list comprehensions and everything else, a lot more code had
|
||||
to be written.
|
||||
|
||||
Jedi has been tested very well, so you can just start modifying code. It's best
|
||||
to write your own test first for your "new" feature. Don't be scared of
|
||||
breaking stuff. As long as the tests pass, you're most likely to be fine.
|
||||
|
||||
I need to mention now that lazy type inference is really good because it
|
||||
only *inferes* what needs to be *inferred*. All the statements and modules
|
||||
that are not used are just being ignored.
|
||||
"""
|
||||
import parso
|
||||
from jedi.file_io import FileIO
|
||||
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.inference import imports
|
||||
from jedi.inference import recursion
|
||||
from jedi.inference.cache import inference_state_function_cache
|
||||
from jedi.inference import helpers
|
||||
from jedi.inference.names import TreeNameDefinition
|
||||
from jedi.inference.base_value import ContextualizedNode, \
|
||||
ValueSet, iterate_values
|
||||
from jedi.inference.value import ClassValue, FunctionValue
|
||||
from jedi.inference.syntax_tree import infer_expr_stmt, \
|
||||
check_tuple_assignments, tree_name_to_values
|
||||
from jedi.inference.imports import follow_error_node_imports_if_possible
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
class InferenceState(object):
|
||||
def __init__(self, project, environment=None, script_path=None):
|
||||
if environment is None:
|
||||
environment = project.get_environment()
|
||||
self.environment = environment
|
||||
self.script_path = script_path
|
||||
self.compiled_subprocess = environment.get_inference_state_subprocess(self)
|
||||
self.grammar = environment.get_grammar()
|
||||
|
||||
self.latest_grammar = parso.load_grammar(version='3.7')
|
||||
self.memoize_cache = {} # for memoize decorators
|
||||
self.module_cache = imports.ModuleCache() # does the job of `sys.modules`.
|
||||
self.stub_module_cache = {} # Dict[Tuple[str, ...], Optional[ModuleValue]]
|
||||
self.compiled_cache = {} # see `inference.compiled.create()`
|
||||
self.inferred_element_counts = {}
|
||||
self.mixed_cache = {} # see `inference.compiled.mixed._create()`
|
||||
self.analysis = []
|
||||
self.dynamic_params_depth = 0
|
||||
self.is_analysis = False
|
||||
self.project = project
|
||||
self.access_cache = {}
|
||||
self.allow_descriptor_getattr = False
|
||||
self.flow_analysis_enabled = True
|
||||
|
||||
self.reset_recursion_limitations()
|
||||
|
||||
def import_module(self, import_names, sys_path=None, prefer_stubs=True):
|
||||
return imports.import_module_by_names(
|
||||
self, import_names, sys_path, prefer_stubs=prefer_stubs)
|
||||
|
||||
@staticmethod
|
||||
@plugin_manager.decorate()
|
||||
def execute(value, arguments):
|
||||
debug.dbg('execute: %s %s', value, arguments)
|
||||
with debug.increase_indent_cm():
|
||||
value_set = value.py__call__(arguments=arguments)
|
||||
debug.dbg('execute result: %s in %s', value_set, value)
|
||||
return value_set
|
||||
|
||||
@property
|
||||
@inference_state_function_cache()
|
||||
def builtins_module(self):
|
||||
module_name = u'builtins'
|
||||
if self.environment.version_info.major == 2:
|
||||
module_name = u'__builtin__'
|
||||
builtins_module, = self.import_module((module_name,), sys_path=())
|
||||
return builtins_module
|
||||
|
||||
@property
|
||||
@inference_state_function_cache()
|
||||
def typing_module(self):
|
||||
typing_module, = self.import_module((u'typing',))
|
||||
return typing_module
|
||||
|
||||
def reset_recursion_limitations(self):
|
||||
self.recursion_detector = recursion.RecursionDetector()
|
||||
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
|
||||
|
||||
def get_sys_path(self, **kwargs):
|
||||
"""Convenience function"""
|
||||
return self.project._get_sys_path(self, **kwargs)
|
||||
|
||||
def infer(self, context, name):
|
||||
def_ = name.get_definition(import_name_always=True)
|
||||
if def_ is not None:
|
||||
type_ = def_.type
|
||||
is_classdef = type_ == 'classdef'
|
||||
if is_classdef or type_ == 'funcdef':
|
||||
if is_classdef:
|
||||
c = ClassValue(self, context, name.parent)
|
||||
else:
|
||||
c = FunctionValue.from_context(context, name.parent)
|
||||
return ValueSet([c])
|
||||
|
||||
if type_ == 'expr_stmt':
|
||||
is_simple_name = name.parent.type not in ('power', 'trailer')
|
||||
if is_simple_name:
|
||||
return infer_expr_stmt(context, def_, name)
|
||||
if type_ == 'for_stmt':
|
||||
container_types = context.infer_node(def_.children[3])
|
||||
cn = ContextualizedNode(context, def_.children[3])
|
||||
for_types = iterate_values(container_types, cn)
|
||||
n = TreeNameDefinition(context, name)
|
||||
return check_tuple_assignments(n, for_types)
|
||||
if type_ in ('import_from', 'import_name'):
|
||||
return imports.infer_import(context, name)
|
||||
if type_ == 'with_stmt':
|
||||
return tree_name_to_values(self, context, name)
|
||||
elif type_ == 'param':
|
||||
return context.py__getattribute__(name.value, position=name.end_pos)
|
||||
else:
|
||||
result = follow_error_node_imports_if_possible(context, name)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return helpers.infer_call_of_leaf(context, name)
|
||||
|
||||
def parse_and_get_code(self, code=None, path=None, encoding='utf-8',
|
||||
use_latest_grammar=False, file_io=None, **kwargs):
|
||||
if code is None:
|
||||
if file_io is None:
|
||||
file_io = FileIO(path)
|
||||
code = file_io.read()
|
||||
# We cannot just use parso, because it doesn't use errors='replace'.
|
||||
code = parso.python_bytes_to_unicode(code, encoding=encoding, errors='replace')
|
||||
|
||||
if len(code) > settings._cropped_file_size:
|
||||
code = code[:settings._cropped_file_size]
|
||||
|
||||
grammar = self.latest_grammar if use_latest_grammar else self.grammar
|
||||
return grammar.parse(code=code, path=path, file_io=file_io, **kwargs), code
|
||||
|
||||
def parse(self, *args, **kwargs):
|
||||
return self.parse_and_get_code(*args, **kwargs)[0]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
217
venv/Lib/site-packages/jedi/inference/analysis.py
Normal file
217
venv/Lib/site-packages/jedi/inference/analysis.py
Normal file
|
@ -0,0 +1,217 @@
|
|||
"""
|
||||
Module for statical analysis.
|
||||
"""
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import force_unicode
|
||||
from jedi import debug
|
||||
from jedi.inference.helpers import is_string
|
||||
|
||||
|
||||
CODES = {
|
||||
'attribute-error': (1, AttributeError, 'Potential AttributeError.'),
|
||||
'name-error': (2, NameError, 'Potential NameError.'),
|
||||
'import-error': (3, ImportError, 'Potential ImportError.'),
|
||||
'type-error-too-many-arguments': (4, TypeError, None),
|
||||
'type-error-too-few-arguments': (5, TypeError, None),
|
||||
'type-error-keyword-argument': (6, TypeError, None),
|
||||
'type-error-multiple-values': (7, TypeError, None),
|
||||
'type-error-star-star': (8, TypeError, None),
|
||||
'type-error-star': (9, TypeError, None),
|
||||
'type-error-operation': (10, TypeError, None),
|
||||
'type-error-not-iterable': (11, TypeError, None),
|
||||
'type-error-isinstance': (12, TypeError, None),
|
||||
'type-error-not-subscriptable': (13, TypeError, None),
|
||||
'value-error-too-many-values': (14, ValueError, None),
|
||||
'value-error-too-few-values': (15, ValueError, None),
|
||||
}
|
||||
|
||||
|
||||
class Error(object):
|
||||
def __init__(self, name, module_path, start_pos, message=None):
|
||||
self.path = module_path
|
||||
self._start_pos = start_pos
|
||||
self.name = name
|
||||
if message is None:
|
||||
message = CODES[self.name][2]
|
||||
self.message = message
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
return self._start_pos[0]
|
||||
|
||||
@property
|
||||
def column(self):
|
||||
return self._start_pos[1]
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
# The class name start
|
||||
first = self.__class__.__name__[0]
|
||||
return first + str(CODES[self.name][0])
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
|
||||
self.code, self.message)
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.path == other.path and self.name == other.name
|
||||
and self._start_pos == other._start_pos)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.path, self._start_pos, self.name))
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s: %s@%s,%s>' % (self.__class__.__name__,
|
||||
self.name, self.path,
|
||||
self._start_pos[0], self._start_pos[1])
|
||||
|
||||
|
||||
class Warning(Error):
|
||||
pass
|
||||
|
||||
|
||||
def add(node_context, error_name, node, message=None, typ=Error, payload=None):
|
||||
exception = CODES[error_name][1]
|
||||
if _check_for_exception_catch(node_context, node, exception, payload):
|
||||
return
|
||||
|
||||
# TODO this path is probably not right
|
||||
module_context = node_context.get_root_context()
|
||||
module_path = module_context.py__file__()
|
||||
issue_instance = typ(error_name, module_path, node.start_pos, message)
|
||||
debug.warning(str(issue_instance), format=False)
|
||||
node_context.inference_state.analysis.append(issue_instance)
|
||||
return issue_instance
|
||||
|
||||
|
||||
def _check_for_setattr(instance):
|
||||
"""
|
||||
Check if there's any setattr method inside an instance. If so, return True.
|
||||
"""
|
||||
module = instance.get_root_context()
|
||||
node = module.tree_node
|
||||
if node is None:
|
||||
# If it's a compiled module or doesn't have a tree_node
|
||||
return False
|
||||
|
||||
try:
|
||||
stmt_names = node.get_used_names()['setattr']
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
return any(node.start_pos < n.start_pos < node.end_pos
|
||||
# Check if it's a function called setattr.
|
||||
and not (n.parent.type == 'funcdef' and n.parent.name == n)
|
||||
for n in stmt_names)
|
||||
|
||||
|
||||
def add_attribute_error(name_context, lookup_value, name):
|
||||
message = ('AttributeError: %s has no attribute %s.' % (lookup_value, name))
|
||||
# Check for __getattr__/__getattribute__ existance and issue a warning
|
||||
# instead of an error, if that happens.
|
||||
typ = Error
|
||||
if lookup_value.is_instance() and not lookup_value.is_compiled():
|
||||
# TODO maybe make a warning for __getattr__/__getattribute__
|
||||
|
||||
if _check_for_setattr(lookup_value):
|
||||
typ = Warning
|
||||
|
||||
payload = lookup_value, name
|
||||
add(name_context, 'attribute-error', name, message, typ, payload)
|
||||
|
||||
|
||||
def _check_for_exception_catch(node_context, jedi_name, exception, payload=None):
|
||||
"""
|
||||
Checks if a jedi object (e.g. `Statement`) sits inside a try/catch and
|
||||
doesn't count as an error (if equal to `exception`).
|
||||
Also checks `hasattr` for AttributeErrors and uses the `payload` to compare
|
||||
it.
|
||||
Returns True if the exception was catched.
|
||||
"""
|
||||
def check_match(cls, exception):
|
||||
if not cls.is_class():
|
||||
return False
|
||||
|
||||
for python_cls in exception.mro():
|
||||
if cls.py__name__() == python_cls.__name__ \
|
||||
and cls.parent_context.is_builtins_module():
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_try_for_except(obj, exception):
|
||||
# Only nodes in try
|
||||
iterator = iter(obj.children)
|
||||
for branch_type in iterator:
|
||||
next(iterator) # The colon
|
||||
suite = next(iterator)
|
||||
if branch_type == 'try' \
|
||||
and not (branch_type.start_pos < jedi_name.start_pos <= suite.end_pos):
|
||||
return False
|
||||
|
||||
for node in obj.get_except_clause_tests():
|
||||
if node is None:
|
||||
return True # An exception block that catches everything.
|
||||
else:
|
||||
except_classes = node_context.infer_node(node)
|
||||
for cls in except_classes:
|
||||
from jedi.inference.value import iterable
|
||||
if isinstance(cls, iterable.Sequence) and \
|
||||
cls.array_type == 'tuple':
|
||||
# multiple exceptions
|
||||
for lazy_value in cls.py__iter__():
|
||||
for typ in lazy_value.infer():
|
||||
if check_match(typ, exception):
|
||||
return True
|
||||
else:
|
||||
if check_match(cls, exception):
|
||||
return True
|
||||
|
||||
def check_hasattr(node, suite):
|
||||
try:
|
||||
assert suite.start_pos <= jedi_name.start_pos < suite.end_pos
|
||||
assert node.type in ('power', 'atom_expr')
|
||||
base = node.children[0]
|
||||
assert base.type == 'name' and base.value == 'hasattr'
|
||||
trailer = node.children[1]
|
||||
assert trailer.type == 'trailer'
|
||||
arglist = trailer.children[1]
|
||||
assert arglist.type == 'arglist'
|
||||
from jedi.inference.arguments import TreeArguments
|
||||
args = TreeArguments(node_context.inference_state, node_context, arglist)
|
||||
unpacked_args = list(args.unpack())
|
||||
# Arguments should be very simple
|
||||
assert len(unpacked_args) == 2
|
||||
|
||||
# Check name
|
||||
key, lazy_value = unpacked_args[1]
|
||||
names = list(lazy_value.infer())
|
||||
assert len(names) == 1 and is_string(names[0])
|
||||
assert force_unicode(names[0].get_safe_value()) == payload[1].value
|
||||
|
||||
# Check objects
|
||||
key, lazy_value = unpacked_args[0]
|
||||
objects = lazy_value.infer()
|
||||
return payload[0] in objects
|
||||
except AssertionError:
|
||||
return False
|
||||
|
||||
obj = jedi_name
|
||||
while obj is not None and not isinstance(obj, (tree.Function, tree.Class)):
|
||||
if isinstance(obj, tree.Flow):
|
||||
# try/except catch check
|
||||
if obj.type == 'try_stmt' and check_try_for_except(obj, exception):
|
||||
return True
|
||||
# hasattr check
|
||||
if exception == AttributeError and obj.type in ('if_stmt', 'while_stmt'):
|
||||
if check_hasattr(obj.children[1], obj.children[3]):
|
||||
return True
|
||||
obj = obj.parent
|
||||
|
||||
return False
|
343
venv/Lib/site-packages/jedi/inference/arguments.py
Normal file
343
venv/Lib/site-packages/jedi/inference/arguments.py
Normal file
|
@ -0,0 +1,343 @@
|
|||
import re
|
||||
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import zip_longest
|
||||
from jedi import debug
|
||||
from jedi.inference.utils import PushBackIterator
|
||||
from jedi.inference import analysis
|
||||
from jedi.inference.lazy_value import LazyKnownValue, LazyKnownValues, \
|
||||
LazyTreeValue, get_merged_lazy_value
|
||||
from jedi.inference.names import ParamName, TreeNameDefinition, AnonymousParamName
|
||||
from jedi.inference.base_value import NO_VALUES, ValueSet, ContextualizedNode
|
||||
from jedi.inference.value import iterable
|
||||
from jedi.inference.cache import inference_state_as_method_param_cache
|
||||
|
||||
|
||||
def try_iter_content(types, depth=0):
|
||||
"""Helper method for static analysis."""
|
||||
if depth > 10:
|
||||
# It's possible that a loop has references on itself (especially with
|
||||
# CompiledValue). Therefore don't loop infinitely.
|
||||
return
|
||||
|
||||
for typ in types:
|
||||
try:
|
||||
f = typ.py__iter__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for lazy_value in f():
|
||||
try_iter_content(lazy_value.infer(), depth + 1)
|
||||
|
||||
|
||||
class ParamIssue(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def repack_with_argument_clinic(clinic_string):
|
||||
"""
|
||||
Transforms a function or method with arguments to the signature that is
|
||||
given as an argument clinic notation.
|
||||
|
||||
Argument clinic is part of CPython and used for all the functions that are
|
||||
implemented in C (Python 3.7):
|
||||
|
||||
str.split.__text_signature__
|
||||
# Results in: '($self, /, sep=None, maxsplit=-1)'
|
||||
"""
|
||||
def decorator(func):
|
||||
def wrapper(value, arguments):
|
||||
try:
|
||||
args = tuple(iterate_argument_clinic(
|
||||
value.inference_state,
|
||||
arguments,
|
||||
clinic_string,
|
||||
))
|
||||
except ParamIssue:
|
||||
return NO_VALUES
|
||||
else:
|
||||
return func(value, *args)
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def iterate_argument_clinic(inference_state, arguments, clinic_string):
|
||||
"""Uses a list with argument clinic information (see PEP 436)."""
|
||||
clinic_args = list(_parse_argument_clinic(clinic_string))
|
||||
|
||||
iterator = PushBackIterator(arguments.unpack())
|
||||
for i, (name, optional, allow_kwargs, stars) in enumerate(clinic_args):
|
||||
if stars == 1:
|
||||
lazy_values = []
|
||||
for key, argument in iterator:
|
||||
if key is not None:
|
||||
iterator.push_back((key, argument))
|
||||
break
|
||||
|
||||
lazy_values.append(argument)
|
||||
yield ValueSet([iterable.FakeTuple(inference_state, lazy_values)])
|
||||
lazy_values
|
||||
continue
|
||||
elif stars == 2:
|
||||
raise NotImplementedError()
|
||||
key, argument = next(iterator, (None, None))
|
||||
if key is not None:
|
||||
debug.warning('Keyword arguments in argument clinic are currently not supported.')
|
||||
raise ParamIssue
|
||||
if argument is None and not optional:
|
||||
debug.warning('TypeError: %s expected at least %s arguments, got %s',
|
||||
name, len(clinic_args), i)
|
||||
raise ParamIssue
|
||||
|
||||
value_set = NO_VALUES if argument is None else argument.infer()
|
||||
|
||||
if not value_set and not optional:
|
||||
# For the stdlib we always want values. If we don't get them,
|
||||
# that's ok, maybe something is too hard to resolve, however,
|
||||
# we will not proceed with the type inference of that function.
|
||||
debug.warning('argument_clinic "%s" not resolvable.', name)
|
||||
raise ParamIssue
|
||||
yield value_set
|
||||
|
||||
|
||||
def _parse_argument_clinic(string):
|
||||
allow_kwargs = False
|
||||
optional = False
|
||||
while string:
|
||||
# Optional arguments have to begin with a bracket. And should always be
|
||||
# at the end of the arguments. This is therefore not a proper argument
|
||||
# clinic implementation. `range()` for exmple allows an optional start
|
||||
# value at the beginning.
|
||||
match = re.match(r'(?:(?:(\[),? ?|, ?|)(\**\w+)|, ?/)\]*', string)
|
||||
string = string[len(match.group(0)):]
|
||||
if not match.group(2): # A slash -> allow named arguments
|
||||
allow_kwargs = True
|
||||
continue
|
||||
optional = optional or bool(match.group(1))
|
||||
word = match.group(2)
|
||||
stars = word.count('*')
|
||||
word = word[stars:]
|
||||
yield (word, optional, allow_kwargs, stars)
|
||||
if stars:
|
||||
allow_kwargs = True
|
||||
|
||||
|
||||
class _AbstractArgumentsMixin(object):
|
||||
def unpack(self, funcdef=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_calling_nodes(self):
|
||||
return []
|
||||
|
||||
|
||||
class AbstractArguments(_AbstractArgumentsMixin):
|
||||
context = None
|
||||
argument_node = None
|
||||
trailer = None
|
||||
|
||||
|
||||
def unpack_arglist(arglist):
|
||||
if arglist is None:
|
||||
return
|
||||
|
||||
# Allow testlist here as well for Python2's class inheritance
|
||||
# definitions.
|
||||
if not (arglist.type in ('arglist', 'testlist') or (
|
||||
# in python 3.5 **arg is an argument, not arglist
|
||||
arglist.type == 'argument' and arglist.children[0] in ('*', '**'))):
|
||||
yield 0, arglist
|
||||
return
|
||||
|
||||
iterator = iter(arglist.children)
|
||||
for child in iterator:
|
||||
if child == ',':
|
||||
continue
|
||||
elif child in ('*', '**'):
|
||||
c = next(iterator, None)
|
||||
assert c is not None
|
||||
yield len(child.value), c
|
||||
elif child.type == 'argument' and \
|
||||
child.children[0] in ('*', '**'):
|
||||
assert len(child.children) == 2
|
||||
yield len(child.children[0].value), child.children[1]
|
||||
else:
|
||||
yield 0, child
|
||||
|
||||
|
||||
class TreeArguments(AbstractArguments):
|
||||
def __init__(self, inference_state, context, argument_node, trailer=None):
|
||||
"""
|
||||
:param argument_node: May be an argument_node or a list of nodes.
|
||||
"""
|
||||
self.argument_node = argument_node
|
||||
self.context = context
|
||||
self._inference_state = inference_state
|
||||
self.trailer = trailer # Can be None, e.g. in a class definition.
|
||||
|
||||
@classmethod
|
||||
@inference_state_as_method_param_cache()
|
||||
def create_cached(cls, *args, **kwargs):
|
||||
return cls(*args, **kwargs)
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
named_args = []
|
||||
for star_count, el in unpack_arglist(self.argument_node):
|
||||
if star_count == 1:
|
||||
arrays = self.context.infer_node(el)
|
||||
iterators = [_iterate_star_args(self.context, a, el, funcdef)
|
||||
for a in arrays]
|
||||
for values in list(zip_longest(*iterators)):
|
||||
# TODO zip_longest yields None, that means this would raise
|
||||
# an exception?
|
||||
yield None, get_merged_lazy_value(
|
||||
[v for v in values if v is not None]
|
||||
)
|
||||
elif star_count == 2:
|
||||
arrays = self.context.infer_node(el)
|
||||
for dct in arrays:
|
||||
for key, values in _star_star_dict(self.context, dct, el, funcdef):
|
||||
yield key, values
|
||||
else:
|
||||
if el.type == 'argument':
|
||||
c = el.children
|
||||
if len(c) == 3: # Keyword argument.
|
||||
named_args.append((c[0].value, LazyTreeValue(self.context, c[2]),))
|
||||
else: # Generator comprehension.
|
||||
# Include the brackets with the parent.
|
||||
sync_comp_for = el.children[1]
|
||||
if sync_comp_for.type == 'comp_for':
|
||||
sync_comp_for = sync_comp_for.children[1]
|
||||
comp = iterable.GeneratorComprehension(
|
||||
self._inference_state,
|
||||
defining_context=self.context,
|
||||
sync_comp_for_node=sync_comp_for,
|
||||
entry_node=el.children[0],
|
||||
)
|
||||
yield None, LazyKnownValue(comp)
|
||||
else:
|
||||
yield None, LazyTreeValue(self.context, el)
|
||||
|
||||
# Reordering arguments is necessary, because star args sometimes appear
|
||||
# after named argument, but in the actual order it's prepended.
|
||||
for named_arg in named_args:
|
||||
yield named_arg
|
||||
|
||||
def _as_tree_tuple_objects(self):
|
||||
for star_count, argument in unpack_arglist(self.argument_node):
|
||||
default = None
|
||||
if argument.type == 'argument':
|
||||
if len(argument.children) == 3: # Keyword argument.
|
||||
argument, default = argument.children[::2]
|
||||
yield argument, default, star_count
|
||||
|
||||
def iter_calling_names_with_star(self):
|
||||
for name, default, star_count in self._as_tree_tuple_objects():
|
||||
# TODO this function is a bit strange. probably refactor?
|
||||
if not star_count or not isinstance(name, tree.Name):
|
||||
continue
|
||||
|
||||
yield TreeNameDefinition(self.context, name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
|
||||
|
||||
def get_calling_nodes(self):
|
||||
old_arguments_list = []
|
||||
arguments = self
|
||||
|
||||
while arguments not in old_arguments_list:
|
||||
if not isinstance(arguments, TreeArguments):
|
||||
break
|
||||
|
||||
old_arguments_list.append(arguments)
|
||||
for calling_name in reversed(list(arguments.iter_calling_names_with_star())):
|
||||
names = calling_name.goto()
|
||||
if len(names) != 1:
|
||||
break
|
||||
if isinstance(names[0], AnonymousParamName):
|
||||
# Dynamic parameters should not have calling nodes, because
|
||||
# they are dynamic and extremely random.
|
||||
return []
|
||||
if not isinstance(names[0], ParamName):
|
||||
break
|
||||
executed_param_name = names[0].get_executed_param_name()
|
||||
arguments = executed_param_name.arguments
|
||||
break
|
||||
|
||||
if arguments.argument_node is not None:
|
||||
return [ContextualizedNode(arguments.context, arguments.argument_node)]
|
||||
if arguments.trailer is not None:
|
||||
return [ContextualizedNode(arguments.context, arguments.trailer)]
|
||||
return []
|
||||
|
||||
|
||||
class ValuesArguments(AbstractArguments):
|
||||
def __init__(self, values_list):
|
||||
self._values_list = values_list
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
for values in self._values_list:
|
||||
yield None, LazyKnownValues(values)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._values_list)
|
||||
|
||||
|
||||
class TreeArgumentsWrapper(_AbstractArgumentsMixin):
|
||||
def __init__(self, arguments):
|
||||
self._wrapped_arguments = arguments
|
||||
|
||||
@property
|
||||
def context(self):
|
||||
return self._wrapped_arguments.context
|
||||
|
||||
@property
|
||||
def argument_node(self):
|
||||
return self._wrapped_arguments.argument_node
|
||||
|
||||
@property
|
||||
def trailer(self):
|
||||
return self._wrapped_arguments.trailer
|
||||
|
||||
def unpack(self, func=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_calling_nodes(self):
|
||||
return self._wrapped_arguments.get_calling_nodes()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_arguments)
|
||||
|
||||
|
||||
def _iterate_star_args(context, array, input_node, funcdef=None):
|
||||
if not array.py__getattribute__('__iter__'):
|
||||
if funcdef is not None:
|
||||
# TODO this funcdef should not be needed.
|
||||
m = "TypeError: %s() argument after * must be a sequence, not %s" \
|
||||
% (funcdef.name.value, array)
|
||||
analysis.add(context, 'type-error-star', input_node, message=m)
|
||||
try:
|
||||
iter_ = array.py__iter__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for lazy_value in iter_():
|
||||
yield lazy_value
|
||||
|
||||
|
||||
def _star_star_dict(context, array, input_node, funcdef):
|
||||
from jedi.inference.value.instance import CompiledInstance
|
||||
if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
|
||||
# For now ignore this case. In the future add proper iterators and just
|
||||
# make one call without crazy isinstance checks.
|
||||
return {}
|
||||
elif isinstance(array, iterable.Sequence) and array.array_type == 'dict':
|
||||
return array.exact_key_items()
|
||||
else:
|
||||
if funcdef is not None:
|
||||
m = "TypeError: %s argument after ** must be a mapping, not %s" \
|
||||
% (funcdef.name.value, array)
|
||||
analysis.add(context, 'type-error-star-star', input_node, message=m)
|
||||
return {}
|
556
venv/Lib/site-packages/jedi/inference/base_value.py
Normal file
556
venv/Lib/site-packages/jedi/inference/base_value.py
Normal file
|
@ -0,0 +1,556 @@
|
|||
"""
|
||||
Values are the "values" that Python would return. However Values are at the
|
||||
same time also the "values" that a user is currently sitting in.
|
||||
|
||||
A ValueSet is typically used to specify the return of a function or any other
|
||||
static analysis operation. In jedi there are always multiple returns and not
|
||||
just one.
|
||||
"""
|
||||
from functools import reduce
|
||||
from operator import add
|
||||
from parso.python.tree import Name
|
||||
|
||||
from jedi import debug
|
||||
from jedi._compatibility import zip_longest, unicode
|
||||
from jedi.parser_utils import clean_scope_docstring
|
||||
from jedi.inference.helpers import SimpleGetItemNotFound
|
||||
from jedi.inference.utils import safe_property
|
||||
from jedi.inference.cache import inference_state_as_method_param_cache
|
||||
from jedi.cache import memoize_method
|
||||
|
||||
sentinel = object()
|
||||
|
||||
|
||||
class HelperValueMixin(object):
|
||||
def get_root_context(self):
|
||||
value = self
|
||||
if value.parent_context is None:
|
||||
return value.as_context()
|
||||
|
||||
while True:
|
||||
if value.parent_context is None:
|
||||
return value
|
||||
value = value.parent_context
|
||||
|
||||
def execute(self, arguments):
|
||||
return self.inference_state.execute(self, arguments=arguments)
|
||||
|
||||
def execute_with_values(self, *value_list):
|
||||
from jedi.inference.arguments import ValuesArguments
|
||||
arguments = ValuesArguments([ValueSet([value]) for value in value_list])
|
||||
return self.inference_state.execute(self, arguments)
|
||||
|
||||
def execute_annotation(self):
|
||||
return self.execute_with_values()
|
||||
|
||||
def gather_annotation_classes(self):
|
||||
return ValueSet([self])
|
||||
|
||||
def merge_types_of_iterate(self, contextualized_node=None, is_async=False):
|
||||
return ValueSet.from_sets(
|
||||
lazy_value.infer()
|
||||
for lazy_value in self.iterate(contextualized_node, is_async)
|
||||
)
|
||||
|
||||
def _get_value_filters(self, name_or_str):
|
||||
origin_scope = name_or_str if isinstance(name_or_str, Name) else None
|
||||
for f in self.get_filters(origin_scope=origin_scope):
|
||||
yield f
|
||||
# This covers the case where a stub files are incomplete.
|
||||
if self.is_stub():
|
||||
from jedi.inference.gradual.conversion import convert_values
|
||||
for c in convert_values(ValueSet({self})):
|
||||
for f in c.get_filters():
|
||||
yield f
|
||||
|
||||
def goto(self, name_or_str, name_context=None, analysis_errors=True):
|
||||
from jedi.inference import finder
|
||||
filters = self._get_value_filters(name_or_str)
|
||||
names = finder.filter_name(filters, name_or_str)
|
||||
debug.dbg('context.goto %s in (%s): %s', name_or_str, self, names)
|
||||
return names
|
||||
|
||||
def py__getattribute__(self, name_or_str, name_context=None, position=None,
|
||||
analysis_errors=True):
|
||||
"""
|
||||
:param position: Position of the last statement -> tuple of line, column
|
||||
"""
|
||||
if name_context is None:
|
||||
name_context = self
|
||||
names = self.goto(name_or_str, name_context, analysis_errors)
|
||||
values = ValueSet.from_sets(name.infer() for name in names)
|
||||
if not values:
|
||||
n = name_or_str.value if isinstance(name_or_str, Name) else name_or_str
|
||||
values = self.py__getattribute__alternatives(n)
|
||||
|
||||
if not names and not values and analysis_errors:
|
||||
if isinstance(name_or_str, Name):
|
||||
from jedi.inference import analysis
|
||||
analysis.add_attribute_error(
|
||||
name_context, self, name_or_str)
|
||||
debug.dbg('context.names_to_types: %s -> %s', names, values)
|
||||
return values
|
||||
|
||||
def py__await__(self):
|
||||
await_value_set = self.py__getattribute__(u"__await__")
|
||||
if not await_value_set:
|
||||
debug.warning('Tried to run __await__ on value %s', self)
|
||||
return await_value_set.execute_with_values()
|
||||
|
||||
def py__name__(self):
|
||||
return self.name.string_name
|
||||
|
||||
def iterate(self, contextualized_node=None, is_async=False):
|
||||
debug.dbg('iterate %s', self)
|
||||
if is_async:
|
||||
from jedi.inference.lazy_value import LazyKnownValues
|
||||
# TODO if no __aiter__ values are there, error should be:
|
||||
# TypeError: 'async for' requires an object with __aiter__ method, got int
|
||||
return iter([
|
||||
LazyKnownValues(
|
||||
self.py__getattribute__('__aiter__').execute_with_values()
|
||||
.py__getattribute__('__anext__').execute_with_values()
|
||||
.py__getattribute__('__await__').execute_with_values()
|
||||
.py__stop_iteration_returns()
|
||||
) # noqa
|
||||
])
|
||||
return self.py__iter__(contextualized_node)
|
||||
|
||||
def is_sub_class_of(self, class_value):
|
||||
with debug.increase_indent_cm('subclass matching of %s <=> %s' % (self, class_value),
|
||||
color='BLUE'):
|
||||
for cls in self.py__mro__():
|
||||
if cls.is_same_class(class_value):
|
||||
debug.dbg('matched subclass True', color='BLUE')
|
||||
return True
|
||||
debug.dbg('matched subclass False', color='BLUE')
|
||||
return False
|
||||
|
||||
def is_same_class(self, class2):
|
||||
# Class matching should prefer comparisons that are not this function.
|
||||
if type(class2).is_same_class != HelperValueMixin.is_same_class:
|
||||
return class2.is_same_class(self)
|
||||
return self == class2
|
||||
|
||||
@memoize_method
|
||||
def as_context(self, *args, **kwargs):
|
||||
return self._as_context(*args, **kwargs)
|
||||
|
||||
|
||||
class Value(HelperValueMixin):
|
||||
"""
|
||||
To be implemented by subclasses.
|
||||
"""
|
||||
tree_node = None
|
||||
# Possible values: None, tuple, list, dict and set. Here to deal with these
|
||||
# very important containers.
|
||||
array_type = None
|
||||
api_type = 'not_defined_please_report_bug'
|
||||
|
||||
def __init__(self, inference_state, parent_context=None):
|
||||
self.inference_state = inference_state
|
||||
self.parent_context = parent_context
|
||||
|
||||
def py__getitem__(self, index_value_set, contextualized_node):
|
||||
from jedi.inference import analysis
|
||||
# TODO this value is probably not right.
|
||||
analysis.add(
|
||||
contextualized_node.context,
|
||||
'type-error-not-subscriptable',
|
||||
contextualized_node.node,
|
||||
message="TypeError: '%s' object is not subscriptable" % self
|
||||
)
|
||||
return NO_VALUES
|
||||
|
||||
def py__simple_getitem__(self, index):
|
||||
raise SimpleGetItemNotFound
|
||||
|
||||
def py__iter__(self, contextualized_node=None):
|
||||
if contextualized_node is not None:
|
||||
from jedi.inference import analysis
|
||||
analysis.add(
|
||||
contextualized_node.context,
|
||||
'type-error-not-iterable',
|
||||
contextualized_node.node,
|
||||
message="TypeError: '%s' object is not iterable" % self)
|
||||
return iter([])
|
||||
|
||||
def py__next__(self, contextualized_node=None):
|
||||
return self.py__iter__(contextualized_node)
|
||||
|
||||
def get_signatures(self):
|
||||
return []
|
||||
|
||||
def is_class(self):
|
||||
return False
|
||||
|
||||
def is_class_mixin(self):
|
||||
return False
|
||||
|
||||
def is_instance(self):
|
||||
return False
|
||||
|
||||
def is_function(self):
|
||||
return False
|
||||
|
||||
def is_module(self):
|
||||
return False
|
||||
|
||||
def is_namespace(self):
|
||||
return False
|
||||
|
||||
def is_compiled(self):
|
||||
return False
|
||||
|
||||
def is_bound_method(self):
|
||||
return False
|
||||
|
||||
def is_builtins_module(self):
|
||||
return False
|
||||
|
||||
def py__bool__(self):
|
||||
"""
|
||||
Since Wrapper is a super class for classes, functions and modules,
|
||||
the return value will always be true.
|
||||
"""
|
||||
return True
|
||||
|
||||
def py__doc__(self):
|
||||
try:
|
||||
self.tree_node.get_doc_node
|
||||
except AttributeError:
|
||||
return ''
|
||||
else:
|
||||
return clean_scope_docstring(self.tree_node)
|
||||
|
||||
def get_safe_value(self, default=sentinel):
|
||||
if default is sentinel:
|
||||
raise ValueError("There exists no safe value for value %s" % self)
|
||||
return default
|
||||
|
||||
def execute_operation(self, other, operator):
|
||||
debug.warning("%s not possible between %s and %s", operator, self, other)
|
||||
return NO_VALUES
|
||||
|
||||
def py__call__(self, arguments):
|
||||
debug.warning("no execution possible %s", self)
|
||||
return NO_VALUES
|
||||
|
||||
def py__stop_iteration_returns(self):
|
||||
debug.warning("Not possible to return the stop iterations of %s", self)
|
||||
return NO_VALUES
|
||||
|
||||
def py__getattribute__alternatives(self, name_or_str):
|
||||
"""
|
||||
For now a way to add values in cases like __getattr__.
|
||||
"""
|
||||
return NO_VALUES
|
||||
|
||||
def py__get__(self, instance, class_value):
|
||||
debug.warning("No __get__ defined on %s", self)
|
||||
return ValueSet([self])
|
||||
|
||||
def py__get__on_class(self, calling_instance, instance, class_value):
|
||||
return NotImplemented
|
||||
|
||||
def get_qualified_names(self):
|
||||
# Returns Optional[Tuple[str, ...]]
|
||||
return None
|
||||
|
||||
def is_stub(self):
|
||||
# The root value knows if it's a stub or not.
|
||||
return self.parent_context.is_stub()
|
||||
|
||||
def _as_context(self):
|
||||
raise NotImplementedError('Not all values need to be converted to contexts: %s', self)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
return None
|
||||
|
||||
def infer_type_vars(self, value_set):
|
||||
"""
|
||||
When the current instance represents a type annotation, this method
|
||||
tries to find information about undefined type vars and returns a dict
|
||||
from type var name to value set.
|
||||
|
||||
This is for example important to understand what `iter([1])` returns.
|
||||
According to typeshed, `iter` returns an `Iterator[_T]`:
|
||||
|
||||
def iter(iterable: Iterable[_T]) -> Iterator[_T]: ...
|
||||
|
||||
This functions would generate `int` for `_T` in this case, because it
|
||||
unpacks the `Iterable`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
||||
`self`: represents the annotation of the current parameter to infer the
|
||||
value for. In the above example, this would initially be the
|
||||
`Iterable[_T]` of the `iterable` parameter and then, when recursing,
|
||||
just the `_T` generic parameter.
|
||||
|
||||
`value_set`: represents the actual argument passed to the parameter
|
||||
we're inferrined for, or (for recursive calls) their types. In the
|
||||
above example this would first be the representation of the list
|
||||
`[1]` and then, when recursing, just of `1`.
|
||||
"""
|
||||
return {}
|
||||
|
||||
|
||||
def iterate_values(values, contextualized_node=None, is_async=False):
|
||||
"""
|
||||
Calls `iterate`, on all values but ignores the ordering and just returns
|
||||
all values that the iterate functions yield.
|
||||
"""
|
||||
return ValueSet.from_sets(
|
||||
lazy_value.infer()
|
||||
for lazy_value in values.iterate(contextualized_node, is_async=is_async)
|
||||
)
|
||||
|
||||
|
||||
class _ValueWrapperBase(HelperValueMixin):
|
||||
@safe_property
|
||||
def name(self):
|
||||
from jedi.inference.names import ValueName
|
||||
wrapped_name = self._wrapped_value.name
|
||||
if wrapped_name.tree_name is not None:
|
||||
return ValueName(self, wrapped_name.tree_name)
|
||||
else:
|
||||
from jedi.inference.compiled import CompiledValueName
|
||||
return CompiledValueName(self, wrapped_name.string_name)
|
||||
|
||||
@classmethod
|
||||
@inference_state_as_method_param_cache()
|
||||
def create_cached(cls, inference_state, *args, **kwargs):
|
||||
return cls(*args, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
assert name != '_wrapped_value', 'Problem with _get_wrapped_value'
|
||||
return getattr(self._wrapped_value, name)
|
||||
|
||||
|
||||
class LazyValueWrapper(_ValueWrapperBase):
|
||||
@safe_property
|
||||
@memoize_method
|
||||
def _wrapped_value(self):
|
||||
with debug.increase_indent_cm('Resolve lazy value wrapper'):
|
||||
return self._get_wrapped_value()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s>' % (self.__class__.__name__)
|
||||
|
||||
def _get_wrapped_value(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ValueWrapper(_ValueWrapperBase):
|
||||
def __init__(self, wrapped_value):
|
||||
self._wrapped_value = wrapped_value
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, self._wrapped_value)
|
||||
|
||||
|
||||
class TreeValue(Value):
|
||||
def __init__(self, inference_state, parent_context, tree_node):
|
||||
super(TreeValue, self).__init__(inference_state, parent_context)
|
||||
self.tree_node = tree_node
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
|
||||
|
||||
|
||||
class ContextualizedNode(object):
|
||||
def __init__(self, context, node):
|
||||
self.context = context
|
||||
self.node = node
|
||||
|
||||
def get_root_context(self):
|
||||
return self.context.get_root_context()
|
||||
|
||||
def infer(self):
|
||||
return self.context.infer_node(self.node)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s in %s>' % (self.__class__.__name__, self.node, self.context)
|
||||
|
||||
|
||||
def _getitem(value, index_values, contextualized_node):
|
||||
# The actual getitem call.
|
||||
result = NO_VALUES
|
||||
unused_values = set()
|
||||
for index_value in index_values:
|
||||
index = index_value.get_safe_value(default=None)
|
||||
if type(index) in (float, int, str, unicode, slice, bytes):
|
||||
try:
|
||||
result |= value.py__simple_getitem__(index)
|
||||
continue
|
||||
except SimpleGetItemNotFound:
|
||||
pass
|
||||
|
||||
unused_values.add(index_value)
|
||||
|
||||
# The index was somehow not good enough or simply a wrong type.
|
||||
# Therefore we now iterate through all the values and just take
|
||||
# all results.
|
||||
if unused_values or not index_values:
|
||||
result |= value.py__getitem__(
|
||||
ValueSet(unused_values),
|
||||
contextualized_node
|
||||
)
|
||||
debug.dbg('py__getitem__ result: %s', result)
|
||||
return result
|
||||
|
||||
|
||||
class ValueSet(object):
|
||||
def __init__(self, iterable):
|
||||
self._set = frozenset(iterable)
|
||||
for value in iterable:
|
||||
assert not isinstance(value, ValueSet)
|
||||
|
||||
@classmethod
|
||||
def _from_frozen_set(cls, frozenset_):
|
||||
self = cls.__new__(cls)
|
||||
self._set = frozenset_
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_sets(cls, sets):
|
||||
"""
|
||||
Used to work with an iterable of set.
|
||||
"""
|
||||
aggregated = set()
|
||||
for set_ in sets:
|
||||
if isinstance(set_, ValueSet):
|
||||
aggregated |= set_._set
|
||||
else:
|
||||
aggregated |= frozenset(set_)
|
||||
return cls._from_frozen_set(frozenset(aggregated))
|
||||
|
||||
def __or__(self, other):
|
||||
return self._from_frozen_set(self._set | other._set)
|
||||
|
||||
def __and__(self, other):
|
||||
return self._from_frozen_set(self._set & other._set)
|
||||
|
||||
def __iter__(self):
|
||||
for element in self._set:
|
||||
yield element
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._set)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._set)
|
||||
|
||||
def __repr__(self):
|
||||
return 'S{%s}' % (', '.join(str(s) for s in self._set))
|
||||
|
||||
def filter(self, filter_func):
|
||||
return self.__class__(filter(filter_func, self._set))
|
||||
|
||||
def __getattr__(self, name):
|
||||
def mapper(*args, **kwargs):
|
||||
return self.from_sets(
|
||||
getattr(value, name)(*args, **kwargs)
|
||||
for value in self._set
|
||||
)
|
||||
return mapper
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._set == other._set
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self._set)
|
||||
|
||||
def py__class__(self):
|
||||
return ValueSet(c.py__class__() for c in self._set)
|
||||
|
||||
def iterate(self, contextualized_node=None, is_async=False):
|
||||
from jedi.inference.lazy_value import get_merged_lazy_value
|
||||
type_iters = [c.iterate(contextualized_node, is_async=is_async) for c in self._set]
|
||||
for lazy_values in zip_longest(*type_iters):
|
||||
yield get_merged_lazy_value(
|
||||
[l for l in lazy_values if l is not None]
|
||||
)
|
||||
|
||||
def execute(self, arguments):
|
||||
return ValueSet.from_sets(c.inference_state.execute(c, arguments) for c in self._set)
|
||||
|
||||
def execute_with_values(self, *args, **kwargs):
|
||||
return ValueSet.from_sets(c.execute_with_values(*args, **kwargs) for c in self._set)
|
||||
|
||||
def goto(self, *args, **kwargs):
|
||||
return reduce(add, [c.goto(*args, **kwargs) for c in self._set], [])
|
||||
|
||||
def py__getattribute__(self, *args, **kwargs):
|
||||
return ValueSet.from_sets(c.py__getattribute__(*args, **kwargs) for c in self._set)
|
||||
|
||||
def get_item(self, *args, **kwargs):
|
||||
return ValueSet.from_sets(_getitem(c, *args, **kwargs) for c in self._set)
|
||||
|
||||
def try_merge(self, function_name):
|
||||
value_set = self.__class__([])
|
||||
for c in self._set:
|
||||
try:
|
||||
method = getattr(c, function_name)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
value_set |= method()
|
||||
return value_set
|
||||
|
||||
def gather_annotation_classes(self):
|
||||
return ValueSet.from_sets([c.gather_annotation_classes() for c in self._set])
|
||||
|
||||
def get_signatures(self):
|
||||
return [sig for c in self._set for sig in c.get_signatures()]
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
t = [v.get_type_hint(add_class_info=add_class_info) for v in self._set]
|
||||
type_hints = sorted(filter(None, t))
|
||||
if len(type_hints) == 1:
|
||||
return type_hints[0]
|
||||
|
||||
optional = 'None' in type_hints
|
||||
if optional:
|
||||
type_hints.remove('None')
|
||||
|
||||
if len(type_hints) == 0:
|
||||
return None
|
||||
elif len(type_hints) == 1:
|
||||
s = type_hints[0]
|
||||
else:
|
||||
s = 'Union[%s]' % ', '.join(type_hints)
|
||||
if optional:
|
||||
s = 'Optional[%s]' % s
|
||||
return s
|
||||
|
||||
def infer_type_vars(self, value_set):
|
||||
# Circular
|
||||
from jedi.inference.gradual.annotation import merge_type_var_dicts
|
||||
|
||||
type_var_dict = {}
|
||||
for value in self._set:
|
||||
merge_type_var_dicts(
|
||||
type_var_dict,
|
||||
value.infer_type_vars(value_set),
|
||||
)
|
||||
return type_var_dict
|
||||
|
||||
|
||||
NO_VALUES = ValueSet([])
|
||||
|
||||
|
||||
def iterator_to_value_set(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
return ValueSet(func(*args, **kwargs))
|
||||
|
||||
return wrapper
|
126
venv/Lib/site-packages/jedi/inference/cache.py
Normal file
126
venv/Lib/site-packages/jedi/inference/cache.py
Normal file
|
@ -0,0 +1,126 @@
|
|||
"""
|
||||
- the popular ``_memoize_default`` works like a typical memoize and returns the
|
||||
default otherwise.
|
||||
- ``CachedMetaClass`` uses ``_memoize_default`` to do the same with classes.
|
||||
"""
|
||||
from functools import wraps
|
||||
|
||||
from jedi import debug
|
||||
|
||||
_NO_DEFAULT = object()
|
||||
_RECURSION_SENTINEL = object()
|
||||
|
||||
|
||||
def _memoize_default(default=_NO_DEFAULT, inference_state_is_first_arg=False,
|
||||
second_arg_is_inference_state=False):
|
||||
""" This is a typical memoization decorator, BUT there is one difference:
|
||||
To prevent recursion it sets defaults.
|
||||
|
||||
Preventing recursion is in this case the much bigger use than speed. I
|
||||
don't think, that there is a big speed difference, but there are many cases
|
||||
where recursion could happen (think about a = b; b = a).
|
||||
"""
|
||||
def func(function):
|
||||
def wrapper(obj, *args, **kwargs):
|
||||
# TODO These checks are kind of ugly and slow.
|
||||
if inference_state_is_first_arg:
|
||||
cache = obj.memoize_cache
|
||||
elif second_arg_is_inference_state:
|
||||
cache = args[0].memoize_cache # needed for meta classes
|
||||
else:
|
||||
cache = obj.inference_state.memoize_cache
|
||||
|
||||
try:
|
||||
memo = cache[function]
|
||||
except KeyError:
|
||||
cache[function] = memo = {}
|
||||
|
||||
key = (obj, args, frozenset(kwargs.items()))
|
||||
if key in memo:
|
||||
return memo[key]
|
||||
else:
|
||||
if default is not _NO_DEFAULT:
|
||||
memo[key] = default
|
||||
rv = function(obj, *args, **kwargs)
|
||||
memo[key] = rv
|
||||
return rv
|
||||
return wrapper
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def inference_state_function_cache(default=_NO_DEFAULT):
|
||||
def decorator(func):
|
||||
return _memoize_default(default=default, inference_state_is_first_arg=True)(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def inference_state_method_cache(default=_NO_DEFAULT):
|
||||
def decorator(func):
|
||||
return _memoize_default(default=default)(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def inference_state_as_method_param_cache():
|
||||
def decorator(call):
|
||||
return _memoize_default(second_arg_is_inference_state=True)(call)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class CachedMetaClass(type):
|
||||
"""
|
||||
This is basically almost the same than the decorator above, it just caches
|
||||
class initializations. Either you do it this way or with decorators, but
|
||||
with decorators you lose class access (isinstance, etc).
|
||||
"""
|
||||
@inference_state_as_method_param_cache()
|
||||
def __call__(self, *args, **kwargs):
|
||||
return super(CachedMetaClass, self).__call__(*args, **kwargs)
|
||||
|
||||
|
||||
def inference_state_method_generator_cache():
|
||||
"""
|
||||
This is a special memoizer. It memoizes generators and also checks for
|
||||
recursion errors and returns no further iterator elemends in that case.
|
||||
"""
|
||||
def func(function):
|
||||
@wraps(function)
|
||||
def wrapper(obj, *args, **kwargs):
|
||||
cache = obj.inference_state.memoize_cache
|
||||
try:
|
||||
memo = cache[function]
|
||||
except KeyError:
|
||||
cache[function] = memo = {}
|
||||
|
||||
key = (obj, args, frozenset(kwargs.items()))
|
||||
|
||||
if key in memo:
|
||||
actual_generator, cached_lst = memo[key]
|
||||
else:
|
||||
actual_generator = function(obj, *args, **kwargs)
|
||||
cached_lst = []
|
||||
memo[key] = actual_generator, cached_lst
|
||||
|
||||
i = 0
|
||||
while True:
|
||||
try:
|
||||
next_element = cached_lst[i]
|
||||
if next_element is _RECURSION_SENTINEL:
|
||||
debug.warning('Found a generator recursion for %s' % obj)
|
||||
# This means we have hit a recursion.
|
||||
return
|
||||
except IndexError:
|
||||
cached_lst.append(_RECURSION_SENTINEL)
|
||||
next_element = next(actual_generator, None)
|
||||
if next_element is None:
|
||||
cached_lst.pop()
|
||||
return
|
||||
cached_lst[-1] = next_element
|
||||
yield next_element
|
||||
i += 1
|
||||
return wrapper
|
||||
|
||||
return func
|
68
venv/Lib/site-packages/jedi/inference/compiled/__init__.py
Normal file
68
venv/Lib/site-packages/jedi/inference/compiled/__init__.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
from jedi._compatibility import unicode
|
||||
from jedi.inference.compiled.value import CompiledValue, CompiledName, \
|
||||
CompiledValueFilter, CompiledValueName, create_from_access_path
|
||||
from jedi.inference.base_value import LazyValueWrapper
|
||||
|
||||
|
||||
def builtin_from_name(inference_state, string):
|
||||
typing_builtins_module = inference_state.builtins_module
|
||||
if string in ('None', 'True', 'False'):
|
||||
builtins, = typing_builtins_module.non_stub_value_set
|
||||
filter_ = next(builtins.get_filters())
|
||||
else:
|
||||
filter_ = next(typing_builtins_module.get_filters())
|
||||
name, = filter_.get(string)
|
||||
value, = name.infer()
|
||||
return value
|
||||
|
||||
|
||||
class ExactValue(LazyValueWrapper):
|
||||
"""
|
||||
This class represents exact values, that makes operations like additions
|
||||
and exact boolean values possible, while still being a "normal" stub.
|
||||
"""
|
||||
def __init__(self, compiled_value):
|
||||
self.inference_state = compiled_value.inference_state
|
||||
self._compiled_value = compiled_value
|
||||
|
||||
def __getattribute__(self, name):
|
||||
if name in ('get_safe_value', 'execute_operation', 'access_handle',
|
||||
'negate', 'py__bool__', 'is_compiled'):
|
||||
return getattr(self._compiled_value, name)
|
||||
return super(ExactValue, self).__getattribute__(name)
|
||||
|
||||
def _get_wrapped_value(self):
|
||||
instance, = builtin_from_name(
|
||||
self.inference_state, self._compiled_value.name.string_name).execute_with_values()
|
||||
return instance
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._compiled_value)
|
||||
|
||||
|
||||
def create_simple_object(inference_state, obj):
|
||||
"""
|
||||
Only allows creations of objects that are easily picklable across Python
|
||||
versions.
|
||||
"""
|
||||
assert type(obj) in (int, float, str, bytes, unicode, slice, complex, bool), obj
|
||||
compiled_value = create_from_access_path(
|
||||
inference_state,
|
||||
inference_state.compiled_subprocess.create_simple_object(obj)
|
||||
)
|
||||
return ExactValue(compiled_value)
|
||||
|
||||
|
||||
def get_string_value_set(inference_state):
|
||||
return builtin_from_name(inference_state, u'str').execute_with_values()
|
||||
|
||||
|
||||
def load_module(inference_state, dotted_name, **kwargs):
|
||||
# Temporary, some tensorflow builtins cannot be loaded, so it's tried again
|
||||
# and again and it's really slow.
|
||||
if dotted_name.startswith('tensorflow.'):
|
||||
return None
|
||||
access_path = inference_state.compiled_subprocess.load_module(dotted_name=dotted_name, **kwargs)
|
||||
if access_path is None:
|
||||
return None
|
||||
return create_from_access_path(inference_state, access_path)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
564
venv/Lib/site-packages/jedi/inference/compiled/access.py
Normal file
564
venv/Lib/site-packages/jedi/inference/compiled/access.py
Normal file
|
@ -0,0 +1,564 @@
|
|||
from __future__ import print_function
|
||||
import inspect
|
||||
import types
|
||||
import sys
|
||||
import operator as op
|
||||
from collections import namedtuple
|
||||
import warnings
|
||||
import re
|
||||
|
||||
from jedi._compatibility import unicode, is_py3, builtins, \
|
||||
py_version, force_unicode
|
||||
from jedi.inference.compiled.getattr_static import getattr_static
|
||||
|
||||
ALLOWED_GETITEM_TYPES = (str, list, tuple, unicode, bytes, bytearray, dict)
|
||||
|
||||
MethodDescriptorType = type(str.replace)
|
||||
# These are not considered classes and access is granted even though they have
|
||||
# a __class__ attribute.
|
||||
NOT_CLASS_TYPES = (
|
||||
types.BuiltinFunctionType,
|
||||
types.CodeType,
|
||||
types.FrameType,
|
||||
types.FunctionType,
|
||||
types.GeneratorType,
|
||||
types.GetSetDescriptorType,
|
||||
types.LambdaType,
|
||||
types.MemberDescriptorType,
|
||||
types.MethodType,
|
||||
types.ModuleType,
|
||||
types.TracebackType,
|
||||
MethodDescriptorType
|
||||
)
|
||||
|
||||
if is_py3:
|
||||
NOT_CLASS_TYPES += (
|
||||
types.MappingProxyType,
|
||||
types.SimpleNamespace,
|
||||
types.DynamicClassAttribute,
|
||||
)
|
||||
|
||||
|
||||
# Those types don't exist in typing.
|
||||
MethodDescriptorType = type(str.replace)
|
||||
WrapperDescriptorType = type(set.__iter__)
|
||||
# `object.__subclasshook__` is an already executed descriptor.
|
||||
object_class_dict = type.__dict__["__dict__"].__get__(object)
|
||||
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
|
||||
|
||||
_sentinel = object()
|
||||
|
||||
# Maps Python syntax to the operator module.
|
||||
COMPARISON_OPERATORS = {
|
||||
'==': op.eq,
|
||||
'!=': op.ne,
|
||||
'is': op.is_,
|
||||
'is not': op.is_not,
|
||||
'<': op.lt,
|
||||
'<=': op.le,
|
||||
'>': op.gt,
|
||||
'>=': op.ge,
|
||||
}
|
||||
|
||||
_OPERATORS = {
|
||||
'+': op.add,
|
||||
'-': op.sub,
|
||||
}
|
||||
_OPERATORS.update(COMPARISON_OPERATORS)
|
||||
|
||||
ALLOWED_DESCRIPTOR_ACCESS = (
|
||||
types.FunctionType,
|
||||
types.GetSetDescriptorType,
|
||||
types.MemberDescriptorType,
|
||||
MethodDescriptorType,
|
||||
WrapperDescriptorType,
|
||||
ClassMethodDescriptorType,
|
||||
staticmethod,
|
||||
classmethod,
|
||||
)
|
||||
|
||||
|
||||
def safe_getattr(obj, name, default=_sentinel):
|
||||
try:
|
||||
attr, is_get_descriptor = getattr_static(obj, name)
|
||||
except AttributeError:
|
||||
if default is _sentinel:
|
||||
raise
|
||||
return default
|
||||
else:
|
||||
if isinstance(attr, ALLOWED_DESCRIPTOR_ACCESS):
|
||||
# In case of descriptors that have get methods we cannot return
|
||||
# it's value, because that would mean code execution.
|
||||
# Since it's an isinstance call, code execution is still possible,
|
||||
# but this is not really a security feature, but much more of a
|
||||
# safety feature. Code execution is basically always possible when
|
||||
# a module is imported. This is here so people don't shoot
|
||||
# themselves in the foot.
|
||||
return getattr(obj, name)
|
||||
return attr
|
||||
|
||||
|
||||
SignatureParam = namedtuple(
|
||||
'SignatureParam',
|
||||
'name has_default default default_string has_annotation annotation annotation_string kind_name'
|
||||
)
|
||||
|
||||
|
||||
def shorten_repr(func):
|
||||
def wrapper(self):
|
||||
r = func(self)
|
||||
if len(r) > 50:
|
||||
r = r[:50] + '..'
|
||||
return r
|
||||
return wrapper
|
||||
|
||||
|
||||
def create_access(inference_state, obj):
|
||||
return inference_state.compiled_subprocess.get_or_create_access_handle(obj)
|
||||
|
||||
|
||||
def load_module(inference_state, dotted_name, sys_path):
|
||||
temp, sys.path = sys.path, sys_path
|
||||
try:
|
||||
__import__(dotted_name)
|
||||
except ImportError:
|
||||
# If a module is "corrupt" or not really a Python module or whatever.
|
||||
print('Module %s not importable in path %s.' % (dotted_name, sys_path), file=sys.stderr)
|
||||
return None
|
||||
except Exception:
|
||||
# Since __import__ pretty much makes code execution possible, just
|
||||
# catch any error here and print it.
|
||||
import traceback
|
||||
print("Cannot import:\n%s" % traceback.format_exc(), file=sys.stderr)
|
||||
return None
|
||||
finally:
|
||||
sys.path = temp
|
||||
|
||||
# Just access the cache after import, because of #59 as well as the very
|
||||
# complicated import structure of Python.
|
||||
module = sys.modules[dotted_name]
|
||||
return create_access_path(inference_state, module)
|
||||
|
||||
|
||||
class AccessPath(object):
|
||||
def __init__(self, accesses):
|
||||
self.accesses = accesses
|
||||
|
||||
# Writing both of these methods here looks a bit ridiculous. However with
|
||||
# the differences of Python 2/3 it's actually necessary, because we will
|
||||
# otherwise have a accesses attribute that is bytes instead of unicode.
|
||||
def __getstate__(self):
|
||||
return self.accesses
|
||||
|
||||
def __setstate__(self, value):
|
||||
self.accesses = value
|
||||
|
||||
|
||||
def create_access_path(inference_state, obj):
|
||||
access = create_access(inference_state, obj)
|
||||
return AccessPath(access.get_access_path_tuples())
|
||||
|
||||
|
||||
def _force_unicode_decorator(func):
|
||||
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
|
||||
|
||||
|
||||
def get_api_type(obj):
|
||||
if inspect.isclass(obj):
|
||||
return u'class'
|
||||
elif inspect.ismodule(obj):
|
||||
return u'module'
|
||||
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
|
||||
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
|
||||
return u'function'
|
||||
# Everything else...
|
||||
return u'instance'
|
||||
|
||||
|
||||
class DirectObjectAccess(object):
|
||||
def __init__(self, inference_state, obj):
|
||||
self._inference_state = inference_state
|
||||
self._obj = obj
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, self.get_repr())
|
||||
|
||||
def _create_access(self, obj):
|
||||
return create_access(self._inference_state, obj)
|
||||
|
||||
def _create_access_path(self, obj):
|
||||
return create_access_path(self._inference_state, obj)
|
||||
|
||||
def py__bool__(self):
|
||||
return bool(self._obj)
|
||||
|
||||
def py__file__(self):
|
||||
try:
|
||||
return self._obj.__file__
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def py__doc__(self):
|
||||
return force_unicode(inspect.getdoc(self._obj)) or u''
|
||||
|
||||
def py__name__(self):
|
||||
if not _is_class_instance(self._obj) or \
|
||||
inspect.ismethoddescriptor(self._obj): # slots
|
||||
cls = self._obj
|
||||
else:
|
||||
try:
|
||||
cls = self._obj.__class__
|
||||
except AttributeError:
|
||||
# happens with numpy.core.umath._UFUNC_API (you get it
|
||||
# automatically by doing `import numpy`.
|
||||
return None
|
||||
|
||||
try:
|
||||
return force_unicode(cls.__name__)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def py__mro__accesses(self):
|
||||
return tuple(self._create_access_path(cls) for cls in self._obj.__mro__[1:])
|
||||
|
||||
def py__getitem__all_values(self):
|
||||
if isinstance(self._obj, dict):
|
||||
return [self._create_access_path(v) for v in self._obj.values()]
|
||||
return self.py__iter__list()
|
||||
|
||||
def py__simple_getitem__(self, index):
|
||||
if type(self._obj) not in ALLOWED_GETITEM_TYPES:
|
||||
# Get rid of side effects, we won't call custom `__getitem__`s.
|
||||
return None
|
||||
|
||||
return self._create_access_path(self._obj[index])
|
||||
|
||||
def py__iter__list(self):
|
||||
if not hasattr(self._obj, '__getitem__'):
|
||||
return None
|
||||
|
||||
if type(self._obj) not in ALLOWED_GETITEM_TYPES:
|
||||
# Get rid of side effects, we won't call custom `__getitem__`s.
|
||||
return []
|
||||
|
||||
lst = []
|
||||
for i, part in enumerate(self._obj):
|
||||
if i > 20:
|
||||
# Should not go crazy with large iterators
|
||||
break
|
||||
lst.append(self._create_access_path(part))
|
||||
return lst
|
||||
|
||||
def py__class__(self):
|
||||
return self._create_access_path(self._obj.__class__)
|
||||
|
||||
def py__bases__(self):
|
||||
return [self._create_access_path(base) for base in self._obj.__bases__]
|
||||
|
||||
def py__path__(self):
|
||||
paths = getattr(self._obj, '__path__', None)
|
||||
# Avoid some weird hacks that would just fail, because they cannot be
|
||||
# used by pickle.
|
||||
if not isinstance(paths, list) \
|
||||
or not all(isinstance(p, (bytes, unicode)) for p in paths):
|
||||
return None
|
||||
return paths
|
||||
|
||||
@_force_unicode_decorator
|
||||
@shorten_repr
|
||||
def get_repr(self):
|
||||
builtins = 'builtins', '__builtin__'
|
||||
|
||||
if inspect.ismodule(self._obj):
|
||||
return repr(self._obj)
|
||||
# Try to avoid execution of the property.
|
||||
if safe_getattr(self._obj, '__module__', default='') in builtins:
|
||||
return repr(self._obj)
|
||||
|
||||
type_ = type(self._obj)
|
||||
if type_ == type:
|
||||
return type.__repr__(self._obj)
|
||||
|
||||
if safe_getattr(type_, '__module__', default='') in builtins:
|
||||
# Allow direct execution of repr for builtins.
|
||||
return repr(self._obj)
|
||||
return object.__repr__(self._obj)
|
||||
|
||||
def is_class(self):
|
||||
return inspect.isclass(self._obj)
|
||||
|
||||
def is_function(self):
|
||||
return inspect.isfunction(self._obj) or inspect.ismethod(self._obj)
|
||||
|
||||
def is_module(self):
|
||||
return inspect.ismodule(self._obj)
|
||||
|
||||
def is_instance(self):
|
||||
return _is_class_instance(self._obj)
|
||||
|
||||
def ismethoddescriptor(self):
|
||||
return inspect.ismethoddescriptor(self._obj)
|
||||
|
||||
def get_qualified_names(self):
|
||||
def try_to_get_name(obj):
|
||||
return getattr(obj, '__qualname__', getattr(obj, '__name__', None))
|
||||
|
||||
if self.is_module():
|
||||
return ()
|
||||
name = try_to_get_name(self._obj)
|
||||
if name is None:
|
||||
name = try_to_get_name(type(self._obj))
|
||||
if name is None:
|
||||
return ()
|
||||
return tuple(force_unicode(n) for n in name.split('.'))
|
||||
|
||||
def dir(self):
|
||||
return list(map(force_unicode, dir(self._obj)))
|
||||
|
||||
def has_iter(self):
|
||||
try:
|
||||
iter(self._obj)
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
def is_allowed_getattr(self, name, unsafe=False):
|
||||
# TODO this API is ugly.
|
||||
if unsafe:
|
||||
# Unsafe is mostly used to check for __getattr__/__getattribute__.
|
||||
# getattr_static works for properties, but the underscore methods
|
||||
# are just ignored (because it's safer and avoids more code
|
||||
# execution). See also GH #1378.
|
||||
|
||||
# Avoid warnings, see comment in the next function.
|
||||
with warnings.catch_warnings(record=True):
|
||||
warnings.simplefilter("always")
|
||||
try:
|
||||
return hasattr(self._obj, name), False
|
||||
except Exception:
|
||||
# Obviously has an attribute (propably a property) that
|
||||
# gets executed, so just avoid all exceptions here.
|
||||
return False, False
|
||||
try:
|
||||
attr, is_get_descriptor = getattr_static(self._obj, name)
|
||||
except AttributeError:
|
||||
return False, False
|
||||
else:
|
||||
if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS:
|
||||
# In case of descriptors that have get methods we cannot return
|
||||
# it's value, because that would mean code execution.
|
||||
return True, True
|
||||
return True, False
|
||||
|
||||
def getattr_paths(self, name, default=_sentinel):
|
||||
try:
|
||||
# Make sure no warnings are printed here, this is autocompletion,
|
||||
# warnings should not be shown. See also GH #1383.
|
||||
with warnings.catch_warnings(record=True):
|
||||
warnings.simplefilter("always")
|
||||
return_obj = getattr(self._obj, name)
|
||||
except Exception as e:
|
||||
if default is _sentinel:
|
||||
if isinstance(e, AttributeError):
|
||||
# Happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
raise
|
||||
# Just in case anything happens, return an AttributeError. It
|
||||
# should not crash.
|
||||
raise AttributeError
|
||||
return_obj = default
|
||||
access = self._create_access(return_obj)
|
||||
if inspect.ismodule(return_obj):
|
||||
return [access]
|
||||
|
||||
try:
|
||||
module = return_obj.__module__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if module is not None:
|
||||
try:
|
||||
__import__(module)
|
||||
# For some modules like _sqlite3, the __module__ for classes is
|
||||
# different, in this case it's sqlite3. So we have to try to
|
||||
# load that "original" module, because it's not loaded yet. If
|
||||
# we don't do that, we don't really have a "parent" module and
|
||||
# we would fall back to builtins.
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
module = inspect.getmodule(return_obj)
|
||||
if module is None:
|
||||
module = inspect.getmodule(type(return_obj))
|
||||
if module is None:
|
||||
module = builtins
|
||||
return [self._create_access(module), access]
|
||||
|
||||
def get_safe_value(self):
|
||||
if type(self._obj) in (bool, bytes, float, int, str, unicode, slice) or self._obj is None:
|
||||
return self._obj
|
||||
raise ValueError("Object is type %s and not simple" % type(self._obj))
|
||||
|
||||
def get_api_type(self):
|
||||
return get_api_type(self._obj)
|
||||
|
||||
def get_array_type(self):
|
||||
if isinstance(self._obj, dict):
|
||||
return 'dict'
|
||||
return None
|
||||
|
||||
def get_key_paths(self):
|
||||
def iter_partial_keys():
|
||||
# We could use list(keys()), but that might take a lot more memory.
|
||||
for (i, k) in enumerate(self._obj.keys()):
|
||||
# Limit key listing at some point. This is artificial, but this
|
||||
# way we don't get stalled because of slow completions
|
||||
if i > 50:
|
||||
break
|
||||
yield k
|
||||
|
||||
return [self._create_access_path(k) for k in iter_partial_keys()]
|
||||
|
||||
def get_access_path_tuples(self):
|
||||
accesses = [create_access(self._inference_state, o) for o in self._get_objects_path()]
|
||||
return [(access.py__name__(), access) for access in accesses]
|
||||
|
||||
def _get_objects_path(self):
|
||||
def get():
|
||||
obj = self._obj
|
||||
yield obj
|
||||
try:
|
||||
obj = obj.__objclass__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
yield obj
|
||||
|
||||
try:
|
||||
# Returns a dotted string path.
|
||||
imp_plz = obj.__module__
|
||||
except AttributeError:
|
||||
# Unfortunately in some cases like `int` there's no __module__
|
||||
if not inspect.ismodule(obj):
|
||||
yield builtins
|
||||
else:
|
||||
if imp_plz is None:
|
||||
# Happens for example in `(_ for _ in []).send.__module__`.
|
||||
yield builtins
|
||||
else:
|
||||
try:
|
||||
yield sys.modules[imp_plz]
|
||||
except KeyError:
|
||||
# __module__ can be something arbitrary that doesn't exist.
|
||||
yield builtins
|
||||
|
||||
return list(reversed(list(get())))
|
||||
|
||||
def execute_operation(self, other_access_handle, operator):
|
||||
other_access = other_access_handle.access
|
||||
op = _OPERATORS[operator]
|
||||
return self._create_access_path(op(self._obj, other_access._obj))
|
||||
|
||||
def get_annotation_name_and_args(self):
|
||||
"""
|
||||
Returns Tuple[Optional[str], Tuple[AccessPath, ...]]
|
||||
"""
|
||||
if sys.version_info < (3, 5):
|
||||
return None, ()
|
||||
|
||||
name = None
|
||||
args = ()
|
||||
if safe_getattr(self._obj, '__module__', default='') == 'typing':
|
||||
m = re.match(r'typing.(\w+)\[', repr(self._obj))
|
||||
if m is not None:
|
||||
name = m.group(1)
|
||||
|
||||
import typing
|
||||
if sys.version_info >= (3, 8):
|
||||
args = typing.get_args(self._obj)
|
||||
else:
|
||||
args = safe_getattr(self._obj, '__args__', default=None)
|
||||
return name, tuple(self._create_access_path(arg) for arg in args)
|
||||
|
||||
def needs_type_completions(self):
|
||||
return inspect.isclass(self._obj) and self._obj != type
|
||||
|
||||
def _annotation_to_str(self, annotation):
|
||||
if py_version < 30:
|
||||
return ''
|
||||
return inspect.formatannotation(annotation)
|
||||
|
||||
def get_signature_params(self):
|
||||
return [
|
||||
SignatureParam(
|
||||
name=p.name,
|
||||
has_default=p.default is not p.empty,
|
||||
default=self._create_access_path(p.default),
|
||||
default_string=repr(p.default),
|
||||
has_annotation=p.annotation is not p.empty,
|
||||
annotation=self._create_access_path(p.annotation),
|
||||
annotation_string=self._annotation_to_str(p.annotation),
|
||||
kind_name=str(p.kind)
|
||||
) for p in self._get_signature().parameters.values()
|
||||
]
|
||||
|
||||
def _get_signature(self):
|
||||
obj = self._obj
|
||||
if py_version < 33:
|
||||
raise ValueError("inspect.signature was introduced in 3.3")
|
||||
try:
|
||||
return inspect.signature(obj)
|
||||
except (RuntimeError, TypeError):
|
||||
# Reading the code of the function in Python 3.6 implies there are
|
||||
# at least these errors that might occur if something is wrong with
|
||||
# the signature. In that case we just want a simple escape for now.
|
||||
raise ValueError
|
||||
|
||||
def get_return_annotation(self):
|
||||
try:
|
||||
o = self._obj.__annotations__.get('return')
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
if o is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Python 2 doesn't have typing.
|
||||
import typing
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
o = typing.get_type_hints(self._obj).get('return')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return self._create_access_path(o)
|
||||
|
||||
def negate(self):
|
||||
return self._create_access_path(-self._obj)
|
||||
|
||||
def get_dir_infos(self):
|
||||
"""
|
||||
Used to return a couple of infos that are needed when accessing the sub
|
||||
objects of an objects
|
||||
"""
|
||||
tuples = dict(
|
||||
(force_unicode(name), self.is_allowed_getattr(name))
|
||||
for name in self.dir()
|
||||
)
|
||||
return self.needs_type_completions(), tuples
|
||||
|
||||
|
||||
def _is_class_instance(obj):
|
||||
"""Like inspect.* methods."""
|
||||
try:
|
||||
cls = obj.__class__
|
||||
except AttributeError:
|
||||
return False
|
||||
else:
|
||||
# The isinstance check for cls is just there so issubclass doesn't
|
||||
# raise an exception.
|
||||
return cls != type and isinstance(cls, type) and not issubclass(cls, NOT_CLASS_TYPES)
|
184
venv/Lib/site-packages/jedi/inference/compiled/getattr_static.py
Normal file
184
venv/Lib/site-packages/jedi/inference/compiled/getattr_static.py
Normal file
|
@ -0,0 +1,184 @@
|
|||
"""
|
||||
A static version of getattr.
|
||||
This is a backport of the Python 3 code with a little bit of additional
|
||||
information returned to enable Jedi to make decisions.
|
||||
"""
|
||||
|
||||
import types
|
||||
|
||||
from jedi import debug
|
||||
from jedi._compatibility import py_version
|
||||
|
||||
_sentinel = object()
|
||||
|
||||
|
||||
def _check_instance(obj, attr):
|
||||
instance_dict = {}
|
||||
try:
|
||||
instance_dict = object.__getattribute__(obj, "__dict__")
|
||||
except AttributeError:
|
||||
pass
|
||||
return dict.get(instance_dict, attr, _sentinel)
|
||||
|
||||
|
||||
def _check_class(klass, attr):
|
||||
for entry in _static_getmro(klass):
|
||||
if _shadowed_dict(type(entry)) is _sentinel:
|
||||
try:
|
||||
return entry.__dict__[attr]
|
||||
except KeyError:
|
||||
pass
|
||||
return _sentinel
|
||||
|
||||
|
||||
def _is_type(obj):
|
||||
try:
|
||||
_static_getmro(obj)
|
||||
except TypeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _shadowed_dict_newstyle(klass):
|
||||
dict_attr = type.__dict__["__dict__"]
|
||||
for entry in _static_getmro(klass):
|
||||
try:
|
||||
class_dict = dict_attr.__get__(entry)["__dict__"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if not (type(class_dict) is types.GetSetDescriptorType
|
||||
and class_dict.__name__ == "__dict__"
|
||||
and class_dict.__objclass__ is entry):
|
||||
return class_dict
|
||||
return _sentinel
|
||||
|
||||
|
||||
def _static_getmro_newstyle(klass):
|
||||
mro = type.__dict__['__mro__'].__get__(klass)
|
||||
if not isinstance(mro, (tuple, list)):
|
||||
# There are unfortunately no tests for this, I was not able to
|
||||
# reproduce this in pure Python. However should still solve the issue
|
||||
# raised in GH #1517.
|
||||
debug.warning('mro of %s returned %s, should be a tuple' % (klass, mro))
|
||||
return ()
|
||||
return mro
|
||||
|
||||
|
||||
if py_version >= 30:
|
||||
_shadowed_dict = _shadowed_dict_newstyle
|
||||
_get_type = type
|
||||
_static_getmro = _static_getmro_newstyle
|
||||
else:
|
||||
def _shadowed_dict(klass):
|
||||
"""
|
||||
In Python 2 __dict__ is not overwritable:
|
||||
|
||||
class Foo(object): pass
|
||||
setattr(Foo, '__dict__', 4)
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: __dict__ must be a dictionary object
|
||||
|
||||
It applies to both newstyle and oldstyle classes:
|
||||
|
||||
class Foo(object): pass
|
||||
setattr(Foo, '__dict__', 4)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
AttributeError: attribute '__dict__' of 'type' objects is not writable
|
||||
|
||||
It also applies to instances of those objects. However to keep things
|
||||
straight forward, newstyle classes always use the complicated way of
|
||||
accessing it while oldstyle classes just use getattr.
|
||||
"""
|
||||
if type(klass) is _oldstyle_class_type:
|
||||
return getattr(klass, '__dict__', _sentinel)
|
||||
return _shadowed_dict_newstyle(klass)
|
||||
|
||||
class _OldStyleClass:
|
||||
pass
|
||||
|
||||
_oldstyle_instance_type = type(_OldStyleClass())
|
||||
_oldstyle_class_type = type(_OldStyleClass)
|
||||
|
||||
def _get_type(obj):
|
||||
type_ = object.__getattribute__(obj, '__class__')
|
||||
if type_ is _oldstyle_instance_type:
|
||||
# Somehow for old style classes we need to access it directly.
|
||||
return obj.__class__
|
||||
return type_
|
||||
|
||||
def _static_getmro(klass):
|
||||
if type(klass) is _oldstyle_class_type:
|
||||
def oldstyle_mro(klass):
|
||||
"""
|
||||
Oldstyle mro is a really simplistic way of look up mro:
|
||||
https://stackoverflow.com/questions/54867/what-is-the-difference-between-old-style-and-new-style-classes-in-python
|
||||
"""
|
||||
yield klass
|
||||
for base in klass.__bases__:
|
||||
for yield_from in oldstyle_mro(base):
|
||||
yield yield_from
|
||||
|
||||
return oldstyle_mro(klass)
|
||||
|
||||
return _static_getmro_newstyle(klass)
|
||||
|
||||
|
||||
def _safe_hasattr(obj, name):
|
||||
return _check_class(_get_type(obj), name) is not _sentinel
|
||||
|
||||
|
||||
def _safe_is_data_descriptor(obj):
|
||||
return _safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__')
|
||||
|
||||
|
||||
def getattr_static(obj, attr, default=_sentinel):
|
||||
"""Retrieve attributes without triggering dynamic lookup via the
|
||||
descriptor protocol, __getattr__ or __getattribute__.
|
||||
|
||||
Note: this function may not be able to retrieve all attributes
|
||||
that getattr can fetch (like dynamically created attributes)
|
||||
and may find attributes that getattr can't (like descriptors
|
||||
that raise AttributeError). It can also return descriptor objects
|
||||
instead of instance members in some cases. See the
|
||||
documentation for details.
|
||||
|
||||
Returns a tuple `(attr, is_get_descriptor)`. is_get_descripter means that
|
||||
the attribute is a descriptor that has a `__get__` attribute.
|
||||
"""
|
||||
instance_result = _sentinel
|
||||
if not _is_type(obj):
|
||||
klass = _get_type(obj)
|
||||
dict_attr = _shadowed_dict(klass)
|
||||
if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType):
|
||||
instance_result = _check_instance(obj, attr)
|
||||
else:
|
||||
klass = obj
|
||||
|
||||
klass_result = _check_class(klass, attr)
|
||||
|
||||
if instance_result is not _sentinel and klass_result is not _sentinel:
|
||||
if _safe_hasattr(klass_result, '__get__') \
|
||||
and _safe_is_data_descriptor(klass_result):
|
||||
# A get/set descriptor has priority over everything.
|
||||
return klass_result, True
|
||||
|
||||
if instance_result is not _sentinel:
|
||||
return instance_result, False
|
||||
if klass_result is not _sentinel:
|
||||
return klass_result, _safe_hasattr(klass_result, '__get__')
|
||||
|
||||
if obj is klass:
|
||||
# for types we check the metaclass too
|
||||
for entry in _static_getmro(type(klass)):
|
||||
if _shadowed_dict(type(entry)) is _sentinel:
|
||||
try:
|
||||
return entry.__dict__[attr], False
|
||||
except KeyError:
|
||||
pass
|
||||
if default is not _sentinel:
|
||||
return default, False
|
||||
raise AttributeError(attr)
|
304
venv/Lib/site-packages/jedi/inference/compiled/mixed.py
Normal file
304
venv/Lib/site-packages/jedi/inference/compiled/mixed.py
Normal file
|
@ -0,0 +1,304 @@
|
|||
"""
|
||||
Used only for REPL Completion.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
|
||||
from jedi.parser_utils import get_cached_code_lines
|
||||
|
||||
from jedi._compatibility import unwrap
|
||||
from jedi import settings
|
||||
from jedi.cache import memoize_method
|
||||
from jedi.inference import compiled
|
||||
from jedi.file_io import FileIO
|
||||
from jedi.inference.names import NameWrapper
|
||||
from jedi.inference.base_value import ValueSet, ValueWrapper, NO_VALUES
|
||||
from jedi.inference.value import ModuleValue
|
||||
from jedi.inference.cache import inference_state_function_cache, \
|
||||
inference_state_method_cache
|
||||
from jedi.inference.compiled.access import ALLOWED_GETITEM_TYPES, get_api_type
|
||||
from jedi.inference.gradual.conversion import to_stub
|
||||
from jedi.inference.context import CompiledContext, CompiledModuleContext, \
|
||||
TreeContextMixin
|
||||
|
||||
_sentinel = object()
|
||||
|
||||
|
||||
class MixedObject(ValueWrapper):
|
||||
"""
|
||||
A ``MixedObject`` is used in two ways:
|
||||
|
||||
1. It uses the default logic of ``parser.python.tree`` objects,
|
||||
2. except for getattr calls and signatures. The names dicts are generated
|
||||
in a fashion like ``CompiledValue``.
|
||||
|
||||
This combined logic makes it possible to provide more powerful REPL
|
||||
completion. It allows side effects that are not noticable with the default
|
||||
parser structure to still be completeable.
|
||||
|
||||
The biggest difference from CompiledValue to MixedObject is that we are
|
||||
generally dealing with Python code and not with C code. This will generate
|
||||
fewer special cases, because we in Python you don't have the same freedoms
|
||||
to modify the runtime.
|
||||
"""
|
||||
def __init__(self, compiled_value, tree_value):
|
||||
super(MixedObject, self).__init__(tree_value)
|
||||
self.compiled_value = compiled_value
|
||||
self.access_handle = compiled_value.access_handle
|
||||
|
||||
def get_filters(self, *args, **kwargs):
|
||||
yield MixedObjectFilter(
|
||||
self.inference_state, self.compiled_value, self._wrapped_value)
|
||||
|
||||
def get_signatures(self):
|
||||
# Prefer `inspect.signature` over somehow analyzing Python code. It
|
||||
# should be very precise, especially for stuff like `partial`.
|
||||
return self.compiled_value.get_signatures()
|
||||
|
||||
@inference_state_method_cache(default=NO_VALUES)
|
||||
def py__call__(self, arguments):
|
||||
# Fallback to the wrapped value if to stub returns no values.
|
||||
values = to_stub(self._wrapped_value)
|
||||
if not values:
|
||||
values = self._wrapped_value
|
||||
return values.py__call__(arguments)
|
||||
|
||||
def get_safe_value(self, default=_sentinel):
|
||||
if default is _sentinel:
|
||||
return self.compiled_value.get_safe_value()
|
||||
else:
|
||||
return self.compiled_value.get_safe_value(default)
|
||||
|
||||
@property
|
||||
def array_type(self):
|
||||
return self.compiled_value.array_type
|
||||
|
||||
def get_key_values(self):
|
||||
return self.compiled_value.get_key_values()
|
||||
|
||||
def py__simple_getitem__(self, index):
|
||||
python_object = self.compiled_value.access_handle.access._obj
|
||||
if type(python_object) in ALLOWED_GETITEM_TYPES:
|
||||
return self.compiled_value.py__simple_getitem__(index)
|
||||
return self._wrapped_value.py__simple_getitem__(index)
|
||||
|
||||
def negate(self):
|
||||
return self.compiled_value.negate()
|
||||
|
||||
def _as_context(self):
|
||||
if self.parent_context is None:
|
||||
return MixedModuleContext(self)
|
||||
return MixedContext(self)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s; %s>' % (
|
||||
type(self).__name__,
|
||||
self.access_handle.get_repr(),
|
||||
self._wrapped_value,
|
||||
)
|
||||
|
||||
|
||||
class MixedContext(CompiledContext, TreeContextMixin):
|
||||
@property
|
||||
def compiled_value(self):
|
||||
return self._value.compiled_value
|
||||
|
||||
|
||||
class MixedModuleContext(CompiledModuleContext, MixedContext):
|
||||
pass
|
||||
|
||||
|
||||
class MixedName(NameWrapper):
|
||||
"""
|
||||
The ``CompiledName._compiled_value`` is our MixedObject.
|
||||
"""
|
||||
def __init__(self, wrapped_name, parent_tree_value):
|
||||
super(MixedName, self).__init__(wrapped_name)
|
||||
self._parent_tree_value = parent_tree_value
|
||||
|
||||
@property
|
||||
def start_pos(self):
|
||||
values = list(self.infer())
|
||||
if not values:
|
||||
# This means a start_pos that doesn't exist (compiled objects).
|
||||
return 0, 0
|
||||
return values[0].name.start_pos
|
||||
|
||||
@memoize_method
|
||||
def infer(self):
|
||||
compiled_value = self._wrapped_name.infer_compiled_value()
|
||||
tree_value = self._parent_tree_value
|
||||
if tree_value.is_instance() or tree_value.is_class():
|
||||
tree_values = tree_value.py__getattribute__(self.string_name)
|
||||
if compiled_value.is_function():
|
||||
return ValueSet({MixedObject(compiled_value, v) for v in tree_values})
|
||||
|
||||
module_context = tree_value.get_root_context()
|
||||
return _create(self._inference_state, compiled_value, module_context)
|
||||
|
||||
|
||||
class MixedObjectFilter(compiled.CompiledValueFilter):
|
||||
def __init__(self, inference_state, compiled_value, tree_value):
|
||||
super(MixedObjectFilter, self).__init__(inference_state, compiled_value)
|
||||
self._tree_value = tree_value
|
||||
|
||||
def _create_name(self, name):
|
||||
return MixedName(
|
||||
super(MixedObjectFilter, self)._create_name(name),
|
||||
self._tree_value,
|
||||
)
|
||||
|
||||
|
||||
@inference_state_function_cache()
|
||||
def _load_module(inference_state, path):
|
||||
return inference_state.parse(
|
||||
path=path,
|
||||
cache=True,
|
||||
diff_cache=settings.fast_parser,
|
||||
cache_path=settings.cache_directory
|
||||
).get_root_node()
|
||||
|
||||
|
||||
def _get_object_to_check(python_object):
|
||||
"""Check if inspect.getfile has a chance to find the source."""
|
||||
if sys.version_info[0] > 2:
|
||||
try:
|
||||
python_object = unwrap(python_object)
|
||||
except ValueError:
|
||||
# Can return a ValueError when it wraps around
|
||||
pass
|
||||
|
||||
if (inspect.ismodule(python_object)
|
||||
or inspect.isclass(python_object)
|
||||
or inspect.ismethod(python_object)
|
||||
or inspect.isfunction(python_object)
|
||||
or inspect.istraceback(python_object)
|
||||
or inspect.isframe(python_object)
|
||||
or inspect.iscode(python_object)):
|
||||
return python_object
|
||||
|
||||
try:
|
||||
return python_object.__class__
|
||||
except AttributeError:
|
||||
raise TypeError # Prevents computation of `repr` within inspect.
|
||||
|
||||
|
||||
def _find_syntax_node_name(inference_state, python_object):
|
||||
original_object = python_object
|
||||
try:
|
||||
python_object = _get_object_to_check(python_object)
|
||||
path = inspect.getsourcefile(python_object)
|
||||
except TypeError:
|
||||
# The type might not be known (e.g. class_with_dict.__weakref__)
|
||||
return None
|
||||
if path is None or not os.path.exists(path):
|
||||
# The path might not exist or be e.g. <stdin>.
|
||||
return None
|
||||
|
||||
file_io = FileIO(path)
|
||||
module_node = _load_module(inference_state, path)
|
||||
|
||||
if inspect.ismodule(python_object):
|
||||
# We don't need to check names for modules, because there's not really
|
||||
# a way to write a module in a module in Python (and also __name__ can
|
||||
# be something like ``email.utils``).
|
||||
code_lines = get_cached_code_lines(inference_state.grammar, path)
|
||||
return module_node, module_node, file_io, code_lines
|
||||
|
||||
try:
|
||||
name_str = python_object.__name__
|
||||
except AttributeError:
|
||||
# Stuff like python_function.__code__.
|
||||
return None
|
||||
|
||||
if name_str == '<lambda>':
|
||||
return None # It's too hard to find lambdas.
|
||||
|
||||
# Doesn't always work (e.g. os.stat_result)
|
||||
names = module_node.get_used_names().get(name_str, [])
|
||||
# Only functions and classes are relevant. If a name e.g. points to an
|
||||
# import, it's probably a builtin (like collections.deque) and needs to be
|
||||
# ignored.
|
||||
names = [
|
||||
n for n in names
|
||||
if n.parent.type in ('funcdef', 'classdef') and n.parent.name == n
|
||||
]
|
||||
if not names:
|
||||
return None
|
||||
|
||||
try:
|
||||
code = python_object.__code__
|
||||
# By using the line number of a code object we make the lookup in a
|
||||
# file pretty easy. There's still a possibility of people defining
|
||||
# stuff like ``a = 3; foo(a); a = 4`` on the same line, but if people
|
||||
# do so we just don't care.
|
||||
line_nr = code.co_firstlineno
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
line_names = [name for name in names if name.start_pos[0] == line_nr]
|
||||
# There's a chance that the object is not available anymore, because
|
||||
# the code has changed in the background.
|
||||
if line_names:
|
||||
names = line_names
|
||||
|
||||
code_lines = get_cached_code_lines(inference_state.grammar, path)
|
||||
# It's really hard to actually get the right definition, here as a last
|
||||
# resort we just return the last one. This chance might lead to odd
|
||||
# completions at some points but will lead to mostly correct type
|
||||
# inference, because people tend to define a public name in a module only
|
||||
# once.
|
||||
tree_node = names[-1].parent
|
||||
if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance':
|
||||
# If an instance is given and we're landing on a function (e.g.
|
||||
# partial in 3.5), something is completely wrong and we should not
|
||||
# return that.
|
||||
return None
|
||||
return module_node, tree_node, file_io, code_lines
|
||||
|
||||
|
||||
@inference_state_function_cache()
|
||||
def _create(inference_state, compiled_value, module_context):
|
||||
# TODO accessing this is bad, but it probably doesn't matter that much,
|
||||
# because we're working with interpreteters only here.
|
||||
python_object = compiled_value.access_handle.access._obj
|
||||
result = _find_syntax_node_name(inference_state, python_object)
|
||||
if result is None:
|
||||
# TODO Care about generics from stuff like `[1]` and don't return like this.
|
||||
if type(python_object) in (dict, list, tuple):
|
||||
return ValueSet({compiled_value})
|
||||
|
||||
tree_values = to_stub(compiled_value)
|
||||
if not tree_values:
|
||||
return ValueSet({compiled_value})
|
||||
else:
|
||||
module_node, tree_node, file_io, code_lines = result
|
||||
|
||||
if module_context is None or module_context.tree_node != module_node:
|
||||
root_compiled_value = compiled_value.get_root_context().get_value()
|
||||
# TODO this __name__ might be wrong.
|
||||
name = root_compiled_value.py__name__()
|
||||
string_names = tuple(name.split('.'))
|
||||
module_value = ModuleValue(
|
||||
inference_state, module_node,
|
||||
file_io=file_io,
|
||||
string_names=string_names,
|
||||
code_lines=code_lines,
|
||||
is_package=root_compiled_value.is_package(),
|
||||
)
|
||||
if name is not None:
|
||||
inference_state.module_cache.add(string_names, ValueSet([module_value]))
|
||||
module_context = module_value.as_context()
|
||||
|
||||
tree_values = ValueSet({module_context.create_value(tree_node)})
|
||||
if tree_node.type == 'classdef':
|
||||
if not compiled_value.is_class():
|
||||
# Is an instance, not a class.
|
||||
tree_values = tree_values.execute_with_values()
|
||||
|
||||
return ValueSet(
|
||||
MixedObject(compiled_value, tree_value=tree_value)
|
||||
for tree_value in tree_values
|
||||
)
|
|
@ -0,0 +1,404 @@
|
|||
"""
|
||||
Makes it possible to do the compiled analysis in a subprocess. This has two
|
||||
goals:
|
||||
|
||||
1. Making it safer - Segfaults and RuntimeErrors as well as stdout/stderr can
|
||||
be ignored and dealt with.
|
||||
2. Make it possible to handle different Python versions as well as virtualenvs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import socket
|
||||
import errno
|
||||
import traceback
|
||||
from functools import partial
|
||||
from threading import Thread
|
||||
try:
|
||||
from queue import Queue, Empty
|
||||
except ImportError:
|
||||
from Queue import Queue, Empty # python 2.7
|
||||
|
||||
from jedi._compatibility import queue, is_py3, force_unicode, \
|
||||
pickle_dump, pickle_load, GeneralizedPopen, weakref
|
||||
from jedi import debug
|
||||
from jedi.cache import memoize_method
|
||||
from jedi.inference.compiled.subprocess import functions
|
||||
from jedi.inference.compiled.access import DirectObjectAccess, AccessPath, \
|
||||
SignatureParam
|
||||
from jedi.api.exceptions import InternalError
|
||||
|
||||
|
||||
_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
|
||||
|
||||
|
||||
def _enqueue_output(out, queue):
|
||||
for line in iter(out.readline, b''):
|
||||
queue.put(line)
|
||||
|
||||
|
||||
def _add_stderr_to_debug(stderr_queue):
|
||||
while True:
|
||||
# Try to do some error reporting from the subprocess and print its
|
||||
# stderr contents.
|
||||
try:
|
||||
line = stderr_queue.get_nowait()
|
||||
line = line.decode('utf-8', 'replace')
|
||||
debug.warning('stderr output: %s' % line.rstrip('\n'))
|
||||
except Empty:
|
||||
break
|
||||
|
||||
|
||||
def _get_function(name):
|
||||
return getattr(functions, name)
|
||||
|
||||
|
||||
def _cleanup_process(process, thread):
|
||||
try:
|
||||
process.kill()
|
||||
process.wait()
|
||||
except OSError:
|
||||
# Raised if the process is already killed.
|
||||
pass
|
||||
thread.join()
|
||||
for stream in [process.stdin, process.stdout, process.stderr]:
|
||||
try:
|
||||
stream.close()
|
||||
except OSError:
|
||||
# Raised if the stream is broken.
|
||||
pass
|
||||
|
||||
|
||||
class _InferenceStateProcess(object):
|
||||
def __init__(self, inference_state):
|
||||
self._inference_state_weakref = weakref.ref(inference_state)
|
||||
self._inference_state_id = id(inference_state)
|
||||
self._handles = {}
|
||||
|
||||
def get_or_create_access_handle(self, obj):
|
||||
id_ = id(obj)
|
||||
try:
|
||||
return self.get_access_handle(id_)
|
||||
except KeyError:
|
||||
access = DirectObjectAccess(self._inference_state_weakref(), obj)
|
||||
handle = AccessHandle(self, access, id_)
|
||||
self.set_access_handle(handle)
|
||||
return handle
|
||||
|
||||
def get_access_handle(self, id_):
|
||||
return self._handles[id_]
|
||||
|
||||
def set_access_handle(self, handle):
|
||||
self._handles[handle.id] = handle
|
||||
|
||||
|
||||
class InferenceStateSameProcess(_InferenceStateProcess):
|
||||
"""
|
||||
Basically just an easy access to functions.py. It has the same API
|
||||
as InferenceStateSubprocess and does the same thing without using a subprocess.
|
||||
This is necessary for the Interpreter process.
|
||||
"""
|
||||
def __getattr__(self, name):
|
||||
return partial(_get_function(name), self._inference_state_weakref())
|
||||
|
||||
|
||||
class InferenceStateSubprocess(_InferenceStateProcess):
|
||||
def __init__(self, inference_state, compiled_subprocess):
|
||||
super(InferenceStateSubprocess, self).__init__(inference_state)
|
||||
self._used = False
|
||||
self._compiled_subprocess = compiled_subprocess
|
||||
|
||||
def __getattr__(self, name):
|
||||
func = _get_function(name)
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
self._used = True
|
||||
|
||||
result = self._compiled_subprocess.run(
|
||||
self._inference_state_weakref(),
|
||||
func,
|
||||
args=args,
|
||||
kwargs=kwargs,
|
||||
)
|
||||
# IMO it should be possible to create a hook in pickle.load to
|
||||
# mess with the loaded objects. However it's extremely complicated
|
||||
# to work around this so just do it with this call. ~ dave
|
||||
return self._convert_access_handles(result)
|
||||
|
||||
return wrapper
|
||||
|
||||
def _convert_access_handles(self, obj):
|
||||
if isinstance(obj, SignatureParam):
|
||||
return SignatureParam(*self._convert_access_handles(tuple(obj)))
|
||||
elif isinstance(obj, tuple):
|
||||
return tuple(self._convert_access_handles(o) for o in obj)
|
||||
elif isinstance(obj, list):
|
||||
return [self._convert_access_handles(o) for o in obj]
|
||||
elif isinstance(obj, AccessHandle):
|
||||
try:
|
||||
# Rewrite the access handle to one we're already having.
|
||||
obj = self.get_access_handle(obj.id)
|
||||
except KeyError:
|
||||
obj.add_subprocess(self)
|
||||
self.set_access_handle(obj)
|
||||
elif isinstance(obj, AccessPath):
|
||||
return AccessPath(self._convert_access_handles(obj.accesses))
|
||||
return obj
|
||||
|
||||
def __del__(self):
|
||||
if self._used and not self._compiled_subprocess.is_crashed:
|
||||
self._compiled_subprocess.delete_inference_state(self._inference_state_id)
|
||||
|
||||
|
||||
class CompiledSubprocess(object):
|
||||
is_crashed = False
|
||||
# Start with 2, gets set after _get_info.
|
||||
_pickle_protocol = 2
|
||||
|
||||
def __init__(self, executable, env_vars=None):
|
||||
self._executable = executable
|
||||
self._env_vars = env_vars
|
||||
self._inference_state_deletion_queue = queue.deque()
|
||||
self._cleanup_callable = lambda: None
|
||||
|
||||
def __repr__(self):
|
||||
pid = os.getpid()
|
||||
return '<%s _executable=%r, _pickle_protocol=%r, is_crashed=%r, pid=%r>' % (
|
||||
self.__class__.__name__,
|
||||
self._executable,
|
||||
self._pickle_protocol,
|
||||
self.is_crashed,
|
||||
pid,
|
||||
)
|
||||
|
||||
@memoize_method
|
||||
def _get_process(self):
|
||||
debug.dbg('Start environment subprocess %s', self._executable)
|
||||
parso_path = sys.modules['parso'].__file__
|
||||
args = (
|
||||
self._executable,
|
||||
_MAIN_PATH,
|
||||
os.path.dirname(os.path.dirname(parso_path)),
|
||||
'.'.join(str(x) for x in sys.version_info[:3]),
|
||||
)
|
||||
process = GeneralizedPopen(
|
||||
args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
# Use system default buffering on Python 2 to improve performance
|
||||
# (this is already the case on Python 3).
|
||||
bufsize=-1,
|
||||
env=self._env_vars
|
||||
)
|
||||
self._stderr_queue = Queue()
|
||||
self._stderr_thread = t = Thread(
|
||||
target=_enqueue_output,
|
||||
args=(process.stderr, self._stderr_queue)
|
||||
)
|
||||
t.daemon = True
|
||||
t.start()
|
||||
# Ensure the subprocess is properly cleaned up when the object
|
||||
# is garbage collected.
|
||||
self._cleanup_callable = weakref.finalize(self,
|
||||
_cleanup_process,
|
||||
process,
|
||||
t)
|
||||
return process
|
||||
|
||||
def run(self, inference_state, function, args=(), kwargs={}):
|
||||
# Delete old inference_states.
|
||||
while True:
|
||||
try:
|
||||
inference_state_id = self._inference_state_deletion_queue.pop()
|
||||
except IndexError:
|
||||
break
|
||||
else:
|
||||
self._send(inference_state_id, None)
|
||||
|
||||
assert callable(function)
|
||||
return self._send(id(inference_state), function, args, kwargs)
|
||||
|
||||
def get_sys_path(self):
|
||||
return self._send(None, functions.get_sys_path, (), {})
|
||||
|
||||
def _kill(self):
|
||||
self.is_crashed = True
|
||||
self._cleanup_callable()
|
||||
|
||||
def _send(self, inference_state_id, function, args=(), kwargs={}):
|
||||
if self.is_crashed:
|
||||
raise InternalError("The subprocess %s has crashed." % self._executable)
|
||||
|
||||
if not is_py3:
|
||||
# Python 2 compatibility
|
||||
kwargs = {force_unicode(key): value for key, value in kwargs.items()}
|
||||
|
||||
data = inference_state_id, function, args, kwargs
|
||||
try:
|
||||
pickle_dump(data, self._get_process().stdin, self._pickle_protocol)
|
||||
except (socket.error, IOError) as e:
|
||||
# Once Python2 will be removed we can just use `BrokenPipeError`.
|
||||
# Also, somehow in windows it returns EINVAL instead of EPIPE if
|
||||
# the subprocess dies.
|
||||
if e.errno not in (errno.EPIPE, errno.EINVAL):
|
||||
# Not a broken pipe
|
||||
raise
|
||||
self._kill()
|
||||
raise InternalError("The subprocess %s was killed. Maybe out of memory?"
|
||||
% self._executable)
|
||||
|
||||
try:
|
||||
is_exception, traceback, result = pickle_load(self._get_process().stdout)
|
||||
except EOFError as eof_error:
|
||||
try:
|
||||
stderr = self._get_process().stderr.read().decode('utf-8', 'replace')
|
||||
except Exception as exc:
|
||||
stderr = '<empty/not available (%r)>' % exc
|
||||
self._kill()
|
||||
_add_stderr_to_debug(self._stderr_queue)
|
||||
raise InternalError(
|
||||
"The subprocess %s has crashed (%r, stderr=%s)." % (
|
||||
self._executable,
|
||||
eof_error,
|
||||
stderr,
|
||||
))
|
||||
|
||||
_add_stderr_to_debug(self._stderr_queue)
|
||||
|
||||
if is_exception:
|
||||
# Replace the attribute error message with a the traceback. It's
|
||||
# way more informative.
|
||||
result.args = (traceback,)
|
||||
raise result
|
||||
return result
|
||||
|
||||
def delete_inference_state(self, inference_state_id):
|
||||
"""
|
||||
Currently we are not deleting inference_state instantly. They only get
|
||||
deleted once the subprocess is used again. It would probably a better
|
||||
solution to move all of this into a thread. However, the memory usage
|
||||
of a single inference_state shouldn't be that high.
|
||||
"""
|
||||
# With an argument - the inference_state gets deleted.
|
||||
self._inference_state_deletion_queue.append(inference_state_id)
|
||||
|
||||
|
||||
class Listener(object):
|
||||
def __init__(self, pickle_protocol):
|
||||
self._inference_states = {}
|
||||
# TODO refactor so we don't need to process anymore just handle
|
||||
# controlling.
|
||||
self._process = _InferenceStateProcess(Listener)
|
||||
self._pickle_protocol = pickle_protocol
|
||||
|
||||
def _get_inference_state(self, function, inference_state_id):
|
||||
from jedi.inference import InferenceState
|
||||
|
||||
try:
|
||||
inference_state = self._inference_states[inference_state_id]
|
||||
except KeyError:
|
||||
from jedi import InterpreterEnvironment
|
||||
inference_state = InferenceState(
|
||||
# The project is not actually needed. Nothing should need to
|
||||
# access it.
|
||||
project=None,
|
||||
environment=InterpreterEnvironment()
|
||||
)
|
||||
self._inference_states[inference_state_id] = inference_state
|
||||
return inference_state
|
||||
|
||||
def _run(self, inference_state_id, function, args, kwargs):
|
||||
if inference_state_id is None:
|
||||
return function(*args, **kwargs)
|
||||
elif function is None:
|
||||
del self._inference_states[inference_state_id]
|
||||
else:
|
||||
inference_state = self._get_inference_state(function, inference_state_id)
|
||||
|
||||
# Exchange all handles
|
||||
args = list(args)
|
||||
for i, arg in enumerate(args):
|
||||
if isinstance(arg, AccessHandle):
|
||||
args[i] = inference_state.compiled_subprocess.get_access_handle(arg.id)
|
||||
for key, value in kwargs.items():
|
||||
if isinstance(value, AccessHandle):
|
||||
kwargs[key] = inference_state.compiled_subprocess.get_access_handle(value.id)
|
||||
|
||||
return function(inference_state, *args, **kwargs)
|
||||
|
||||
def listen(self):
|
||||
stdout = sys.stdout
|
||||
# Mute stdout. Nobody should actually be able to write to it,
|
||||
# because stdout is used for IPC.
|
||||
sys.stdout = open(os.devnull, 'w')
|
||||
stdin = sys.stdin
|
||||
if sys.version_info[0] > 2:
|
||||
stdout = stdout.buffer
|
||||
stdin = stdin.buffer
|
||||
# Python 2 opens streams in text mode on Windows. Set stdout and stdin
|
||||
# to binary mode.
|
||||
elif sys.platform == 'win32':
|
||||
import msvcrt
|
||||
msvcrt.setmode(stdout.fileno(), os.O_BINARY)
|
||||
msvcrt.setmode(stdin.fileno(), os.O_BINARY)
|
||||
|
||||
while True:
|
||||
try:
|
||||
payload = pickle_load(stdin)
|
||||
except EOFError:
|
||||
# It looks like the parent process closed.
|
||||
# Don't make a big fuss here and just exit.
|
||||
exit(0)
|
||||
try:
|
||||
result = False, None, self._run(*payload)
|
||||
except Exception as e:
|
||||
result = True, traceback.format_exc(), e
|
||||
|
||||
pickle_dump(result, stdout, self._pickle_protocol)
|
||||
|
||||
|
||||
class AccessHandle(object):
|
||||
def __init__(self, subprocess, access, id_):
|
||||
self.access = access
|
||||
self._subprocess = subprocess
|
||||
self.id = id_
|
||||
|
||||
def add_subprocess(self, subprocess):
|
||||
self._subprocess = subprocess
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
detail = self.access
|
||||
except AttributeError:
|
||||
detail = '#' + str(self.id)
|
||||
return '<%s of %s>' % (self.__class__.__name__, detail)
|
||||
|
||||
def __getstate__(self):
|
||||
return self.id
|
||||
|
||||
def __setstate__(self, state):
|
||||
self.id = state
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in ('id', 'access') or name.startswith('_'):
|
||||
raise AttributeError("Something went wrong with unpickling")
|
||||
|
||||
# if not is_py3: print >> sys.stderr, name
|
||||
# print('getattr', name, file=sys.stderr)
|
||||
return partial(self._workaround, force_unicode(name))
|
||||
|
||||
def _workaround(self, name, *args, **kwargs):
|
||||
"""
|
||||
TODO Currently we're passing slice objects around. This should not
|
||||
happen. They are also the only unhashable objects that we're passing
|
||||
around.
|
||||
"""
|
||||
if args and isinstance(args[0], slice):
|
||||
return self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs)
|
||||
return self._cached_results(name, *args, **kwargs)
|
||||
|
||||
@memoize_method
|
||||
def _cached_results(self, name, *args, **kwargs):
|
||||
return self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs)
|
|
@ -0,0 +1,55 @@
|
|||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def _get_paths():
|
||||
# Get the path to jedi.
|
||||
_d = os.path.dirname
|
||||
_jedi_path = _d(_d(_d(_d(_d(__file__)))))
|
||||
_parso_path = sys.argv[1]
|
||||
# The paths are the directory that jedi and parso lie in.
|
||||
return {'jedi': _jedi_path, 'parso': _parso_path}
|
||||
|
||||
|
||||
# Remove the first entry, because it's simply a directory entry that equals
|
||||
# this directory.
|
||||
del sys.path[0]
|
||||
|
||||
if sys.version_info > (3, 4):
|
||||
from importlib.machinery import PathFinder
|
||||
|
||||
class _ExactImporter(object):
|
||||
def __init__(self, path_dct):
|
||||
self._path_dct = path_dct
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if path is None and fullname in self._path_dct:
|
||||
p = self._path_dct[fullname]
|
||||
loader = PathFinder.find_module(fullname, path=[p])
|
||||
return loader
|
||||
return None
|
||||
|
||||
# Try to import jedi/parso.
|
||||
sys.meta_path.insert(0, _ExactImporter(_get_paths()))
|
||||
from jedi.inference.compiled import subprocess # NOQA
|
||||
sys.meta_path.pop(0)
|
||||
else:
|
||||
import imp
|
||||
|
||||
def load(name):
|
||||
paths = list(_get_paths().values())
|
||||
fp, pathname, description = imp.find_module(name, paths)
|
||||
return imp.load_module(name, fp, pathname, description)
|
||||
|
||||
load('parso')
|
||||
load('jedi')
|
||||
from jedi.inference.compiled import subprocess # NOQA
|
||||
|
||||
from jedi._compatibility import highest_pickle_protocol # noqa: E402
|
||||
|
||||
|
||||
# Retrieve the pickle protocol.
|
||||
host_sys_version = [int(x) for x in sys.argv[2].split('.')]
|
||||
pickle_protocol = highest_pickle_protocol([sys.version_info, host_sys_version])
|
||||
# And finally start the client.
|
||||
subprocess.Listener(pickle_protocol=pickle_protocol).listen()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,115 @@
|
|||
from __future__ import print_function
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import inspect
|
||||
|
||||
from jedi._compatibility import find_module, cast_path, force_unicode, \
|
||||
all_suffixes, scandir
|
||||
from jedi.inference.compiled import access
|
||||
from jedi import debug
|
||||
from jedi import parser_utils
|
||||
|
||||
|
||||
def get_sys_path():
|
||||
return list(map(cast_path, 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 list(map(force_unicode, 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:
|
||||
dirs = scandir(path)
|
||||
except OSError:
|
||||
# The file might not exist or reading it might lead to an error.
|
||||
debug.warning("Not possible to list directory: %s", path)
|
||||
continue
|
||||
for dir_entry in dirs:
|
||||
name = dir_entry.name
|
||||
# First Namespaces then modules/stubs
|
||||
if dir_entry.is_dir():
|
||||
# pycache is obviously not an interestin namespace. Also the
|
||||
# name must be a valid identifier.
|
||||
# TODO use str.isidentifier, once Python 2 is removed
|
||||
if name != '__pycache__' and not re.search(r'\W|^\d', name):
|
||||
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
|
629
venv/Lib/site-packages/jedi/inference/compiled/value.py
Normal file
629
venv/Lib/site-packages/jedi/inference/compiled/value.py
Normal file
|
@ -0,0 +1,629 @@
|
|||
"""
|
||||
Imitate the parser representation.
|
||||
"""
|
||||
import re
|
||||
from functools import partial
|
||||
|
||||
from jedi import debug
|
||||
from jedi.inference.utils import to_list
|
||||
from jedi._compatibility import force_unicode, Parameter, cast_path
|
||||
from jedi.cache import memoize_method
|
||||
from jedi.inference.filters import AbstractFilter
|
||||
from jedi.inference.names import AbstractNameDefinition, ValueNameMixin, \
|
||||
ParamNameInterface
|
||||
from jedi.inference.base_value import Value, ValueSet, NO_VALUES
|
||||
from jedi.inference.lazy_value import LazyKnownValue
|
||||
from jedi.inference.compiled.access import _sentinel
|
||||
from jedi.inference.cache import inference_state_function_cache
|
||||
from jedi.inference.helpers import reraise_getitem_errors
|
||||
from jedi.inference.signature import BuiltinSignature
|
||||
from jedi.inference.context import CompiledContext, CompiledModuleContext
|
||||
|
||||
|
||||
class CheckAttribute(object):
|
||||
"""Raises :exc:`AttributeError` if the attribute X is not available."""
|
||||
def __init__(self, check_name=None):
|
||||
# Remove the py in front of e.g. py__call__.
|
||||
self.check_name = check_name
|
||||
|
||||
def __call__(self, func):
|
||||
self.func = func
|
||||
if self.check_name is None:
|
||||
self.check_name = force_unicode(func.__name__[2:])
|
||||
return self
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
|
||||
# This might raise an AttributeError. That's wanted.
|
||||
instance.access_handle.getattr_paths(self.check_name)
|
||||
return partial(self.func, instance)
|
||||
|
||||
|
||||
class CompiledValue(Value):
|
||||
def __init__(self, inference_state, access_handle, parent_context=None):
|
||||
super(CompiledValue, self).__init__(inference_state, parent_context)
|
||||
self.access_handle = access_handle
|
||||
|
||||
def py__call__(self, arguments):
|
||||
return_annotation = self.access_handle.get_return_annotation()
|
||||
if return_annotation is not None:
|
||||
# TODO the return annotation may also be a string.
|
||||
return create_from_access_path(
|
||||
self.inference_state,
|
||||
return_annotation
|
||||
).execute_annotation()
|
||||
|
||||
try:
|
||||
self.access_handle.getattr_paths(u'__call__')
|
||||
except AttributeError:
|
||||
return super(CompiledValue, self).py__call__(arguments)
|
||||
else:
|
||||
if self.access_handle.is_class():
|
||||
from jedi.inference.value import CompiledInstance
|
||||
return ValueSet([
|
||||
CompiledInstance(self.inference_state, self.parent_context, self, arguments)
|
||||
])
|
||||
else:
|
||||
return ValueSet(self._execute_function(arguments))
|
||||
|
||||
@CheckAttribute()
|
||||
def py__class__(self):
|
||||
return create_from_access_path(self.inference_state, self.access_handle.py__class__())
|
||||
|
||||
@CheckAttribute()
|
||||
def py__mro__(self):
|
||||
return (self,) + tuple(
|
||||
create_from_access_path(self.inference_state, access)
|
||||
for access in self.access_handle.py__mro__accesses()
|
||||
)
|
||||
|
||||
@CheckAttribute()
|
||||
def py__bases__(self):
|
||||
return tuple(
|
||||
create_from_access_path(self.inference_state, access)
|
||||
for access in self.access_handle.py__bases__()
|
||||
)
|
||||
|
||||
def get_qualified_names(self):
|
||||
return self.access_handle.get_qualified_names()
|
||||
|
||||
def py__bool__(self):
|
||||
return self.access_handle.py__bool__()
|
||||
|
||||
def is_class(self):
|
||||
return self.access_handle.is_class()
|
||||
|
||||
def is_function(self):
|
||||
return self.access_handle.is_function()
|
||||
|
||||
def is_module(self):
|
||||
return self.access_handle.is_module()
|
||||
|
||||
def is_compiled(self):
|
||||
return True
|
||||
|
||||
def is_stub(self):
|
||||
return False
|
||||
|
||||
def is_instance(self):
|
||||
return self.access_handle.is_instance()
|
||||
|
||||
def py__doc__(self):
|
||||
return self.access_handle.py__doc__()
|
||||
|
||||
@to_list
|
||||
def get_param_names(self):
|
||||
try:
|
||||
signature_params = self.access_handle.get_signature_params()
|
||||
except ValueError: # Has no signature
|
||||
params_str, ret = self._parse_function_doc()
|
||||
if not params_str:
|
||||
tokens = []
|
||||
else:
|
||||
tokens = params_str.split(',')
|
||||
if self.access_handle.ismethoddescriptor():
|
||||
tokens.insert(0, 'self')
|
||||
for p in tokens:
|
||||
name, _, default = p.strip().partition('=')
|
||||
yield UnresolvableParamName(self, name, default)
|
||||
else:
|
||||
for signature_param in signature_params:
|
||||
yield SignatureParamName(self, signature_param)
|
||||
|
||||
def get_signatures(self):
|
||||
_, return_string = self._parse_function_doc()
|
||||
return [BuiltinSignature(self, return_string)]
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.access_handle.get_repr())
|
||||
|
||||
@memoize_method
|
||||
def _parse_function_doc(self):
|
||||
doc = self.py__doc__()
|
||||
if doc is None:
|
||||
return '', ''
|
||||
|
||||
return _parse_function_doc(doc)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return self.access_handle.get_api_type()
|
||||
|
||||
def get_filters(self, is_instance=False, origin_scope=None):
|
||||
yield self._ensure_one_filter(is_instance)
|
||||
|
||||
@memoize_method
|
||||
def _ensure_one_filter(self, is_instance):
|
||||
return CompiledValueFilter(self.inference_state, self, is_instance)
|
||||
|
||||
def py__simple_getitem__(self, index):
|
||||
with reraise_getitem_errors(IndexError, KeyError, TypeError):
|
||||
try:
|
||||
access = self.access_handle.py__simple_getitem__(index)
|
||||
except AttributeError:
|
||||
return super(CompiledValue, self).py__simple_getitem__(index)
|
||||
if access is None:
|
||||
return NO_VALUES
|
||||
|
||||
return ValueSet([create_from_access_path(self.inference_state, access)])
|
||||
|
||||
def py__getitem__(self, index_value_set, contextualized_node):
|
||||
all_access_paths = self.access_handle.py__getitem__all_values()
|
||||
if all_access_paths is None:
|
||||
# This means basically that no __getitem__ has been defined on this
|
||||
# object.
|
||||
return super(CompiledValue, self).py__getitem__(index_value_set, contextualized_node)
|
||||
return ValueSet(
|
||||
create_from_access_path(self.inference_state, access)
|
||||
for access in all_access_paths
|
||||
)
|
||||
|
||||
def py__iter__(self, contextualized_node=None):
|
||||
# Python iterators are a bit strange, because there's no need for
|
||||
# the __iter__ function as long as __getitem__ is defined (it will
|
||||
# just start with __getitem__(0). This is especially true for
|
||||
# Python 2 strings, where `str.__iter__` is not even defined.
|
||||
if not self.access_handle.has_iter():
|
||||
for x in super(CompiledValue, self).py__iter__(contextualized_node):
|
||||
yield x
|
||||
|
||||
access_path_list = self.access_handle.py__iter__list()
|
||||
if access_path_list is None:
|
||||
# There is no __iter__ method on this object.
|
||||
return
|
||||
|
||||
for access in access_path_list:
|
||||
yield LazyKnownValue(create_from_access_path(self.inference_state, access))
|
||||
|
||||
def py__name__(self):
|
||||
return self.access_handle.py__name__()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
name = self.py__name__()
|
||||
if name is None:
|
||||
name = self.access_handle.get_repr()
|
||||
return CompiledValueName(self, name)
|
||||
|
||||
def _execute_function(self, params):
|
||||
from jedi.inference import docstrings
|
||||
from jedi.inference.compiled import builtin_from_name
|
||||
if self.api_type != 'function':
|
||||
return
|
||||
|
||||
for name in self._parse_function_doc()[1].split():
|
||||
try:
|
||||
# TODO wtf is this? this is exactly the same as the thing
|
||||
# below. It uses getattr as well.
|
||||
self.inference_state.builtins_module.access_handle.getattr_paths(name)
|
||||
except AttributeError:
|
||||
continue
|
||||
else:
|
||||
bltn_obj = builtin_from_name(self.inference_state, name)
|
||||
for result in self.inference_state.execute(bltn_obj, params):
|
||||
yield result
|
||||
for type_ in docstrings.infer_return_types(self):
|
||||
yield type_
|
||||
|
||||
def get_safe_value(self, default=_sentinel):
|
||||
try:
|
||||
return self.access_handle.get_safe_value()
|
||||
except ValueError:
|
||||
if default == _sentinel:
|
||||
raise
|
||||
return default
|
||||
|
||||
def execute_operation(self, other, operator):
|
||||
try:
|
||||
return ValueSet([create_from_access_path(
|
||||
self.inference_state,
|
||||
self.access_handle.execute_operation(other.access_handle, operator)
|
||||
)])
|
||||
except TypeError:
|
||||
return NO_VALUES
|
||||
|
||||
def execute_annotation(self):
|
||||
if self.access_handle.get_repr() == 'None':
|
||||
# None as an annotation doesn't need to be executed.
|
||||
return ValueSet([self])
|
||||
|
||||
name, args = self.access_handle.get_annotation_name_and_args()
|
||||
arguments = [
|
||||
ValueSet([create_from_access_path(self.inference_state, path)])
|
||||
for path in args
|
||||
]
|
||||
if name == 'Union':
|
||||
return ValueSet.from_sets(arg.execute_annotation() for arg in arguments)
|
||||
elif name:
|
||||
# While with_generics only exists on very specific objects, we
|
||||
# should probably be fine, because we control all the typing
|
||||
# objects.
|
||||
return ValueSet([
|
||||
v.with_generics(arguments)
|
||||
for v in self.inference_state.typing_module.py__getattribute__(name)
|
||||
]).execute_annotation()
|
||||
return super(CompiledValue, self).execute_annotation()
|
||||
|
||||
def negate(self):
|
||||
return create_from_access_path(self.inference_state, self.access_handle.negate())
|
||||
|
||||
def get_metaclasses(self):
|
||||
return NO_VALUES
|
||||
|
||||
def _as_context(self):
|
||||
return CompiledContext(self)
|
||||
|
||||
@property
|
||||
def array_type(self):
|
||||
return self.access_handle.get_array_type()
|
||||
|
||||
def get_key_values(self):
|
||||
return [
|
||||
create_from_access_path(self.inference_state, k)
|
||||
for k in self.access_handle.get_key_paths()
|
||||
]
|
||||
|
||||
def get_type_hint(self, add_class_info=True):
|
||||
if self.access_handle.get_repr() in ('None', "<class 'NoneType'>"):
|
||||
return 'None'
|
||||
return None
|
||||
|
||||
|
||||
class CompiledModule(CompiledValue):
|
||||
file_io = None # For modules
|
||||
|
||||
def _as_context(self):
|
||||
return CompiledModuleContext(self)
|
||||
|
||||
def py__path__(self):
|
||||
paths = self.access_handle.py__path__()
|
||||
if paths is None:
|
||||
return None
|
||||
return map(cast_path, paths)
|
||||
|
||||
def is_package(self):
|
||||
return self.py__path__() is not None
|
||||
|
||||
@property
|
||||
def string_names(self):
|
||||
# For modules
|
||||
name = self.py__name__()
|
||||
if name is None:
|
||||
return ()
|
||||
return tuple(name.split('.'))
|
||||
|
||||
def py__file__(self):
|
||||
return cast_path(self.access_handle.py__file__())
|
||||
|
||||
|
||||
class CompiledName(AbstractNameDefinition):
|
||||
def __init__(self, inference_state, parent_value, name):
|
||||
self._inference_state = inference_state
|
||||
self.parent_context = parent_value.as_context()
|
||||
self._parent_value = parent_value
|
||||
self.string_name = name
|
||||
|
||||
def py__doc__(self):
|
||||
value, = self.infer()
|
||||
return value.py__doc__()
|
||||
|
||||
def _get_qualified_names(self):
|
||||
parent_qualified_names = self.parent_context.get_qualified_names()
|
||||
if parent_qualified_names is None:
|
||||
return None
|
||||
return parent_qualified_names + (self.string_name,)
|
||||
|
||||
def get_defining_qualified_value(self):
|
||||
context = self.parent_context
|
||||
if context.is_module() or context.is_class():
|
||||
return self.parent_context.get_value() # Might be None
|
||||
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
name = self.parent_context.name # __name__ is not defined all the time
|
||||
except AttributeError:
|
||||
name = None
|
||||
return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
api = self.infer()
|
||||
# If we can't find the type, assume it is an instance variable
|
||||
if not api:
|
||||
return "instance"
|
||||
return next(iter(api)).api_type
|
||||
|
||||
@memoize_method
|
||||
def infer(self):
|
||||
return ValueSet([self.infer_compiled_value()])
|
||||
|
||||
def infer_compiled_value(self):
|
||||
return create_from_name(self._inference_state, self._parent_value, self.string_name)
|
||||
|
||||
|
||||
class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
|
||||
def __init__(self, compiled_value, signature_param):
|
||||
self.parent_context = compiled_value.parent_context
|
||||
self._signature_param = signature_param
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
return self._signature_param.name
|
||||
|
||||
def to_string(self):
|
||||
s = self._kind_string() + self.string_name
|
||||
if self._signature_param.has_annotation:
|
||||
s += ': ' + self._signature_param.annotation_string
|
||||
if self._signature_param.has_default:
|
||||
s += '=' + self._signature_param.default_string
|
||||
return s
|
||||
|
||||
def get_kind(self):
|
||||
return getattr(Parameter, self._signature_param.kind_name)
|
||||
|
||||
def infer(self):
|
||||
p = self._signature_param
|
||||
inference_state = self.parent_context.inference_state
|
||||
values = NO_VALUES
|
||||
if p.has_default:
|
||||
values = ValueSet([create_from_access_path(inference_state, p.default)])
|
||||
if p.has_annotation:
|
||||
annotation = create_from_access_path(inference_state, p.annotation)
|
||||
values |= annotation.execute_with_values()
|
||||
return values
|
||||
|
||||
|
||||
class UnresolvableParamName(ParamNameInterface, AbstractNameDefinition):
|
||||
def __init__(self, compiled_value, name, default):
|
||||
self.parent_context = compiled_value.parent_context
|
||||
self.string_name = name
|
||||
self._default = default
|
||||
|
||||
def get_kind(self):
|
||||
return Parameter.POSITIONAL_ONLY
|
||||
|
||||
def to_string(self):
|
||||
string = self.string_name
|
||||
if self._default:
|
||||
string += '=' + self._default
|
||||
return string
|
||||
|
||||
def infer(self):
|
||||
return NO_VALUES
|
||||
|
||||
|
||||
class CompiledValueName(ValueNameMixin, AbstractNameDefinition):
|
||||
def __init__(self, value, name):
|
||||
self.string_name = name
|
||||
self._value = value
|
||||
self.parent_context = value.parent_context
|
||||
|
||||
|
||||
class EmptyCompiledName(AbstractNameDefinition):
|
||||
"""
|
||||
Accessing some names will raise an exception. To avoid not having any
|
||||
completions, just give Jedi the option to return this object. It infers to
|
||||
nothing.
|
||||
"""
|
||||
def __init__(self, inference_state, name):
|
||||
self.parent_context = inference_state.builtins_module
|
||||
self.string_name = name
|
||||
|
||||
def infer(self):
|
||||
return NO_VALUES
|
||||
|
||||
|
||||
class CompiledValueFilter(AbstractFilter):
|
||||
def __init__(self, inference_state, compiled_value, is_instance=False):
|
||||
self._inference_state = inference_state
|
||||
self.compiled_value = compiled_value
|
||||
self.is_instance = is_instance
|
||||
|
||||
def get(self, name):
|
||||
access_handle = self.compiled_value.access_handle
|
||||
return self._get(
|
||||
name,
|
||||
lambda name, unsafe: access_handle.is_allowed_getattr(name, unsafe),
|
||||
lambda name: name in access_handle.dir(),
|
||||
check_has_attribute=True
|
||||
)
|
||||
|
||||
def _get(self, name, allowed_getattr_callback, in_dir_callback, check_has_attribute=False):
|
||||
"""
|
||||
To remove quite a few access calls we introduced the callback here.
|
||||
"""
|
||||
# Always use unicode objects in Python 2 from here.
|
||||
name = force_unicode(name)
|
||||
|
||||
if self._inference_state.allow_descriptor_getattr:
|
||||
pass
|
||||
|
||||
has_attribute, is_descriptor = allowed_getattr_callback(
|
||||
name,
|
||||
unsafe=self._inference_state.allow_descriptor_getattr
|
||||
)
|
||||
if check_has_attribute and not has_attribute:
|
||||
return []
|
||||
|
||||
if (is_descriptor or not has_attribute) \
|
||||
and not self._inference_state.allow_descriptor_getattr:
|
||||
return [self._get_cached_name(name, is_empty=True)]
|
||||
|
||||
if self.is_instance and not in_dir_callback(name):
|
||||
return []
|
||||
return [self._get_cached_name(name)]
|
||||
|
||||
@memoize_method
|
||||
def _get_cached_name(self, name, is_empty=False):
|
||||
if is_empty:
|
||||
return EmptyCompiledName(self._inference_state, name)
|
||||
else:
|
||||
return self._create_name(name)
|
||||
|
||||
def values(self):
|
||||
from jedi.inference.compiled import builtin_from_name
|
||||
names = []
|
||||
needs_type_completions, dir_infos = self.compiled_value.access_handle.get_dir_infos()
|
||||
# We could use `unsafe` here as well, especially as a parameter to
|
||||
# get_dir_infos. But this would lead to a lot of property executions
|
||||
# that are probably not wanted. The drawback for this is that we
|
||||
# have a different name for `get` and `values`. For `get` we always
|
||||
# execute.
|
||||
for name in dir_infos:
|
||||
names += self._get(
|
||||
name,
|
||||
lambda name, unsafe: dir_infos[name],
|
||||
lambda name: name in dir_infos,
|
||||
)
|
||||
|
||||
# ``dir`` doesn't include the type names.
|
||||
if not self.is_instance and needs_type_completions:
|
||||
for filter in builtin_from_name(self._inference_state, u'type').get_filters():
|
||||
names += filter.values()
|
||||
return names
|
||||
|
||||
def _create_name(self, name):
|
||||
return CompiledName(
|
||||
self._inference_state,
|
||||
self.compiled_value,
|
||||
name
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s>" % (self.__class__.__name__, self.compiled_value)
|
||||
|
||||
|
||||
docstr_defaults = {
|
||||
'floating point number': u'float',
|
||||
'character': u'str',
|
||||
'integer': u'int',
|
||||
'dictionary': u'dict',
|
||||
'string': u'str',
|
||||
}
|
||||
|
||||
|
||||
def _parse_function_doc(doc):
|
||||
"""
|
||||
Takes a function and returns the params and return value as a tuple.
|
||||
This is nothing more than a docstring parser.
|
||||
|
||||
TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
|
||||
TODO docstrings like 'tuple of integers'
|
||||
"""
|
||||
doc = force_unicode(doc)
|
||||
# parse round parentheses: def func(a, (b,c))
|
||||
try:
|
||||
count = 0
|
||||
start = doc.index('(')
|
||||
for i, s in enumerate(doc[start:]):
|
||||
if s == '(':
|
||||
count += 1
|
||||
elif s == ')':
|
||||
count -= 1
|
||||
if count == 0:
|
||||
end = start + i
|
||||
break
|
||||
param_str = doc[start + 1:end]
|
||||
except (ValueError, UnboundLocalError):
|
||||
# ValueError for doc.index
|
||||
# UnboundLocalError for undefined end in last line
|
||||
debug.dbg('no brackets found - no param')
|
||||
end = 0
|
||||
param_str = u''
|
||||
else:
|
||||
# remove square brackets, that show an optional param ( = None)
|
||||
def change_options(m):
|
||||
args = m.group(1).split(',')
|
||||
for i, a in enumerate(args):
|
||||
if a and '=' not in a:
|
||||
args[i] += '=None'
|
||||
return ','.join(args)
|
||||
|
||||
while True:
|
||||
param_str, changes = re.subn(r' ?\[([^\[\]]+)\]',
|
||||
change_options, param_str)
|
||||
if changes == 0:
|
||||
break
|
||||
param_str = param_str.replace('-', '_') # see: isinstance.__doc__
|
||||
|
||||
# parse return value
|
||||
r = re.search(u'-[>-]* ', doc[end:end + 7])
|
||||
if r is None:
|
||||
ret = u''
|
||||
else:
|
||||
index = end + r.end()
|
||||
# get result type, which can contain newlines
|
||||
pattern = re.compile(r'(,\n|[^\n-])+')
|
||||
ret_str = pattern.match(doc, index).group(0).strip()
|
||||
# New object -> object()
|
||||
ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str)
|
||||
|
||||
ret = docstr_defaults.get(ret_str, ret_str)
|
||||
|
||||
return param_str, ret
|
||||
|
||||
|
||||
def create_from_name(inference_state, compiled_value, name):
|
||||
access_paths = compiled_value.access_handle.getattr_paths(name, default=None)
|
||||
|
||||
value = None
|
||||
for access_path in access_paths:
|
||||
value = create_cached_compiled_value(
|
||||
inference_state,
|
||||
access_path,
|
||||
parent_context=None if value is None else value.as_context(),
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def _normalize_create_args(func):
|
||||
"""The cache doesn't care about keyword vs. normal args."""
|
||||
def wrapper(inference_state, obj, parent_context=None):
|
||||
return func(inference_state, obj, parent_context)
|
||||
return wrapper
|
||||
|
||||
|
||||
def create_from_access_path(inference_state, access_path):
|
||||
value = None
|
||||
for name, access in access_path.accesses:
|
||||
value = create_cached_compiled_value(
|
||||
inference_state,
|
||||
access,
|
||||
parent_context=None if value is None else value.as_context()
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
@_normalize_create_args
|
||||
@inference_state_function_cache()
|
||||
def create_cached_compiled_value(inference_state, access_handle, parent_context):
|
||||
assert not isinstance(parent_context, CompiledValue)
|
||||
if parent_context is None:
|
||||
cls = CompiledModule
|
||||
else:
|
||||
cls = CompiledValue
|
||||
return cls(inference_state, access_handle, parent_context)
|
500
venv/Lib/site-packages/jedi/inference/context.py
Normal file
500
venv/Lib/site-packages/jedi/inference/context.py
Normal file
|
@ -0,0 +1,500 @@
|
|||
from abc import abstractmethod
|
||||
from contextlib import contextmanager
|
||||
|
||||
from parso.tree import search_ancestor
|
||||
from parso.python.tree import Name
|
||||
|
||||
from jedi.inference.filters import ParserTreeFilter, MergedFilter, \
|
||||
GlobalNameFilter
|
||||
from jedi.inference.names import AnonymousParamName, TreeNameDefinition
|
||||
from jedi.inference.base_value import NO_VALUES, ValueSet
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
from jedi import debug
|
||||
from jedi import parser_utils
|
||||
|
||||
|
||||
class AbstractContext(object):
|
||||
# Must be defined: inference_state and tree_node and parent_context as an attribute/property
|
||||
|
||||
def __init__(self, inference_state):
|
||||
self.inference_state = inference_state
|
||||
self.predefined_names = {}
|
||||
|
||||
@abstractmethod
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def goto(self, name_or_str, position):
|
||||
from jedi.inference import finder
|
||||
filters = _get_global_filters_for_name(
|
||||
self, name_or_str if isinstance(name_or_str, Name) else None, position,
|
||||
)
|
||||
names = finder.filter_name(filters, name_or_str)
|
||||
debug.dbg('context.goto %s in (%s): %s', name_or_str, self, names)
|
||||
return names
|
||||
|
||||
def py__getattribute__(self, name_or_str, name_context=None, position=None,
|
||||
analysis_errors=True):
|
||||
"""
|
||||
:param position: Position of the last statement -> tuple of line, column
|
||||
"""
|
||||
if name_context is None:
|
||||
name_context = self
|
||||
names = self.goto(name_or_str, position)
|
||||
|
||||
string_name = name_or_str.value if isinstance(name_or_str, Name) else name_or_str
|
||||
|
||||
# This paragraph is currently needed for proper branch type inference
|
||||
# (static analysis).
|
||||
found_predefined_types = None
|
||||
if self.predefined_names and isinstance(name_or_str, Name):
|
||||
node = name_or_str
|
||||
while node is not None and not parser_utils.is_scope(node):
|
||||
node = node.parent
|
||||
if node.type in ("if_stmt", "for_stmt", "comp_for", 'sync_comp_for'):
|
||||
try:
|
||||
name_dict = self.predefined_names[node]
|
||||
types = name_dict[string_name]
|
||||
except KeyError:
|
||||
continue
|
||||
else:
|
||||
found_predefined_types = types
|
||||
break
|
||||
if found_predefined_types is not None and names:
|
||||
from jedi.inference import flow_analysis
|
||||
check = flow_analysis.reachability_check(
|
||||
context=self,
|
||||
value_scope=self.tree_node,
|
||||
node=name_or_str,
|
||||
)
|
||||
if check is flow_analysis.UNREACHABLE:
|
||||
values = NO_VALUES
|
||||
else:
|
||||
values = found_predefined_types
|
||||
else:
|
||||
values = ValueSet.from_sets(name.infer() for name in names)
|
||||
|
||||
if not names and not values and analysis_errors:
|
||||
if isinstance(name_or_str, Name):
|
||||
from jedi.inference import analysis
|
||||
message = ("NameError: name '%s' is not defined." % string_name)
|
||||
analysis.add(name_context, 'name-error', name_or_str, message)
|
||||
|
||||
debug.dbg('context.names_to_types: %s -> %s', names, values)
|
||||
if values:
|
||||
return values
|
||||
return self._check_for_additional_knowledge(name_or_str, name_context, position)
|
||||
|
||||
def _check_for_additional_knowledge(self, name_or_str, name_context, position):
|
||||
name_context = name_context or self
|
||||
# Add isinstance and other if/assert knowledge.
|
||||
if isinstance(name_or_str, Name) and not name_context.is_instance():
|
||||
flow_scope = name_or_str
|
||||
base_nodes = [name_context.tree_node]
|
||||
|
||||
if any(b.type in ('comp_for', 'sync_comp_for') for b in base_nodes):
|
||||
return NO_VALUES
|
||||
from jedi.inference.finder import check_flow_information
|
||||
while True:
|
||||
flow_scope = get_parent_scope(flow_scope, include_flows=True)
|
||||
n = check_flow_information(name_context, flow_scope,
|
||||
name_or_str, position)
|
||||
if n is not None:
|
||||
return n
|
||||
if flow_scope in base_nodes:
|
||||
break
|
||||
return NO_VALUES
|
||||
|
||||
def get_root_context(self):
|
||||
parent_context = self.parent_context
|
||||
if parent_context is None:
|
||||
return self
|
||||
return parent_context.get_root_context()
|
||||
|
||||
def is_module(self):
|
||||
return False
|
||||
|
||||
def is_builtins_module(self):
|
||||
return False
|
||||
|
||||
def is_class(self):
|
||||
return False
|
||||
|
||||
def is_stub(self):
|
||||
return False
|
||||
|
||||
def is_instance(self):
|
||||
return False
|
||||
|
||||
def is_compiled(self):
|
||||
return False
|
||||
|
||||
def is_bound_method(self):
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
def py__name__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_value(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return None
|
||||
|
||||
def get_qualified_names(self):
|
||||
return ()
|
||||
|
||||
def py__doc__(self):
|
||||
return ''
|
||||
|
||||
@contextmanager
|
||||
def predefine_names(self, flow_scope, dct):
|
||||
predefined = self.predefined_names
|
||||
predefined[flow_scope] = dct
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
del predefined[flow_scope]
|
||||
|
||||
|
||||
class ValueContext(AbstractContext):
|
||||
"""
|
||||
Should be defined, otherwise the API returns empty types.
|
||||
"""
|
||||
def __init__(self, value):
|
||||
super(ValueContext, self).__init__(value.inference_state)
|
||||
self._value = value
|
||||
|
||||
@property
|
||||
def tree_node(self):
|
||||
return self._value.tree_node
|
||||
|
||||
@property
|
||||
def parent_context(self):
|
||||
return self._value.parent_context
|
||||
|
||||
def is_module(self):
|
||||
return self._value.is_module()
|
||||
|
||||
def is_builtins_module(self):
|
||||
return self._value == self.inference_state.builtins_module
|
||||
|
||||
def is_class(self):
|
||||
return self._value.is_class()
|
||||
|
||||
def is_stub(self):
|
||||
return self._value.is_stub()
|
||||
|
||||
def is_instance(self):
|
||||
return self._value.is_instance()
|
||||
|
||||
def is_compiled(self):
|
||||
return self._value.is_compiled()
|
||||
|
||||
def is_bound_method(self):
|
||||
return self._value.is_bound_method()
|
||||
|
||||
def py__name__(self):
|
||||
return self._value.py__name__()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._value.name
|
||||
|
||||
def get_qualified_names(self):
|
||||
return self._value.get_qualified_names()
|
||||
|
||||
def py__doc__(self):
|
||||
return self._value.py__doc__()
|
||||
|
||||
def get_value(self):
|
||||
return self._value
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, self._value)
|
||||
|
||||
|
||||
class TreeContextMixin(object):
|
||||
def infer_node(self, node):
|
||||
from jedi.inference.syntax_tree import infer_node
|
||||
return infer_node(self, node)
|
||||
|
||||
def create_value(self, node):
|
||||
from jedi.inference import value
|
||||
|
||||
if node == self.tree_node:
|
||||
assert self.is_module()
|
||||
return self.get_value()
|
||||
|
||||
parent_context = self.create_context(node)
|
||||
|
||||
if node.type in ('funcdef', 'lambdef'):
|
||||
func = value.FunctionValue.from_context(parent_context, node)
|
||||
if parent_context.is_class():
|
||||
class_value = parent_context.parent_context.create_value(parent_context.tree_node)
|
||||
instance = value.AnonymousInstance(
|
||||
self.inference_state, parent_context.parent_context, class_value)
|
||||
func = value.BoundMethod(
|
||||
instance=instance,
|
||||
class_context=class_value.as_context(),
|
||||
function=func
|
||||
)
|
||||
return func
|
||||
elif node.type == 'classdef':
|
||||
return value.ClassValue(self.inference_state, parent_context, node)
|
||||
else:
|
||||
raise NotImplementedError("Probably shouldn't happen: %s" % node)
|
||||
|
||||
def create_context(self, node):
|
||||
def from_scope_node(scope_node, is_nested=True):
|
||||
if scope_node == self.tree_node:
|
||||
return self
|
||||
|
||||
if scope_node.type in ('funcdef', 'lambdef', 'classdef'):
|
||||
return self.create_value(scope_node).as_context()
|
||||
elif scope_node.type in ('comp_for', 'sync_comp_for'):
|
||||
parent_scope = parser_utils.get_parent_scope(scope_node)
|
||||
parent_context = from_scope_node(parent_scope)
|
||||
if node.start_pos >= scope_node.children[-1].start_pos:
|
||||
return parent_context
|
||||
return CompForContext(parent_context, scope_node)
|
||||
raise Exception("There's a scope that was not managed: %s" % scope_node)
|
||||
|
||||
def parent_scope(node):
|
||||
while True:
|
||||
node = node.parent
|
||||
|
||||
if parser_utils.is_scope(node):
|
||||
return node
|
||||
elif node.type in ('argument', 'testlist_comp'):
|
||||
if node.children[1].type in ('comp_for', 'sync_comp_for'):
|
||||
return node.children[1]
|
||||
elif node.type == 'dictorsetmaker':
|
||||
for n in node.children[1:4]:
|
||||
# In dictionaries it can be pretty much anything.
|
||||
if n.type in ('comp_for', 'sync_comp_for'):
|
||||
return n
|
||||
|
||||
scope_node = parent_scope(node)
|
||||
if scope_node.type in ('funcdef', 'classdef'):
|
||||
colon = scope_node.children[scope_node.children.index(':')]
|
||||
if node.start_pos < colon.start_pos:
|
||||
parent = node.parent
|
||||
if not (parent.type == 'param' and parent.name == node):
|
||||
scope_node = parent_scope(scope_node)
|
||||
return from_scope_node(scope_node, is_nested=True)
|
||||
|
||||
def create_name(self, tree_name):
|
||||
definition = tree_name.get_definition()
|
||||
if definition and definition.type == 'param' and definition.name == tree_name:
|
||||
funcdef = search_ancestor(definition, 'funcdef', 'lambdef')
|
||||
func = self.create_value(funcdef)
|
||||
return AnonymousParamName(func, tree_name)
|
||||
else:
|
||||
context = self.create_context(tree_name)
|
||||
return TreeNameDefinition(context, tree_name)
|
||||
|
||||
|
||||
class FunctionContext(TreeContextMixin, ValueContext):
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
yield ParserTreeFilter(
|
||||
self.inference_state,
|
||||
parent_context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
|
||||
|
||||
class ModuleContext(TreeContextMixin, ValueContext):
|
||||
def py__file__(self):
|
||||
return self._value.py__file__()
|
||||
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
filters = self._value.get_filters(origin_scope)
|
||||
# Skip the first filter and replace it.
|
||||
next(filters, None)
|
||||
yield MergedFilter(
|
||||
ParserTreeFilter(
|
||||
parent_context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
),
|
||||
self.get_global_filter(),
|
||||
)
|
||||
for f in filters: # Python 2...
|
||||
yield f
|
||||
|
||||
def get_global_filter(self):
|
||||
return GlobalNameFilter(self, self.tree_node)
|
||||
|
||||
@property
|
||||
def string_names(self):
|
||||
return self._value.string_names
|
||||
|
||||
@property
|
||||
def code_lines(self):
|
||||
return self._value.code_lines
|
||||
|
||||
def get_value(self):
|
||||
"""
|
||||
This is the only function that converts a context back to a value.
|
||||
This is necessary for stub -> python conversion and vice versa. However
|
||||
this method shouldn't be moved to AbstractContext.
|
||||
"""
|
||||
return self._value
|
||||
|
||||
|
||||
class NamespaceContext(TreeContextMixin, ValueContext):
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
return self._value.get_filters()
|
||||
|
||||
def get_value(self):
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def string_names(self):
|
||||
return self._value.string_names
|
||||
|
||||
def py__file__(self):
|
||||
return self._value.py__file__()
|
||||
|
||||
|
||||
class ClassContext(TreeContextMixin, ValueContext):
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
yield self.get_global_filter(until_position, origin_scope)
|
||||
|
||||
def get_global_filter(self, until_position=None, origin_scope=None):
|
||||
return ParserTreeFilter(
|
||||
parent_context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
|
||||
|
||||
class CompForContext(TreeContextMixin, AbstractContext):
|
||||
def __init__(self, parent_context, comp_for):
|
||||
super(CompForContext, self).__init__(parent_context.inference_state)
|
||||
self.tree_node = comp_for
|
||||
self.parent_context = parent_context
|
||||
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
yield ParserTreeFilter(self)
|
||||
|
||||
def get_value(self):
|
||||
return None
|
||||
|
||||
def py__name__(self):
|
||||
return '<comprehension context>'
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, self.tree_node)
|
||||
|
||||
|
||||
class CompiledContext(ValueContext):
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
return self._value.get_filters()
|
||||
|
||||
|
||||
class CompiledModuleContext(CompiledContext):
|
||||
code_lines = None
|
||||
|
||||
def get_value(self):
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def string_names(self):
|
||||
return self._value.string_names
|
||||
|
||||
def py__file__(self):
|
||||
return self._value.py__file__()
|
||||
|
||||
|
||||
def _get_global_filters_for_name(context, name_or_none, position):
|
||||
# For functions and classes the defaults don't belong to the
|
||||
# function and get inferred in the value before the function. So
|
||||
# make sure to exclude the function/class name.
|
||||
if name_or_none is not None:
|
||||
ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef', 'lambdef')
|
||||
lambdef = None
|
||||
if ancestor == 'lambdef':
|
||||
# For lambdas it's even more complicated since parts will
|
||||
# be inferred later.
|
||||
lambdef = ancestor
|
||||
ancestor = search_ancestor(name_or_none, 'funcdef', 'classdef')
|
||||
if ancestor is not None:
|
||||
colon = ancestor.children[-2]
|
||||
if position is not None and position < colon.start_pos:
|
||||
if lambdef is None or position < lambdef.children[-2].start_pos:
|
||||
position = ancestor.start_pos
|
||||
|
||||
return get_global_filters(context, position, name_or_none)
|
||||
|
||||
|
||||
def get_global_filters(context, until_position, origin_scope):
|
||||
"""
|
||||
Returns all filters in order of priority for name resolution.
|
||||
|
||||
For global name lookups. The filters will handle name resolution
|
||||
themselves, but here we gather possible filters downwards.
|
||||
|
||||
>>> from jedi._compatibility import u, no_unicode_pprint
|
||||
>>> from jedi import Script
|
||||
>>> script = Script(u('''
|
||||
... x = ['a', 'b', 'c']
|
||||
... def func():
|
||||
... y = None
|
||||
... '''))
|
||||
>>> module_node = script._module_node
|
||||
>>> scope = next(module_node.iter_funcdefs())
|
||||
>>> scope
|
||||
<Function: func@3-5>
|
||||
>>> context = script._get_module_context().create_context(scope)
|
||||
>>> filters = list(get_global_filters(context, (4, 0), None))
|
||||
|
||||
First we get the names from the function scope.
|
||||
|
||||
>>> no_unicode_pprint(filters[0]) # doctest: +ELLIPSIS
|
||||
MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
|
||||
>>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE
|
||||
['<TreeNameDefinition: string_name=func start_pos=(3, 4)>',
|
||||
'<TreeNameDefinition: string_name=x start_pos=(2, 0)>']
|
||||
>>> filters[0]._filters[0]._until_position
|
||||
(4, 0)
|
||||
>>> filters[0]._filters[1]._until_position
|
||||
|
||||
Then it yields the names from one level "lower". In this example, this is
|
||||
the module scope (including globals).
|
||||
As a side note, you can see, that the position in the filter is None on the
|
||||
globals filter, because there the whole module is searched.
|
||||
|
||||
>>> list(filters[1].values()) # package modules -> Also empty.
|
||||
[]
|
||||
>>> sorted(name.string_name for name in filters[2].values()) # Module attributes
|
||||
['__doc__', '__name__', '__package__']
|
||||
|
||||
Finally, it yields the builtin filter, if `include_builtin` is
|
||||
true (default).
|
||||
|
||||
>>> list(filters[3].values()) # doctest: +ELLIPSIS
|
||||
[...]
|
||||
"""
|
||||
base_context = context
|
||||
from jedi.inference.value.function import BaseFunctionExecutionContext
|
||||
while context is not None:
|
||||
# Names in methods cannot be resolved within the class.
|
||||
for filter in context.get_filters(
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope):
|
||||
yield filter
|
||||
if isinstance(context, (BaseFunctionExecutionContext, ModuleContext)):
|
||||
# The position should be reset if the current scope is a function.
|
||||
until_position = None
|
||||
|
||||
context = context.parent_context
|
||||
|
||||
b = next(base_context.inference_state.builtins_module.get_filters(), None)
|
||||
assert b is not None
|
||||
# Add builtins to the global scope.
|
||||
yield b
|
307
venv/Lib/site-packages/jedi/inference/docstrings.py
Normal file
307
venv/Lib/site-packages/jedi/inference/docstrings.py
Normal file
|
@ -0,0 +1,307 @@
|
|||
"""
|
||||
Docstrings are another source of information for functions and classes.
|
||||
:mod:`jedi.inference.dynamic_params` tries to find all executions of functions,
|
||||
while the docstring parsing is much easier. There are three different types of
|
||||
docstrings that |jedi| understands:
|
||||
|
||||
- `Sphinx <http://sphinx-doc.org/markup/desc.html#info-field-lists>`_
|
||||
- `Epydoc <http://epydoc.sourceforge.net/manual-fields.html>`_
|
||||
- `Numpydoc <https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt>`_
|
||||
|
||||
For example, the sphinx annotation ``:type foo: str`` clearly states that the
|
||||
type of ``foo`` is ``str``.
|
||||
|
||||
As an addition to parameter searching, this module also provides return
|
||||
annotations.
|
||||
"""
|
||||
|
||||
import re
|
||||
import warnings
|
||||
from textwrap import dedent
|
||||
|
||||
from parso import parse, ParserSyntaxError
|
||||
|
||||
from jedi._compatibility import u
|
||||
from jedi import debug
|
||||
from jedi.common import indent_block
|
||||
from jedi.inference.cache import inference_state_method_cache
|
||||
from jedi.inference.base_value import iterator_to_value_set, ValueSet, \
|
||||
NO_VALUES
|
||||
from jedi.inference.lazy_value import LazyKnownValues
|
||||
|
||||
|
||||
DOCSTRING_PARAM_PATTERNS = [
|
||||
r'\s*:type\s+%s:\s*([^\n]+)', # Sphinx
|
||||
r'\s*:param\s+(\w+)\s+%s:[^\n]*', # Sphinx param with type
|
||||
r'\s*@type\s+%s:\s*([^\n]+)', # Epydoc
|
||||
]
|
||||
|
||||
DOCSTRING_RETURN_PATTERNS = [
|
||||
re.compile(r'\s*:rtype:\s*([^\n]+)', re.M), # Sphinx
|
||||
re.compile(r'\s*@rtype:\s*([^\n]+)', re.M), # Epydoc
|
||||
]
|
||||
|
||||
REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`')
|
||||
|
||||
|
||||
_numpy_doc_string_cache = None
|
||||
|
||||
|
||||
def _get_numpy_doc_string_cls():
|
||||
global _numpy_doc_string_cache
|
||||
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
|
||||
raise _numpy_doc_string_cache
|
||||
from numpydoc.docscrape import NumpyDocString
|
||||
_numpy_doc_string_cache = NumpyDocString
|
||||
return _numpy_doc_string_cache
|
||||
|
||||
|
||||
def _search_param_in_numpydocstr(docstr, param_str):
|
||||
"""Search `docstr` (in numpydoc format) for type(-s) of `param_str`."""
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
try:
|
||||
# This is a non-public API. If it ever changes we should be
|
||||
# prepared and return gracefully.
|
||||
params = _get_numpy_doc_string_cls()(docstr)._parsed_data['Parameters']
|
||||
except Exception:
|
||||
return []
|
||||
for p_name, p_type, p_descr in params:
|
||||
if p_name == param_str:
|
||||
m = re.match(r'([^,]+(,[^,]+)*?)(,[ ]*optional)?$', p_type)
|
||||
if m:
|
||||
p_type = m.group(1)
|
||||
return list(_expand_typestr(p_type))
|
||||
return []
|
||||
|
||||
|
||||
def _search_return_in_numpydocstr(docstr):
|
||||
"""
|
||||
Search `docstr` (in numpydoc format) for type(-s) of function returns.
|
||||
"""
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
try:
|
||||
doc = _get_numpy_doc_string_cls()(docstr)
|
||||
except Exception:
|
||||
return
|
||||
try:
|
||||
# This is a non-public API. If it ever changes we should be
|
||||
# prepared and return gracefully.
|
||||
returns = doc._parsed_data['Returns']
|
||||
returns += doc._parsed_data['Yields']
|
||||
except Exception:
|
||||
return
|
||||
for r_name, r_type, r_descr in returns:
|
||||
# Return names are optional and if so the type is in the name
|
||||
if not r_type:
|
||||
r_type = r_name
|
||||
for type_ in _expand_typestr(r_type):
|
||||
yield type_
|
||||
|
||||
|
||||
def _expand_typestr(type_str):
|
||||
"""
|
||||
Attempts to interpret the possible types in `type_str`
|
||||
"""
|
||||
# Check if alternative types are specified with 'or'
|
||||
if re.search(r'\bor\b', type_str):
|
||||
for t in type_str.split('or'):
|
||||
yield t.split('of')[0].strip()
|
||||
# Check if like "list of `type`" and set type to list
|
||||
elif re.search(r'\bof\b', type_str):
|
||||
yield type_str.split('of')[0]
|
||||
# Check if type has is a set of valid literal values eg: {'C', 'F', 'A'}
|
||||
elif type_str.startswith('{'):
|
||||
node = parse(type_str, version='3.7').children[0]
|
||||
if node.type == 'atom':
|
||||
for leaf in node.children[1].children:
|
||||
if leaf.type == 'number':
|
||||
if '.' in leaf.value:
|
||||
yield 'float'
|
||||
else:
|
||||
yield 'int'
|
||||
elif leaf.type == 'string':
|
||||
if 'b' in leaf.string_prefix.lower():
|
||||
yield 'bytes'
|
||||
else:
|
||||
yield 'str'
|
||||
# Ignore everything else.
|
||||
|
||||
# Otherwise just work with what we have.
|
||||
else:
|
||||
yield type_str
|
||||
|
||||
|
||||
def _search_param_in_docstr(docstr, param_str):
|
||||
"""
|
||||
Search `docstr` for type(-s) of `param_str`.
|
||||
|
||||
>>> _search_param_in_docstr(':type param: int', 'param')
|
||||
['int']
|
||||
>>> _search_param_in_docstr('@type param: int', 'param')
|
||||
['int']
|
||||
>>> _search_param_in_docstr(
|
||||
... ':type param: :class:`threading.Thread`', 'param')
|
||||
['threading.Thread']
|
||||
>>> bool(_search_param_in_docstr('no document', 'param'))
|
||||
False
|
||||
>>> _search_param_in_docstr(':param int param: some description', 'param')
|
||||
['int']
|
||||
|
||||
"""
|
||||
# look at #40 to see definitions of those params
|
||||
patterns = [re.compile(p % re.escape(param_str))
|
||||
for p in DOCSTRING_PARAM_PATTERNS]
|
||||
for pattern in patterns:
|
||||
match = pattern.search(docstr)
|
||||
if match:
|
||||
return [_strip_rst_role(match.group(1))]
|
||||
|
||||
return _search_param_in_numpydocstr(docstr, param_str)
|
||||
|
||||
|
||||
def _strip_rst_role(type_str):
|
||||
"""
|
||||
Strip off the part looks like a ReST role in `type_str`.
|
||||
|
||||
>>> _strip_rst_role(':class:`ClassName`') # strip off :class:
|
||||
'ClassName'
|
||||
>>> _strip_rst_role(':py:obj:`module.Object`') # works with domain
|
||||
'module.Object'
|
||||
>>> _strip_rst_role('ClassName') # do nothing when not ReST role
|
||||
'ClassName'
|
||||
|
||||
See also:
|
||||
http://sphinx-doc.org/domains.html#cross-referencing-python-objects
|
||||
|
||||
"""
|
||||
match = REST_ROLE_PATTERN.match(type_str)
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
return type_str
|
||||
|
||||
|
||||
def _infer_for_statement_string(module_context, string):
|
||||
code = dedent(u("""
|
||||
def pseudo_docstring_stuff():
|
||||
'''
|
||||
Create a pseudo function for docstring statements.
|
||||
Need this docstring so that if the below part is not valid Python this
|
||||
is still a function.
|
||||
'''
|
||||
{}
|
||||
"""))
|
||||
if string is None:
|
||||
return []
|
||||
|
||||
for element in re.findall(r'((?:\w+\.)*\w+)\.', string):
|
||||
# Try to import module part in dotted name.
|
||||
# (e.g., 'threading' in 'threading.Thread').
|
||||
string = 'import %s\n' % element + string
|
||||
|
||||
# Take the default grammar here, if we load the Python 2.7 grammar here, it
|
||||
# will be impossible to use `...` (Ellipsis) as a token. Docstring types
|
||||
# don't need to conform with the current grammar.
|
||||
debug.dbg('Parse docstring code %s', string, color='BLUE')
|
||||
grammar = module_context.inference_state.latest_grammar
|
||||
try:
|
||||
module = grammar.parse(code.format(indent_block(string)), error_recovery=False)
|
||||
except ParserSyntaxError:
|
||||
return []
|
||||
try:
|
||||
funcdef = next(module.iter_funcdefs())
|
||||
# First pick suite, then simple_stmt and then the node,
|
||||
# which is also not the last item, because there's a newline.
|
||||
stmt = funcdef.children[-1].children[-1].children[-2]
|
||||
except (AttributeError, IndexError):
|
||||
return []
|
||||
|
||||
if stmt.type not in ('name', 'atom', 'atom_expr'):
|
||||
return []
|
||||
|
||||
from jedi.inference.value import FunctionValue
|
||||
function_value = FunctionValue(
|
||||
module_context.inference_state,
|
||||
module_context,
|
||||
funcdef
|
||||
)
|
||||
func_execution_context = function_value.as_context()
|
||||
# Use the module of the param.
|
||||
# TODO this module is not the module of the param in case of a function
|
||||
# call. In that case it's the module of the function call.
|
||||
# stuffed with content from a function call.
|
||||
return list(_execute_types_in_stmt(func_execution_context, stmt))
|
||||
|
||||
|
||||
def _execute_types_in_stmt(module_context, stmt):
|
||||
"""
|
||||
Executing all types or general elements that we find in a statement. This
|
||||
doesn't include tuple, list and dict literals, because the stuff they
|
||||
contain is executed. (Used as type information).
|
||||
"""
|
||||
definitions = module_context.infer_node(stmt)
|
||||
return ValueSet.from_sets(
|
||||
_execute_array_values(module_context.inference_state, d)
|
||||
for d in definitions
|
||||
)
|
||||
|
||||
|
||||
def _execute_array_values(inference_state, array):
|
||||
"""
|
||||
Tuples indicate that there's not just one return value, but the listed
|
||||
ones. `(str, int)` means that it returns a tuple with both types.
|
||||
"""
|
||||
from jedi.inference.value.iterable import SequenceLiteralValue, FakeTuple, FakeList
|
||||
if isinstance(array, SequenceLiteralValue) and array.array_type in ('tuple', 'list'):
|
||||
values = []
|
||||
for lazy_value in array.py__iter__():
|
||||
objects = ValueSet.from_sets(
|
||||
_execute_array_values(inference_state, typ)
|
||||
for typ in lazy_value.infer()
|
||||
)
|
||||
values.append(LazyKnownValues(objects))
|
||||
cls = FakeTuple if array.array_type == 'tuple' else FakeList
|
||||
return {cls(inference_state, values)}
|
||||
else:
|
||||
return array.execute_annotation()
|
||||
|
||||
|
||||
@inference_state_method_cache()
|
||||
def infer_param(function_value, param):
|
||||
def infer_docstring(docstring):
|
||||
return ValueSet(
|
||||
p
|
||||
for param_str in _search_param_in_docstr(docstring, param.name.value)
|
||||
for p in _infer_for_statement_string(module_context, param_str)
|
||||
)
|
||||
module_context = function_value.get_root_context()
|
||||
func = param.get_parent_function()
|
||||
if func.type == 'lambdef':
|
||||
return NO_VALUES
|
||||
|
||||
types = infer_docstring(function_value.py__doc__())
|
||||
if function_value.is_bound_method() \
|
||||
and function_value.py__name__() == '__init__':
|
||||
types |= infer_docstring(function_value.class_context.py__doc__())
|
||||
|
||||
debug.dbg('Found param types for docstring: %s', types, color='BLUE')
|
||||
return types
|
||||
|
||||
|
||||
@inference_state_method_cache()
|
||||
@iterator_to_value_set
|
||||
def infer_return_types(function_value):
|
||||
def search_return_in_docstr(code):
|
||||
for p in DOCSTRING_RETURN_PATTERNS:
|
||||
match = p.search(code)
|
||||
if match:
|
||||
yield _strip_rst_role(match.group(1))
|
||||
# Check for numpy style return hint
|
||||
for type_ in _search_return_in_numpydocstr(code):
|
||||
yield type_
|
||||
|
||||
for type_str in search_return_in_docstr(function_value.py__doc__()):
|
||||
for value in _infer_for_statement_string(function_value.get_root_context(), type_str):
|
||||
yield value
|
226
venv/Lib/site-packages/jedi/inference/dynamic_params.py
Normal file
226
venv/Lib/site-packages/jedi/inference/dynamic_params.py
Normal file
|
@ -0,0 +1,226 @@
|
|||
"""
|
||||
One of the really important features of |jedi| is to have an option to
|
||||
understand code like this::
|
||||
|
||||
def foo(bar):
|
||||
bar. # completion here
|
||||
foo(1)
|
||||
|
||||
There's no doubt wheter bar is an ``int`` or not, but if there's also a call
|
||||
like ``foo('str')``, what would happen? Well, we'll just show both. Because
|
||||
that's what a human would expect.
|
||||
|
||||
It works as follows:
|
||||
|
||||
- |Jedi| sees a param
|
||||
- search for function calls named ``foo``
|
||||
- execute these calls and check the input.
|
||||
"""
|
||||
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
from jedi.inference.cache import inference_state_method_cache
|
||||
from jedi.inference.arguments import TreeArguments
|
||||
from jedi.inference.param import get_executed_param_names
|
||||
from jedi.inference.helpers import is_stdlib_path
|
||||
from jedi.inference.utils import to_list
|
||||
from jedi.inference.value import instance
|
||||
from jedi.inference.base_value import ValueSet, NO_VALUES
|
||||
from jedi.inference.references import get_module_contexts_containing_name
|
||||
from jedi.inference import recursion
|
||||
|
||||
|
||||
MAX_PARAM_SEARCHES = 20
|
||||
|
||||
|
||||
def _avoid_recursions(func):
|
||||
def wrapper(function_value, param_index):
|
||||
inf = function_value.inference_state
|
||||
with recursion.execution_allowed(inf, function_value.tree_node) as allowed:
|
||||
# We need to catch recursions that may occur, because an
|
||||
# anonymous functions can create an anonymous parameter that is
|
||||
# more or less self referencing.
|
||||
if allowed:
|
||||
inf.dynamic_params_depth += 1
|
||||
try:
|
||||
return func(function_value, param_index)
|
||||
finally:
|
||||
inf.dynamic_params_depth -= 1
|
||||
return NO_VALUES
|
||||
return wrapper
|
||||
|
||||
|
||||
@debug.increase_indent
|
||||
@_avoid_recursions
|
||||
def dynamic_param_lookup(function_value, param_index):
|
||||
"""
|
||||
A dynamic search for param values. If you try to complete a type:
|
||||
|
||||
>>> def func(foo):
|
||||
... foo
|
||||
>>> func(1)
|
||||
>>> func("")
|
||||
|
||||
It is not known what the type ``foo`` without analysing the whole code. You
|
||||
have to look for all calls to ``func`` to find out what ``foo`` possibly
|
||||
is.
|
||||
"""
|
||||
funcdef = function_value.tree_node
|
||||
|
||||
if not settings.dynamic_params:
|
||||
return NO_VALUES
|
||||
|
||||
path = function_value.get_root_context().py__file__()
|
||||
if path is not None and is_stdlib_path(path):
|
||||
# We don't want to search for references in the stdlib. Usually people
|
||||
# don't work with it (except if you are a core maintainer, sorry).
|
||||
# This makes everything slower. Just disable it and run the tests,
|
||||
# you will see the slowdown, especially in 3.6.
|
||||
return NO_VALUES
|
||||
|
||||
if funcdef.type == 'lambdef':
|
||||
string_name = _get_lambda_name(funcdef)
|
||||
if string_name is None:
|
||||
return NO_VALUES
|
||||
else:
|
||||
string_name = funcdef.name.value
|
||||
debug.dbg('Dynamic param search in %s.', string_name, color='MAGENTA')
|
||||
|
||||
module_context = function_value.get_root_context()
|
||||
arguments_list = _search_function_arguments(module_context, funcdef, string_name)
|
||||
values = ValueSet.from_sets(
|
||||
get_executed_param_names(
|
||||
function_value, arguments
|
||||
)[param_index].infer()
|
||||
for arguments in arguments_list
|
||||
)
|
||||
debug.dbg('Dynamic param result finished', color='MAGENTA')
|
||||
return values
|
||||
|
||||
|
||||
@inference_state_method_cache(default=None)
|
||||
@to_list
|
||||
def _search_function_arguments(module_context, funcdef, string_name):
|
||||
"""
|
||||
Returns a list of param names.
|
||||
"""
|
||||
compare_node = funcdef
|
||||
if string_name == '__init__':
|
||||
cls = get_parent_scope(funcdef)
|
||||
if cls.type == 'classdef':
|
||||
string_name = cls.name.value
|
||||
compare_node = cls
|
||||
|
||||
found_arguments = False
|
||||
i = 0
|
||||
inference_state = module_context.inference_state
|
||||
|
||||
if settings.dynamic_params_for_other_modules:
|
||||
module_contexts = get_module_contexts_containing_name(
|
||||
inference_state, [module_context], string_name,
|
||||
# Limit the amounts of files to be opened massively.
|
||||
limit_reduction=5,
|
||||
)
|
||||
else:
|
||||
module_contexts = [module_context]
|
||||
|
||||
for for_mod_context in module_contexts:
|
||||
for name, trailer in _get_potential_nodes(for_mod_context, string_name):
|
||||
i += 1
|
||||
|
||||
# This is a simple way to stop Jedi's dynamic param recursion
|
||||
# from going wild: The deeper Jedi's in the recursion, the less
|
||||
# code should be inferred.
|
||||
if i * inference_state.dynamic_params_depth > MAX_PARAM_SEARCHES:
|
||||
return
|
||||
|
||||
random_context = for_mod_context.create_context(name)
|
||||
for arguments in _check_name_for_execution(
|
||||
inference_state, random_context, compare_node, name, trailer):
|
||||
found_arguments = True
|
||||
yield arguments
|
||||
|
||||
# If there are results after processing a module, we're probably
|
||||
# good to process. This is a speed optimization.
|
||||
if found_arguments:
|
||||
return
|
||||
|
||||
|
||||
def _get_lambda_name(node):
|
||||
stmt = node.parent
|
||||
if stmt.type == 'expr_stmt':
|
||||
first_operator = next(stmt.yield_operators(), None)
|
||||
if first_operator == '=':
|
||||
first = stmt.children[0]
|
||||
if first.type == 'name':
|
||||
return first.value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_potential_nodes(module_value, func_string_name):
|
||||
try:
|
||||
names = module_value.tree_node.get_used_names()[func_string_name]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
for name in names:
|
||||
bracket = name.get_next_leaf()
|
||||
trailer = bracket.parent
|
||||
if trailer.type == 'trailer' and bracket == '(':
|
||||
yield name, trailer
|
||||
|
||||
|
||||
def _check_name_for_execution(inference_state, context, compare_node, name, trailer):
|
||||
from jedi.inference.value.function import BaseFunctionExecutionContext
|
||||
|
||||
def create_args(value):
|
||||
arglist = trailer.children[1]
|
||||
if arglist == ')':
|
||||
arglist = None
|
||||
args = TreeArguments(inference_state, context, arglist, trailer)
|
||||
from jedi.inference.value.instance import InstanceArguments
|
||||
if value.tree_node.type == 'classdef':
|
||||
created_instance = instance.TreeInstance(
|
||||
inference_state,
|
||||
value.parent_context,
|
||||
value,
|
||||
args
|
||||
)
|
||||
return InstanceArguments(created_instance, args)
|
||||
else:
|
||||
if value.is_bound_method():
|
||||
args = InstanceArguments(value.instance, args)
|
||||
return args
|
||||
|
||||
for value in inference_state.infer(context, name):
|
||||
value_node = value.tree_node
|
||||
if compare_node == value_node:
|
||||
yield create_args(value)
|
||||
elif isinstance(value.parent_context, BaseFunctionExecutionContext) \
|
||||
and compare_node.type == 'funcdef':
|
||||
# Here we're trying to find decorators by checking the first
|
||||
# parameter. It's not very generic though. Should find a better
|
||||
# solution that also applies to nested decorators.
|
||||
param_names = value.parent_context.get_param_names()
|
||||
if len(param_names) != 1:
|
||||
continue
|
||||
values = param_names[0].infer()
|
||||
if [v.tree_node for v in values] == [compare_node]:
|
||||
# Found a decorator.
|
||||
module_context = context.get_root_context()
|
||||
execution_context = value.as_context(create_args(value))
|
||||
potential_nodes = _get_potential_nodes(module_context, param_names[0].string_name)
|
||||
for name, trailer in potential_nodes:
|
||||
if value_node.start_pos < name.start_pos < value_node.end_pos:
|
||||
random_context = execution_context.create_context(name)
|
||||
iterator = _check_name_for_execution(
|
||||
inference_state,
|
||||
random_context,
|
||||
compare_node,
|
||||
name,
|
||||
trailer
|
||||
)
|
||||
for arguments in iterator:
|
||||
yield arguments
|
353
venv/Lib/site-packages/jedi/inference/filters.py
Normal file
353
venv/Lib/site-packages/jedi/inference/filters.py
Normal file
|
@ -0,0 +1,353 @@
|
|||
"""
|
||||
Filters are objects that you can use to filter names in different scopes. They
|
||||
are needed for name resolution.
|
||||
"""
|
||||
from abc import abstractmethod
|
||||
import weakref
|
||||
|
||||
from parso.tree import search_ancestor
|
||||
|
||||
from jedi._compatibility import use_metaclass
|
||||
from jedi.inference import flow_analysis
|
||||
from jedi.inference.base_value import ValueSet, ValueWrapper, \
|
||||
LazyValueWrapper
|
||||
from jedi.parser_utils import get_cached_parent_scope
|
||||
from jedi.inference.utils import to_list
|
||||
from jedi.inference.names import TreeNameDefinition, ParamName, \
|
||||
AnonymousParamName, AbstractNameDefinition
|
||||
|
||||
_definition_name_cache = weakref.WeakKeyDictionary()
|
||||
|
||||
|
||||
class AbstractFilter(object):
|
||||
_until_position = None
|
||||
|
||||
def _filter(self, names):
|
||||
if self._until_position is not None:
|
||||
return [n for n in names if n.start_pos < self._until_position]
|
||||
return names
|
||||
|
||||
@abstractmethod
|
||||
def get(self, name):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def values(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class FilterWrapper(object):
|
||||
name_wrapper_class = None
|
||||
|
||||
def __init__(self, wrapped_filter):
|
||||
self._wrapped_filter = wrapped_filter
|
||||
|
||||
def wrap_names(self, names):
|
||||
return [self.name_wrapper_class(name) for name in names]
|
||||
|
||||
def get(self, name):
|
||||
return self.wrap_names(self._wrapped_filter.get(name))
|
||||
|
||||
def values(self):
|
||||
return self.wrap_names(self._wrapped_filter.values())
|
||||
|
||||
|
||||
def _get_definition_names(used_names, name_key):
|
||||
try:
|
||||
for_module = _definition_name_cache[used_names]
|
||||
except KeyError:
|
||||
for_module = _definition_name_cache[used_names] = {}
|
||||
|
||||
try:
|
||||
return for_module[name_key]
|
||||
except KeyError:
|
||||
names = used_names.get(name_key, ())
|
||||
result = for_module[name_key] = tuple(
|
||||
name for name in names if name.is_definition(include_setitem=True)
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
class AbstractUsedNamesFilter(AbstractFilter):
|
||||
name_class = TreeNameDefinition
|
||||
|
||||
def __init__(self, parent_context, parser_scope):
|
||||
self._parser_scope = parser_scope
|
||||
self._module_node = self._parser_scope.get_root_node()
|
||||
self._used_names = self._module_node.get_used_names()
|
||||
self.parent_context = parent_context
|
||||
|
||||
def get(self, name):
|
||||
return self._convert_names(self._filter(
|
||||
_get_definition_names(self._used_names, name),
|
||||
))
|
||||
|
||||
def _convert_names(self, names):
|
||||
return [self.name_class(self.parent_context, name) for name in names]
|
||||
|
||||
def values(self):
|
||||
return self._convert_names(
|
||||
name
|
||||
for name_key in self._used_names
|
||||
for name in self._filter(
|
||||
_get_definition_names(self._used_names, name_key),
|
||||
)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.parent_context)
|
||||
|
||||
|
||||
class ParserTreeFilter(AbstractUsedNamesFilter):
|
||||
def __init__(self, parent_context, node_context=None, until_position=None,
|
||||
origin_scope=None):
|
||||
"""
|
||||
node_context is an option to specify a second value for use cases
|
||||
like the class mro where the parent class of a new name would be the
|
||||
value, but for some type inference it's important to have a local
|
||||
value of the other classes.
|
||||
"""
|
||||
if node_context is None:
|
||||
node_context = parent_context
|
||||
super(ParserTreeFilter, self).__init__(parent_context, node_context.tree_node)
|
||||
self._node_context = node_context
|
||||
self._origin_scope = origin_scope
|
||||
self._until_position = until_position
|
||||
|
||||
def _filter(self, names):
|
||||
names = super(ParserTreeFilter, self)._filter(names)
|
||||
names = [n for n in names if self._is_name_reachable(n)]
|
||||
return list(self._check_flows(names))
|
||||
|
||||
def _is_name_reachable(self, name):
|
||||
parent = name.parent
|
||||
if parent.type == 'trailer':
|
||||
return False
|
||||
base_node = parent if parent.type in ('classdef', 'funcdef') else name
|
||||
return get_cached_parent_scope(self._used_names, base_node) == self._parser_scope
|
||||
|
||||
def _check_flows(self, names):
|
||||
for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
|
||||
check = flow_analysis.reachability_check(
|
||||
context=self._node_context,
|
||||
value_scope=self._parser_scope,
|
||||
node=name,
|
||||
origin_scope=self._origin_scope
|
||||
)
|
||||
if check is not flow_analysis.UNREACHABLE:
|
||||
yield name
|
||||
|
||||
if check is flow_analysis.REACHABLE:
|
||||
break
|
||||
|
||||
|
||||
class _FunctionExecutionFilter(ParserTreeFilter):
|
||||
def __init__(self, parent_context, function_value, until_position, origin_scope):
|
||||
super(_FunctionExecutionFilter, self).__init__(
|
||||
parent_context,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope,
|
||||
)
|
||||
self._function_value = function_value
|
||||
|
||||
def _convert_param(self, param, name):
|
||||
raise NotImplementedError
|
||||
|
||||
@to_list
|
||||
def _convert_names(self, names):
|
||||
for name in names:
|
||||
param = search_ancestor(name, 'param')
|
||||
# Here we don't need to check if the param is a default/annotation,
|
||||
# because those are not definitions and never make it to this
|
||||
# point.
|
||||
if param:
|
||||
yield self._convert_param(param, name)
|
||||
else:
|
||||
yield TreeNameDefinition(self.parent_context, name)
|
||||
|
||||
|
||||
class FunctionExecutionFilter(_FunctionExecutionFilter):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._arguments = kwargs.pop('arguments') # Python 2
|
||||
super(FunctionExecutionFilter, self).__init__(*args, **kwargs)
|
||||
|
||||
def _convert_param(self, param, name):
|
||||
return ParamName(self._function_value, name, self._arguments)
|
||||
|
||||
|
||||
class AnonymousFunctionExecutionFilter(_FunctionExecutionFilter):
|
||||
def _convert_param(self, param, name):
|
||||
return AnonymousParamName(self._function_value, name)
|
||||
|
||||
|
||||
class GlobalNameFilter(AbstractUsedNamesFilter):
|
||||
def get(self, name):
|
||||
try:
|
||||
names = self._used_names[name]
|
||||
except KeyError:
|
||||
return []
|
||||
return self._convert_names(self._filter(names))
|
||||
|
||||
@to_list
|
||||
def _filter(self, names):
|
||||
for name in names:
|
||||
if name.parent.type == 'global_stmt':
|
||||
yield name
|
||||
|
||||
def values(self):
|
||||
return self._convert_names(
|
||||
name for name_list in self._used_names.values()
|
||||
for name in self._filter(name_list)
|
||||
)
|
||||
|
||||
|
||||
class DictFilter(AbstractFilter):
|
||||
def __init__(self, dct):
|
||||
self._dct = dct
|
||||
|
||||
def get(self, name):
|
||||
try:
|
||||
value = self._convert(name, self._dct[name])
|
||||
except KeyError:
|
||||
return []
|
||||
else:
|
||||
return list(self._filter([value]))
|
||||
|
||||
def values(self):
|
||||
def yielder():
|
||||
for item in self._dct.items():
|
||||
try:
|
||||
yield self._convert(*item)
|
||||
except KeyError:
|
||||
pass
|
||||
return self._filter(yielder())
|
||||
|
||||
def _convert(self, name, value):
|
||||
return value
|
||||
|
||||
def __repr__(self):
|
||||
keys = ', '.join(self._dct.keys())
|
||||
return '<%s: for {%s}>' % (self.__class__.__name__, keys)
|
||||
|
||||
|
||||
class MergedFilter(object):
|
||||
def __init__(self, *filters):
|
||||
self._filters = filters
|
||||
|
||||
def get(self, name):
|
||||
return [n for filter in self._filters for n in filter.get(name)]
|
||||
|
||||
def values(self):
|
||||
return [n for filter in self._filters for n in filter.values()]
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, ', '.join(str(f) for f in self._filters))
|
||||
|
||||
|
||||
class _BuiltinMappedMethod(ValueWrapper):
|
||||
"""``Generator.__next__`` ``dict.values`` methods and so on."""
|
||||
api_type = u'function'
|
||||
|
||||
def __init__(self, value, method, builtin_func):
|
||||
super(_BuiltinMappedMethod, self).__init__(builtin_func)
|
||||
self._value = value
|
||||
self._method = method
|
||||
|
||||
def py__call__(self, arguments):
|
||||
# TODO add TypeError if params are given/or not correct.
|
||||
return self._method(self._value, arguments)
|
||||
|
||||
|
||||
class SpecialMethodFilter(DictFilter):
|
||||
"""
|
||||
A filter for methods that are defined in this module on the corresponding
|
||||
classes like Generator (for __next__, etc).
|
||||
"""
|
||||
class SpecialMethodName(AbstractNameDefinition):
|
||||
api_type = u'function'
|
||||
|
||||
def __init__(self, parent_context, string_name, value, builtin_value):
|
||||
callable_, python_version = value
|
||||
if python_version is not None and \
|
||||
python_version != parent_context.inference_state.environment.version_info.major:
|
||||
raise KeyError
|
||||
|
||||
self.parent_context = parent_context
|
||||
self.string_name = string_name
|
||||
self._callable = callable_
|
||||
self._builtin_value = builtin_value
|
||||
|
||||
def infer(self):
|
||||
for filter in self._builtin_value.get_filters():
|
||||
# We can take the first index, because on builtin methods there's
|
||||
# always only going to be one name. The same is true for the
|
||||
# inferred values.
|
||||
for name in filter.get(self.string_name):
|
||||
builtin_func = next(iter(name.infer()))
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
return ValueSet([
|
||||
_BuiltinMappedMethod(self.parent_context, self._callable, builtin_func)
|
||||
])
|
||||
|
||||
def __init__(self, value, dct, builtin_value):
|
||||
super(SpecialMethodFilter, self).__init__(dct)
|
||||
self.value = value
|
||||
self._builtin_value = builtin_value
|
||||
"""
|
||||
This value is what will be used to introspect the name, where as the
|
||||
other value will be used to execute the function.
|
||||
|
||||
We distinguish, because we have to.
|
||||
"""
|
||||
|
||||
def _convert(self, name, value):
|
||||
return self.SpecialMethodName(self.value, name, value, self._builtin_value)
|
||||
|
||||
|
||||
class _OverwriteMeta(type):
|
||||
def __init__(cls, name, bases, dct):
|
||||
super(_OverwriteMeta, cls).__init__(name, bases, dct)
|
||||
|
||||
base_dct = {}
|
||||
for base_cls in reversed(cls.__bases__):
|
||||
try:
|
||||
base_dct.update(base_cls.overwritten_methods)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
for func in cls.__dict__.values():
|
||||
try:
|
||||
base_dct.update(func.registered_overwritten_methods)
|
||||
except AttributeError:
|
||||
pass
|
||||
cls.overwritten_methods = base_dct
|
||||
|
||||
|
||||
class _AttributeOverwriteMixin(object):
|
||||
def get_filters(self, *args, **kwargs):
|
||||
yield SpecialMethodFilter(self, self.overwritten_methods, self._wrapped_value)
|
||||
|
||||
for filter in self._wrapped_value.get_filters(*args, **kwargs):
|
||||
yield filter
|
||||
|
||||
|
||||
class LazyAttributeOverwrite(use_metaclass(_OverwriteMeta, _AttributeOverwriteMixin,
|
||||
LazyValueWrapper)):
|
||||
def __init__(self, inference_state):
|
||||
self.inference_state = inference_state
|
||||
|
||||
|
||||
class AttributeOverwrite(use_metaclass(_OverwriteMeta, _AttributeOverwriteMixin,
|
||||
ValueWrapper)):
|
||||
pass
|
||||
|
||||
|
||||
def publish_method(method_name, python_version_match=None):
|
||||
def decorator(func):
|
||||
dct = func.__dict__.setdefault('registered_overwritten_methods', {})
|
||||
dct[method_name] = func, python_version_match
|
||||
return func
|
||||
return decorator
|
146
venv/Lib/site-packages/jedi/inference/finder.py
Normal file
146
venv/Lib/site-packages/jedi/inference/finder.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
"""
|
||||
Searching for names with given scope and name. This is very central in Jedi and
|
||||
Python. The name resolution is quite complicated with descripter,
|
||||
``__getattribute__``, ``__getattr__``, ``global``, etc.
|
||||
|
||||
If you want to understand name resolution, please read the first few chapters
|
||||
in http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/.
|
||||
|
||||
Flow checks
|
||||
+++++++++++
|
||||
|
||||
Flow checks are not really mature. There's only a check for ``isinstance``. It
|
||||
would check whether a flow has the form of ``if isinstance(a, type_or_tuple)``.
|
||||
Unfortunately every other thing is being ignored (e.g. a == '' would be easy to
|
||||
check for -> a is a string). There's big potential in these checks.
|
||||
"""
|
||||
|
||||
from parso.tree import search_ancestor
|
||||
from parso.python.tree import Name
|
||||
|
||||
from jedi import settings
|
||||
from jedi.inference.arguments import TreeArguments
|
||||
from jedi.inference.value import iterable
|
||||
from jedi.inference.base_value import NO_VALUES
|
||||
from jedi.parser_utils import is_scope
|
||||
|
||||
|
||||
def filter_name(filters, name_or_str):
|
||||
"""
|
||||
Searches names that are defined in a scope (the different
|
||||
``filters``), until a name fits.
|
||||
"""
|
||||
string_name = name_or_str.value if isinstance(name_or_str, Name) else name_or_str
|
||||
names = []
|
||||
for filter in filters:
|
||||
names = filter.get(string_name)
|
||||
if names:
|
||||
break
|
||||
|
||||
return list(_remove_del_stmt(names))
|
||||
|
||||
|
||||
def _remove_del_stmt(names):
|
||||
# Catch del statements and remove them from results.
|
||||
for name in names:
|
||||
if name.tree_name is not None:
|
||||
definition = name.tree_name.get_definition()
|
||||
if definition is not None and definition.type == 'del_stmt':
|
||||
continue
|
||||
yield name
|
||||
|
||||
|
||||
def check_flow_information(value, flow, search_name, pos):
|
||||
""" Try to find out the type of a variable just with the information that
|
||||
is given by the flows: e.g. It is also responsible for assert checks.::
|
||||
|
||||
if isinstance(k, str):
|
||||
k. # <- completion here
|
||||
|
||||
ensures that `k` is a string.
|
||||
"""
|
||||
if not settings.dynamic_flow_information:
|
||||
return None
|
||||
|
||||
result = None
|
||||
if is_scope(flow):
|
||||
# Check for asserts.
|
||||
module_node = flow.get_root_node()
|
||||
try:
|
||||
names = module_node.get_used_names()[search_name.value]
|
||||
except KeyError:
|
||||
return None
|
||||
names = reversed([
|
||||
n for n in names
|
||||
if flow.start_pos <= n.start_pos < (pos or flow.end_pos)
|
||||
])
|
||||
|
||||
for name in names:
|
||||
ass = search_ancestor(name, 'assert_stmt')
|
||||
if ass is not None:
|
||||
result = _check_isinstance_type(value, ass.assertion, search_name)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
if flow.type in ('if_stmt', 'while_stmt'):
|
||||
potential_ifs = [c for c in flow.children[1::4] if c != ':']
|
||||
for if_test in reversed(potential_ifs):
|
||||
if search_name.start_pos > if_test.end_pos:
|
||||
return _check_isinstance_type(value, if_test, search_name)
|
||||
return result
|
||||
|
||||
|
||||
def _get_isinstance_trailer_arglist(node):
|
||||
if node.type in ('power', 'atom_expr') and len(node.children) == 2:
|
||||
# This might be removed if we analyze and, etc
|
||||
first, trailer = node.children
|
||||
if first.type == 'name' and first.value == 'isinstance' \
|
||||
and trailer.type == 'trailer' and trailer.children[0] == '(':
|
||||
return trailer
|
||||
return None
|
||||
|
||||
|
||||
def _check_isinstance_type(value, node, search_name):
|
||||
lazy_cls = None
|
||||
trailer = _get_isinstance_trailer_arglist(node)
|
||||
if trailer is not None and len(trailer.children) == 3:
|
||||
arglist = trailer.children[1]
|
||||
args = TreeArguments(value.inference_state, value, arglist, trailer)
|
||||
param_list = list(args.unpack())
|
||||
# Disallow keyword arguments
|
||||
if len(param_list) == 2 and len(arglist.children) == 3:
|
||||
(key1, _), (key2, lazy_value_cls) = param_list
|
||||
if key1 is None and key2 is None:
|
||||
call = _get_call_string(search_name)
|
||||
is_instance_call = _get_call_string(arglist.children[0])
|
||||
# Do a simple get_code comparison of the strings . They should
|
||||
# just have the same code, and everything will be all right.
|
||||
# There are ways that this is not correct, if some stuff is
|
||||
# redefined in between. However here we don't care, because
|
||||
# it's a heuristic that works pretty well.
|
||||
if call == is_instance_call:
|
||||
lazy_cls = lazy_value_cls
|
||||
if lazy_cls is None:
|
||||
return None
|
||||
|
||||
value_set = NO_VALUES
|
||||
for cls_or_tup in lazy_cls.infer():
|
||||
if isinstance(cls_or_tup, iterable.Sequence) and cls_or_tup.array_type == 'tuple':
|
||||
for lazy_value in cls_or_tup.py__iter__():
|
||||
value_set |= lazy_value.infer().execute_with_values()
|
||||
else:
|
||||
value_set |= cls_or_tup.execute_with_values()
|
||||
return value_set
|
||||
|
||||
|
||||
def _get_call_string(node):
|
||||
if node.parent.type == 'atom_expr':
|
||||
return _get_call_string(node.parent)
|
||||
|
||||
code = ''
|
||||
leaf = node.get_first_leaf()
|
||||
end = node.get_last_leaf().end_pos
|
||||
while leaf.start_pos < end:
|
||||
code += leaf.value
|
||||
leaf = leaf.get_next_leaf()
|
||||
return code
|
123
venv/Lib/site-packages/jedi/inference/flow_analysis.py
Normal file
123
venv/Lib/site-packages/jedi/inference/flow_analysis.py
Normal file
|
@ -0,0 +1,123 @@
|
|||
from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
|
||||
from jedi.inference.recursion import execution_allowed
|
||||
from jedi.inference.helpers import is_big_annoying_library
|
||||
|
||||
|
||||
class Status(object):
|
||||
lookup_table = {}
|
||||
|
||||
def __init__(self, value, name):
|
||||
self._value = value
|
||||
self._name = name
|
||||
Status.lookup_table[value] = self
|
||||
|
||||
def invert(self):
|
||||
if self is REACHABLE:
|
||||
return UNREACHABLE
|
||||
elif self is UNREACHABLE:
|
||||
return REACHABLE
|
||||
else:
|
||||
return UNSURE
|
||||
|
||||
def __and__(self, other):
|
||||
if UNSURE in (self, other):
|
||||
return UNSURE
|
||||
else:
|
||||
return REACHABLE if self._value and other._value else UNREACHABLE
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self._name)
|
||||
|
||||
|
||||
REACHABLE = Status(True, 'reachable')
|
||||
UNREACHABLE = Status(False, 'unreachable')
|
||||
UNSURE = Status(None, 'unsure')
|
||||
|
||||
|
||||
def _get_flow_scopes(node):
|
||||
while True:
|
||||
node = get_parent_scope(node, include_flows=True)
|
||||
if node is None or is_scope(node):
|
||||
return
|
||||
yield node
|
||||
|
||||
|
||||
def reachability_check(context, value_scope, node, origin_scope=None):
|
||||
if is_big_annoying_library(context) \
|
||||
or not context.inference_state.flow_analysis_enabled:
|
||||
return UNSURE
|
||||
|
||||
first_flow_scope = get_parent_scope(node, include_flows=True)
|
||||
if origin_scope is not None:
|
||||
origin_flow_scopes = list(_get_flow_scopes(origin_scope))
|
||||
node_flow_scopes = list(_get_flow_scopes(node))
|
||||
|
||||
branch_matches = True
|
||||
for flow_scope in origin_flow_scopes:
|
||||
if flow_scope in node_flow_scopes:
|
||||
node_keyword = get_flow_branch_keyword(flow_scope, node)
|
||||
origin_keyword = get_flow_branch_keyword(flow_scope, origin_scope)
|
||||
branch_matches = node_keyword == origin_keyword
|
||||
if flow_scope.type == 'if_stmt':
|
||||
if not branch_matches:
|
||||
return UNREACHABLE
|
||||
elif flow_scope.type == 'try_stmt':
|
||||
if not branch_matches and origin_keyword == 'else' \
|
||||
and node_keyword == 'except':
|
||||
return UNREACHABLE
|
||||
if branch_matches:
|
||||
break
|
||||
|
||||
# Direct parents get resolved, we filter scopes that are separate
|
||||
# branches. This makes sense for autocompletion and static analysis.
|
||||
# For actual Python it doesn't matter, because we're talking about
|
||||
# potentially unreachable code.
|
||||
# e.g. `if 0:` would cause all name lookup within the flow make
|
||||
# unaccessible. This is not a "problem" in Python, because the code is
|
||||
# never called. In Jedi though, we still want to infer types.
|
||||
while origin_scope is not None:
|
||||
if first_flow_scope == origin_scope and branch_matches:
|
||||
return REACHABLE
|
||||
origin_scope = origin_scope.parent
|
||||
|
||||
return _break_check(context, value_scope, first_flow_scope, node)
|
||||
|
||||
|
||||
def _break_check(context, value_scope, flow_scope, node):
|
||||
reachable = REACHABLE
|
||||
if flow_scope.type == 'if_stmt':
|
||||
if flow_scope.is_node_after_else(node):
|
||||
for check_node in flow_scope.get_test_nodes():
|
||||
reachable = _check_if(context, check_node)
|
||||
if reachable in (REACHABLE, UNSURE):
|
||||
break
|
||||
reachable = reachable.invert()
|
||||
else:
|
||||
flow_node = flow_scope.get_corresponding_test_node(node)
|
||||
if flow_node is not None:
|
||||
reachable = _check_if(context, flow_node)
|
||||
elif flow_scope.type in ('try_stmt', 'while_stmt'):
|
||||
return UNSURE
|
||||
|
||||
# Only reachable branches need to be examined further.
|
||||
if reachable in (UNREACHABLE, UNSURE):
|
||||
return reachable
|
||||
|
||||
if value_scope != flow_scope and value_scope != flow_scope.parent:
|
||||
flow_scope = get_parent_scope(flow_scope, include_flows=True)
|
||||
return reachable & _break_check(context, value_scope, flow_scope, node)
|
||||
else:
|
||||
return reachable
|
||||
|
||||
|
||||
def _check_if(context, node):
|
||||
with execution_allowed(context.inference_state, node) as allowed:
|
||||
if not allowed:
|
||||
return UNSURE
|
||||
|
||||
types = context.infer_node(node)
|
||||
values = set(x.py__bool__() for x in types)
|
||||
if len(values) == 1:
|
||||
return Status.lookup_table[values.pop()]
|
||||
else:
|
||||
return UNSURE
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue