]> crepu.dev Git - config.git/blame - djavu-asus/emacs/elpy/rpc-venv/lib/python3.11/site-packages/jedi/api/environment.py
Reorganización de directorios
[config.git] / djavu-asus / emacs / elpy / rpc-venv / lib / python3.11 / site-packages / jedi / api / environment.py
CommitLineData
53e6db90
DC
1"""
2Environments are a way to activate different Python versions or Virtualenvs for
3static analysis. The Python binary in that environment is going to be executed.
4"""
5import os
6import sys
7import hashlib
8import filecmp
9from collections import namedtuple
10from shutil import which
11
12from jedi.cache import memoize_method, time_cache
13from jedi.inference.compiled.subprocess import CompiledSubprocess, \
14 InferenceStateSameProcess, InferenceStateSubprocess
15
16import parso
17
18_VersionInfo = namedtuple('VersionInfo', 'major minor micro') # type: ignore[name-match]
19
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)
24
25
26class InvalidPythonEnvironment(Exception):
27 """
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.
30 """
31
32
33class _BaseEnvironment:
34 @memoize_method
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)
38
39 @property
40 def _sha256(self):
41 try:
42 return self._hash
43 except AttributeError:
44 self._hash = _calculate_sha256_for_file(self.executable)
45 return self._hash
46
47
48def _get_info():
49 return (
50 sys.executable,
51 sys.prefix,
52 sys.version_info[:3],
53 )
54
55
56class Environment(_BaseEnvironment):
57 """
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.
61 """
62 _subprocess = None
63
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()
69
70 def _get_subprocess(self):
71 if self._subprocess is not None and not self._subprocess.is_crashed:
72 return self._subprocess
73
74 try:
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,
82 exc))
83
84 # Since it could change and might not be the same(?) as the one given,
85 # set it here.
86 self.executable = info[0]
87 """
88 The Python executable, matches ``sys.executable``.
89 """
90 self.path = info[1]
91 """
92 The path to an environment, matches ``sys.prefix``.
93 """
94 self.version_info = _VersionInfo(*info[2])
95 """
96 Like :data:`sys.version_info`: a tuple to show the current
97 Environment's Python version.
98 """
99 return self._subprocess
100
101 def __repr__(self):
102 version = '.'.join(str(i) for i in self.version_info)
103 return '<%s: %s in %s>' % (self.__class__.__name__, version, self.path)
104
105 def get_inference_state_subprocess(self, inference_state):
106 return InferenceStateSubprocess(inference_state, self._get_subprocess())
107
108 @memoize_method
109 def get_sys_path(self):
110 """
111 The sys path for this environment. Does not include potential
112 modifications from e.g. appending to :data:`sys.path`.
113
114 :returns: list of str
115 """
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()
122
123
124class _SameEnvironmentMixin:
125 def __init__(self):
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
130
131
132class SameEnvironment(_SameEnvironmentMixin, Environment):
133 pass
134
135
136class InterpreterEnvironment(_SameEnvironmentMixin, _BaseEnvironment):
137 def get_inference_state_subprocess(self, inference_state):
138 return InferenceStateSameProcess(inference_state)
139
140 def get_sys_path(self):
141 return sys.path
142
143
144def _get_virtual_env_from_var(env_var='VIRTUAL_ENV'):
145 """Get virtualenv environment from VIRTUAL_ENV environment variable.
146
147 It uses `safe=False` with ``create_environment``, because the environment
148 variable is considered to be safe / controlled by the user solely.
149 """
150 var = os.environ.get(env_var)
151 if 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
155 # compare them.
156 if os.path.realpath(var) == os.path.realpath(sys.prefix):
157 return _try_get_same_env()
158
159 try:
160 return create_environment(var, safe=False)
161 except InvalidPythonEnvironment:
162 pass
163
164
165def _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''):
169 sha256.update(block)
170 return sha256.hexdigest()
171
172
173def get_default_environment():
174 """
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.
180
181 :returns: :class:`.Environment`
182 """
183 virtual_env = _get_virtual_env_from_var()
184 if virtual_env is not None:
185 return virtual_env
186
187 conda_env = _get_virtual_env_from_var(_CONDA_VAR)
188 if conda_env is not None:
189 return conda_env
190
191 return _try_get_same_env()
192
193
194def _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:
203 #
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.
207 #
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.
211 if os.name == 'nt':
212 # The first case would be a virtualenv and the second a normal
213 # Python installation.
214 checks = (r'Scripts\python.exe', 'python.exe')
215 else:
216 # For unix it looks like Python is always in a bin folder.
217 checks = (
218 'bin/python%s.%s' % (sys.version_info[0], sys.version[1]),
219 'bin/python%s' % (sys.version_info[0]),
220 'bin/python',
221 )
222 for check in checks:
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
230 # using.
231 return env
232
233
234def get_cached_default_environment():
235 var = os.environ.get('VIRTUAL_ENV') or os.environ.get(_CONDA_VAR)
236 environment = _get_cached_default_environment()
237
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
241 # compare them.
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()
245 return environment
246
247
248@time_cache(seconds=10 * 60) # 10 Minutes
249def _get_cached_default_environment():
250 try:
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()
258
259
260def find_virtualenvs(paths=None, *, safe=True, use_environment_vars=True):
261 """
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
264 Python binaries.
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
269 be executed.
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
273 environment.
274
275 :yields: :class:`.Environment`
276 """
277 if paths is None:
278 paths = []
279
280 _used_paths = set()
281
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:
287 yield virtual_env
288 _used_paths.add(virtual_env.path)
289
290 conda_env = _get_virtual_env_from_var(_CONDA_VAR)
291 if conda_env is not None:
292 yield conda_env
293 _used_paths.add(conda_env.path)
294
295 for directory in paths:
296 if not os.path.isdir(directory):
297 continue
298
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.
304 continue
305 _used_paths.add(path)
306
307 try:
308 executable = _get_executable_path(path, safe=safe)
309 yield Environment(executable)
310 except InvalidPythonEnvironment:
311 pass
312
313
314def find_system_environments(*, env_vars=None):
315 """
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
318 a portable version.
319
320 The environments are sorted from latest to oldest Python version.
321
322 :yields: :class:`.Environment`
323 """
324 for version_string in _SUPPORTED_PYTHONS:
325 try:
326 yield get_system_environment(version_string, env_vars=env_vars)
327 except InvalidPythonEnvironment:
328 pass
329
330
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.
333def get_system_environment(version, *, env_vars=None):
334 """
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.
337
338 :raises: :exc:`.InvalidPythonEnvironment`
339 :returns: :class:`.Environment`
340 """
341 exe = which('python' + version)
342 if exe:
343 if exe == sys.executable:
344 return SameEnvironment()
345 return Environment(exe)
346
347 if os.name == 'nt':
348 for exe in _get_executables_from_windows_registry(version):
349 try:
350 return Environment(exe, env_vars=env_vars)
351 except InvalidPythonEnvironment:
352 pass
353 raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
354
355
356def create_environment(path, *, safe=True, env_vars=None):
357 """
358 Make it possible to manually create an Environment object by specifying a
359 Virtualenv path or an executable path and optional environment variables.
360
361 :raises: :exc:`.InvalidPythonEnvironment`
362 :returns: :class:`.Environment`
363 """
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)
368
369
370def _get_executable_path(path, safe=True):
371 """
372 Returns None if it's not actually a virtual env.
373 """
374
375 if os.name == 'nt':
376 python = os.path.join(path, 'Scripts', 'python.exe')
377 else:
378 python = os.path.join(path, 'bin', 'python')
379 if not os.path.exists(python):
380 raise InvalidPythonEnvironment("%s seems to be missing." % python)
381
382 _assert_safe(python, safe)
383 return python
384
385
386def _get_executables_from_windows_registry(version):
387 import winreg
388
389 # TODO: support Python Anaconda.
390 sub_keys = [
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'
395 ]
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)
399 try:
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):
404 yield exe
405 except WindowsError:
406 pass
407
408
409def _assert_safe(executable_path, safe):
410 if safe and not _is_safe(executable_path):
411 raise InvalidPythonEnvironment(
412 "The python binary is potentially unsafe.")
413
414
415def _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)
419
420 if _is_unix_safe_simple(real_path):
421 return True
422
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:
428 return True
429
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
433 # Python files.
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._sha256 == _calculate_sha256_for_file(real_path):
439 return True
440 return False
441
442
443def _is_unix_safe_simple(real_path):
444 if _is_unix_admin():
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)
448
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
458 # foobar/bin/python.
459 return uid == 0
460
461
462def _is_unix_admin():
463 try:
464 return os.getuid() == 0
465 except AttributeError:
466 return False # Windows