diff --git a/jedi/__init__.py b/jedi/__init__.py index 08e41e7d..17531367 100644 --- a/jedi/__init__.py +++ b/jedi/__init__.py @@ -41,3 +41,4 @@ __version__ = '0.11.0' from jedi.api import Script, Interpreter, set_debug_function, \ preload_module, names from jedi import settings +from jedi.api.virtualenv import find_virtualenvs, create_environment diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index cef8c252..d35e09bb 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -121,7 +121,7 @@ class Script(object): compiled_subprocess = None else: if environment is None: - environment = DefaultEnvironment(self.path) + environment = DefaultEnvironment() compiled_subprocess = environment.get_subprocess() self._evaluator = Evaluator(self._grammar, project, compiled_subprocess) project.add_script_path(self.path) diff --git a/jedi/api/virtualenv.py b/jedi/api/virtualenv.py index 5caa745e..565e4fd1 100644 --- a/jedi/api/virtualenv.py +++ b/jedi/api/virtualenv.py @@ -3,6 +3,9 @@ import re import sys from subprocess import Popen, PIPE from collections import namedtuple +# When dropping Python 2.7 support we should consider switching to +# `shutil.which`. +from distutils.spawn import find_executable from jedi.evaluate.project import Project from jedi.cache import memoize_method @@ -10,19 +13,21 @@ from jedi.evaluate.compiled.subprocess import get_subprocess _VersionInfo = namedtuple('VersionInfo', 'major minor micro') +_SUPPORTED_PYTHONS = ['2.7', '3.3', '3.4', '3.5', '3.6'] -class NoVirtualEnv(Exception): + +class InvalidPythonEnvironment(Exception): pass class Environment(object): def __init__(self, path, executable): - self._path = path + self._base_path = path self._executable = executable self.version_info = _get_version(self._executable) def __repr__(self): - return '<%s: %s>' % (self.__class__.__name__, self._path) + return '<%s: %s>' % (self.__class__.__name__, self._base_path) def get_project(self): return Project(self.get_sys_path()) @@ -41,10 +46,8 @@ class Environment(object): class DefaultEnvironment(Environment): - def __init__(self, script_path): - # TODO make this usable - path = script_path - super(DefaultEnvironment, self).__init__(path, sys.executable) + def __init__(self): + super(DefaultEnvironment, self).__init__(sys.prefix, sys.executable) def find_virtualenvs(paths=None): @@ -55,10 +58,35 @@ def find_virtualenvs(paths=None): executable = _get_executable_path(path) try: yield Environment(path, executable) - except NoVirtualEnv: + except InvalidPythonEnvironment: pass +def find_python_environments(): + """ + Ignores virtualenvs and returns the different Python versions. + """ + current_version = '%s.%s' % (sys.version_info.major, sys.version_info.minor) + for version_string in _SUPPORTED_PYTHONS: + if version_string == current_version: + yield DefaultEnvironment() + else: + exe = find_executable('python' + version_string) + if exe is not None: + path = os.path.dirname(os.path.dirname(exe)) + try: + yield Environment(path, exe) + except InvalidPythonEnvironment: + pass + + +def create_environment(path): + """ + Make it possible to create + """ + return Environment(path, _get_executable_path(path)) + + def _get_executable_path(path): """ Returns None if it's not actually a virtual env. @@ -77,15 +105,15 @@ def _get_version(executable): stdout, stderr = process.communicate() retcode = process.poll() if retcode: - raise NoVirtualEnv() + raise InvalidPythonEnvironment() except OSError: - raise NoVirtualEnv() + raise InvalidPythonEnvironment() # Until Python 3.4 wthe version string is part of stderr, after that # stdout. output = stdout + stderr match = re.match(br'Python (\d+)\.(\d+)\.(\d+)', output) if match is None: - raise NoVirtualEnv() + raise InvalidPythonEnvironment() return _VersionInfo(*match.groups()) diff --git a/test/test_api/test_environment.py b/test/test_api/test_environment.py index 42b64737..d5be09c7 100644 --- a/test/test_api/test_environment.py +++ b/test/test_api/test_environment.py @@ -2,23 +2,31 @@ import pytest import jedi from jedi._compatibility import py_version -from jedi.api.virtualenv import Environment, DefaultEnvironment, NoVirtualEnv +from jedi.api.virtualenv import Environment, DefaultEnvironment, \ + InvalidPythonEnvironment, find_python_environments def test_sys_path(): - assert DefaultEnvironment('/foo').get_sys_path() + assert DefaultEnvironment().get_sys_path() + + +def test_find_python_environments(): + envs = list(find_python_environments()) + assert len(envs) + for env in envs: + assert env.version_info + assert env.get_sys_path() @pytest.mark.parametrize( 'version', - # TODO add '2.6', '2.7', - ['3.3', '3.4', '3.5', '3.6', '3.7'] + ['2.7', '3.3', '3.4', '3.5', '3.6', '3.7'] ) def test_versions(version): executable = 'python' + version try: env = Environment('some path', executable) - except NoVirtualEnv: + except InvalidPythonEnvironment: if int(version.replace('.', '')) == py_version: # At least the current version has to work raise