comparison venv/lib/python2.7/site-packages/pip/req/req_uninstall.py @ 0:d67268158946 draft

planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author bcclaywell
date Mon, 12 Oct 2015 17:43:33 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d67268158946
1 from __future__ import absolute_import
2
3 import imp
4 import logging
5 import os
6 import sys
7 import tempfile
8
9 from pip.compat import uses_pycache, WINDOWS
10 from pip.exceptions import UninstallationError
11 from pip.utils import (rmtree, ask, is_local, dist_is_local, renames,
12 normalize_path)
13 from pip.utils.logging import indent_log
14
15
16 logger = logging.getLogger(__name__)
17
18
19 class UninstallPathSet(object):
20 """A set of file paths to be removed in the uninstallation of a
21 requirement."""
22 def __init__(self, dist):
23 self.paths = set()
24 self._refuse = set()
25 self.pth = {}
26 self.dist = dist
27 self.save_dir = None
28 self._moved_paths = []
29
30 def _permitted(self, path):
31 """
32 Return True if the given path is one we are permitted to
33 remove/modify, False otherwise.
34
35 """
36 return is_local(path)
37
38 def _can_uninstall(self):
39 if not dist_is_local(self.dist):
40 logger.info(
41 "Not uninstalling %s at %s, outside environment %s",
42 self.dist.project_name,
43 normalize_path(self.dist.location),
44 sys.prefix,
45 )
46 return False
47 return True
48
49 def add(self, path):
50 path = normalize_path(path, resolve_symlinks=False)
51 if not os.path.exists(path):
52 return
53 if self._permitted(path):
54 self.paths.add(path)
55 else:
56 self._refuse.add(path)
57
58 # __pycache__ files can show up after 'installed-files.txt' is created,
59 # due to imports
60 if os.path.splitext(path)[1] == '.py' and uses_pycache:
61 self.add(imp.cache_from_source(path))
62
63 def add_pth(self, pth_file, entry):
64 pth_file = normalize_path(pth_file)
65 if self._permitted(pth_file):
66 if pth_file not in self.pth:
67 self.pth[pth_file] = UninstallPthEntries(pth_file)
68 self.pth[pth_file].add(entry)
69 else:
70 self._refuse.add(pth_file)
71
72 def compact(self, paths):
73 """Compact a path set to contain the minimal number of paths
74 necessary to contain all paths in the set. If /a/path/ and
75 /a/path/to/a/file.txt are both in the set, leave only the
76 shorter path."""
77 short_paths = set()
78 for path in sorted(paths, key=len):
79 if not any([
80 (path.startswith(shortpath) and
81 path[len(shortpath.rstrip(os.path.sep))] == os.path.sep)
82 for shortpath in short_paths]):
83 short_paths.add(path)
84 return short_paths
85
86 def _stash(self, path):
87 return os.path.join(
88 self.save_dir, os.path.splitdrive(path)[1].lstrip(os.path.sep))
89
90 def remove(self, auto_confirm=False):
91 """Remove paths in ``self.paths`` with confirmation (unless
92 ``auto_confirm`` is True)."""
93 if not self._can_uninstall():
94 return
95 if not self.paths:
96 logger.info(
97 "Can't uninstall '%s'. No files were found to uninstall.",
98 self.dist.project_name,
99 )
100 return
101 logger.info(
102 'Uninstalling %s-%s:',
103 self.dist.project_name, self.dist.version
104 )
105
106 with indent_log():
107 paths = sorted(self.compact(self.paths))
108
109 if auto_confirm:
110 response = 'y'
111 else:
112 for path in paths:
113 logger.info(path)
114 response = ask('Proceed (y/n)? ', ('y', 'n'))
115 if self._refuse:
116 logger.info('Not removing or modifying (outside of prefix):')
117 for path in self.compact(self._refuse):
118 logger.info(path)
119 if response == 'y':
120 self.save_dir = tempfile.mkdtemp(suffix='-uninstall',
121 prefix='pip-')
122 for path in paths:
123 new_path = self._stash(path)
124 logger.debug('Removing file or directory %s', path)
125 self._moved_paths.append(path)
126 renames(path, new_path)
127 for pth in self.pth.values():
128 pth.remove()
129 logger.info(
130 'Successfully uninstalled %s-%s',
131 self.dist.project_name, self.dist.version
132 )
133
134 def rollback(self):
135 """Rollback the changes previously made by remove()."""
136 if self.save_dir is None:
137 logger.error(
138 "Can't roll back %s; was not uninstalled",
139 self.dist.project_name,
140 )
141 return False
142 logger.info('Rolling back uninstall of %s', self.dist.project_name)
143 for path in self._moved_paths:
144 tmp_path = self._stash(path)
145 logger.debug('Replacing %s', path)
146 renames(tmp_path, path)
147 for pth in self.pth.values():
148 pth.rollback()
149
150 def commit(self):
151 """Remove temporary save dir: rollback will no longer be possible."""
152 if self.save_dir is not None:
153 rmtree(self.save_dir)
154 self.save_dir = None
155 self._moved_paths = []
156
157
158 class UninstallPthEntries(object):
159 def __init__(self, pth_file):
160 if not os.path.isfile(pth_file):
161 raise UninstallationError(
162 "Cannot remove entries from nonexistent file %s" % pth_file
163 )
164 self.file = pth_file
165 self.entries = set()
166 self._saved_lines = None
167
168 def add(self, entry):
169 entry = os.path.normcase(entry)
170 # On Windows, os.path.normcase converts the entry to use
171 # backslashes. This is correct for entries that describe absolute
172 # paths outside of site-packages, but all the others use forward
173 # slashes.
174 if WINDOWS and not os.path.splitdrive(entry)[0]:
175 entry = entry.replace('\\', '/')
176 self.entries.add(entry)
177
178 def remove(self):
179 logger.debug('Removing pth entries from %s:', self.file)
180 with open(self.file, 'rb') as fh:
181 # windows uses '\r\n' with py3k, but uses '\n' with py2.x
182 lines = fh.readlines()
183 self._saved_lines = lines
184 if any(b'\r\n' in line for line in lines):
185 endline = '\r\n'
186 else:
187 endline = '\n'
188 for entry in self.entries:
189 try:
190 logger.debug('Removing entry: %s', entry)
191 lines.remove((entry + endline).encode("utf-8"))
192 except ValueError:
193 pass
194 with open(self.file, 'wb') as fh:
195 fh.writelines(lines)
196
197 def rollback(self):
198 if self._saved_lines is None:
199 logger.error(
200 'Cannot roll back changes to %s, none were made', self.file
201 )
202 return False
203 logger.debug('Rolling %s back to previous state', self.file)
204 with open(self.file, 'wb') as fh:
205 fh.writelines(self._saved_lines)
206 return True