diff --git a/jedi/_compatibility.py b/jedi/_compatibility.py index 52a20fe2..3481e52a 100644 --- a/jedi/_compatibility.py +++ b/jedi/_compatibility.py @@ -289,3 +289,25 @@ def utf8_repr(func): return func else: return wrapper + + +try: + from subprocess import check_output +except ImportError: + def check_output(*popenargs, **kwargs): + """ + Taken from Python 2.6. + """ + from subprocess import Popen, PIPE, CalledProcessError + + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = Popen(stdout=PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise CalledProcessError(retcode, cmd, output=output) + return output diff --git a/jedi/api/virtualenv.py b/jedi/api/virtualenv.py new file mode 100644 index 00000000..d2043b20 --- /dev/null +++ b/jedi/api/virtualenv.py @@ -0,0 +1,106 @@ +import os +import re +import sysconfig +from subprocess import CalledProcessError +from collections import namedtuple + +from jedi._compatibility import check_output +from jedi.evaluate.project import Project +from jedi.cache import memoize_method + + +_VersionInfo = namedtuple('VersionInfo', 'major minor micro') + + +class NoVirtualEnv(Exception): + pass + + +class Environment(object): + def __init__(self, path, executable): + self._path = path + self._executable = executable + self.version_info = _get_version(self._executable) + + def __repr__(self): + return '<%s: %s>' % (self.__class__.__name__, self._path) + + def get_project(self): + return Project(self.get_sys_path()) + + def get_sys_path(self): + vars = { + 'base': self._path + } + lib_path = sysconfig.get_path('stdlib', vars=vars) + # 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. + 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'] + + + {'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 + + +def find_virtualenvs(paths=None): + if paths is None: + paths = [] + + for path in paths: + executable = _get_executable_path(path) + try: + yield Environment(path, executable) + except NoVirtualEnv: + pass + + +def _get_executable_path(path): + """ + Returns None if it's not actually a virtual env. + """ + bin_folder = os.path.join(path, 'bin') + activate = os.path.join(bin_folder, 'activate') + python = os.path.join(bin_folder, 'python') + if not all(os.path.exists(p) for p in (activate, python)): + return None + return python + + +def _get_version(executable): + try: + output = check_output(executable, '--version') + except (CalledProcessError, OSError): + raise NoVirtualEnv() + + match = re.match(r'Python (\d+)\.(\d+)\.(\d+)', output) + if match is None: + raise NoVirtualEnv() + + return _VersionInfo(*match.groups()) diff --git a/jedi/evaluate/project.py b/jedi/evaluate/project.py index b90f0f0c..fe3fb5d7 100644 --- a/jedi/evaluate/project.py +++ b/jedi/evaluate/project.py @@ -2,7 +2,7 @@ import os import sys from jedi.evaluate.sys_path import get_venv_path, detect_additional_paths -from jedi.cache import underscore_memoization +from jedi.cache import memoize_method class Project(object): @@ -32,7 +32,7 @@ class Project(object): self._evaluator = evaluator @property - @underscore_memoization + @memoize_method def sys_path(self): if self._script_path is None: return self._base_sys_path diff --git a/jedi/evaluate/site.py b/jedi/evaluate/site.py index bf884fae..de9443f2 100644 --- a/jedi/evaluate/site.py +++ b/jedi/evaluate/site.py @@ -17,6 +17,7 @@ from __future__ import print_function import sys import os +from sysconfig import get_config_var def makepath(*paths): @@ -108,3 +109,8 @@ def addsitedir(sys_path, sitedir, known_paths=None): if reset: known_paths = None return known_paths + + +def getuserbase(): + """Returns the `user base` directory path.""" + return get_config_var('userbase')