diff --git a/pip/basecommand.py b/pip/basecommand.py index 2f081253089..4508963b929 100644 --- a/pip/basecommand.py +++ b/pip/basecommand.py @@ -16,7 +16,7 @@ from pip.baseparser import ConfigOptionParser, UpdatingDefaultsHelpFormatter from pip.status_codes import (SUCCESS, ERROR, UNKNOWN_ERROR, VIRTUALENV_NOT_FOUND, PREVIOUS_BUILD_DIR_ERROR) -from pip.util import get_prog +from pip.util import get_prog, latest_version_check __all__ = ['Command'] @@ -115,6 +115,10 @@ def main(self, args): else: log_fp = None + # Check if we're using the latest version of pip available + if not options.disable_version_check: + latest_version_check(session=self._build_session(options)) + exit = SUCCESS store_log = False try: diff --git a/pip/cmdoptions.py b/pip/cmdoptions.py index 1cc4811a18f..bffd90dec97 100644 --- a/pip/cmdoptions.py +++ b/pip/cmdoptions.py @@ -304,6 +304,13 @@ def make(self): default=False, help="Don't clean up build directories.") +disable_version_check = OptionMaker( + "--disable-version-check", + dest="disable_version_check", + action="store_true", + default=False, + help="Don't check PyPI for new versions of pip.") + ########## # groups # @@ -327,6 +334,7 @@ def make(self): skip_requirements_regex, exists_action, cert, + disable_version_check, ] } diff --git a/pip/util.py b/pip/util.py index 58336e59e4e..2f751a3ee10 100644 --- a/pip/util.py +++ b/pip/util.py @@ -1,3 +1,5 @@ +import datetime +import json import sys import shutil import os @@ -717,3 +719,65 @@ def is_prerelease(vers): parsed = version._normalized_key(normalized) return any([any([y in set(["a", "b", "c", "rc", "dev"]) for y in x]) for x in parsed]) + + +def latest_version_check(session, frequency=7 * 24 * 60 * 60): + """ + Attempt to determine if we're using the latest version of pip or not. Keeps + a small state file stored in ~/.pip/ to store when the last time we checked + was and what the version was. + """ + pypi_version = None + + try: + import pip # imported here to prevent circular imports + + current_time = datetime.datetime.utcnow() + + fmt = "%Y-%m-%dT%H:%M:%S" + statefile_path = os.path.expanduser("~/.pip/versioncheck") + + # Load the existing state + try: + with open(statefile_path) as statefile: + state = json.load(statefile) + except (IOError, ValueError): + state = {} + + # Determine if we need to refresh the state + if "last-check" in state and "version" in state: + last_check = datetime.datetime.strptime(state["last-check"], fmt) + if (current_time - last_check).total_seconds() < frequency: + pypi_version = state["version"] + + # Refresh the version if we need to + if pypi_version is None: + resp = session.get("https://pypi.python.org/pypi/pip/json", + headers={"Accept": "application/json"}, + ) + resp.raise_for_status() + pypi_version = resp.json()["info"]["version"] + + # Determine if our pypi_version is older + need_update = (pkg_resources.parse_version(pip.__version__) + < pkg_resources.parse_version(pypi_version)) + if need_update: + logger.warn( + "You are using pip version %s, however version %s is " + "available. You should consider upgrading via the pip install " + "--upgrade pip command." % (pip.__version__, pypi_version)) + + # Attempt to write out our version check file + with open(statefile_path, "w") as statefile: + json.dump( + { + "last-check": current_time.strftime(fmt), + "pypi_version": pypi_version, + }, + statefile, + ) + except Exception: + logger.debug( + "There was an error checking the latest version of pip", + exc_info=True, + ) diff --git a/tests/unit/test_basecommand.py b/tests/unit/test_basecommand.py index 864510f9283..67e07fdac2f 100644 --- a/tests/unit/test_basecommand.py +++ b/tests/unit/test_basecommand.py @@ -6,9 +6,15 @@ class FakeCommand(Command): name = 'fake' summary = name + def __init__(self, error=False): self.error = error super(FakeCommand, self).__init__() + + def main(self, args): + args.append("--disable-version-check") + return super(FakeCommand, self).main(args) + def run(self, options, args): logger.info("fake") if self.error: