Repository: pypa/pip

Issue:
Stop erroring on installs of argparse and wsgiref.
<!-- Reviewable:start -->

[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/pypa/pip/3406)

<!-- Reviewable:end -->



Discussion:

Resolving patch:
diff --git a/pip/compat/__init__.py b/pip/compat/__init__.py
--- a/pip/compat/__init__.py
+++ b/pip/compat/__init__.py
@@ -23,6 +23,26 @@
         ipaddress.ip_network = ipaddress.IPNetwork
 
 
+try:
+    import sysconfig
+
+    def get_stdlib():
+        paths = [
+            sysconfig.get_path("stdlib"),
+            sysconfig.get_path("platstdlib"),
+        ]
+        return set(filter(bool, paths))
+except ImportError:
+    from distutils import sysconfig
+
+    def get_stdlib():
+        paths = [
+            sysconfig.get_python_lib(standard_lib=True),
+            sysconfig.get_python_lib(standard_lib=True, plat_specific=True),
+        ]
+        return set(filter(bool, paths))
+
+
 __all__ = [
     "logging_dictConfig", "ipaddress", "uses_pycache", "console_to_str",
     "native_str", "get_path_uid", "stdlib_pkgs", "WINDOWS", "samefile"
diff --git a/pip/req/req_install.py b/pip/req/req_install.py
--- a/pip/req/req_install.py
+++ b/pip/req/req_install.py
@@ -9,8 +9,8 @@
 import traceback
 import zipfile
 
-from distutils.util import change_root
 from distutils import sysconfig
+from distutils.util import change_root
 from email.parser import FeedParser
 
 from pip._vendor import pkg_resources, six
@@ -20,7 +20,7 @@
 
 import pip.wheel
 
-from pip.compat import native_str, WINDOWS
+from pip.compat import native_str, get_stdlib, WINDOWS
 from pip.download import is_url, url_to_path, path_to_url, is_archive_file
 from pip.exceptions import (
     InstallationError, UninstallationError, UnsupportedWheel,
@@ -32,7 +32,7 @@
     display_path, rmtree, ask_path_exists, backup_dir, is_installable_dir,
     dist_in_usersite, dist_in_site_packages, egg_link_path,
     call_subprocess, read_text_file, FakeFile, _make_build_dir, ensure_dir,
-    get_installed_version, canonicalize_name
+    get_installed_version, canonicalize_name, normalize_path, dist_is_local,
 )
 from pip.utils.hashes import Hashes
 from pip.utils.logging import indent_log
@@ -114,6 +114,9 @@ def __init__(self, req, comes_from, source_dir=None, editable=False,
         self.install_succeeded = None
         # UninstallPathSet of uninstalled distribution (for possible rollback)
         self.uninstalled = None
+        # Set True if a legitimate do-nothing-on-uninstall has happened - e.g.
+        # system site packages, stdlib packages.
+        self.nothing_to_uninstall = False
         self.use_user_site = False
         self.target_dir = None
         self.options = options if options else {}
@@ -606,6 +609,26 @@ def uninstall(self, auto_confirm=False):
             )
         dist = self.satisfied_by or self.conflicts_with
 
+        dist_path = normalize_path(dist.location)
+        if not dist_is_local(dist):
+            logger.info(
+                "Not uninstalling %s at %s, outside environment %s",
+                dist.key,
+                dist_path,
+                sys.prefix,
+            )
+            self.nothing_to_uninstall = True
+            return
+
+        if dist_path in get_stdlib():
+            logger.info(
+                "Not uninstalling %s at %s, as it is in the standard library.",
+                dist.key,
+                dist_path,
+            )
+            self.nothing_to_uninstall = True
+            return
+
         paths_to_remove = UninstallPathSet(dist)
         develop_egg_link = egg_link_path(dist)
         develop_egg_link_egg_info = '{0}.egg-info'.format(
@@ -735,9 +758,9 @@ def rollback_uninstall(self):
     def commit_uninstall(self):
         if self.uninstalled:
             self.uninstalled.commit()
-        else:
+        elif not self.nothing_to_uninstall:
             logger.error(
-                "Can't commit %s, nothing uninstalled.", self.project_name,
+                "Can't commit %s, nothing uninstalled.", self.name,
             )
 
     def archive(self, build_dir):


Test patch:
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -305,6 +305,29 @@ def test_editable_install_from_local_directory_with_no_setup_py(script, data):
     assert "is not installable. File 'setup.py' not found." in result.stderr
 
 
+@pytest.mark.skipif("sys.version_info < (2,7) or sys.version_info >= (3,4)")
+@pytest.mark.xfail
+def test_install_argparse_shadowed(script, data):
+    # When argparse is in the stdlib, we support installing it
+    # even though thats pretty useless because older packages did need to
+    # depend on it, and not having its metadata will cause pkg_resources
+    # requirements checks to fail // trigger easy-install, both of which are
+    # bad.
+    # XXX: Note, this test hits the outside-environment check, not the
+    # in-stdlib check, because our tests run in virtualenvs...
+    result = script.pip('install', 'argparse>=1.4')
+    assert "Not uninstalling argparse" in result.stdout
+
+
+@pytest.mark.skipif("sys.version_info < (3,4)")
+def test_upgrade_argparse_shadowed(script, data):
+    # If argparse is installed - even if shadowed for imported - we support
+    # upgrading it and properly remove the older versions files.
+    script.pip('install', 'argparse==1.3')
+    result = script.pip('install', 'argparse>=1.4')
+    assert "Not uninstalling argparse" not in result.stdout
+
+
 def test_install_as_egg(script, data):
     """
     Test installing as egg, instead of flat install.