mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-21 21:11:13 +08:00
Merge branch 'master' of github.com:davidhalter/jedi
This commit is contained in:
@@ -3,7 +3,9 @@ To ensure compatibility from Python ``2.7`` - ``3.x``, a module has been
|
|||||||
created. Clearly there is huge need to use conforming syntax.
|
created. Clearly there is huge need to use conforming syntax.
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
import atexit
|
||||||
import errno
|
import errno
|
||||||
|
import functools
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -11,6 +13,7 @@ import pkgutil
|
|||||||
import warnings
|
import warnings
|
||||||
import inspect
|
import inspect
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import weakref
|
||||||
try:
|
try:
|
||||||
import importlib
|
import importlib
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -635,3 +638,49 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
|||||||
if _access_check(name, mode):
|
if _access_check(name, mode):
|
||||||
return name
|
return name
|
||||||
return None
|
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
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
"""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(None)
|
||||||
|
except Exception:
|
||||||
|
sys.excepthook(*sys.exc_info())
|
||||||
|
assert finalizer not in cls._registry
|
||||||
|
|
||||||
|
atexit.register(finalize._exitfunc)
|
||||||
|
weakref.finalize = finalize
|
||||||
|
|||||||
@@ -102,6 +102,7 @@ class BaseDefinition(object):
|
|||||||
to Jedi, :meth:`jedi.Script.goto_definitions` should return a list of
|
to Jedi, :meth:`jedi.Script.goto_definitions` should return a list of
|
||||||
definition for ``sys``, ``f``, ``C`` and ``x``.
|
definition for ``sys``, ``f``, ``C`` and ``x``.
|
||||||
|
|
||||||
|
>>> from jedi._compatibility import no_unicode_pprint
|
||||||
>>> from jedi import Script
|
>>> from jedi import Script
|
||||||
>>> source = '''
|
>>> source = '''
|
||||||
... import keyword
|
... import keyword
|
||||||
@@ -127,9 +128,11 @@ class BaseDefinition(object):
|
|||||||
so that it is easy to relate the result to the source code.
|
so that it is easy to relate the result to the source code.
|
||||||
|
|
||||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||||
>>> defs # doctest: +NORMALIZE_WHITESPACE
|
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
|
||||||
[<Definition module keyword>, <Definition class C>,
|
[<Definition full_name='keyword', description='module keyword'>,
|
||||||
<Definition instance D>, <Definition def f>]
|
<Definition full_name='__main__.C', description='class C'>,
|
||||||
|
<Definition full_name='__main__.D', description='instance D'>,
|
||||||
|
<Definition full_name='__main__.f', description='def f'>]
|
||||||
|
|
||||||
Finally, here is what you can get from :attr:`type`:
|
Finally, here is what you can get from :attr:`type`:
|
||||||
|
|
||||||
@@ -207,7 +210,7 @@ class BaseDefinition(object):
|
|||||||
>>> source = 'import json'
|
>>> source = 'import json'
|
||||||
>>> script = Script(source, path='example.py')
|
>>> script = Script(source, path='example.py')
|
||||||
>>> d = script.goto_definitions()[0]
|
>>> d = script.goto_definitions()[0]
|
||||||
>>> print(d.module_name) # doctest: +ELLIPSIS
|
>>> print(d.module_name) # doctest: +ELLIPSIS
|
||||||
json
|
json
|
||||||
"""
|
"""
|
||||||
return self._get_module().name.string_name
|
return self._get_module().name.string_name
|
||||||
@@ -515,6 +518,7 @@ class Definition(BaseDefinition):
|
|||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
>>> from jedi._compatibility import no_unicode_pprint
|
||||||
>>> from jedi import Script
|
>>> from jedi import Script
|
||||||
>>> source = '''
|
>>> source = '''
|
||||||
... def f():
|
... def f():
|
||||||
@@ -527,8 +531,9 @@ class Definition(BaseDefinition):
|
|||||||
>>> script = Script(source, column=3) # line is maximum by default
|
>>> script = Script(source, column=3) # line is maximum by default
|
||||||
>>> defs = script.goto_definitions()
|
>>> defs = script.goto_definitions()
|
||||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||||
>>> defs
|
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
|
||||||
[<Definition def f>, <Definition class C>]
|
[<Definition full_name='__main__.f', description='def f'>,
|
||||||
|
<Definition full_name='__main__.C', description='class C'>]
|
||||||
>>> str(defs[0].description) # strip literals in python2
|
>>> str(defs[0].description) # strip literals in python2
|
||||||
'def f'
|
'def f'
|
||||||
>>> str(defs[1].description)
|
>>> str(defs[1].description)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import sys
|
|||||||
import subprocess
|
import subprocess
|
||||||
import socket
|
import socket
|
||||||
import errno
|
import errno
|
||||||
import weakref
|
|
||||||
import traceback
|
import traceback
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
@@ -22,7 +21,7 @@ except ImportError:
|
|||||||
from Queue import Queue, Empty # python 2.7
|
from Queue import Queue, Empty # python 2.7
|
||||||
|
|
||||||
from jedi._compatibility import queue, is_py3, force_unicode, \
|
from jedi._compatibility import queue, is_py3, force_unicode, \
|
||||||
pickle_dump, pickle_load, GeneralizedPopen
|
pickle_dump, pickle_load, GeneralizedPopen, weakref
|
||||||
from jedi import debug
|
from jedi import debug
|
||||||
from jedi.cache import memoize_method
|
from jedi.cache import memoize_method
|
||||||
from jedi.evaluate.compiled.subprocess import functions
|
from jedi.evaluate.compiled.subprocess import functions
|
||||||
@@ -37,7 +36,6 @@ _MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py')
|
|||||||
def _enqueue_output(out, queue):
|
def _enqueue_output(out, queue):
|
||||||
for line in iter(out.readline, b''):
|
for line in iter(out.readline, b''):
|
||||||
queue.put(line)
|
queue.put(line)
|
||||||
out.close()
|
|
||||||
|
|
||||||
|
|
||||||
def _add_stderr_to_debug(stderr_queue):
|
def _add_stderr_to_debug(stderr_queue):
|
||||||
@@ -56,6 +54,22 @@ def _get_function(name):
|
|||||||
return getattr(functions, 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 _EvaluatorProcess(object):
|
class _EvaluatorProcess(object):
|
||||||
def __init__(self, evaluator):
|
def __init__(self, evaluator):
|
||||||
self._evaluator_weakref = weakref.ref(evaluator)
|
self._evaluator_weakref = weakref.ref(evaluator)
|
||||||
@@ -145,6 +159,7 @@ class CompiledSubprocess(object):
|
|||||||
def __init__(self, executable):
|
def __init__(self, executable):
|
||||||
self._executable = executable
|
self._executable = executable
|
||||||
self._evaluator_deletion_queue = queue.deque()
|
self._evaluator_deletion_queue = queue.deque()
|
||||||
|
self._cleanup_callable = lambda: None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
@@ -182,6 +197,12 @@ class CompiledSubprocess(object):
|
|||||||
)
|
)
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
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
|
return process
|
||||||
|
|
||||||
def run(self, evaluator, function, args=(), kwargs={}):
|
def run(self, evaluator, function, args=(), kwargs={}):
|
||||||
@@ -202,18 +223,7 @@ class CompiledSubprocess(object):
|
|||||||
|
|
||||||
def _kill(self):
|
def _kill(self):
|
||||||
self.is_crashed = True
|
self.is_crashed = True
|
||||||
try:
|
self._cleanup_callable()
|
||||||
self._get_process().kill()
|
|
||||||
self._get_process().wait()
|
|
||||||
except (AttributeError, TypeError):
|
|
||||||
# If the Python process is terminating, it will remove some modules
|
|
||||||
# earlier than others and in general it's unclear how to deal with
|
|
||||||
# that so we just ignore the exceptions here.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
if not self.is_crashed:
|
|
||||||
self._kill()
|
|
||||||
|
|
||||||
def _send(self, evaluator_id, function, args=(), kwargs={}):
|
def _send(self, evaluator_id, function, args=(), kwargs={}):
|
||||||
if self.is_crashed:
|
if self.is_crashed:
|
||||||
|
|||||||
@@ -359,10 +359,11 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
|
|||||||
|
|
||||||
First we get the names from the function scope.
|
First we get the names from the function scope.
|
||||||
|
|
||||||
>>> no_unicode_pprint(filters[0]) #doctest: +ELLIPSIS
|
>>> no_unicode_pprint(filters[0]) # doctest: +ELLIPSIS
|
||||||
MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
|
MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
|
||||||
>>> sorted(str(n) for n in filters[0].values())
|
>>> sorted(str(n) for n in filters[0].values()) # doctest: +NORMALIZE_WHITESPACE
|
||||||
['<TreeNameDefinition: func@(3, 4)>', '<TreeNameDefinition: x@(2, 0)>']
|
['<TreeNameDefinition: string_name=func start_pos=(3, 4)>',
|
||||||
|
'<TreeNameDefinition: string_name=x start_pos=(2, 0)>']
|
||||||
>>> filters[0]._filters[0]._until_position
|
>>> filters[0]._filters[0]._until_position
|
||||||
(4, 0)
|
(4, 0)
|
||||||
>>> filters[0]._filters[1]._until_position
|
>>> filters[0]._filters[1]._until_position
|
||||||
@@ -380,7 +381,7 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
|
|||||||
Finally, it yields the builtin filter, if `include_builtin` is
|
Finally, it yields the builtin filter, if `include_builtin` is
|
||||||
true (default).
|
true (default).
|
||||||
|
|
||||||
>>> list(filters[3].values()) #doctest: +ELLIPSIS
|
>>> list(filters[3].values()) # doctest: +ELLIPSIS
|
||||||
[...]
|
[...]
|
||||||
"""
|
"""
|
||||||
from jedi.evaluate.context.function import FunctionExecutionContext
|
from jedi.evaluate.context.function import FunctionExecutionContext
|
||||||
|
|||||||
@@ -13,14 +13,20 @@ else:
|
|||||||
TestCase = unittest.TestCase
|
TestCase = unittest.TestCase
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import pytest
|
||||||
from os.path import abspath, dirname, join
|
from os.path import abspath, dirname, join
|
||||||
import functools
|
from functools import partial, wraps
|
||||||
|
|
||||||
test_dir = dirname(abspath(__file__))
|
test_dir = dirname(abspath(__file__))
|
||||||
root_dir = dirname(test_dir)
|
root_dir = dirname(test_dir)
|
||||||
|
|
||||||
sample_int = 1 # This is used in completion/imports.py
|
sample_int = 1 # This is used in completion/imports.py
|
||||||
|
|
||||||
|
skip_if_windows = partial(pytest.param,
|
||||||
|
marks=pytest.mark.skipif("sys.platform=='win32'"))
|
||||||
|
skip_if_not_windows = partial(pytest.param,
|
||||||
|
marks=pytest.mark.skipif("sys.platform!='win32'"))
|
||||||
|
|
||||||
|
|
||||||
def get_example_dir(name):
|
def get_example_dir(name):
|
||||||
return join(test_dir, 'examples', name)
|
return join(test_dir, 'examples', name)
|
||||||
@@ -34,7 +40,7 @@ def cwd_at(path):
|
|||||||
:arg path: relative path from repository root (e.g., ``'jedi'``).
|
:arg path: relative path from repository root (e.g., ``'jedi'``).
|
||||||
"""
|
"""
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
@functools.wraps(func)
|
@wraps(func)
|
||||||
def wrapper(Script, **kwargs):
|
def wrapper(Script, **kwargs):
|
||||||
with set_cwd(path):
|
with set_cwd(path):
|
||||||
return func(Script, **kwargs)
|
return func(Script, **kwargs)
|
||||||
|
|||||||
@@ -357,9 +357,11 @@ def collect_dir_tests(base_dir, test_files, check_thirdparty=False):
|
|||||||
path = os.path.join(base_dir, f_name)
|
path = os.path.join(base_dir, f_name)
|
||||||
|
|
||||||
if is_py3:
|
if is_py3:
|
||||||
source = open(path, encoding='utf-8').read()
|
with open(path, encoding='utf-8') as f:
|
||||||
|
source = f.read()
|
||||||
else:
|
else:
|
||||||
source = unicode(open(path).read(), 'UTF-8')
|
with open(path) as f:
|
||||||
|
source = unicode(f.read(), 'UTF-8')
|
||||||
|
|
||||||
for case in collect_file_tests(path, StringIO(source),
|
for case in collect_file_tests(path, StringIO(source),
|
||||||
lines_to_execute):
|
lines_to_execute):
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ def test_find_module_not_package():
|
|||||||
assert is_package is False
|
assert is_package is False
|
||||||
|
|
||||||
|
|
||||||
pkg_zip_path = os.path.join(os.path.dirname(__file__), 'zipped_imports/pkg.zip')
|
pkg_zip_path = os.path.join(os.path.dirname(__file__),
|
||||||
|
'zipped_imports',
|
||||||
|
'pkg.zip')
|
||||||
|
|
||||||
|
|
||||||
def test_find_module_package_zipped(Script, evaluator, environment):
|
def test_find_module_package_zipped(Script, evaluator, environment):
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import sys
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from ..helpers import skip_if_windows, skip_if_not_windows
|
||||||
|
|
||||||
from jedi.evaluate import sys_path
|
from jedi.evaluate import sys_path
|
||||||
from jedi.api.environment import create_environment
|
from jedi.api.environment import create_environment
|
||||||
@@ -87,8 +88,12 @@ _s = ['/a', '/b', '/c/d/']
|
|||||||
|
|
||||||
(['/foo'], '/foo/bar/__init__.py', ('bar',), True),
|
(['/foo'], '/foo/bar/__init__.py', ('bar',), True),
|
||||||
(['/foo'], '/foo/bar/baz/__init__.py', ('bar', 'baz'), True),
|
(['/foo'], '/foo/bar/baz/__init__.py', ('bar', 'baz'), True),
|
||||||
(['/foo'], '/foo/bar.so', ('bar',), False),
|
|
||||||
(['/foo'], '/foo/bar/__init__.so', ('bar',), True),
|
skip_if_windows(['/foo'], '/foo/bar.so', ('bar',), False),
|
||||||
|
skip_if_windows(['/foo'], '/foo/bar/__init__.so', ('bar',), True),
|
||||||
|
skip_if_not_windows(['/foo'], '/foo/bar.pyd', ('bar',), False),
|
||||||
|
skip_if_not_windows(['/foo'], '/foo/bar/__init__.pyd', ('bar',), True),
|
||||||
|
|
||||||
(['/foo'], '/x/bar.py', None, False),
|
(['/foo'], '/x/bar.py', None, False),
|
||||||
(['/foo'], '/foo/bar.xyz', ('bar.xyz',), False),
|
(['/foo'], '/foo/bar.xyz', ('bar.xyz',), False),
|
||||||
|
|
||||||
|
|||||||
4
tox.ini
4
tox.ini
@@ -8,6 +8,8 @@ deps =
|
|||||||
py34: typing
|
py34: typing
|
||||||
# numpydoc for typing scipy stack
|
# numpydoc for typing scipy stack
|
||||||
numpydoc
|
numpydoc
|
||||||
|
# sphinx, a dependency of numpydoc, dropped Python 2 support in version 2.0
|
||||||
|
sphinx < 2.0
|
||||||
cov: coverage
|
cov: coverage
|
||||||
# Overwrite the parso version (only used sometimes).
|
# Overwrite the parso version (only used sometimes).
|
||||||
# git+https://github.com/davidhalter/parso.git
|
# git+https://github.com/davidhalter/parso.git
|
||||||
@@ -16,6 +18,8 @@ setenv =
|
|||||||
# https://github.com/tomchristie/django-rest-framework/issues/1957
|
# https://github.com/tomchristie/django-rest-framework/issues/1957
|
||||||
# tox corrupts __pycache__, solution from here:
|
# tox corrupts __pycache__, solution from here:
|
||||||
PYTHONDONTWRITEBYTECODE=1
|
PYTHONDONTWRITEBYTECODE=1
|
||||||
|
# Enable all warnings.
|
||||||
|
PYTHONWARNINGS=always
|
||||||
# To test Jedi in different versions than the same Python version, set a
|
# To test Jedi in different versions than the same Python version, set a
|
||||||
# different test environment.
|
# different test environment.
|
||||||
env27: JEDI_TEST_ENVIRONMENT=27
|
env27: JEDI_TEST_ENVIRONMENT=27
|
||||||
|
|||||||
Reference in New Issue
Block a user