-
Notifications
You must be signed in to change notification settings - Fork 0
/
uninstall.py
148 lines (126 loc) · 4.93 KB
/
uninstall.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import logging
from pathlib import Path
from shutil import which
from typing import List, Optional, Set
from pipx.commands.common import (
add_suffix,
can_symlink,
get_exposed_app_paths_for_package,
)
from pipx.constants import (
EXIT_CODE_OK,
EXIT_CODE_UNINSTALL_ERROR,
EXIT_CODE_UNINSTALL_VENV_NONEXISTENT,
ExitCode,
)
from pipx.emojis import hazard, sleep, stars
from pipx.pipx_metadata_file import PackageInfo
from pipx.util import rmdir, safe_unlink
from pipx.venv import Venv, VenvContainer
from pipx.venv_inspect import VenvMetadata
logger = logging.getLogger(__name__)
def _venv_metadata_to_package_info(
venv_metadata: VenvMetadata,
package_name: str,
package_or_url: str = "",
pip_args: Optional[List[str]] = None,
include_apps: bool = True,
include_dependencies: bool = False,
suffix: str = "",
) -> PackageInfo:
if pip_args is None:
pip_args = []
return PackageInfo(
package=package_name,
package_or_url=package_or_url,
pip_args=pip_args,
include_apps=include_apps,
include_dependencies=include_dependencies,
apps=venv_metadata.apps,
app_paths=venv_metadata.app_paths,
apps_of_dependencies=venv_metadata.apps_of_dependencies,
app_paths_of_dependencies=venv_metadata.app_paths_of_dependencies,
package_version=venv_metadata.package_version,
suffix=suffix,
)
def _get_package_bin_dir_app_paths(
venv: Venv, package_info: PackageInfo, local_bin_dir: Path
) -> Set[Path]:
suffix = package_info.suffix
apps = []
if package_info.include_apps:
apps += package_info.apps
if package_info.include_dependencies:
apps += package_info.apps_of_dependencies
return get_exposed_app_paths_for_package(
venv.bin_path, local_bin_dir, [add_suffix(app, suffix) for app in apps]
)
def _get_venv_bin_dir_app_paths(venv: Venv, local_bin_dir: Path) -> Set[Path]:
bin_dir_app_paths = set()
if venv.pipx_metadata.main_package.package is not None:
# Valid metadata for venv
for package_info in venv.package_metadata.values():
bin_dir_app_paths |= _get_package_bin_dir_app_paths(
venv, package_info, local_bin_dir
)
elif venv.python_path.is_file():
# No metadata from pipx_metadata.json, but valid python interpreter.
# In pre-metadata-pipx venv.root.name is name of main package
# In pre-metadata-pipx there is no suffix
# We make the conservative assumptions: no injected packages,
# not include_dependencies. Other PackageInfo fields are irrelevant
# here.
venv_metadata = venv.get_venv_metadata_for_package(venv.root.name, set())
main_package_info = _venv_metadata_to_package_info(
venv_metadata, venv.root.name
)
bin_dir_app_paths = _get_package_bin_dir_app_paths(
venv, main_package_info, local_bin_dir
)
else:
# No metadata and no valid python interpreter.
# We'll take our best guess on what to uninstall here based on symlink
# location for symlink-capable systems.
# The heuristic here is any symlink in ~/.local/bin pointing to
# .local/share/pipx/venvs/VENV_NAME/{bin,Scripts} should be uninstalled.
# For non-symlink systems we give up and return an empty set.
if not can_symlink(local_bin_dir):
return set()
bin_dir_app_paths = get_exposed_app_paths_for_package(
venv.bin_path, local_bin_dir
)
return bin_dir_app_paths
def uninstall(venv_dir: Path, local_bin_dir: Path, verbose: bool) -> ExitCode:
"""Uninstall entire venv_dir, including main package and all injected
packages.
Returns pipx exit code.
"""
if not venv_dir.exists():
print(f"Nothing to uninstall for {venv_dir.name} {sleep}")
app = which(venv_dir.name)
if app:
print(
f"{hazard} Note: '{app}' still exists on your system and is on your PATH"
)
return EXIT_CODE_UNINSTALL_VENV_NONEXISTENT
venv = Venv(venv_dir, verbose=verbose)
bin_dir_app_paths = _get_venv_bin_dir_app_paths(venv, local_bin_dir)
for bin_dir_app_path in bin_dir_app_paths:
try:
safe_unlink(bin_dir_app_path)
except FileNotFoundError:
logger.info(f"tried to remove but couldn't find {bin_dir_app_path}")
else:
logger.info(f"removed file {bin_dir_app_path}")
rmdir(venv_dir)
print(f"uninstalled {venv.name}! {stars}")
return EXIT_CODE_OK
def uninstall_all(
venv_container: VenvContainer, local_bin_dir: Path, verbose: bool
) -> ExitCode:
"""Returns pipx exit code."""
all_success = True
for venv_dir in venv_container.iter_venv_dirs():
return_val = uninstall(venv_dir, local_bin_dir, verbose)
all_success &= return_val == 0
return EXIT_CODE_OK if all_success else EXIT_CODE_UNINSTALL_ERROR