From 96149d2e6a10407c76459711c1d1eef4da8d3127 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Tue, 14 Nov 2017 18:25:37 +0100 Subject: [PATCH] Make it possible to connect to a subprocess to get the sys path --- jedi/api/__init__.py | 16 +++---- jedi/api/virtualenv.py | 43 ++++++------------- jedi/evaluate/compiled/subprocess/__init__.py | 30 +++++++++---- jedi/evaluate/compiled/subprocess/__main__.py | 2 +- .../evaluate/compiled/subprocess/functions.py | 5 +++ jedi/evaluate/project.py | 2 - test/test_api/test_environment.py | 5 +++ 7 files changed, 51 insertions(+), 52 deletions(-) create mode 100644 jedi/evaluate/compiled/subprocess/functions.py create mode 100644 test/test_api/test_environment.py diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index a98f53a4..30bec3b9 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -24,6 +24,7 @@ from jedi.api import classes from jedi.api import interpreter from jedi.api import helpers from jedi.api.completion import Completion +from jedi.api.virtualenv import DefaultEnvironment from jedi.evaluate import Evaluator from jedi.evaluate import imports from jedi.evaluate import usages @@ -117,22 +118,15 @@ class Script(object): project = Project(sys_path=sys_path) if isinstance(self, Interpreter): # It's not possible to use a subprocess for the interpreter. - self._compiled_subprocess = None + compiled_subprocess = None else: if environment is None: - executable = sys.executable - else: - executable = environment.executable - self._compiled_subprocess = get_subprocess(executable) - self._evaluator = Evaluator(self._grammar, project, self._compiled_subprocess) + environment = DefaultEnvironment() + compiled_subprocess = environment.get_subprocess() + self._evaluator = Evaluator(self._grammar, project, compiled_subprocess) project.add_script_path(self.path) debug.speed('init') - def __del__(self): - if self._compiled_subprocess is not None: - self._compiled_subprocess.delete_evaluator(evaluator) - self._evaluator.cleanup_evaluator - @cache.memoize_method def _get_module_node(self): return self._grammar.parse( diff --git a/jedi/api/virtualenv.py b/jedi/api/virtualenv.py index d2043b20..f347cf15 100644 --- a/jedi/api/virtualenv.py +++ b/jedi/api/virtualenv.py @@ -1,5 +1,6 @@ import os import re +import sys import sysconfig from subprocess import CalledProcessError from collections import namedtuple @@ -7,7 +8,7 @@ from collections import namedtuple from jedi._compatibility import check_output from jedi.evaluate.project import Project from jedi.cache import memoize_method - +from jedi.evaluate.compiled.subprocess import get_subprocess _VersionInfo = namedtuple('VersionInfo', 'major minor micro') @@ -28,6 +29,10 @@ class Environment(object): def get_project(self): return Project(self.get_sys_path()) + def get_subprocess(self): + return get_subprocess(self._executable) + + @memoize_method def get_sys_path(self): vars = { 'base': self._path @@ -38,35 +43,15 @@ class Environment(object): # on how the Python version was compiled (ENV variables). # If you omit -S when starting Python (normal case), additionally # site.py gets executed. - write - # venv - ['', '/usr/lib/python3.3', '/usr/lib/python3.3/plat-x86_64-linux-gnu', - '/usr/lib/python3.3/lib-dynload', - '/home/dave/source/python/virtenv/venv3.3/lib/python3.3/site-packages'] - - ['/usr/lib/python3.4', '/usr/lib/python3.4/plat-x86_64-linux-gnu', - '/usr/lib/python3.4/lib-dynload', - '/home/dave/source/stuff_cloudscale/rgw-metrics/venv/lib/python3.4/site-packages'] - - ['', '/usr/lib/python35.zip', '/usr/lib/python3.5', - '/usr/lib/python3.5/plat-x86_64-linux-gnu', - '/usr/lib/python3.5/lib-dynload', - '/home/dave/source/python/virtenv/venv3.5/lib/python3.5/site-packages'] + return self.get_subprocess().get_sys_path() - {'purelib': '{base}/local/lib/python{py_version_short}/dist-packages', - 'stdlib': '{base}/lib/python{py_version_short}', - 'scripts': '{base}/local/bin', - 'platinclude': '{platbase}/local/include/python{py_version_short}', - 'include': '{base}/local/include/python{py_version_short}', - 'data': '{base}/local', - 'platstdlib': '{platbase}/lib/python{py_version_short}', - 'platlib': '{platbase}/local/lib/python{py_version_short}/dist-packages'} - return [] + additional_paths - - def _get_long_running_process(self): - return process +class DefaultEnvironment(Environment): + def __init__(self, script_path): + # TODO make this usable + path = script_path + super(DefaultEnvironment, self).__init__(path, sys.executable) def find_virtualenvs(paths=None): @@ -95,11 +80,11 @@ def _get_executable_path(path): def _get_version(executable): try: - output = check_output(executable, '--version') + output = check_output([executable, '--version']) except (CalledProcessError, OSError): raise NoVirtualEnv() - match = re.match(r'Python (\d+)\.(\d+)\.(\d+)', output) + match = re.match(rb'Python (\d+)\.(\d+)\.(\d+)', output) if match is None: raise NoVirtualEnv() diff --git a/jedi/evaluate/compiled/subprocess/__init__.py b/jedi/evaluate/compiled/subprocess/__init__.py index 14a1cc6f..bfaf0812 100644 --- a/jedi/evaluate/compiled/subprocess/__init__.py +++ b/jedi/evaluate/compiled/subprocess/__init__.py @@ -14,7 +14,7 @@ import pickle from functools import partial from jedi.cache import memoize_method -from jedi.evaluate.compiled.subprocess import commands +from jedi.evaluate.compiled.subprocess import functions _PICKLE_PROTOCOL = 2 @@ -29,6 +29,11 @@ def get_subprocess(executable): return sub +def _get_function(evaluator, name): + function = getattr(functions, name) + return partial(function, evaluator) + + class EvaluatorSameProcess(object): """ Basically just an easy access to functions.py. It has the same API @@ -38,9 +43,8 @@ class EvaluatorSameProcess(object): def __init__(self, evaluator): self._evaluator = evaluator - def __getattr__(self): - function = getattr(commands, name) - return partial(function, self._evaluator) + def __getattr__(self, name): + return _get_function(self._evaluator, name) class EvaluatorSubprocess(object): @@ -50,11 +54,10 @@ class EvaluatorSubprocess(object): self._compiled_subprocess = compiled_subprocess def __getattr__(self, name): - function = getattr(commands, name) - return partial(function, self._evaluator_weakref()) + return _get_function(self._evaluator_weakref(), name) def __del__(self): - self.delete_evaluator(self._evaluator_weakref() + self.delete_evaluator(self._evaluator_weakref()) class _Subprocess(object): @@ -93,6 +96,9 @@ class _CompiledSubprocess(_Subprocess): assert callable(function) return self._send(id(evaluator), function, args, kwargs) + def get_sys_path(self): + return self._send(None, functions.get_sys_path, (), {}) + def delete_evaluator(self, evaluator_id): # With an argument - the evaluator gets deleted. self._send(evaluator_id, None) @@ -102,7 +108,7 @@ class Listener(): def __init__(self): self._evaluators = {} - def _run(self, evaluator_id, function, args, kwargs): + def _get_evaluator(self, function, evaluator_id): from jedi.evaluate import Evaluator if function is None: @@ -116,8 +122,14 @@ class Listener(): except KeyError: evaluator = Evaluator(None, None) self.evaluators[evaluator_id] = evaluator + return evaluator - return function(evaluator, *args, **kwargs) + def _run(self, evaluator_id, function, args, kwargs): + if evaluator_id is None: + return function(*args, **kwargs) + else: + evaluator = self._get_evaluator(evaluator_id) + return function(evaluator, *args, **kwargs) def listen(self): stdout = sys.stdout diff --git a/jedi/evaluate/compiled/subprocess/__main__.py b/jedi/evaluate/compiled/subprocess/__main__.py index 31db417c..a0ce7f92 100644 --- a/jedi/evaluate/compiled/subprocess/__main__.py +++ b/jedi/evaluate/compiled/subprocess/__main__.py @@ -1,3 +1,3 @@ from jedi.evaluate.compiled import subprocess -subprocess.listen() +subprocess.Listener().listen() diff --git a/jedi/evaluate/compiled/subprocess/functions.py b/jedi/evaluate/compiled/subprocess/functions.py new file mode 100644 index 00000000..49417235 --- /dev/null +++ b/jedi/evaluate/compiled/subprocess/functions.py @@ -0,0 +1,5 @@ +import sys + + +def get_sys_path(): + return sys.path diff --git a/jedi/evaluate/project.py b/jedi/evaluate/project.py index 8a74538c..fe3fb5d7 100644 --- a/jedi/evaluate/project.py +++ b/jedi/evaluate/project.py @@ -3,12 +3,10 @@ import sys from jedi.evaluate.sys_path import get_venv_path, detect_additional_paths from jedi.cache import memoize_method -from jedi.evaluate.compiled.subprocess import CompiledSubProcess class Project(object): def __init__(self, sys_path=None): - self._compiled_subprocess = CompiledSubProcess() if sys_path is not None: self._sys_path = sys_path diff --git a/test/test_api/test_environment.py b/test/test_api/test_environment.py new file mode 100644 index 00000000..7e654be7 --- /dev/null +++ b/test/test_api/test_environment.py @@ -0,0 +1,5 @@ +from jedi.api.virtualenv import Environment, DefaultEnvironment + + +def test_sys_path(): + assert DefaultEnvironment('/foo').get_sys_path()