Repository: pypa/pip

Issue:
Can't change the index url for pip version check
There should be a possibility to do that. By existing option --index-url or a new one.


Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index 1eac77423..9ed67ea95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,7 @@ MANIFEST
 .tox
 .cache
 *.egg
+*.eggs
 *.py[cod]
 *~
 .coverage
diff --git a/news/4336.feature b/news/4336.feature
new file mode 100644
index 000000000..f27be7d09
--- /dev/null
+++ b/news/4336.feature
@@ -0,0 +1,3 @@
+Change pip outdated to use PackageFinder in order to do the version lookup
+so that local mirrors in Environments that do not have Internet connections
+can be used as the Source of Truth for latest version.
diff --git a/pip/basecommand.py b/pip/basecommand.py
index 49c134071..bd2f788a1 100644
--- a/pip/basecommand.py
+++ b/pip/basecommand.py
@@ -248,7 +248,7 @@ class Command(object):
                         options,
                         retries=0,
                         timeout=min(5, options.timeout)) as session:
-                    pip_version_check(session)
+                    pip_version_check(session, options)
 
         return SUCCESS
 
diff --git a/pip/models/index.py b/pip/models/index.py
index be9911988..3d0042e3a 100644
--- a/pip/models/index.py
+++ b/pip/models/index.py
@@ -7,7 +7,6 @@ class Index(object):
         self.netloc = urllib_parse.urlsplit(url).netloc
         self.simple_url = self.url_to_path('simple')
         self.pypi_url = self.url_to_path('pypi')
-        self.pip_json_url = self.url_to_path('pypi/pip/json')
 
     def url_to_path(self, path):
         return urllib_parse.urljoin(self.url, path)
diff --git a/pip/utils/outdated.py b/pip/utils/outdated.py
index 78ca2bbeb..bf6ff3fa7 100644
--- a/pip/utils/outdated.py
+++ b/pip/utils/outdated.py
@@ -10,7 +10,7 @@ from pip._vendor import lockfile
 from pip._vendor.packaging import version as packaging_version
 
 from pip.compat import WINDOWS
-from pip.models import PyPI
+from pip.index import PackageFinder
 from pip.locations import USER_CACHE_DIR, running_under_virtualenv
 from pip.utils import ensure_dir, get_installed_version
 from pip.utils.filesystem import check_path_owner
@@ -92,7 +92,7 @@ def load_selfcheck_statefile():
         return GlobalSelfCheckState()
 
 
-def pip_version_check(session):
+def pip_version_check(session, options):
     """Check for an update for pip.
 
     Limit the frequency of checks to once per week. State is stored either in
@@ -100,7 +100,7 @@ def pip_version_check(session):
     of the pip script path.
     """
     installed_version = get_installed_version("pip")
-    if installed_version is None:
+    if not installed_version:
         return
 
     pip_version = packaging_version.parse(installed_version)
@@ -121,18 +121,21 @@ def pip_version_check(session):
 
         # Refresh the version if we need to or just see if we need to warn
         if pypi_version is None:
-            resp = session.get(
-                PyPI.pip_json_url,
-                headers={"Accept": "application/json"},
+            # Lets use PackageFinder to see what the latest pip version is
+            finder = PackageFinder(
+                find_links=options.find_links,
+                index_urls=[options.index_url] + options.extra_index_urls,
+                allow_all_prereleases=False,  # Explicitly set to False
+                trusted_hosts=options.trusted_hosts,
+                process_dependency_links=options.process_dependency_links,
+                session=session,
+            )
+            all_candidates = finder.find_all_candidates("pip")
+            if not all_candidates:
+                return
+            pypi_version = str(
+                max(all_candidates, key=lambda c: c.version).version
             )
-            resp.raise_for_status()
-            pypi_version = [
-                v for v in sorted(
-                    list(resp.json()["releases"]),
-                    key=packaging_version.parse,
-                )
-                if not packaging_version.parse(v).is_prerelease
-            ][-1]
 
             # save that we've performed a check
             state.save(pypi_version, current_time)
@@ -154,7 +157,6 @@ def pip_version_check(session):
                 "'%s install --upgrade pip' command.",
                 pip_version, pypi_version, pip_cmd
             )
