Repository: pipxproject/pipx (python)

Issue:
How to upgrade pip (stop warnings about pip version)
I went to install another command (towncrier) today, and when I did, I was getting warnings about the pip version:

```
setuptools master $ pipx install towncrier                                                                                              
WARNING: You are using pip version 19.2.3, however version 19.3.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
  installed package towncrier 19.2.0, Python 3.8.0
  These apps are now globally available
    - towncrier
done! ✨ 🌟 ✨
```

I'd like not to have these warnings. I tried upgrading pip, but it's a shared dependency.

```
setuptools master $ ~/.local/pipx/venvs/towncrier/bin/python -m pip --version                                                           
pip 19.2.3 from /Users/jaraco/.local/pipx/shared/lib/python3.8/site-packages/pip (python 3.8)
setuptools master $ ~/.local/pipx/venvs/towncrier/bin/python -m pip install -U pip                                                      
Collecting pip
  Using cached https://files.pythonhosted.org/packages/00/b6/9cfa56b4081ad13874b0c6f96af8ce16cfbc1cb06bedf8e9164ce5551ec1/pip-19.3.1-py2.py3-none-any.whl
Installing collected packages: pip
  Found existing installation: pip 19.2.3
    Not uninstalling pip at /Users/jaraco/.local/pipx/shared/lib/python3.8/site-packages, outside environment /Users/jaraco/.local/pipx/venvs/towncrier
    Can't uninstall 'pip'. No files were found to uninstall.
Successfully installed pip-19.3.1
setuptools master $ pipx uninstall towncrier                                                                                            
uninstalled towncrier! ✨ 🌟 ✨
setuptools master $ pipx install towncrier                                                                                              
WARNING: You are using pip version 19.2.3, however version 19.3.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
done! ✨ 🌟 ✨g package 'towncrier'
  installed package towncrier 19.2.0, Python 3.8.0
  These apps are now globally available
    - towncrier
```

How does one upgrade pip for pipx?

Pull request description:
suppress pip version warning and auto-update shared libraries
This PR suppresses pip warnings such as `WARNING: You are using pip version 19.2.3, however version 19.3.1 is available.`. It also automatically upgrades shared libraries if they have not been upgraded in over one month. cc @jaraco  

I can add unit tests but wanted to make sure there is approval of the general approach before I add them.

fixes #264

Patch:
diff --git a/README.md b/README.md
index 69f34cb..fe09433 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ See Contributing for how to update this file.
 <a href="https://travis-ci.org/pipxproject/pipx"><img src="https://travis-ci.org/pipxproject/pipx.svg?branch=master" /></a>
 
 <a href="https://pypi.python.org/pypi/pipx/">
-<img src="https://img.shields.io/badge/pypi-0.15.0.1-blue.svg" /></a>
+<img src="https://img.shields.io/badge/pypi-0.15.1.0-blue.svg" /></a>
 <a href="https://github.com/ambv/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
 </p>
 
diff --git a/docs/changelog.md b/docs/changelog.md
index 9b0b0fb..4fd66a9 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 69f34cb..fe09433 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -21,7 +21,7 @@ See Contributing for how to update this file.
 <a href="https://travis-ci.org/pipxproject/pipx"><img src="https://travis-ci.org/pipxproject/pipx.svg?branch=master" /></a>
 
 <a href="https://pypi.python.org/pypi/pipx/">
-<img src="https://img.shields.io/badge/pypi-0.15.0.1-blue.svg" /></a>
+<img src="https://img.shields.io/badge/pypi-0.15.1.0-blue.svg" /></a>
 <a href="https://github.com/ambv/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
 </p>
 
diff --git a/src/pipx/main.py b/src/pipx/main.py
index 7b3a26c..b3f2349 100644
--- a/src/pipx/main.py
+++ b/src/pipx/main.py
@@ -21,7 +21,7 @@ from . import constants
 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 50bb850..9a375c2 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 @@ class _SharedLibs:
 
     @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 @@ class _SharedLibs:
                     ]
                 )
                 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 4026128..ba01a0a 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 ea0a421..5f7f1e8 100644
--- a/src/pipx/venv.py
+++ b/src/pipx/venv.py
@@ -88,17 +88,16 @@ class Venv:
         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"


Test patch:
diff --git a/tests/test_shared_libs.py b/tests/test_shared_libs.py
new file mode 100644
index 0000000..365fecd
--- /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