jezdez / pip-uninstall (http://bitbucket.org/ianb/pip/)
fork of pip-uninstall
No description has been added.
Clone this repository (size: 379.5 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/jezdez/pip-uninstall/
| commit 238: | bba07f42e8a0 |
| parent 166: | 37298aa26931 |
| parent 237: | 5154bfa5a432 |
| branch: | trunk |
Automated merge with http://bitbucket.org/ianb/pip/
- View jezdez's profile
-
jezdez's public repos »
- django-adminfiles-de
- pip-standalone
- djangologging
- django-endless-pagination-de
- django-endless-pagination-fixes
- creole
- pip-wininst
- transifex-buildout
- hgstuff
- akismet
- django-dbtemplates
- Sphinx-PyPI-upload
- django-robots
- django-authority
- pip-config
- jezdez.bitbucket.org
- ports
- virtualenv-packaging
- pip
- django-registration-de
- pycompletion
- django-piston
- django-piston-python-oauth2
- django-licenses
- jzdz
- django-vcstorage
- textmate-missingdrawer
- virtualenv
- pip-uninstall
- setuptools_hg
- djangolocales
- django-staticfiles
- Send message
9 months ago
Changed (Δ8.1 KB):
raw changeset »
pip.py (199 lines added, 9 lines removed)
| … | … | @@ -33,6 +33,7 @@ import httplib |
33 |
33 |
import time |
34 |
34 |
import logging |
35 |
35 |
import ConfigParser |
36 |
from collections import defaultdict |
|
36 |
37 |
|
37 |
38 |
class InstallationError(Exception): |
38 |
39 |
"""General exception during installation""" |
| … | … | @@ -51,6 +52,12 @@ else: |
51 |
52 |
base_src_prefix = os.path.join(os.getcwd(), 'src') |
52 |
53 |
base_download_prefix = os.path.join(os.getcwd(), 'download') |
53 |
54 |
|
55 |
# FIXME doesn't account for venv linked to global site-packages |
|
56 |
if sys.platform == 'win32': |
|
57 |
lib_py = os.path.join(sys.prefix, 'Lib') |
|
58 |
else: |
|
59 |
lib_py = os.path.join(sys.prefix, 'lib', 'python%s' % sys.version[:3]) |
|
60 |
||
54 |
61 |
pypi_url = "http://pypi.python.org/simple" |
55 |
62 |
|
56 |
63 |
default_timeout = 15 |
| … | … | @@ -450,6 +457,41 @@ class InstallCommand(Command): |
450 |
457 |
|
451 |
458 |
InstallCommand() |
452 |
459 |
|
460 |
class UninstallCommand(Command): |
|
461 |
name = 'uninstall' |
|
462 |
usage = '%prog [OPTIONS] PACKAGE_NAMES ...' |
|
463 |
summary = 'Uninstall packages' |
|
464 |
||
465 |
def __init__(self): |
|
466 |
super(UninstallCommand, self).__init__() |
|
467 |
self.parser.add_option( |
|
468 |
'-r', '--requirement', |
|
469 |
dest='requirements', |
|
470 |
action='append', |
|
471 |
default=[], |
|
472 |
metavar='FILENAME', |
|
473 |
help='Uninstall all the packages listed in the given requirements file. ' |
|
474 |
'This option can be used multiple times.') |
|
475 |
self.parser.add_option( |
|
476 |
'-y', '--yes', |
|
477 |
dest='yes', |
|
478 |
action='store_true', |
|
479 |
help="Don't ask for confirmation of uninstall deletions. " |
|
480 |
"If this breaks your system, you get to keep the pieces.") |
|
481 |
||
482 |
def run(self, options, args): |
|
483 |
requirement_set = RequirementSet( |
|
484 |
build_dir=None, |
|
485 |
src_dir=None) |
|
486 |
for name in args: |
|
487 |
requirement_set.add_requirement( |
|
488 |
InstallRequirement.from_line(name)) |
|
489 |
for filename in options.requirements: |
|
490 |
for req in parse_requirements(filename): |
|
491 |
requirement_set.add_requirement(req) |
|
492 |
requirement_set.uninstall(auto_confirm=options.yes) |
|
493 |
||
494 |
UninstallCommand() |
|
453 |
495 |
|
454 |
496 |
class BundleCommand(InstallCommand): |
455 |
497 |
name = 'bundle' |
| … | … | @@ -1582,6 +1624,94 @@ execfile(__file__) |
1582 |
1624 |
'Unexpected version control type (in %s): %s' |
1583 |
1625 |
% (self.url, vc_type)) |
1584 |
1626 |
|
1627 |
def uninstall(self, auto_confirm=False): |
|
1628 |
""" |
|
1629 |
Uninstall the distribution currently satisfying this requirement. |
|
1630 |
||
1631 |
Prompts before removing or modifying files unless |
|
1632 |
``auto_confirm`` is True. |
|
1633 |
||
1634 |
Refuses to delete or modify files outside of ``sys.prefix`` - |
|
1635 |
thus uninstallation within a virtual environment can only |
|
1636 |
modify that virtual environment, even if the virtualenv is |
|
1637 |
linked to global site-packages. |
|
1638 |
||
1639 |
""" |
|
1640 |
assert self.check_if_exists(), "Cannot uninstall requirement %s, not installed" % (self.name,) |
|
1641 |
dist = self.satisfied_by |
|
1642 |
paths_to_remove = set() |
|
1643 |
entries_to_remove = defaultdict(set) |
|
1644 |
||
1645 |
logger.notify('Uninstalling %s' % self.name) |
|
1646 |
logger.indent += 2 |
|
1647 |
try: |
|
1648 |
pip_egg_info_path = os.path.join(dist.location, |
|
1649 |
dist.egg_name()) + '.egg-info' |
|
1650 |
easy_install_egg = dist.egg_name() + '.egg' |
|
1651 |
# FIXME this won't find a globally-installed develop egg |
|
1652 |
# if we're in a virtualenv (lib_py is based on |
|
1653 |
# sys.prefix). (There doesn't seem to be any metadata in |
|
1654 |
# the Distribution object for a develop egg that points |
|
1655 |
# back to its .egg-link and easy-install.pth files). |
|
1656 |
# That's OK, because we restrict ourselves to making |
|
1657 |
# changes within sys.prefix anyway. |
|
1658 |
develop_egg_link = os.path.join(lib_py, 'site-packages', |
|
1659 |
dist.project_name) + '.egg-link' |
|
1660 |
if os.path.exists(pip_egg_info_path): |
|
1661 |
# package installed by pip |
|
1662 |
paths_to_remove.add(pip_egg_info_path) |
|
1663 |
if dist.has_metadata('installed-files.txt'): |
|
1664 |
for installed_file in dist.get_metadata('installed-files.txt').splitlines(): |
|
1665 |
path = os.path.normpath(os.path.join(pip_egg_info_path, installed_file)) |
|
1666 |
if os.path.exists(path): |
|
1667 |
paths_to_remove.add(path) |
|
1668 |
if dist.has_metadata('top_level.txt'): |
|
1669 |
for top_level_pkg in [p for p |
|
1670 |
in dist.get_metadata('top_level.txt').splitlines() |
|
1671 |
if p]: |
|
1672 |
path = os.path.join(dist.location, top_level_pkg) |
|
1673 |
if os.path.exists(path): |
|
1674 |
paths_to_remove.add(path) |
|
1675 |
elif os.path.exists(path + '.py'): |
|
1676 |
paths_to_remove.add(path + '.py') |
|
1677 |
if os.path.exists(path + '.pyc'): |
|
1678 |
paths_to_remove.add(path + '.pyc') |
|
1679 |
||
1680 |
elif dist.location.endswith(easy_install_egg): |
|
1681 |
# package installed by easy_install |
|
1682 |
paths_to_remove.add(dist.location) |
|
1683 |
easy_install_pth = os.path.join(os.path.dirname(dist.location), |
|
1684 |
'easy-install.pth') |
|
1685 |
entries_to_remove[easy_install_pth].add('./' + easy_install_egg) |
|
1686 |
||
1687 |
elif os.path.isfile(develop_egg_link): |
|
1688 |
# develop egg |
|
1689 |
fh = open(develop_egg_link, 'r') |
|
1690 |
link_pointer = fh.readline().strip() |
|
1691 |
fh.close() |
|
1692 |
assert (link_pointer == dist.location), 'Egg-link %s does not match installed location of %s (at %s)' % (link_pointer, self.name, dist.location) |
|
1693 |
paths_to_remove.add(develop_egg_link) |
|
1694 |
easy_install_pth = os.path.join(os.path.dirname(develop_egg_link), |
|
1695 |
'easy-install.pth') |
|
1696 |
entries_to_remove[easy_install_pth].add(dist.location) |
|
1697 |
||
1698 |
for filename, entries in entries_to_remove.items(): |
|
1699 |
if strip_sys_prefix(filename) is None: |
|
1700 |
logger.notify('Will not modify %s, outside local environment.' % filename) |
|
1701 |
continue |
|
1702 |
remove_entries_from_file(filename, entries, auto_confirm) |
|
1703 |
||
1704 |
to_remove = set() |
|
1705 |
for path in paths_to_remove: |
|
1706 |
if strip_sys_prefix(path) is None: |
|
1707 |
logger.notify('Will not remove %s, outside of local environment.' % path) |
|
1708 |
else: |
|
1709 |
to_remove.add(path) |
|
1710 |
if to_remove: |
|
1711 |
remove_paths(to_remove, auto_confirm) |
|
1712 |
finally: |
|
1713 |
logger.indent -= 2 |
|
1714 |
||
1585 |
1715 |
def archive(self, build_dir): |
1586 |
1716 |
assert self.source_dir |
1587 |
1717 |
archive_name = '%s-%s.zip' % (self.name, self.installed_version) |
| … | … | @@ -1615,12 +1745,6 @@ execfile(__file__) |
1615 |
1745 |
if self.editable: |
1616 |
1746 |
self.install_editable() |
1617 |
1747 |
return |
1618 |
## FIXME: this is not a useful record: |
|
1619 |
## Also a bad location |
|
1620 |
if sys.platform == 'win32': |
|
1621 |
install_location = os.path.join(sys.prefix, 'Lib') |
|
1622 |
else: |
|
1623 |
install_location = os.path.join(sys.prefix, 'lib', 'python%s' % sys.version[:3]) |
|
1624 |
1748 |
temp_location = tempfile.mkdtemp('-record', 'pip-') |
1625 |
1749 |
record_filename = os.path.join(temp_location, 'install-record.txt') |
1626 |
1750 |
## FIXME: I'm not sure if this is a reasonable location; probably not |
| … | … | @@ -1835,6 +1959,10 @@ class RequirementSet(object): |
1835 |
1959 |
return self.requirements[self.requirement_aliases[name]] |
1836 |
1960 |
raise KeyError("No project with the name %r" % project_name) |
1837 |
1961 |
|
1962 |
def uninstall(self, auto_confirm=False): |
|
1963 |
for req in self.requirements.values(): |
|
1964 |
req.uninstall(auto_confirm=auto_confirm) |
|
1965 |
||
1838 |
1966 |
def install_files(self, finder, force_root_egg_info=False, only_download=False): |
1839 |
1967 |
unnamed = list(self.unnamed_requirements) |
1840 |
1968 |
reqs = self.requirements.values() |
| … | … | @@ -2072,7 +2200,6 @@ class RequirementSet(object): |
2072 |
2200 |
and is_svn_page(file_contents(filename))): |
2073 |
2201 |
# We don't really care about this |
2074 |
2202 |
Subversion('svn+' + link.url).unpack(location) |
2075 |
||
2076 |
2203 |
else: |
2077 |
2204 |
## FIXME: handle? |
2078 |
2205 |
## FIXME: magic signatures? |
| … | … | @@ -3497,7 +3624,7 @@ def get_file_content(url, comes_from=Non |
3497 |
3624 |
f.close() |
3498 |
3625 |
return url, content |
3499 |
3626 |
|
3500 |
def parse_requirements(filename, finder |
|
3627 |
def parse_requirements(filename, finder=None, comes_from=None): |
|
3501 |
3628 |
skip_match = None |
3502 |
3629 |
if os.environ.get('PIP_SKIP_REQUIREMENTS_REGEX'): |
3503 |
3630 |
skip_match = re.compile(os.environ['PIP_SKIP_REQUIREMENTS_REGEX']) |
| … | … | @@ -3525,7 +3652,7 @@ def parse_requirements(filename, finder, |
3525 |
3652 |
# No longer used, but previously these were used in |
3526 |
3653 |
# requirement files, so we'll ignore. |
3527 |
3654 |
pass |
3528 |
elif |
|
3655 |
elif finder and line.startswith('-f') or line.startswith('--find-links'): |
|
3529 |
3656 |
if line.startswith('-f'): |
3530 |
3657 |
line = line[2:].strip() |
3531 |
3658 |
else: |
| … | … | @@ -4028,6 +4155,69 @@ def package_to_requirement(package_name) |
4028 |
4155 |
else: |
4029 |
4156 |
return name |
4030 |
4157 |
|
4158 |
def strip_sys_prefix(path): |
|
4159 |
""" If ``path`` begins with sys.prefix, return ``path`` with |
|
4160 |
sys.prefix stripped off. Otherwise return None.""" |
|
4161 |
sys_prefix = os.path.realpath(sys.prefix) |
|
4162 |
if path.startswith(sys_prefix): |
|
4163 |
return path.replace(sys_prefix, '') |
|
4164 |
return None |
|
4165 |
||
4166 |
def remove_entries_from_file(filename, entries, auto_confirm=True): |
|
4167 |
"""Remove ``entries`` from text file ``filename``, with |
|
4168 |
confirmation (unless ``auto_confirm`` is True).""" |
|
4169 |
assert os.path.isfile(filename), "Cannot remove entries from nonexistent file %s" % filename |
|
4170 |
logger.notify('Removing entries from %s:' % filename) |
|
4171 |
logger.indent += 2 |
|
4172 |
try: |
|
4173 |
if auto_confirm: |
|
4174 |
response = 'y' |
|
4175 |
else: |
|
4176 |
for entry in entries: |
|
4177 |
logger.notify(entry) |
|
4178 |
response = ask('Proceed with removal (y/n)? ', ('y', 'n')) |
|
4179 |
if response == 'y': |
|
4180 |
fh = open(filename, 'r') |
|
4181 |
lines = fh.readlines() |
|
4182 |
fh.close() |
|
4183 |
try: |
|
4184 |
for entry in entries: |
|
4185 |
logger.notify('Removing entry: %s' % entry) |
|
4186 |
try: |
|
4187 |
lines.remove(entry + '\n') |
|
4188 |
except ValueError: |
|
4189 |
pass |
|
4190 |
finally: |
|
4191 |
pass |
|
4192 |
fh = open(filename, 'w') |
|
4193 |
fh.writelines(lines) |
|
4194 |
fh.close() |
|
4195 |
finally: |
|
4196 |
logger.indent -= 2 |
|
4197 |
||
4198 |
def remove_paths(paths, auto_confirm=False): |
|
4199 |
"""Remove paths in iterable ``paths`` with confirmation |
|
4200 |
(unless ``auto_confirm`` is True).""" |
|
4201 |
logger.notify('Within environment %s, removing:' % os.path.realpath(sys.prefix)) |
|
4202 |
logger.indent += 2 |
|
4203 |
try: |
|
4204 |
if auto_confirm: |
|
4205 |
response = 'y' |
|
4206 |
else: |
|
4207 |
for path in sorted(paths): |
|
4208 |
logger.notify(strip_sys_prefix(path)) |
|
4209 |
response = ask('Proceed with removal (y/n)? ', ('y', 'n')) |
|
4210 |
if response == 'y': |
|
4211 |
for path in sorted(paths): |
|
4212 |
if os.path.isdir(path): |
|
4213 |
logger.notify('Removing directory %s' % strip_sys_prefix(path)) |
|
4214 |
shutil.rmtree(path) |
|
4215 |
elif os.path.isfile(path): |
|
4216 |
logger.notify('Removing file %s' % strip_sys_prefix(path)) |
|
4217 |
os.remove(path) |
|
4218 |
finally: |
|
4219 |
logger.indent -= 2 |
|
4220 |
||
4031 |
4221 |
def splitext(path): |
4032 |
4222 |
"""Like os.path.splitext, but take off .tar too""" |
4033 |
4223 |
base, ext = posixpath.splitext(path) |
