Mercurial > repos > bcclaywell > argo_navis
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 |