summaryrefslogtreecommitdiff
path: root/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_uninstall.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_uninstall.py')
-rw-r--r--venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_uninstall.py455
1 files changed, 0 insertions, 455 deletions
diff --git a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_uninstall.py b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_uninstall.py
deleted file mode 100644
index a47520f..0000000
--- a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_uninstall.py
+++ /dev/null
@@ -1,455 +0,0 @@
1from __future__ import absolute_import
2
3import csv
4import functools
5import logging
6import os
7import sys
8import sysconfig
9
10from pip._vendor import pkg_resources
11
12from pip._internal.compat import WINDOWS, cache_from_source, uses_pycache
13from pip._internal.exceptions import UninstallationError
14from pip._internal.locations import bin_py, bin_user
15from pip._internal.utils.logging import indent_log
16from pip._internal.utils.misc import (
17 FakeFile, ask, dist_in_usersite, dist_is_local, egg_link_path, is_local,
18 normalize_path, renames,
19)
20from pip._internal.utils.temp_dir import TempDirectory
21
22logger = logging.getLogger(__name__)
23
24
25def _script_names(dist, script_name, is_gui):
26 """Create the fully qualified name of the files created by
27 {console,gui}_scripts for the given ``dist``.
28 Returns the list of file names
29 """
30 if dist_in_usersite(dist):
31 bin_dir = bin_user
32 else:
33 bin_dir = bin_py
34 exe_name = os.path.join(bin_dir, script_name)
35 paths_to_remove = [exe_name]
36 if WINDOWS:
37 paths_to_remove.append(exe_name + '.exe')
38 paths_to_remove.append(exe_name + '.exe.manifest')
39 if is_gui:
40 paths_to_remove.append(exe_name + '-script.pyw')
41 else:
42 paths_to_remove.append(exe_name + '-script.py')
43 return paths_to_remove
44
45
46def _unique(fn):
47 @functools.wraps(fn)
48 def unique(*args, **kw):
49 seen = set()
50 for item in fn(*args, **kw):
51 if item not in seen:
52 seen.add(item)
53 yield item
54 return unique
55
56
57@_unique
58def uninstallation_paths(dist):
59 """
60 Yield all the uninstallation paths for dist based on RECORD-without-.pyc
61
62 Yield paths to all the files in RECORD. For each .py file in RECORD, add
63 the .pyc in the same directory.
64
65 UninstallPathSet.add() takes care of the __pycache__ .pyc.
66 """
67 r = csv.reader(FakeFile(dist.get_metadata_lines('RECORD')))
68 for row in r:
69 path = os.path.join(dist.location, row[0])
70 yield path
71 if path.endswith('.py'):
72 dn, fn = os.path.split(path)
73 base = fn[:-3]
74 path = os.path.join(dn, base + '.pyc')
75 yield path
76
77
78def compact(paths):
79 """Compact a path set to contain the minimal number of paths
80 necessary to contain all paths in the set. If /a/path/ and
81 /a/path/to/a/file.txt are both in the set, leave only the
82 shorter path."""
83
84 sep = os.path.sep
85 short_paths = set()
86 for path in sorted(paths, key=len):
87 should_add = any(
88 path.startswith(shortpath.rstrip("*")) and
89 path[len(shortpath.rstrip("*").rstrip(sep))] == sep
90 for shortpath in short_paths
91 )
92 if not should_add:
93 short_paths.add(path)
94 return short_paths
95
96
97def compress_for_output_listing(paths):
98 """Returns a tuple of 2 sets of which paths to display to user
99
100 The first set contains paths that would be deleted. Files of a package
101 are not added and the top-level directory of the package has a '*' added
102 at the end - to signify that all it's contents are removed.
103
104 The second set contains files that would have been skipped in the above
105 folders.
106 """
107
108 will_remove = list(paths)
109 will_skip = set()
110
111 # Determine folders and files
112 folders = set()
113 files = set()
114 for path in will_remove:
115 if path.endswith(".pyc"):
116 continue
117 if path.endswith("__init__.py") or ".dist-info" in path:
118 folders.add(os.path.dirname(path))
119 files.add(path)
120
121 folders = compact(folders)
122
123 # This walks the tree using os.walk to not miss extra folders
124 # that might get added.
125 for folder in folders:
126 for dirpath, _, dirfiles in os.walk(folder):
127 for fname in dirfiles:
128 if fname.endswith(".pyc"):
129 continue
130
131 file_ = os.path.normcase(os.path.join(dirpath, fname))
132 if os.path.isfile(file_) and file_ not in files:
133 # We are skipping this file. Add it to the set.
134 will_skip.add(file_)
135
136 will_remove = files | {
137 os.path.join(folder, "*") for folder in folders
138 }
139
140 return will_remove, will_skip
141
142
143class UninstallPathSet(object):
144 """A set of file paths to be removed in the uninstallation of a
145 requirement."""
146 def __init__(self, dist):
147 self.paths = set()
148 self._refuse = set()
149 self.pth = {}
150 self.dist = dist
151 self.save_dir = TempDirectory(kind="uninstall")
152 self._moved_paths = []
153
154 def _permitted(self, path):
155 """
156 Return True if the given path is one we are permitted to
157 remove/modify, False otherwise.
158
159 """
160 return is_local(path)
161
162 def add(self, path):
163 head, tail = os.path.split(path)
164
165 # we normalize the head to resolve parent directory symlinks, but not
166 # the tail, since we only want to uninstall symlinks, not their targets
167 path = os.path.join(normalize_path(head), os.path.normcase(tail))
168
169 if not os.path.exists(path):
170 return
171 if self._permitted(path):
172 self.paths.add(path)
173 else:
174 self._refuse.add(path)
175
176 # __pycache__ files can show up after 'installed-files.txt' is created,
177 # due to imports
178 if os.path.splitext(path)[1] == '.py' and uses_pycache:
179 self.add(cache_from_source(path))
180
181 def add_pth(self, pth_file, entry):
182 pth_file = normalize_path(pth_file)
183 if self._permitted(pth_file):
184 if pth_file not in self.pth:
185 self.pth[pth_file] = UninstallPthEntries(pth_file)
186 self.pth[pth_file].add(entry)
187 else:
188 self._refuse.add(pth_file)
189
190 def _stash(self, path):
191 return os.path.join(
192 self.save_dir.path, os.path.splitdrive(path)[1].lstrip(os.path.sep)
193 )
194
195 def remove(self, auto_confirm=False, verbose=False):
196 """Remove paths in ``self.paths`` with confirmation (unless
197 ``auto_confirm`` is True)."""
198
199 if not self.paths:
200 logger.info(
201 "Can't uninstall '%s'. No files were found to uninstall.",
202 self.dist.project_name,
203 )
204 return
205
206 dist_name_version = (
207 self.dist.project_name + "-" + self.dist.version
208 )
209 logger.info('Uninstalling %s:', dist_name_version)
210
211 with indent_log():
212 if auto_confirm or self._allowed_to_proceed(verbose):
213 self.save_dir.create()
214
215 for path in sorted(compact(self.paths)):
216 new_path = self._stash(path)
217 logger.debug('Removing file or directory %s', path)
218 self._moved_paths.append(path)
219 renames(path, new_path)
220 for pth in self.pth.values():
221 pth.remove()
222
223 logger.info('Successfully uninstalled %s', dist_name_version)
224
225 def _allowed_to_proceed(self, verbose):
226 """Display which files would be deleted and prompt for confirmation
227 """
228
229 def _display(msg, paths):
230 if not paths:
231 return
232
233 logger.info(msg)
234 with indent_log():
235 for path in sorted(compact(paths)):
236 logger.info(path)
237
238 if not verbose:
239 will_remove, will_skip = compress_for_output_listing(self.paths)
240 else:
241 # In verbose mode, display all the files that are going to be
242 # deleted.
243 will_remove = list(self.paths)
244 will_skip = set()
245
246 _display('Would remove:', will_remove)
247 _display('Would not remove (might be manually added):', will_skip)
248 _display('Would not remove (outside of prefix):', self._refuse)
249
250 return ask('Proceed (y/n)? ', ('y', 'n')) == 'y'
251
252 def rollback(self):
253 """Rollback the changes previously made by remove()."""
254 if self.save_dir.path is None:
255 logger.error(
256 "Can't roll back %s; was not uninstalled",
257 self.dist.project_name,
258 )
259 return False
260 logger.info('Rolling back uninstall of %s', self.dist.project_name)
261 for path in self._moved_paths:
262 tmp_path = self._stash(path)
263 logger.debug('Replacing %s', path)
264 renames(tmp_path, path)
265 for pth in self.pth.values():
266 pth.rollback()
267
268 def commit(self):
269 """Remove temporary save dir: rollback will no longer be possible."""
270 self.save_dir.cleanup()
271 self._moved_paths = []
272
273 @classmethod
274 def from_dist(cls, dist):
275 dist_path = normalize_path(dist.location)
276 if not dist_is_local(dist):
277 logger.info(
278 "Not uninstalling %s at %s, outside environment %s",
279 dist.key,
280 dist_path,
281 sys.prefix,
282 )
283 return cls(dist)
284
285 if dist_path in {p for p in {sysconfig.get_path("stdlib"),
286 sysconfig.get_path("platstdlib")}
287 if p}:
288 logger.info(
289 "Not uninstalling %s at %s, as it is in the standard library.",
290 dist.key,
291 dist_path,
292 )
293 return cls(dist)
294
295 paths_to_remove = cls(dist)
296 develop_egg_link = egg_link_path(dist)
297 develop_egg_link_egg_info = '{}.egg-info'.format(
298 pkg_resources.to_filename(dist.project_name))
299 egg_info_exists = dist.egg_info and os.path.exists(dist.egg_info)
300 # Special case for distutils installed package
301 distutils_egg_info = getattr(dist._provider, 'path', None)
302
303 # Uninstall cases order do matter as in the case of 2 installs of the
304 # same package, pip needs to uninstall the currently detected version
305 if (egg_info_exists and dist.egg_info.endswith('.egg-info') and
306 not dist.egg_info.endswith(develop_egg_link_egg_info)):
307 # if dist.egg_info.endswith(develop_egg_link_egg_info), we
308 # are in fact in the develop_egg_link case
309 paths_to_remove.add(dist.egg_info)
310 if dist.has_metadata('installed-files.txt'):
311 for installed_file in dist.get_metadata(
312 'installed-files.txt').splitlines():
313 path = os.path.normpath(
314 os.path.join(dist.egg_info, installed_file)
315 )
316 paths_to_remove.add(path)
317 # FIXME: need a test for this elif block
318 # occurs with --single-version-externally-managed/--record outside
319 # of pip
320 elif dist.has_metadata('top_level.txt'):
321 if dist.has_metadata('namespace_packages.txt'):
322 namespaces = dist.get_metadata('namespace_packages.txt')
323 else:
324 namespaces = []
325 for top_level_pkg in [
326 p for p
327 in dist.get_metadata('top_level.txt').splitlines()
328 if p and p not in namespaces]:
329 path = os.path.join(dist.location, top_level_pkg)
330 paths_to_remove.add(path)
331 paths_to_remove.add(path + '.py')
332 paths_to_remove.add(path + '.pyc')
333 paths_to_remove.add(path + '.pyo')
334
335 elif distutils_egg_info:
336 raise UninstallationError(
337 "Cannot uninstall {!r}. It is a distutils installed project "
338 "and thus we cannot accurately determine which files belong "
339 "to it which would lead to only a partial uninstall.".format(
340 dist.project_name,
341 )
342 )
343
344 elif dist.location.endswith('.egg'):
345 # package installed by easy_install
346 # We cannot match on dist.egg_name because it can slightly vary
347 # i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
348 paths_to_remove.add(dist.location)
349 easy_install_egg = os.path.split(dist.location)[1]
350 easy_install_pth = os.path.join(os.path.dirname(dist.location),
351 'easy-install.pth')
352 paths_to_remove.add_pth(easy_install_pth, './' + easy_install_egg)
353
354 elif egg_info_exists and dist.egg_info.endswith('.dist-info'):
355 for path in uninstallation_paths(dist):
356 paths_to_remove.add(path)
357
358 elif develop_egg_link:
359 # develop egg
360 with open(develop_egg_link, 'r') as fh:
361 link_pointer = os.path.normcase(fh.readline().strip())
362 assert (link_pointer == dist.location), (
363 'Egg-link %s does not match installed location of %s '
364 '(at %s)' % (link_pointer, dist.project_name, dist.location)
365 )
366 paths_to_remove.add(develop_egg_link)
367 easy_install_pth = os.path.join(os.path.dirname(develop_egg_link),
368 'easy-install.pth')
369 paths_to_remove.add_pth(easy_install_pth, dist.location)
370
371 else:
372 logger.debug(
373 'Not sure how to uninstall: %s - Check: %s',
374 dist, dist.location,
375 )
376
377 # find distutils scripts= scripts
378 if dist.has_metadata('scripts') and dist.metadata_isdir('scripts'):
379 for script in dist.metadata_listdir('scripts'):
380 if dist_in_usersite(dist):
381 bin_dir = bin_user
382 else:
383 bin_dir = bin_py
384 paths_to_remove.add(os.path.join(bin_dir, script))
385 if WINDOWS:
386 paths_to_remove.add(os.path.join(bin_dir, script) + '.bat')
387
388 # find console_scripts
389 _scripts_to_remove = []
390 console_scripts = dist.get_entry_map(group='console_scripts')
391 for name in console_scripts.keys():
392 _scripts_to_remove.extend(_script_names(dist, name, False))
393 # find gui_scripts
394 gui_scripts = dist.get_entry_map(group='gui_scripts')
395 for name in gui_scripts.keys():
396 _scripts_to_remove.extend(_script_names(dist, name, True))
397
398 for s in _scripts_to_remove:
399 paths_to_remove.add(s)
400
401 return paths_to_remove
402
403
404class UninstallPthEntries(object):
405 def __init__(self, pth_file):
406 if not os.path.isfile(pth_file):
407 raise UninstallationError(
408 "Cannot remove entries from nonexistent file %s" % pth_file
409 )
410 self.file = pth_file
411 self.entries = set()
412 self._saved_lines = None
413
414 def add(self, entry):
415 entry = os.path.normcase(entry)
416 # On Windows, os.path.normcase converts the entry to use
417 # backslashes. This is correct for entries that describe absolute
418 # paths outside of site-packages, but all the others use forward
419 # slashes.
420 if WINDOWS and not os.path.splitdrive(entry)[0]:
421 entry = entry.replace('\\', '/')
422 self.entries.add(entry)
423
424 def remove(self):
425 logger.debug('Removing pth entries from %s:', self.file)
426 with open(self.file, 'rb') as fh:
427 # windows uses '\r\n' with py3k, but uses '\n' with py2.x
428 lines = fh.readlines()
429 self._saved_lines = lines
430 if any(b'\r\n' in line for line in lines):
431 endline = '\r\n'
432 else:
433 endline = '\n'
434 # handle missing trailing newline
435 if lines and not lines[-1].endswith(endline.encode("utf-8")):
436 lines[-1] = lines[-1] + endline.encode("utf-8")
437 for entry in self.entries:
438 try:
439 logger.debug('Removing entry: %s', entry)
440 lines.remove((entry + endline).encode("utf-8"))
441 except ValueError:
442 pass
443 with open(self.file, 'wb') as fh:
444 fh.writelines(lines)
445
446 def rollback(self):
447 if self._saved_lines is None:
448 logger.error(
449 'Cannot roll back changes to %s, none were made', self.file
450 )
451 return False
452 logger.debug('Rolling %s back to previous state', self.file)
453 with open(self.file, 'wb') as fh:
454 fh.writelines(self._saved_lines)
455 return True