-
Notifications
You must be signed in to change notification settings - Fork 502
Feature: Add pipx clean command
#1683
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
Open
ever3001
wants to merge
7
commits into
pypa:main
Choose a base branch
from
ever3001:ever3001/feature/add_clean_option
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
0793175
feat(commands): add `clean` command.
58e306c
fix(clean): fix pre-commit check errors
f713984
fix(mypy): remove unused "type: ignore" comments in tests
8713e60
fix(clean): changes from PR review
773479e
fix(clean): erase venvs folder after all the venvs packages were deleted
1930ba0
refactor(tests/clean): aligned with the style of other tests
bee44d7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| Add `pipx clean` command to remove pipx data. | ||
|
|
||
| The new command supports selective cleanup with `--cache`, `--logs`, | ||
| `--trash`, and `--venvs` flags to remove specific components. Running | ||
| `pipx clean` without flags performs a full cleanup, removing all pipx | ||
| data and resetting to a fresh installation state. All cleanup operations | ||
| require user confirmation unless the `--force` flag is used. Use | ||
| `--verbose` for detailed output including paths and progress information. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| """Clean command for pipx CLI.""" | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| from pipx.colors import bold, red | ||
| from pipx.constants import ( | ||
| EXIT_CODE_CACHE_CLEANUP_FAIL, | ||
| EXIT_CODE_FULL_CLEANUP_FAIL, | ||
| EXIT_CODE_LOGS_CLEANUP_FAIL, | ||
| EXIT_CODE_OK, | ||
| EXIT_CODE_TRASH_CLEANUP_FAIL, | ||
| EXIT_CODE_VENVS_CLEANUP_FAIL, | ||
| ExitCode, | ||
| ) | ||
| from pipx.emojis import hazard, stars | ||
| from pipx.paths import ctx | ||
| from pipx.util import rmdir | ||
| from pipx.venv import VenvContainer | ||
|
|
||
|
|
||
| def _cleanup_directory( | ||
| path: Path, | ||
| description: str, | ||
| error_code: ExitCode, | ||
| verbose: bool, | ||
| ) -> ExitCode: | ||
| """ | ||
| Remove a directory with standardized error handling and output. | ||
| Args: | ||
| path: Directory path to remove | ||
| description: User-friendly description of what's being removed | ||
| error_code: Exit code to return on failure | ||
| verbose: Whether to print detailed output | ||
| Returns: | ||
| EXIT_CODE_OK on success, error_code on failure | ||
| """ | ||
| if not path.exists(): | ||
| if verbose: | ||
| print(f"Skipping {description} (directory doesn't exist)") | ||
| return EXIT_CODE_OK | ||
|
|
||
| action = f"Removing {description}..." | ||
| if verbose: | ||
| print(f"{action}") | ||
| print(f" Path: {path}") | ||
| else: | ||
| print(bold(action)) | ||
|
|
||
| try: | ||
| rmdir(path, safe_rm=False) | ||
| except Exception as e: | ||
| print(f"{red(f'Error removing {description}:')} {e}") | ||
| return error_code | ||
|
|
||
| print(f"{stars} {description.capitalize()} removed.") | ||
| return EXIT_CODE_OK | ||
|
|
||
|
|
||
| def _confirm_action(message: str) -> bool: | ||
| """ | ||
| Prompt user for confirmation. | ||
| Args: | ||
| message: Question to ask the user | ||
| Returns: | ||
| True if user confirms, False otherwise | ||
| """ | ||
| while True: | ||
| response = input(f"{message} [y/N]: ").lower().strip() | ||
| if response in ("y", "yes"): | ||
| return True | ||
| if response in ("n", "no", ""): | ||
| return False | ||
| print("Please answer 'y' or 'n'") | ||
|
|
||
|
|
||
| def _full_cleanup(verbose: bool) -> ExitCode: | ||
| """ | ||
| Remove all pipx data, resetting to first installation state. | ||
| This is the nuclear option - removes everything including: | ||
| - All installed packages (venvs) | ||
| - Cache and temporary data | ||
| - Logs | ||
| - Trash | ||
| - Shared libraries | ||
| Args: | ||
| verbose: Print detailed output | ||
| force: Skip confirmation prompt | ||
| """ | ||
| print(f"{hazard} {red('WARNING')}: This will remove ALL pipx data!") | ||
| print(red("All installed packages will be lost.")) | ||
| print() | ||
|
|
||
| return _cleanup_directory( | ||
| path=ctx.home, | ||
| description="all pipx data", | ||
| error_code=EXIT_CODE_FULL_CLEANUP_FAIL, | ||
| verbose=verbose, | ||
| ) | ||
|
|
||
|
|
||
| def _cache_cleanup(verbose: bool) -> ExitCode: | ||
| """Remove cached virtual environments from 'pipx run' commands.""" | ||
| return _cleanup_directory( | ||
| path=ctx.venv_cache, | ||
| description="cache and temporary data", | ||
| error_code=EXIT_CODE_CACHE_CLEANUP_FAIL, | ||
| verbose=verbose, | ||
| ) | ||
|
|
||
|
|
||
| def _logs_cleanup(verbose: bool) -> ExitCode: | ||
| """Remove pipx log files.""" | ||
| return _cleanup_directory( | ||
| path=ctx.logs, | ||
| description="logs", | ||
| error_code=EXIT_CODE_LOGS_CLEANUP_FAIL, | ||
| verbose=verbose, | ||
| ) | ||
|
|
||
|
|
||
| def _trash_cleanup(verbose: bool) -> ExitCode: | ||
| """Remove files in the trash directory.""" | ||
| return _cleanup_directory( | ||
| path=ctx.trash, | ||
| description="trash", | ||
| error_code=EXIT_CODE_TRASH_CLEANUP_FAIL, | ||
| verbose=verbose, | ||
| ) | ||
|
|
||
|
|
||
| def _venvs_cleanup(verbose: bool) -> ExitCode: | ||
| """Remove all installed packages and their virtual environments.""" | ||
| venv_container = VenvContainer(ctx.venvs) | ||
| venv_dirs = list(venv_container.iter_venv_dirs()) | ||
|
|
||
| if not venv_dirs: | ||
| if verbose: | ||
| print("No installed packages to remove.") | ||
| return EXIT_CODE_OK | ||
|
|
||
| print(bold(f"Removing {len(venv_dirs)} installed package(s)...")) | ||
|
|
||
| failed = [] | ||
| for venv_dir in venv_dirs: | ||
| package_name = venv_dir.name | ||
| try: | ||
| if verbose: | ||
| print(f" Removing {package_name}...") | ||
| rmdir(venv_dir, safe_rm=False) | ||
| except Exception as e: | ||
| failed.append((package_name, e)) | ||
| if verbose: | ||
| print(f" {red('Failed')}: {e}") | ||
|
|
||
| if failed: | ||
| print(f"{red(f'Failed to remove {len(failed)} package(s):')}") | ||
| for package_name, error in failed: | ||
| print(f" - {package_name}: {error}") | ||
| return EXIT_CODE_VENVS_CLEANUP_FAIL | ||
|
|
||
| rmdir(ctx.venvs, safe_rm=True) | ||
|
|
||
| print(f"{stars} All installed packages removed.") | ||
| return EXIT_CODE_OK | ||
|
|
||
|
|
||
| def clean( | ||
| cache: bool = False, | ||
| logs: bool = False, | ||
| trash: bool = False, | ||
| venvs: bool = False, | ||
| verbose: bool = False, | ||
| force: bool = False, | ||
| ) -> ExitCode: | ||
| """ | ||
| Clean pipx data directories. | ||
| If no specific options are provided, performs a full cleanup removing | ||
| all pipx data. Otherwise, removes only the specified components. | ||
| Args: | ||
| cache: Remove cache and temporary virtual environments | ||
| logs: Remove log files | ||
| trash: Empty trash directory | ||
| venvs: Remove all installed packages | ||
| verbose: Print detailed output including paths and progress | ||
| Returns: | ||
| Combined exit code (0 if all succeeded, non-zero if any failed) | ||
| """ | ||
| if not force: | ||
| if not _confirm_action("Are you sure you want to continue?"): | ||
| print("Operation cancelled.") | ||
| return EXIT_CODE_OK | ||
| # Determine what to clean | ||
| any_selected = cache or logs or trash or venvs | ||
|
|
||
| if not any_selected: | ||
| # No specific options: full cleanup | ||
| return _full_cleanup(verbose) | ||
|
|
||
| # Selective cleanup: run requested operations | ||
| cleanup_operations = [] | ||
| if cache: | ||
| cleanup_operations.append(_cache_cleanup) | ||
| if logs: | ||
| cleanup_operations.append(_logs_cleanup) | ||
| if trash: | ||
| cleanup_operations.append(_trash_cleanup) | ||
| if venvs: | ||
| cleanup_operations.append(_venvs_cleanup) | ||
|
|
||
| # Execute all operations and combine exit codes | ||
| for operation in cleanup_operations: | ||
| result: ExitCode = operation(verbose) | ||
|
Member
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. Is |
||
| if result != EXIT_CODE_OK: | ||
| return result | ||
|
|
||
| return EXIT_CODE_OK | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -408,6 +408,15 @@ def run_pipx_command(args: argparse.Namespace, subparsers: Dict[str, argparse.Ar | |
| skip=skip_list, | ||
| python_flag_passed=python_flag_passed, | ||
| ) | ||
| elif args.command == "clean": | ||
| return commands.clean( | ||
| cache=args.cache, | ||
| logs=args.logs, | ||
| trash=args.trash, | ||
| venvs=args.venvs, | ||
| verbose=verbose, | ||
| force=args.force, | ||
| ) | ||
| elif args.command == "runpip": | ||
| if not venv_dir: | ||
| raise PipxError("Developer error: venv_dir is not defined.") | ||
|
|
@@ -734,6 +743,53 @@ def _add_reinstall(subparsers, venv_completer: VenvCompleter, shared_parser: arg | |
| add_python_options(p) | ||
|
|
||
|
|
||
| def _add_clean(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None: | ||
| """Add the clean command to the parser.""" | ||
| p = subparsers.add_parser( | ||
| "clean", | ||
| help="Completely reset pipx to first installation state", | ||
| description=textwrap.dedent( | ||
| """ | ||
| Removes all installed packages, temporary virtual environments, | ||
| and cached data, effectively resetting pipx to its initial state. | ||
| The granularity of the cleanup can be controlled using the following options: | ||
| --venvs : Remove all installed packages and their virtual environments. | ||
| --logs : Remove all pipx log files. | ||
| --trash : Empty the pipx trash directory. | ||
| --cache : Remove cached data and temporary virtual environments, leaving installed packages intact. | ||
| """ | ||
| ), | ||
| parents=[shared_parser], | ||
| ) | ||
| p.add_argument( | ||
| "--cache", | ||
| action="store_true", | ||
| help="Remove cached data and temporary virtual environments, leaving installed packages intact", | ||
| ) | ||
| p.add_argument( | ||
| "--logs", | ||
| action="store_true", | ||
| help="Remove all pipx log files", | ||
| ) | ||
| p.add_argument( | ||
| "--trash", | ||
| action="store_true", | ||
| help="Empty the pipx trash directory", | ||
| ) | ||
| p.add_argument( | ||
| "--venvs", | ||
| action="store_true", | ||
| help="Remove all installed packages and their virtual environments", | ||
| ) | ||
| p.add_argument( | ||
| "--force", | ||
| "-f", | ||
| action="store_true", | ||
| help="Skip confirmation prompts (use with caution!)", | ||
| ) | ||
| p.set_defaults(subparser=p) | ||
|
Member
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. Is this used anywhere else? |
||
|
|
||
|
|
||
| def _add_reinstall_all(subparsers: argparse._SubParsersAction, shared_parser: argparse.ArgumentParser) -> None: | ||
| p = subparsers.add_parser( | ||
| "reinstall-all", | ||
|
|
@@ -994,6 +1050,7 @@ def get_command_parser() -> Tuple[argparse.ArgumentParser, Dict[str, argparse.Ar | |
| _add_uninstall_all(subparsers, shared_parser) | ||
| _add_reinstall(subparsers, completer_venvs.use, shared_parser) | ||
| _add_reinstall_all(subparsers, shared_parser) | ||
| _add_clean(subparsers, shared_parser) | ||
| _add_list(subparsers, shared_parser) | ||
| subparsers_with_subcommands["interpreter"] = _add_interpreter(subparsers, shared_parser) | ||
| _add_run(subparsers, shared_parser) | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the perspective of user, I think these messages should be displayed before the
y/Nprompt?