-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathpef.py
127 lines (105 loc) · 3.49 KB
/
pef.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
# coding: utf8
"""
pef uninstall pip-installed package with all its dependencies.
"""
import sys
import subprocess
import click
import pkg_resources
_ver = sys.version_info
# Python 2.x?
is_py2 = (_ver[0] == 2)
# Python 3.x?
is_py3 = (_ver[0] == 3)
def _encode(u):
"""
:param u:
:return:
"""
if is_py2 and isinstance(u, unicode):
return u.encode('utf8')
return u
def _is_venv():
"""
:return:
"""
return hasattr(sys, 'real_prefix') or getattr(sys, 'base_prefix', sys.prefix) != sys.prefix
class DistInfo(object):
# @TODO: to be expanded
SKIP_MODULES = ['wheel', 'setuptools', 'pip']
def __init__(self, dist):
self.requires = {}
self.references = {}
self.keys = [d.key for d in dist if d.key not in self.SKIP_MODULES]
for d in dist:
if d.key not in self.SKIP_MODULES:
if d.key not in self.requires:
self.requires[d.key] = []
for r in d.requires():
if r.key in self.keys:
self.requires[d.key].append(r.key)
if r.key not in self.references:
self.references[r.key] = 0
self.references[r.key] += 1
self.rm = []
self.kp = []
def __repr__(self):
return 'keys:{0}\nrequires:{1}\nreferences:{2}'.format(self.keys, self.requires, self.references)
def purge(self, node):
"""
:param node:
:return:
"""
rc = self.references.get(node)
if rc:
rc = 0 if rc < 0 else rc - 1
self.references[node] = rc
if rc > 0:
if node not in self.kp:
self.kp.append(node)
return
for r in self.requires.get(node, []):
self.purge(r)
if not rc and node not in self.SKIP_MODULES:
if node not in self.rm:
self.rm.append(node)
if node in self.keys:
self.keys.remove(node)
if self.requires.get(node):
self.requires[node] = []
if node in self.kp:
self.kp.remove(node)
@click.command()
@click.argument('packages', nargs=-1)
@click.option('-y', '--yes', is_flag=True, help="Don't ask for confirmation of uninstall deletions.")
def cli(packages, yes):
"""Uninstall packages with all its dependencies."""
if not _is_venv():
click.secho(
click.style("Warning! You are not in an active virtual environment. This may purge system-level packages!",
fg='red'))
sys.exit(1)
if not packages:
click.secho(click.style("Packages can't be empty, please run `pef --help` for more details.", fg='yellow'))
sys.exit(0)
prune = []
pkg = pkg_resources.working_set
df = DistInfo(pkg)
for p in packages:
if p not in df.keys:
click.secho(click.style('Cannot uninstall requirement {0}, not installed.'.format(p), fg='yellow'))
continue
df.purge(_encode(p))
prune.extend(df.rm)
if df.kp:
click.secho(click.style(
'Module {0} is referenced by more than one other modules, to remain unchanged.'.format(', '.join(df.kp)),
fg='yellow'))
if prune:
cmd = [sys.executable, '-m', 'pip', 'uninstall']
if yes:
cmd.append('-y')
cmd.extend(list(set(prune)))
subprocess.check_call(cmd)
if __name__ == '__main__':
cli()