resolve issues with uninstall.

This commit is contained in:
Matt Davis
2022-09-19 23:19:37 -04:00
parent c882a09c44
commit 7497f0cbc8
5 changed files with 82 additions and 60 deletions
+31 -36
View File
@@ -30,6 +30,7 @@ from pipenv.utils.dependencies import (
get_canonical_names,
get_constraints_from_deps,
get_lockfile_section_using_pipfile_category,
get_pipfile_category_using_lockfile_section,
is_pinned,
is_required_version,
is_star,
@@ -1065,11 +1066,7 @@ def do_lock(
# Resolve package to generate constraints before resolving other categories
for category in lockfile_categories:
pipfile_category = category
if pipfile_category == "develop":
pipfile_category = "dev-packages"
if pipfile_category == "default":
pipfile_category = "packages"
pipfile_category = get_pipfile_category_using_lockfile_section(category)
if project.pipfile_exists:
packages = project.parsed_pipfile.get(pipfile_category, {})
else:
@@ -2426,13 +2423,13 @@ def do_uninstall(
# Automatically use an activated virtualenv.
if project.s.PIPENV_USE_SYSTEM:
system = True
if categories is None:
categories = project.get_package_categories()
# Ensure that virtualenv is available.
ensure_project(project, three=three, python=python, pypi_mirror=pypi_mirror)
# Uninstall all dependencies, if --all was provided.
if not any([packages, editable_packages, all_dev, all, categories]):
if not any([packages, editable_packages, all_dev, all]):
raise exceptions.PipenvUsageError("No package provided!", ctx=ctx)
if not categories:
categories = project.get_package_categories(for_lockfile=True)
editable_pkgs = [
Requirement.from_line(f"-e {p}").name for p in editable_packages if p
]
@@ -2440,8 +2437,6 @@ def do_uninstall(
package_names = {p for p in packages if p}
package_map = {canonicalize_name(p): p for p in packages if p}
installed_package_names = project.installed_package_names
# Intelligently detect if --dev should be used or not.
lockfile_packages = set()
if project.lockfile_exists:
project_pkg_names = project.lockfile_package_names
else:
@@ -2472,16 +2467,19 @@ def do_uninstall(
)
)
preserve_packages = set()
dev_packages = set()
for category in project.get_package_categories(for_lockfile=True):
if category == "develop":
continue
preserve_packages &= set(project_pkg_names[category])
package_names = set(project_pkg_names["develop"]) - preserve_packages
dev_packages |= set(project_pkg_names[category])
else:
preserve_packages |= set(project_pkg_names[category])
package_names = dev_packages - preserve_packages
# Remove known "bad packages" from the list.
bad_pkgs = get_canonical_names(BAD_PACKAGES)
ignored_packages = bad_pkgs & set(list(package_map.keys()))
for ignored_pkg in ignored_packages:
for ignored_pkg in get_canonical_names(ignored_packages):
if project.s.is_verbose():
click.echo(f"Ignoring {ignored_pkg}.", err=True)
package_names.discard(package_map[ignored_pkg])
@@ -2509,15 +2507,10 @@ def do_uninstall(
for normalized, package_name in selected_pkg_map.items()
if normalized in (used_packages - bad_pkgs)
]
for normalized_name, package_name in selected_pkg_map.items():
click.secho(
fix_utf8(f"Uninstalling {click.style(package_name)}..."),
fg="green",
bold=True,
)
found_package = False
for category in categories:
if normalized_name in lockfile_packages:
for category in categories:
category = get_lockfile_section_using_pipfile_category(category)
for normalized_name, package_name in selected_pkg_map.items():
if normalized_name in project.lockfile_content[category]:
click.echo(
"{} {} {} {}".format(
click.style("Removing", fg="cyan"),
@@ -2527,25 +2520,22 @@ def do_uninstall(
)
)
lockfile = project.get_or_create_lockfile()
if normalized_name in lockfile.default:
del lockfile.default[normalized_name]
if normalized_name in lockfile.develop:
del lockfile.develop[normalized_name]
if normalized_name in lockfile[category]:
del lockfile[category][normalized_name]
lockfile.write()
if project.remove_package_from_pipfile(package_name, category=category):
pipfile_category = get_pipfile_category_using_lockfile_section(category)
if project.remove_package_from_pipfile(
package_name, category=pipfile_category
):
click.secho(
fix_utf8(f"Removed {package_name} from Pipfile category {category}"),
fix_utf8(
f"Removed {package_name} from Pipfile category {pipfile_category}"
),
fg="green",
)
found_package = True
if not found_package:
click.echo(
"No package {} to remove from Pipfile.".format(
click.style(package_name, fg="green")
)
)
for normalized_name, package_name in selected_pkg_map.items():
still_remains = False
for category in project.get_package_categories():
if project.get_package_name_in_pipfile(normalized_name, category=category):
@@ -2553,6 +2543,11 @@ def do_uninstall(
if not still_remains:
# Uninstall the package.
if package_name in packages_to_remove:
click.secho(
fix_utf8(f"Uninstalling {click.style(package_name)}..."),
fg="green",
bold=True,
)
with project.environment.activated():
cmd = [
project_python(project, system=system),
+14 -15
View File
@@ -20,7 +20,6 @@ from pipenv.patched.pip._vendor import pkg_resources
from pipenv.utils.constants import is_type_checking
from pipenv.utils.dependencies import (
get_canonical_names,
get_lockfile_section_using_pipfile_category,
is_editable,
is_star,
pep423_name,
@@ -278,7 +277,7 @@ class Project:
category_packages = get_canonical_names(
self.lockfile_content[category].keys()
)
results[category] = category_packages
results[category] = set(category_packages)
results["combined"] = results["combined"] | category_packages
return results
@@ -287,11 +286,10 @@ class Project:
result = {}
combined = set()
for category in self.get_package_categories():
lockfile_section = get_lockfile_section_using_pipfile_category(category)
packages = self.get_pipfile_section(category)
keys = get_canonical_names(packages.keys())
combined |= keys
result[lockfile_section] = keys
result[category] = keys
result["combined"] = combined
return result
@@ -624,9 +622,10 @@ class Project:
@property
def all_packages(self):
"""Returns a list of all packages."""
p = dict(self.parsed_pipfile.get("dev-packages", {}))
p.update(self.parsed_pipfile.get("packages", {}))
return p
packages = {}
for category in self.get_package_categories():
packages.update(self.parsed_pipfile.get(category, {}))
return packages
@property
def packages(self):
@@ -761,17 +760,17 @@ class Project:
formatted_data = tomlkit.dumps(data).rstrip()
except Exception:
document = tomlkit.document()
for section in ("packages", "dev-packages"):
document[section] = tomlkit.table()
for category in self.get_package_categories():
document[category] = tomlkit.table()
# Convert things to inline tables — fancy :)
for package in data.get(section, {}):
if hasattr(data[section][package], "keys"):
for package in data.get(category, {}):
if hasattr(data[category][package], "keys"):
table = tomlkit.inline_table()
table.update(data[section][package])
document[section][package] = table
table.update(data[category][package])
document[category][package] = table
else:
document[section][package] = tomlkit.string(
data[section][package]
document[category][package] = tomlkit.string(
data[category][package]
)
formatted_data = tomlkit.dumps(document).rstrip()
+1 -6
View File
@@ -293,12 +293,7 @@ class Entry:
@property
def pipfile_packages(self):
from pipenv.utils.dependencies import (
get_lockfile_section_using_pipfile_category,
)
lockfile_section = get_lockfile_section_using_pipfile_category(self.category)
return self.project.pipfile_package_names[lockfile_section]
return self.project.pipfile_package_names[self.category]
def create_parent(self, name, specifier="*"):
parent = self.create(
+10
View File
@@ -40,6 +40,16 @@ def get_lockfile_section_using_pipfile_category(category):
return lockfile_section
def get_pipfile_category_using_lockfile_section(category):
if category == "develop":
lockfile_section = "dev-packages"
elif category == "default":
lockfile_section = "packages"
else:
lockfile_section = category
return lockfile_section
class HackedPythonVersion:
"""A Beautiful hack, which allows us to tell pip which version of Python we're using."""
+26 -3
View File
@@ -200,11 +200,34 @@ def test_uninstall_all_dev_with_shared_dependencies(pipenv_instance_pypi):
@pytest.mark.uninstall
def test_uninstall_missing_parameters(pipenv_instance_pypi):
with pipenv_instance_pypi() as p:
c = p.pipenv("install dataclasses-json")
def test_uninstall_missing_parameters(pipenv_instance_private_pypi):
with pipenv_instance_private_pypi() as p:
c = p.pipenv("install six")
assert c.returncode == 0
c = p.pipenv("uninstall")
assert c.returncode != 0
assert "No package provided!" in c.stderr
@pytest.mark.install
@pytest.mark.uninstall
def test_uninstall_category_with_shared_requirement(pipenv_instance_pypi):
with pipenv_instance_pypi() as p:
with open(p.pipfile_path, "w") as f:
contents = """
[packages]
six = "*"
[prereq]
six = "*"
"""
f.write(contents)
c = p.pipenv("install")
assert c.returncode == 0
c = p.pipenv("uninstall six --categories packages")
assert c.returncode == 0
assert "six" in p.lockfile["prereq"]
assert "six" not in p.lockfile["default"]