summaryrefslogtreecommitdiff
path: root/src/virtualenv/discovery
diff options
context:
space:
mode:
authorBernát Gábor <bgabor8@bloomberg.net>2020-06-21 08:28:50 +0100
committerGitHub <noreply@github.com>2020-06-21 08:28:50 +0100
commit0cd009b5a1338f66397f71c85a75f576a2f3eabf (patch)
treed1a1e6564776ba6123f9e8b245fb58c14ea71df9 /src/virtualenv/discovery
parentf99353ca3d0ca9e23cfe4b66e54ba653bf99ab4a (diff)
downloadvirtualenv-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.py2
-rw-r--r--src/virtualenv/discovery/cached_py_info.py82
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()