Repository: pypa/pip

Issue:
'pip cache info' fails when no-cache-dir set
pip version: pip 20.1b1
Python version: CPython 3.8.1
OS: Win 10 64

Testing 20.1 beta, execute 'pip cache info' and crashes.  I'm guessing it's due to pip.ini turning off caching.

pip.ini:
```
[global]
no-cache-dir = false
```

Command execution:
```
> pip cache info
ERROR: Exception:
Traceback (most recent call last):
  File "c:\program files\python38\lib\site-packages\pip\_internal\cli\base_command.py", line 188, in _main
    status = self.run(options, args)
  File "c:\program files\python38\lib\site-packages\pip\_internal\commands\cache.py", line 62, in run
    handlers[action](options, args[1:])
  File "c:\program files\python38\lib\site-packages\pip\_internal\commands\cache.py", line 74, in get_cache_info
    num_packages = len(self._find_wheels(options, '*'))
  File "c:\program files\python38\lib\site-packages\pip\_internal\commands\cache.py", line 145, in _find_wheels
    wheel_dir = self._wheels_cache_dir(options)
  File "c:\program files\python38\lib\site-packages\pip\_internal\commands\cache.py", line 141, in _wheels_cache_dir
    return os.path.join(options.cache_dir, 'wheels')
  File "c:\program files\python38\lib\ntpath.py", line 78, in join
    path = os.fspath(path)
TypeError: expected str, bytes or os.PathLike object, not bool
```



Discussion:
@efahl Thanks for the report!
This will hold true for all `pip cache` commands, if we disable the cache via `--no-cache-dir`, since there is no cache to operate on as the user has explicitly disabled it.

e.g.

```
$ pip cache list twine* --no-cache-dir 
ERROR: Exception:
Traceback (most recent call last):
  File "/Users/devesh/pip/src/pip/_internal/cli/base_command.py", line 188, in _main
    status = self.run(options, args)
  File "/Users/devesh/pip/src/pip/_internal/commands/cache.py", line 62, in run
    handlers[action](options, args[1:])
  File "/Users/devesh/pip/src/pip/_internal/commands/cache.py", line 101, in list_cache_items
    files = self._find_wheels(options, pattern)
  File "/Users/devesh/pip/src/pip/_internal/commands/cache.py", line 145, in _find_wheels
    wheel_dir = self._wheels_cache_dir(options)
  File "/Users/devesh/pip/src/pip/_internal/commands/cache.py", line 141, in _wheels_cache_dir
    return os.path.join(options.cache_dir, 'wheels')
  File "/Users/devesh/.pyenv/versions/3.8.2/lib/python3.8/posixpath.py", line 76, in join
    a = os.fspath(a)
TypeError: expected str, bytes or os.PathLike object, not bool
```

```
$ pip cache remove twine* --no-cache-dir 
ERROR: Exception:
Traceback (most recent call last):
  File "/Users/devesh/pip/src/pip/_internal/cli/base_command.py", line 188, in _main
    status = self.run(options, args)
  File "/Users/devesh/pip/src/pip/_internal/commands/cache.py", line 62, in run
    handlers[action](options, args[1:])
  File "/Users/devesh/pip/src/pip/_internal/commands/cache.py", line 123, in remove_cache_items
    files = self._find_wheels(options, args[0])
  File "/Users/devesh/pip/src/pip/_internal/commands/cache.py", line 145, in _find_wheels
    wheel_dir = self._wheels_cache_dir(options)
  File "/Users/devesh/pip/src/pip/_internal/commands/cache.py", line 141, in _wheels_cache_dir
    return os.path.join(options.cache_dir, 'wheels')
  File "/Users/devesh/.pyenv/versions/3.8.2/lib/python3.8/posixpath.py", line 76, in join
    a = os.fspath(a)
TypeError: expected str, bytes or os.PathLike object, not bool
```

So should we raise a warning in [pip.internal.commands.cache.run](https://github.com/pypa/pip/blob/master/src/pip/_internal/commands/cache.py#L42) stating that `pip cache commands won't function since the cache is disabled. <steps to enable the cache>`
when we detect that `options.cache_dir == False` ?



Resolving patch:
diff --git a/src/pip/_internal/commands/cache.py b/src/pip/_internal/commands/cache.py
--- a/src/pip/_internal/commands/cache.py
+++ b/src/pip/_internal/commands/cache.py
@@ -48,6 +48,11 @@ def run(self, options, args):
             "purge": self.purge_cache,
         }
 
+        if not options.cache_dir:
+            logger.error("pip cache commands can not "
+                         "function since cache is disabled.")
+            return ERROR
+
         # Determine action
         if not args or args[0] not in handlers:
             logger.error("Need an action ({}) to perform.".format(


Test patch:
diff --git a/tests/functional/test_cache.py b/tests/functional/test_cache.py
--- a/tests/functional/test_cache.py
+++ b/tests/functional/test_cache.py
@@ -216,3 +216,15 @@ def test_cache_purge_too_many_args(script, wheel_cache_files):
     # Make sure nothing was deleted.
     for filename in wheel_cache_files:
         assert os.path.exists(filename)
+
+
+@pytest.mark.parametrize("command", ["info", "list", "remove", "purge"])
+def test_cache_abort_when_no_cache_dir(script, command):
+    """Running any pip cache command when cache is disabled should
+    abort and log an informative error"""
+    result = script.pip('cache', command, '--no-cache-dir',
+                        expect_error=True)
+    assert result.stdout == ''
+
+    assert ('ERROR: pip cache commands can not function'
+            ' since cache is disabled.' in result.stderr.splitlines())