diff --git a/README.md b/README.md index 69f34cbea3..fe09433a9f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ See Contributing for how to update this file. - + Code style: black

diff --git a/docs/changelog.md b/docs/changelog.md index 9b0b0fba8b..4fd66a9f3f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,6 +1,8 @@ -0.15.0.1 +0.15.1.0 -- [bugfix] pass pip arguments to pip when determining package name +- Add Python 3.8 to PyPI classifier and travis test matrix +- [feature] auto-upgrade shared libraries, including pip, if older than one month. Hide all pip warnings that a new version is available. (#264) +- [bugfix] pass pip arguments to pip when determining package name (#320) 0.15.0.0 diff --git a/docs/index.md b/docs/index.md index 69f34cbea3..fe09433a9f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,7 +21,7 @@ See Contributing for how to update this file. - + Code style: black

diff --git a/src/pipx/main.py b/src/pipx/main.py index 7b3a26c7b7..b3f2349481 100644 --- a/src/pipx/main.py +++ b/src/pipx/main.py @@ -21,7 +21,7 @@ from .util import PipxError, mkdir from .venv import VenvContainer -__version__ = "0.15.0.1" +__version__ = "0.15.1.0" def simple_parse_version(s, segments=4) -> Tuple[Union[int, str], ...]: diff --git a/src/pipx/shared_libs.py b/src/pipx/shared_libs.py index 50bb850730..9a375c2270 100644 --- a/src/pipx/shared_libs.py +++ b/src/pipx/shared_libs.py @@ -1,16 +1,21 @@ import logging from pathlib import Path from typing import List +import time +import datetime from pipx.animate import animate from pipx.constants import DEFAULT_PYTHON, PIPX_SHARED_LIBS, WINDOWS from pipx.util import get_site_packages, get_venv_paths, run +SHARED_LIBS_MAX_AGE_SEC = datetime.timedelta(days=30).total_seconds() + class _SharedLibs: def __init__(self): self.root = PIPX_SHARED_LIBS self.bin_path, self.python_path = get_venv_paths(self.root) + self.pip_path = self.bin_path / ("pip" if not WINDOWS else "pip.exe") # i.e. bin_path is ~/.local/pipx/shared/bin # i.e. python_path is ~/.local/pipx/shared/python self._site_packages = None @@ -31,10 +36,23 @@ def create(self, pip_args: List[str], verbose: bool = False): @property def is_valid(self): - return ( - self.python_path.is_file() - and (self.bin_path / ("pip" if not WINDOWS else "pip.exe")).is_file() + return self.python_path.is_file() and self.pip_path.is_file() + + @property + def needs_upgrade(self): + if self.has_been_updated_this_run: + return False + + if not self.pip_path.is_file(): + return True + + now = time.time() + time_since_last_update_sec = now - self.pip_path.stat().st_mtime + logging.info( + f"Time since last upgrade of shared libs, in seconds: {time_since_last_update_sec}. " + f"Upgrade will be run by pipx if greater than {SHARED_LIBS_MAX_AGE_SEC}." ) + return time_since_last_update_sec > SHARED_LIBS_MAX_AGE_SEC def upgrade(self, pip_args: List[str], verbose: bool = False): # Don't try to upgrade multiple times per run @@ -64,6 +82,8 @@ def upgrade(self, pip_args: List[str], verbose: bool = False): ] ) self.has_been_updated_this_run = True + self.pip_path.touch() + except Exception: logging.error("Failed to upgrade shared libraries", exc_info=True) diff --git a/src/pipx/util.py b/src/pipx/util.py index 40261281fe..ba01a0add9 100644 --- a/src/pipx/util.py +++ b/src/pipx/util.py @@ -103,6 +103,7 @@ def run_subprocess( # pipx directories to it, and can make it appear to venvs as though # pipx dependencies are in the venv path (#233) env = {k: v for k, v in os.environ.items() if k.upper() != "PYTHONPATH"} + env["PIP_DISABLE_PIP_VERSION_CHECK"] = "1" cmd_str = " ".join(str(c) for c in cmd) logging.info(f"running {cmd_str}") # windows cannot take Path objects, only strings diff --git a/src/pipx/venv.py b/src/pipx/venv.py index ea0a421341..5f7f1e83c9 100644 --- a/src/pipx/venv.py +++ b/src/pipx/venv.py @@ -88,17 +88,16 @@ def __init__( except StopIteration: self._existing = False - if self._existing and self.uses_shared_libs and not shared_libs.is_valid: - logging.warning( - f"Shared libraries not found, but are required for package {self.root.name}. " - "Attempting to install now." - ) - shared_libs.create([]) + if self._existing and self.uses_shared_libs: if shared_libs.is_valid: - logging.info("Successfully created shared libraries") + if shared_libs.needs_upgrade: + shared_libs.upgrade([], verbose) else: + shared_libs.create([], verbose) + + if not shared_libs.is_valid: raise PipxError( - f"Error: pipx's shared venv is invalid and " + f"Error: pipx's shared venv {shared_libs.root} is invalid and " "needs re-installation. To fix this, install or reinstall a " "package. For example,\n" f" pipx install {self.root.name} --force" diff --git a/tests/test_shared_libs.py b/tests/test_shared_libs.py new file mode 100644 index 0000000000..365fecd782 --- /dev/null +++ b/tests/test_shared_libs.py @@ -0,0 +1,24 @@ +import os +import time +import pytest # type: ignore +from pipx import shared_libs + + +now = time.time() + + +@pytest.mark.parametrize( + "mtime,needs_upgrade", + [ + (now - shared_libs.SHARED_LIBS_MAX_AGE_SEC - 600, True), + (now - shared_libs.SHARED_LIBS_MAX_AGE_SEC + 600, False), + ], +) +def test_auto_update_shared_libs(capsys, pipx_temp_env, mtime, needs_upgrade): + shared_libs.shared_libs.create([], verbose=True) + shared_libs.shared_libs.has_been_updated_this_run = False + + access_time = now # this can be anything + os.utime(shared_libs.shared_libs.pip_path, (access_time, mtime)) + + assert shared_libs.shared_libs.needs_upgrade is needs_upgrade