2 Environments are a way to activate different Python versions or Virtualenvs for
3 static analysis. The Python binary in that environment is going to be executed.
9 from collections
import namedtuple
10 from shutil
import which
12 from jedi
.cache
import memoize_method
, time_cache
13 from jedi
.inference
.compiled
.subprocess
import CompiledSubprocess
, \
14 InferenceStateSameProcess
, InferenceStateSubprocess
18 _VersionInfo
= namedtuple('VersionInfo', 'major minor micro') # type: ignore[name-match]
20 _SUPPORTED_PYTHONS
= ['3.12', '3.11', '3.10', '3.9', '3.8', '3.7', '3.6']
21 _SAFE_PATHS
= ['/usr/bin', '/usr/local/bin']
22 _CONDA_VAR
= 'CONDA_PREFIX'
23 _CURRENT_VERSION
= '%s.%s' % (sys
.version_info
.major
, sys
.version_info
.minor
)
26 class InvalidPythonEnvironment(Exception):
28 If you see this exception, the Python executable or Virtualenv you have
29 been trying to use is probably not a correct Python version.
33 class _BaseEnvironment
:
35 def get_grammar(self
):
36 version_string
= '%s.%s' % (self
.version_info
.major
, self
.version_info
.minor
)
37 return parso
.load_grammar(version
=version_string
)
43 except AttributeError:
44 self
._hash
= _calculate_sha256_for_file(self
.executable
)
56 class Environment(_BaseEnvironment
):
58 This class is supposed to be created by internal Jedi architecture. You
59 should not create it directly. Please use create_environment or the other
60 functions instead. It is then returned by that function.
64 def __init__(self
, executable
, env_vars
=None):
65 self
._start
_executable
= executable
66 self
._env
_vars
= env_vars
67 # Initialize the environment
68 self
._get
_subprocess
()
70 def _get_subprocess(self
):
71 if self
._subprocess
is not None and not self
._subprocess
.is_crashed
:
72 return self
._subprocess
75 self
._subprocess
= CompiledSubprocess(self
._start
_executable
,
76 env_vars
=self
._env
_vars
)
77 info
= self
._subprocess
._send
(None, _get_info
)
78 except Exception as exc
:
79 raise InvalidPythonEnvironment(
80 "Could not get version information for %r: %r" % (
81 self
._start
_executable
,
84 # Since it could change and might not be the same(?) as the one given,
86 self
.executable
= info
[0]
88 The Python executable, matches ``sys.executable``.
92 The path to an environment, matches ``sys.prefix``.
94 self
.version_info
= _VersionInfo(*info
[2])
96 Like :data:`sys.version_info`: a tuple to show the current
97 Environment's Python version.
99 return self
._subprocess
102 version
= '.'.join(str(i
) for i
in self
.version_info
)
103 return '<%s: %s in %s>' % (self
.__class
__.__name
__, version
, self
.path
)
105 def get_inference_state_subprocess(self
, inference_state
):
106 return InferenceStateSubprocess(inference_state
, self
._get
_subprocess
())
109 def get_sys_path(self
):
111 The sys path for this environment. Does not include potential
112 modifications from e.g. appending to :data:`sys.path`.
114 :returns: list of str
116 # It's pretty much impossible to generate the sys path without actually
117 # executing Python. The sys path (when starting with -S) itself depends
118 # on how the Python version was compiled (ENV variables).
119 # If you omit -S when starting Python (normal case), additionally
120 # site.py gets executed.
121 return self
._get
_subprocess
().get_sys_path()
124 class _SameEnvironmentMixin
:
126 self
._start
_executable
= self
.executable
= sys
.executable
127 self
.path
= sys
.prefix
128 self
.version_info
= _VersionInfo(*sys
.version_info
[:3])
129 self
._env
_vars
= None
132 class SameEnvironment(_SameEnvironmentMixin
, Environment
):
136 class InterpreterEnvironment(_SameEnvironmentMixin
, _BaseEnvironment
):
137 def get_inference_state_subprocess(self
, inference_state
):
138 return InferenceStateSameProcess(inference_state
)
140 def get_sys_path(self
):
144 def _get_virtual_env_from_var(env_var
='VIRTUAL_ENV'):
145 """Get virtualenv environment from VIRTUAL_ENV environment variable.
147 It uses `safe=False` with ``create_environment``, because the environment
148 variable is considered to be safe / controlled by the user solely.
150 var
= os
.environ
.get(env_var
)
152 # Under macOS in some cases - notably when using Pipenv - the
153 # sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
154 # /path/to/env so we need to fully resolve the paths in order to
156 if os
.path
.realpath(var
) == os
.path
.realpath(sys
.prefix
):
157 return _try_get_same_env()
160 return create_environment(var
, safe
=False)
161 except InvalidPythonEnvironment
:
165 def _calculate_sha256_for_file(path
):
166 sha256
= hashlib
.sha256()
167 with
open(path
, 'rb') as f
:
168 for block
in iter(lambda: f
.read(filecmp
.BUFSIZE
), b
''):
170 return sha256
.hexdigest()
173 def get_default_environment():
175 Tries to return an active Virtualenv or conda environment.
176 If there is no VIRTUAL_ENV variable or no CONDA_PREFIX variable set
177 set it will return the latest Python version installed on the system. This
178 makes it possible to use as many new Python features as possible when using
179 autocompletion and other functionality.
181 :returns: :class:`.Environment`
183 virtual_env
= _get_virtual_env_from_var()
184 if virtual_env
is not None:
187 conda_env
= _get_virtual_env_from_var(_CONDA_VAR
)
188 if conda_env
is not None:
191 return _try_get_same_env()
194 def _try_get_same_env():
195 env
= SameEnvironment()
196 if not os
.path
.basename(env
.executable
).lower().startswith('python'):
197 # This tries to counter issues with embedding. In some cases (e.g.
198 # VIM's Python Mac/Windows, sys.executable is /foo/bar/vim. This
199 # happens, because for Mac a function called `_NSGetExecutablePath` is
200 # used and for Windows `GetModuleFileNameW`. These are both platform
201 # specific functions. For all other systems sys.executable should be
202 # alright. However here we try to generalize:
204 # 1. Check if the executable looks like python (heuristic)
205 # 2. In case it's not try to find the executable
206 # 3. In case we don't find it use an interpreter environment.
208 # The last option will always work, but leads to potential crashes of
209 # Jedi - which is ok, because it happens very rarely and even less,
210 # because the code below should work for most cases.
212 # The first case would be a virtualenv and the second a normal
213 # Python installation.
214 checks
= (r
'Scripts\python.exe', 'python.exe')
216 # For unix it looks like Python is always in a bin folder.
218 'bin/python%s.%s' % (sys
.version_info
[0], sys
.version
[1]),
219 'bin/python%s' % (sys
.version_info
[0]),
223 guess
= os
.path
.join(sys
.exec_prefix
, check
)
224 if os
.path
.isfile(guess
):
225 # Bingo - We think we have our Python.
226 return Environment(guess
)
227 # It looks like there is no reasonable Python to be found.
228 return InterpreterEnvironment()
229 # If no virtualenv is found, use the environment we're already
234 def get_cached_default_environment():
235 var
= os
.environ
.get('VIRTUAL_ENV') or os
.environ
.get(_CONDA_VAR
)
236 environment
= _get_cached_default_environment()
238 # Under macOS in some cases - notably when using Pipenv - the
239 # sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
240 # /path/to/env so we need to fully resolve the paths in order to
242 if var
and os
.path
.realpath(var
) != os
.path
.realpath(environment
.path
):
243 _get_cached_default_environment
.clear_cache()
244 return _get_cached_default_environment()
248 @time_cache(seconds
=10 * 60) # 10 Minutes
249 def _get_cached_default_environment():
251 return get_default_environment()
252 except InvalidPythonEnvironment
:
253 # It's possible that `sys.executable` is wrong. Typically happens
254 # when Jedi is used in an executable that embeds Python. For further
255 # information, have a look at:
256 # https://github.com/davidhalter/jedi/issues/1531
257 return InterpreterEnvironment()
260 def find_virtualenvs(paths
=None, *, safe
=True, use_environment_vars
=True):
262 :param paths: A list of paths in your file system to be scanned for
263 Virtualenvs. It will search in these paths and potentially execute the
265 :param safe: Default True. In case this is False, it will allow this
266 function to execute potential `python` environments. An attacker might
267 be able to drop an executable in a path this function is searching by
268 default. If the executable has not been installed by root, it will not
270 :param use_environment_vars: Default True. If True, the VIRTUAL_ENV
271 variable will be checked if it contains a valid VirtualEnv.
272 CONDA_PREFIX will be checked to see if it contains a valid conda
275 :yields: :class:`.Environment`
282 if use_environment_vars
:
283 # Using this variable should be safe, because attackers might be
284 # able to drop files (via git) but not environment variables.
285 virtual_env
= _get_virtual_env_from_var()
286 if virtual_env
is not None:
288 _used_paths
.add(virtual_env
.path
)
290 conda_env
= _get_virtual_env_from_var(_CONDA_VAR
)
291 if conda_env
is not None:
293 _used_paths
.add(conda_env
.path
)
295 for directory
in paths
:
296 if not os
.path
.isdir(directory
):
299 directory
= os
.path
.abspath(directory
)
300 for path
in os
.listdir(directory
):
301 path
= os
.path
.join(directory
, path
)
302 if path
in _used_paths
:
303 # A path shouldn't be inferred twice.
305 _used_paths
.add(path
)
308 executable
= _get_executable_path(path
, safe
=safe
)
309 yield Environment(executable
)
310 except InvalidPythonEnvironment
:
314 def find_system_environments(*, env_vars
=None):
316 Ignores virtualenvs and returns the Python versions that were installed on
317 your system. This might return nothing, if you're running Python e.g. from
320 The environments are sorted from latest to oldest Python version.
322 :yields: :class:`.Environment`
324 for version_string
in _SUPPORTED_PYTHONS
:
326 yield get_system_environment(version_string
, env_vars
=env_vars
)
327 except InvalidPythonEnvironment
:
331 # TODO: this function should probably return a list of environments since
332 # multiple Python installations can be found on a system for the same version.
333 def get_system_environment(version
, *, env_vars
=None):
335 Return the first Python environment found for a string of the form 'X.Y'
336 where X and Y are the major and minor versions of Python.
338 :raises: :exc:`.InvalidPythonEnvironment`
339 :returns: :class:`.Environment`
341 exe
= which('python' + version
)
343 if exe
== sys
.executable
:
344 return SameEnvironment()
345 return Environment(exe
)
348 for exe
in _get_executables_from_windows_registry(version
):
350 return Environment(exe
, env_vars
=env_vars
)
351 except InvalidPythonEnvironment
:
353 raise InvalidPythonEnvironment("Cannot find executable python%s." % version
)
356 def create_environment(path
, *, safe
=True, env_vars
=None):
358 Make it possible to manually create an Environment object by specifying a
359 Virtualenv path or an executable path and optional environment variables.
361 :raises: :exc:`.InvalidPythonEnvironment`
362 :returns: :class:`.Environment`
364 if os
.path
.isfile(path
):
365 _assert_safe(path
, safe
)
366 return Environment(path
, env_vars
=env_vars
)
367 return Environment(_get_executable_path(path
, safe
=safe
), env_vars
=env_vars
)
370 def _get_executable_path(path
, safe
=True):
372 Returns None if it's not actually a virtual env.
376 python
= os
.path
.join(path
, 'Scripts', 'python.exe')
378 python
= os
.path
.join(path
, 'bin', 'python')
379 if not os
.path
.exists(python
):
380 raise InvalidPythonEnvironment("%s seems to be missing." % python
)
382 _assert_safe(python
, safe
)
386 def _get_executables_from_windows_registry(version
):
389 # TODO: support Python Anaconda.
391 r
'SOFTWARE\Python\PythonCore\{version}\InstallPath',
392 r
'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath',
393 r
'SOFTWARE\Python\PythonCore\{version}-32\InstallPath',
394 r
'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath'
396 for root_key
in [winreg
.HKEY_CURRENT_USER
, winreg
.HKEY_LOCAL_MACHINE
]:
397 for sub_key
in sub_keys
:
398 sub_key
= sub_key
.format(version
=version
)
400 with winreg
.OpenKey(root_key
, sub_key
) as key
:
401 prefix
= winreg
.QueryValueEx(key
, '')[0]
402 exe
= os
.path
.join(prefix
, 'python.exe')
403 if os
.path
.isfile(exe
):
409 def _assert_safe(executable_path
, safe
):
410 if safe
and not _is_safe(executable_path
):
411 raise InvalidPythonEnvironment(
412 "The python binary is potentially unsafe.")
415 def _is_safe(executable_path
):
416 # Resolve sym links. A venv typically is a symlink to a known Python
417 # binary. Only virtualenvs copy symlinks around.
418 real_path
= os
.path
.realpath(executable_path
)
420 if _is_unix_safe_simple(real_path
):
423 # Just check the list of known Python versions. If it's not in there,
424 # it's likely an attacker or some Python that was not properly
425 # installed in the system.
426 for environment
in find_system_environments():
427 if environment
.executable
== real_path
:
430 # If the versions don't match, just compare the binary files. If we
431 # don't do that, only venvs will be working and not virtualenvs.
432 # venvs are symlinks while virtualenvs are actual copies of the
434 # This still means that if the system Python is updated and the
435 # virtualenv's Python is not (which is probably never going to get
436 # upgraded), it will not work with Jedi. IMO that's fine, because
437 # people should just be using venv. ~ dave
438 if environment
._sha
256 == _calculate_sha256_for_file(real_path
):
443 def _is_unix_safe_simple(real_path
):
445 # In case we are root, just be conservative and
446 # only execute known paths.
447 return any(real_path
.startswith(p
) for p
in _SAFE_PATHS
)
449 uid
= os
.stat(real_path
).st_uid
450 # The interpreter needs to be owned by root. This means that it wasn't
451 # written by a user and therefore attacking Jedi is not as simple.
452 # The attack could look like the following:
453 # 1. A user clones a repository.
454 # 2. The repository has an innocent looking folder called foobar. jedi
455 # searches for the folder and executes foobar/bin/python --version if
456 # there's also a foobar/bin/activate.
457 # 3. The attacker has gained code execution, since he controls
462 def _is_unix_admin():
464 return os
.getuid() == 0
465 except AttributeError:
466 return False # Windows