pypa/pip · error · LegacyDistutilsInstall

uninstall-distutils-installed-package

uninstall-distutils-installed-package

Error message

Cannot uninstall {distribution}

What it means

In the uninstall path (req_uninstall.py:510), when a distribution was installed by plain distutils (dist.installed_by_distutils), pip raises LegacyDistutilsInstall (code uninstall-distutils-installed-package). distutils installs don't write a usable RECORD, so pip cannot accurately determine the file set and refuses a partial uninstall.

Solutions

  1. Reinstall the package properly via pip (pip install --force-reinstall <pkg>) so it gets a RECORD, then uninstall.
  2. Manually remove the installed files and .egg-info directory.
  3. Start fresh in a new virtualenv to avoid the legacy install entirely.

Example fix

# before
python setup.py install   # later: pip uninstall fails
# after
pip install .  # then pip uninstall works
Defensive patterns

Strategy: validation

Validate before calling

# Detect distutils-installed packages before uninstall.
import pathlib, sys, importlib.metadata as md
for dist in md.distributions():
    base = pathlib.Path(dist.locate_file(""))
    egg_info = list(base.parent.glob("*.egg-info"))
    installer = (pathlib.Path(dist._path).parent / "INSTALLER").read_text().strip() if hasattr(dist, '_path') else ""
    if egg_info and installer not in ("pip",):
        print(f"Possibly distutils-installed: {dist.metadata['Name']}")

Prevention

When it happens

Trigger: Uninstalling a package that was installed via `python setup.py install` using the stdlib distutils (no wheel, no RECORD).

Common situations: Legacy `python setup.py install` workflows; old system-Python packages; packages from before pip/wheel became standard.

Related errors


AI-assisted analysis of pypa/pip@f399c37189 (2026-08-08). Data as JSON: /api/errors/1a0adb70872ecf69. Report an issue: GitHub.

Appendix: source

Thrown at src/pip/_internal/req/req_uninstall.py:510

                try:
                    namespace_packages = dist.read_text("namespace_packages.txt")
                except FileNotFoundError:
                    namespaces = []
                else:
                    namespaces = namespace_packages.splitlines(keepends=False)
                for top_level_pkg in [
                    p
                    for p in dist.read_text("top_level.txt").splitlines()
                    if p and p not in namespaces
                ]:
                    path = os.path.join(dist_location, top_level_pkg)
                    paths_to_remove.add(path)
                    paths_to_remove.add(f"{path}.py")
                    paths_to_remove.add(f"{path}.pyc")
                    paths_to_remove.add(f"{path}.pyo")

        elif dist.installed_by_distutils:
            raise LegacyDistutilsInstall(distribution=dist)

        elif dist.installed_as_egg:
            # package installed by easy_install
            # We cannot match on dist.egg_name because it can slightly vary
            # i.e. setuptools-0.6c11-py2.6.egg vs setuptools-0.6rc11-py2.6.egg
            # XXX We use normalized_dist_location because dist_location my contain
            # a trailing / if the distribution is a zipped egg
            # (which is not a directory).
            paths_to_remove.add(normalized_dist_location)
            easy_install_egg = os.path.split(normalized_dist_location)[1]
            easy_install_pth = os.path.join(
                os.path.dirname(normalized_dist_location),
                "easy-install.pth",
            )
            paths_to_remove.add_pth(easy_install_pth, "./" + easy_install_egg)

        elif dist.installed_with_dist_info:
            for path in uninstallation_paths(dist):

View on GitHub (pinned to f399c37189)