-
     except Exception:
         logger.debug(
             "There was an error checking the latest version of pip",
diff --git a/setup.py b/setup.py
index 71c6b1593..7029f71d9 100644
--- a/setup.py
+++ b/setup.py
@@ -42,8 +42,14 @@ def find_version(*file_paths):
 
 long_description = read('README.rst')
 
-tests_require = ['pytest', 'virtualenv>=1.10', 'scripttest>=1.3', 'mock',
-                 'pretend']
+tests_require = [
+    'pytest',
+    'mock',
+    'pretend'
+    'scripttest>=1.3',
+    'virtualenv>=1.10',
+    'freezegun',
+]
 
 
 setup(


Test patch:
diff --git a/tests/unit/test_unit_outdated.py b/tests/unit/test_unit_outdated.py
index d2d9c3f14..24313c8f3 100644
--- a/tests/unit/test_unit_outdated.py
+++ b/tests/unit/test_unit_outdated.py
@@ -8,43 +8,73 @@ import pytest
 import pretend
 
 from pip._vendor import lockfile
+from pip.index import InstallationCandidate
 from pip.utils import outdated
 
 
+class MockPackageFinder(object):
+
+    BASE_URL = 'https://pypi.python.org/simple/pip-{0}.tar.gz'
+    PIP_PROJECT_NAME = 'pip'
+    INSTALLATION_CANDIDATES = [
+        InstallationCandidate(PIP_PROJECT_NAME, '6.9.0',
+                              BASE_URL.format('6.9.0')),
+        InstallationCandidate(PIP_PROJECT_NAME, '3.3.1',
+                              BASE_URL.format('3.3.1')),
+        InstallationCandidate(PIP_PROJECT_NAME, '1.0',
+                              BASE_URL.format('1.0')),
+    ]
+
+    def __init__(self, *args, **kwargs):
+        pass
+
+    def find_all_candidates(self, project_name):
+        return self.INSTALLATION_CANDIDATES
+
+
+def _options():
+    ''' Some default options that we pass to outdated.pip_version_check '''
+    return pretend.stub(
+        find_links=False, extra_index_urls=[], index_url='default_url',
+        pre=False, trusted_hosts=False, process_dependency_links=False,
+    )
+
+
 @pytest.mark.parametrize(
-    ['stored_time', 'newver', 'check', 'warn'],
     [
-        ('1970-01-01T10:00:00Z', '2.0', True, True),
-        ('1970-01-01T10:00:00Z', '1.0', True, False),
-        ('1970-01-06T10:00:00Z', '1.0', False, False),
-        ('1970-01-06T10:00:00Z', '2.0', False, True),
+        'stored_time',
+        'installed_ver',
+        'new_ver',
+        'check_if_upgrade_required',
+        'check_warn_logs',
+    ],
+    [
+        # Test we return None when installed version is None
+        ('1970-01-01T10:00:00Z', None, '1.0', False, False),
+        # Need an upgrade - upgrade warning should print
+        ('1970-01-01T10:00:00Z', '1.0', '6.9.0', True, True),
+        # No upgrade - upgrade warning should not print
+        ('1970-01-9T10:00:00Z', '6.9.0', '6.9.0', False, False),
     ]
 )
-def test_pip_version_check(monkeypatch, stored_time, newver, check, warn):
-    monkeypatch.setattr(outdated, 'get_installed_version', lambda name: '1.0')
-
-    resp = pretend.stub(
-        raise_for_status=pretend.call_recorder(lambda: None),
-        json=pretend.call_recorder(lambda: {"releases": {newver: {}}}),
-    )
-    session = pretend.stub(
-        get=pretend.call_recorder(lambda u, headers=None: resp),
-    )
+def test_pip_version_check(monkeypatch, stored_time, installed_ver, new_ver,
+                           check_if_upgrade_required, check_warn_logs):
+    monkeypatch.setattr(outdated, 'get_installed_version',
+                        lambda name: installed_ver)
+    monkeypatch.setattr(outdated, 'PackageFinder', MockPackageFinder)
+    monkeypatch.setattr(outdated.logger, 'warning',
+                        pretend.call_recorder(lambda *a, **kw: None))
+    monkeypatch.setattr(outdated.logger, 'debug',
+                        pretend.call_recorder(lambda s, exc_info=None: None))
 
     fake_state = pretend.stub(
-        state={"last_check": stored_time, 'pypi_version': '1.0'},
+        state={"last_check": stored_time, 'pypi_version': installed_ver},
         save=pretend.call_recorder(lambda v, t: None),
     )
-
     monkeypatch.setattr(
         outdated, 'load_selfcheck_statefile', lambda: fake_state
     )
 
-    monkeypatch.setattr(outdated.logger, 'warning',
-                        pretend.call_recorder(lambda *a, **kw: None))
-    monkeypatch.setattr(outdated.logger, 'debug',
-                        pretend.call_recorder(lambda s, exc_info=None: None))
-
     with freezegun.freeze_time(
             "1970-01-09 10:00:00",
             ignore=[
@@ -52,26 +82,28 @@ def test_pip_version_check(monkeypatch, stored_time, newver, check, warn):
                 "pip._vendor.six.moves",
                 "pip._vendor.requests.packages.urllib3.packages.six.moves",
             ]):
-        outdated.pip_version_check(session)
+        latest_pypi_version = outdated.pip_version_check(None, _options())
 
-    assert not outdated.logger.debug.calls
-
-    if check:
-        assert session.get.calls == [pretend.call(
-            "https://pypi.python.org/pypi/pip/json",
-            headers={"Accept": "application/json"}
-        )]
+    # See we return None if not installed_version
+    if not installed_ver:
+        assert not latest_pypi_version
+    # See that we saved the correct version
+    elif check_if_upgrade_required:
         assert fake_state.save.calls == [
-            pretend.call(newver, datetime.datetime(1970, 1, 9, 10, 00, 00)),
+            pretend.call(new_ver, datetime.datetime(1970, 1, 9, 10, 00, 00)),
         ]
-        if warn:
-            assert len(outdated.logger.warning.calls) == 1
-        else:
-            assert len(outdated.logger.warning.calls) == 0
     else:
-        assert session.get.calls == []
+        # Make sure no Exceptions
+        assert not outdated.logger.debug.calls
+        # See that save was not called
         assert fake_state.save.calls == []
 
+    # Ensure we warn the user or not
+    if check_warn_logs:
+        assert len(outdated.logger.warning.calls) == 1
+    else:
+        assert len(outdated.logger.warning.calls) == 0
+
 
 def test_virtualenv_state(monkeypatch):
     CONTENT = '{"last_check": "1970-01-02T11:00:00Z", "pypi_version": "1.0"}'