-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Add 'pip cache' command #6391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add 'pip cache' command #6391
Changes from all commits
04c0b0e
b0e7b66
b9b29b8
c59ced6
8ae71ad
d57dcd9
50604be
9563dfb
6fb1ee7
c838a67
61dd0bc
94a6593
61a0adc
554133a
2d97830
6fa8498
d74895a
10d1376
d9dc76e
f22f69e
03d5ec1
735375f
8cd8c91
63ba6cc
ed9f885
f8b67c8
8b518b2
d57407a
e1fde1f
e804aa5
6e425d8
274b295
c6b5a52
32ce3ba
ba7c3ac
a20b28d
8858237
b7239f5
0c4eafa
b988417
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ Reference Guide | |
pip_list | ||
pip_show | ||
pip_search | ||
pip_cache | ||
pip_check | ||
pip_config | ||
pip_wheel | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
.. _`pip cache`: | ||
|
||
pip cache | ||
--------- | ||
|
||
.. contents:: | ||
|
||
Usage | ||
***** | ||
|
||
.. pip-command-usage:: cache | ||
|
||
Description | ||
*********** | ||
|
||
.. pip-command-description:: cache | ||
|
||
Options | ||
******* | ||
|
||
.. pip-command-options:: cache |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
:orphan: | ||
|
||
========= | ||
pip-cache | ||
========= | ||
|
||
Description | ||
*********** | ||
|
||
.. pip-command-description:: cache | ||
|
||
Usage | ||
***** | ||
|
||
.. pip-command-usage:: cache | ||
|
||
Options | ||
******* | ||
|
||
.. pip-command-options:: cache |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add ``pip cache`` command for inspecting/managing pip's wheel cache. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
from __future__ import absolute_import | ||
|
||
import logging | ||
import os | ||
import textwrap | ||
|
||
import pip._internal.utils.filesystem as filesystem | ||
from pip._internal.cli.base_command import Command | ||
from pip._internal.cli.status_codes import ERROR, SUCCESS | ||
from pip._internal.exceptions import CommandError, PipError | ||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING | ||
|
||
if MYPY_CHECK_RUNNING: | ||
from optparse import Values | ||
from typing import Any, List | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class CacheCommand(Command): | ||
""" | ||
Inspect and manage pip's wheel cache. | ||
|
||
Subcommands: | ||
|
||
info: Show information about the cache. | ||
list: List filenames of packages stored in the cache. | ||
remove: Remove one or more package from the cache. | ||
purge: Remove all items from the cache. | ||
|
||
<pattern> can be a glob expression or a package name. | ||
""" | ||
|
||
usage = """ | ||
%prog info | ||
%prog list [<pattern>] | ||
%prog remove <pattern> | ||
duckinator marked this conversation as resolved.
Show resolved
Hide resolved
|
||
%prog purge | ||
""" | ||
|
||
def run(self, options, args): | ||
# type: (Values, List[Any]) -> int | ||
handlers = { | ||
"info": self.get_cache_info, | ||
"list": self.list_cache_items, | ||
"remove": self.remove_cache_items, | ||
"purge": self.purge_cache, | ||
} | ||
|
||
# Determine action | ||
if not args or args[0] not in handlers: | ||
logger.error("Need an action ({}) to perform.".format( | ||
", ".join(sorted(handlers))) | ||
) | ||
return ERROR | ||
|
||
action = args[0] | ||
|
||
# Error handling happens here, not in the action-handlers. | ||
try: | ||
handlers[action](options, args[1:]) | ||
except PipError as e: | ||
logger.error(e.args[0]) | ||
return ERROR | ||
|
||
return SUCCESS | ||
|
||
def get_cache_info(self, options, args): | ||
# type: (Values, List[Any]) -> None | ||
if args: | ||
raise CommandError('Too many arguments') | ||
|
||
num_packages = len(self._find_wheels(options, '*')) | ||
|
||
cache_location = self._wheels_cache_dir(options) | ||
cache_size = filesystem.format_directory_size(cache_location) | ||
|
||
message = textwrap.dedent(""" | ||
Location: {location} | ||
Size: {size} | ||
Number of wheels: {package_count} | ||
""").format( | ||
location=cache_location, | ||
package_count=num_packages, | ||
size=cache_size, | ||
).strip() | ||
|
||
logger.info(message) | ||
|
||
def list_cache_items(self, options, args): | ||
# type: (Values, List[Any]) -> None | ||
if len(args) > 1: | ||
raise CommandError('Too many arguments') | ||
|
||
if args: | ||
pattern = args[0] | ||
else: | ||
pattern = '*' | ||
|
||
files = self._find_wheels(options, pattern) | ||
|
||
if not files: | ||
logger.info('Nothing cached.') | ||
return | ||
|
||
results = [] | ||
for filename in files: | ||
wheel = os.path.basename(filename) | ||
size = filesystem.format_file_size(filename) | ||
results.append(' - {} ({})'.format(wheel, size)) | ||
logger.info('Cache contents:\n') | ||
logger.info('\n'.join(sorted(results))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Noting that I thought about sorting on (wheel, size) and then composing this list and the only situation where they'd result in a different output would be if there's 2 wheels with the same name and diff sizes -- which is very unlikely. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe... we'd want to make it possible to get the information as a "sort by size"? But that can definitely be a follow up PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re sorting on (wheel, size): Yeah, it seems unlikely enough to not be worthwhile. We can always change that in the future if it turns out we're both wrong. 😛 Re sort by size: Agree on all counts — seems useful, but I see no issue waiting until a follow-up PR for that. 🙂 |
||
|
||
def remove_cache_items(self, options, args): | ||
# type: (Values, List[Any]) -> None | ||
if len(args) > 1: | ||
raise CommandError('Too many arguments') | ||
|
||
if not args: | ||
pradyunsg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
raise CommandError('Please provide a pattern') | ||
|
||
files = self._find_wheels(options, args[0]) | ||
if not files: | ||
raise CommandError('No matching packages') | ||
|
||
for filename in files: | ||
os.unlink(filename) | ||
logger.debug('Removed %s', filename) | ||
duckinator marked this conversation as resolved.
Show resolved
Hide resolved
|
||
logger.info('Files removed: %s', len(files)) | ||
|
||
def purge_cache(self, options, args): | ||
duckinator marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# type: (Values, List[Any]) -> None | ||
if args: | ||
raise CommandError('Too many arguments') | ||
|
||
return self.remove_cache_items(options, ['*']) | ||
|
||
def _wheels_cache_dir(self, options): | ||
# type: (Values) -> str | ||
return os.path.join(options.cache_dir, 'wheels') | ||
|
||
def _find_wheels(self, options, pattern): | ||
# type: (Values, str) -> List[str] | ||
wheel_dir = self._wheels_cache_dir(options) | ||
|
||
# The wheel filename format, as specified in PEP 427, is: | ||
# {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl | ||
# | ||
# Additionally, non-alphanumeric values in the distribution are | ||
# normalized to underscores (_), meaning hyphens can never occur | ||
# before `-{version}`. | ||
# | ||
# Given that information: | ||
# - If the pattern we're given contains a hyphen (-), the user is | ||
# providing at least the version. Thus, we can just append `*.whl` | ||
# to match the rest of it. | ||
# - If the pattern we're given doesn't contain a hyphen (-), the | ||
# user is only providing the name. Thus, we append `-*.whl` to | ||
# match the hyphen before the version, followed by anything else. | ||
# | ||
# PEP 427: https://www.python.org/dev/peps/pep-0427/ | ||
pattern = pattern + ("*.whl" if "-" in pattern else "-*.whl") | ||
|
||
return filesystem.find_files(wheel_dir, pattern) |
Uh oh!
There was an error while loading. Please reload this page.