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/

Changed (Δ8.1 KB):

raw changeset »

pip.py (199 lines added, 9 lines removed)

Up to file-list pip.py:

@@ -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, comes_from=None):
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 line.startswith('-f') or line.startswith('--find-links'):
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)