Repository: pypa/pip

Issue:
Do not throw error in `pip cache purge` when there are no cache packages to remove
### What's the problem this feature will solve?

`pip cache purge` throws error when no cache packages are not found. This causes issues in automation scripts. For example, when building a Docker image, it is appealing to call `pip cache purge` to clean pip caches to reduce Docker image size. However, this command might fail depending whether pip cache packages are found. 

### Describe the solution you'd like

Do not throw errors when no cache packages are found. In the highlighted part of the screenshot below, just return (instead of throwing an exception).

![image](https://user-images.githubusercontent.com/824507/132580968-2e88c4ce-960a-4055-90a0-40cbb2294827.png)


### Alternative Solutions

An alternative (and more complicated) way is to add an option to disable error throwing so that users can control the behavior of `pip cache purge` when no caching packages are found. 

### Additional context

NA

### Code of Conduct

- [X] I agree to follow the [PSF Code of Conduct](https://www.python.org/psf/conduct/).


Discussion:
Sounds reasonable to me!
> For example, when building a Docker image, it is appealing to call pip cache purge to clean pip caches to reduce Docker image size.

For this use case specifically I think `|| true` is pretty common? (Not saying your proposal is not a good idea.)

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
@@ -170,12 +170,16 @@ def remove_cache_items(self, options: Values, args: List[Any]) -> None:
 
         files = self._find_wheels(options, args[0])
 
-        # Only fetch http files if no specific pattern given
+        no_matching_msg = "No matching packages"
         if args[0] == "*":
+            # Only fetch http files if no specific pattern given
             files += self._find_http_files(options)
+        else:
+            # Add the pattern to the log message
+            no_matching_msg += ' for pattern "{}"'.format(args[0])
 
         if not files:
-            raise CommandError("No matching packages")
+            logger.warning(no_matching_msg)
 
         for filename in files:
             os.unlink(filename)


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
@@ -244,6 +244,24 @@ def test_cache_list_with_empty_cache_abspath(script):
     assert result.stdout.strip() == ""
 
 
+@pytest.mark.usefixtures("empty_wheel_cache")
+def test_cache_purge_with_empty_cache(script):
+    """Running `pip cache purge` with an empty cache should print a warning
+    and exit without an error code."""
+    result = script.pip("cache", "purge", allow_stderr_warning=True)
+    assert result.stderr == "WARNING: No matching packages\n"
+    assert result.stdout == "Files removed: 0\n"
+
+
+@pytest.mark.usefixtures("populate_wheel_cache")
+def test_cache_remove_with_bad_pattern(script):
+    """Running `pip cache remove` with a bad pattern should print a warning
+    and exit without an error code."""
+    result = script.pip("cache", "remove", "aaa", allow_stderr_warning=True)
+    assert result.stderr == 'WARNING: No matching packages for pattern "aaa"\n'
+    assert result.stdout == "Files removed: 0\n"
+
+
 def test_cache_list_too_many_args(script):
     """Passing `pip cache list` too many arguments should cause an error."""
     script.pip("cache", "list", "aaa", "bbb", expect_error=True)