diff options
| author | Bernát Gábor <bgabor8@bloomberg.net> | 2020-06-21 08:28:50 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-21 08:28:50 +0100 |
| commit | 0cd009b5a1338f66397f71c85a75f576a2f3eabf (patch) | |
| tree | d1a1e6564776ba6123f9e8b245fb58c14ea71df9 /src/virtualenv/discovery | |
| parent | f99353ca3d0ca9e23cfe4b66e54ba653bf99ab4a (diff) | |
| download | virtualenv-0cd009b5a1338f66397f71c85a75f576a2f3eabf.tar.gz | |
Implement periodic update feature (#1841)
Co-authored-by: Pradyun Gedam <pradyunsg@gmail.com>
Diffstat (limited to 'src/virtualenv/discovery')
| -rw-r--r-- | src/virtualenv/discovery/builtin.py | 2 | ||||
| -rw-r--r-- | src/virtualenv/discovery/cached_py_info.py | 82 |
2 files changed, 28 insertions, 56 deletions
diff --git a/src/virtualenv/discovery/builtin.py b/src/virtualenv/discovery/builtin.py index 2498555..4d57fa5 100644 --- a/src/virtualenv/discovery/builtin.py +++ b/src/virtualenv/discovery/builtin.py @@ -30,7 +30,7 @@ class Builtin(Discover): ) def run(self): - return get_interpreter(self.python_spec, self.app_data.folder) + return get_interpreter(self.python_spec, self.app_data) def __repr__(self): return ensure_str(self.__unicode__()) diff --git a/src/virtualenv/discovery/cached_py_info.py b/src/virtualenv/discovery/cached_py_info.py index a1fd3f3..13a213d 100644 --- a/src/virtualenv/discovery/cached_py_info.py +++ b/src/virtualenv/discovery/cached_py_info.py @@ -6,21 +6,18 @@ caching. """ from __future__ import absolute_import, unicode_literals -import json import logging import os import pipes import sys from collections import OrderedDict -from hashlib import sha256 +from virtualenv.app_data import AppDataDisabled from virtualenv.discovery.py_info import PythonInfo -from virtualenv.info import PY2, PY3 +from virtualenv.info import PY2 from virtualenv.util.path import Path from virtualenv.util.six import ensure_text from virtualenv.util.subprocess import Popen, subprocess -from virtualenv.util.zipapp import ensure_file_on_disk -from virtualenv.version import __version__ _CACHE = OrderedDict() _CACHE[Path(sys.executable)] = PythonInfo() @@ -28,8 +25,7 @@ _CACHE[Path(sys.executable)] = PythonInfo() def from_exe(cls, app_data, exe, raise_on_error=True, ignore_cache=False): """""" - py_info_cache = _get_py_info_cache(app_data) - result = _get_from_cache(cls, py_info_cache, app_data, exe, ignore_cache=ignore_cache) + result = _get_from_cache(cls, app_data, exe, ignore_cache=ignore_cache) if isinstance(result, Exception): if raise_on_error: raise result @@ -39,21 +35,14 @@ def from_exe(cls, app_data, exe, raise_on_error=True, ignore_cache=False): return result -def _get_py_info_cache(app_data): - return None if app_data is None else app_data / "py_info" / __version__ - - -def _get_from_cache(cls, py_info_cache, app_data, exe, ignore_cache=True): +def _get_from_cache(cls, app_data, exe, ignore_cache=True): # note here we cannot resolve symlinks, as the symlink may trigger different prefix information if there's a # pyenv.cfg somewhere alongside on python3.4+ exe_path = Path(exe) if not ignore_cache and exe_path in _CACHE: # check in the in-memory cache result = _CACHE[exe_path] - elif py_info_cache is None: # cache disabled - failure, py_info = _run_subprocess(cls, exe, app_data) - result = py_info if failure is None else failure - else: # then check the persisted cache - py_info = _get_via_file_cache(cls, py_info_cache, app_data, exe_path, exe) + else: # otherwise go through the app data cache + py_info = _get_via_file_cache(cls, app_data, exe_path, exe) result = _CACHE[exe_path] = py_info # independent if it was from the file or in-memory cache fix the original executable location if isinstance(result, PythonInfo): @@ -61,47 +50,37 @@ def _get_from_cache(cls, py_info_cache, app_data, exe, ignore_cache=True): return result -def _get_via_file_cache(cls, py_info_cache, app_data, resolved_path, exe): - key = sha256(str(resolved_path).encode("utf-8") if PY3 else str(resolved_path)).hexdigest() - py_info = None - resolved_path_text = ensure_text(str(resolved_path)) +def _get_via_file_cache(cls, app_data, path, exe): + path_text = ensure_text(str(path)) try: - resolved_path_modified_timestamp = resolved_path.stat().st_mtime + path_modified = path.stat().st_mtime except OSError: - resolved_path_modified_timestamp = -1 - data_file = py_info_cache / "{}.json".format(key) - with py_info_cache.lock_for_key(key): - data_file_path = data_file.path - if data_file_path.exists() and resolved_path_modified_timestamp != 1: # if exists and matches load - try: - data = json.loads(data_file_path.read_text()) - if data["path"] == resolved_path_text and data["st_mtime"] == resolved_path_modified_timestamp: - logging.debug("get PythonInfo from %s for %s", data_file_path, exe) - py_info = cls._from_dict({k: v for k, v in data["content"].items()}) - else: - raise ValueError("force close as stale") - except (KeyError, ValueError, OSError): - logging.debug("remove PythonInfo %s for %s", data_file_path, exe) - data_file_path.unlink() # close out of date files + path_modified = -1 + if app_data is None: + app_data = AppDataDisabled() + py_info, py_info_store = None, app_data.py_info(path) + with py_info_store.locked(): + if py_info_store.exists(): # if exists and matches load + data = py_info_store.read() + of_path, of_st_mtime, of_content = data["path"], data["st_mtime"], data["content"] + if of_path == path_text and of_st_mtime == path_modified: + py_info = cls._from_dict({k: v for k, v in of_content.items()}) + else: + py_info_store.remove() if py_info is None: # if not loaded run and save failure, py_info = _run_subprocess(cls, exe, app_data) if failure is None: - file_cache_content = { - "st_mtime": resolved_path_modified_timestamp, - "path": resolved_path_text, - "content": py_info._to_dict(), - } - logging.debug("write PythonInfo to %s for %s", data_file_path, exe) - data_file_path.write_text(ensure_text(json.dumps(file_cache_content, indent=2))) + data = {"st_mtime": path_modified, "path": path_text, "content": py_info._to_dict()} + py_info_store.write(data) else: py_info = failure return py_info def _run_subprocess(cls, exe, app_data): - resolved_path = Path(os.path.abspath(__file__)).parent / "py_info.py" - with ensure_file_on_disk(resolved_path, app_data) as resolved_path: - cmd = [exe, str(resolved_path)] + py_info_script = Path(os.path.abspath(__file__)).parent / "py_info.py" + with app_data.ensure_extracted(py_info_script) as py_info_script: + cmd = [exe, str(py_info_script)] # prevent sys.prefix from leaking into the child process - see https://bugs.python.org/issue22490 env = os.environ.copy() env.pop("__PYVENV_LAUNCHER__", None) @@ -155,14 +134,7 @@ class LogCmd(object): def clear(app_data): - py_info_cache = _get_py_info_cache(app_data) - if py_info_cache is not None: - with py_info_cache: - for filename in py_info_cache.path.iterdir(): - if filename.suffix == ".json": - with py_info_cache.lock_for_key(filename.stem): - if filename.exists(): - filename.unlink() + app_data.py_info_clear() _CACHE.clear() |
