Invoke the resolver in the same process as pipenv rather than utilzing subprocess. Restore accidentally commented out part of pip validations.

This commit is contained in:
Matt Davis
2023-07-10 15:02:13 -04:00
committed by Oz Tiram
parent 57406bb52b
commit 47e4d355e0
6 changed files with 43 additions and 181 deletions
@@ -95,13 +95,13 @@ def parse_editable(editable_req: str) -> Tuple[Optional[str], str, Set[str]]:
link = Link(url)
# if not link.is_vcs:
# backends = ", ".join(vcs.all_schemes)
# raise InstallationError(
# f"{editable_req} is not a valid editable requirement. "
# f"It should either be a path to a local project or a VCS URL "
# f"(beginning with {backends})."
# )
if not link.is_vcs:
backends = ", ".join(vcs.all_schemes)
raise InstallationError(
f"{editable_req} is not a valid editable requirement. "
f"It should either be a path to a local project or a VCS URL "
f"(beginning with {backends})."
)
package_name = link.egg_fragment
if not package_name:
+8 -93
View File
@@ -446,58 +446,6 @@ class Entry:
parents.extend(parent.flattened_parents)
return parents
def ensure_least_updates_possible(self):
"""
Mutate the current entry to ensure that we are making the smallest amount of
changes possible to the existing lockfile -- this will keep the old locked
versions of packages if they satisfy new constraints.
:return: None
"""
constraints = self.get_constraints()
can_use_original = True
can_use_updated = True
satisfied_by_versions = set()
for constraint in constraints:
if not constraint.specifier.contains(self.original_version):
self.can_use_original = False
if not constraint.specifier.contains(self.updated_version):
self.can_use_updated = False
satisfied_by_value = getattr(constraint, "satisfied_by", None)
if satisfied_by_value:
satisfied_by = "{}".format(
self.clean_specifier(str(satisfied_by_value.version))
)
satisfied_by_versions.add(satisfied_by)
if can_use_original:
self.entry_dict = self.lockfile_dict.copy()
elif can_use_updated:
if len(satisfied_by_versions) == 1:
self.entry_dict["version"] = next(
iter(sat_by for sat_by in satisfied_by_versions if sat_by), None
)
hashes = None
if self.lockfile_entry.specifiers == satisfied_by:
ireq = self.lockfile_entry.as_ireq
if (
not self.lockfile_entry.hashes
and self.resolver._should_include_hash(ireq)
):
hashes = self.resolver.get_hash(ireq)
else:
hashes = self.lockfile_entry.hashes
else:
if self.resolver._should_include_hash(constraint):
hashes = self.resolver.get_hash(constraint)
if hashes:
self.entry_dict["hashes"] = list(hashes)
self._entry.hashes = frozenset(hashes)
else:
# check for any parents, since they depend on this and the current
# installed versions are not compatible with the new version, so
# we will need to update the top level dependency if possible
self.check_flattened_parents()
def get_constraints(self):
"""
Retrieve all of the relevant constraints, aggregated from the pipfile, resolver,
@@ -668,7 +616,7 @@ def parse_packages(packages, pre, clear, system, requirements_dir=None):
def resolve_packages(
pre, clear, verbose, system, write, requirements_dir, packages, category
pre, clear, verbose, system, requirements_dir, packages, category, constraints=None
):
from pipenv.utils.internet import create_mirror_source, replace_pypi_sources
from pipenv.utils.resolver import resolve_deps
@@ -679,6 +627,9 @@ def resolve_packages(
else None
)
if constraints:
packages += constraints
def resolve(
packages, pre, project, sources, clear, system, category, requirements_dir=None
):
@@ -713,43 +664,9 @@ def resolve_packages(
requirements_dir=requirements_dir,
)
results = clean_results(results, resolver, project, category)
if write:
with open(write, "w") as fh:
if not results:
json.dump([], fh)
else:
json.dump(results, fh)
else:
print("RESULTS:")
if results:
print(json.dumps(results))
else:
print(json.dumps([]))
def _main(
pre,
clear,
verbose,
system,
write,
requirements_dir,
packages,
parse_only=False,
category=None,
):
if parse_only:
parse_packages(
packages,
pre=pre,
clear=clear,
system=system,
requirements_dir=requirements_dir,
)
else:
resolve_packages(
pre, clear, verbose, system, write, requirements_dir, packages, category
)
if results:
return results
return []
def main(argv=None):
@@ -767,15 +684,13 @@ def main(argv=None):
os.environ["PYTHONIOENCODING"] = "utf-8"
os.environ["PYTHONUNBUFFERED"] = "1"
parsed = handle_parsed_args(parsed)
_main(
resolve_packages(
parsed.pre,
parsed.clear,
parsed.verbose,
parsed.system,
parsed.write,
parsed.requirements_dir,
parsed.packages,
parse_only=parsed.parse_only,
category=parsed.category,
)
+24 -58
View File
@@ -1,14 +1,11 @@
import contextlib
import hashlib
import json
import os
import subprocess
import sys
import tempfile
import warnings
from functools import lru_cache
from html.parser import HTMLParser
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple, Union
from urllib import parse
@@ -30,6 +27,7 @@ from pipenv.patched.pip._internal.utils.hashes import FAVORITE_HASH
from pipenv.patched.pip._internal.utils.temp_dir import global_tempdir_manager
from pipenv.patched.pip._vendor import pkg_resources, rich
from pipenv.project import Project
from pipenv.resolver import resolve_packages
from pipenv.vendor import click
from pipenv.vendor.requirementslib.fileutils import create_tracked_tempdir, open_file
from pipenv.vendor.requirementslib.models.requirements import Line, Requirement
@@ -57,7 +55,7 @@ from .dependencies import (
from .indexes import parse_indexes, prepare_pip_source_args
from .internet import _get_requests_session, is_pypi_url
from .locking import format_requirement_for_lockfile, prepare_lockfile
from .shell import make_posix, subprocess_run, temp_environ
from .shell import subprocess_run, temp_environ
console = rich.console.Console()
err = rich.console.Console(stderr=True)
@@ -288,7 +286,8 @@ class Resolver:
except ValueError:
direct_url = DIRECT_URL_RE.match(line)
if direct_url:
line = "{}#egg={}".format(line, direct_url.groupdict()["name"])
name = direct_url.groupdict()["name"]
line = f"{name}@ {line}"
try:
req = Requirement.from_line(line)
except ValueError:
@@ -952,11 +951,8 @@ def actually_resolve_deps(
clear,
pre,
category,
req_dir=None,
req_dir,
):
if not req_dir:
req_dir = create_tracked_tempdir(suffix="-requirements", prefix="pipenv-")
with warnings.catch_warnings(record=True) as warning_list:
resolver = Resolver.create(
deps,
@@ -1047,8 +1043,6 @@ def venv_resolve_deps(
:return: The lock data
:rtype: dict
"""
from pipenv import resolver
lockfile_section = get_lockfile_section_using_pipfile_category(category)
if not deps:
@@ -1063,30 +1057,11 @@ def venv_resolve_deps(
if lockfile is None:
lockfile = project.lockfile(categories=[category])
req_dir = create_tracked_tempdir(prefix="pipenv", suffix="requirements")
cmd = [
which("python", allow_global=allow_global),
Path(resolver.__file__.rstrip("co")).as_posix(),
]
if pre:
cmd.append("--pre")
if clear:
cmd.append("--clear")
if allow_global:
cmd.append("--system")
if category:
cmd.append("--category")
cmd.append(category)
target_file = tempfile.NamedTemporaryFile(
prefix="resolver", suffix=".json", delete=False
)
target_file.close()
cmd.extend(["--write", make_posix(target_file.name)])
results = []
with temp_environ():
os.environ.update({k: str(val) for k, val in os.environ.items()})
if pypi_mirror:
os.environ["PIPENV_PYPI_MIRROR"] = str(pypi_mirror)
os.environ["PIPENV_VERBOSITY"] = str(project.s.PIPENV_VERBOSITY)
os.environ["PIPENV_REQ_DIR"] = req_dir
os.environ["PIP_NO_INPUT"] = "1"
pipenv_site_dir = get_pipenv_sitedir()
if pipenv_site_dir is not None:
@@ -1099,37 +1074,29 @@ def venv_resolve_deps(
# dependency resolution on them, so we are including this step inside the
# spinner context manager for the UX improvement
st.console.print("Building requirements...")
deps = convert_deps_to_pip(deps, project, include_index=True)
deps = convert_deps_to_pip(deps, project)
constraints = set(deps)
with tempfile.NamedTemporaryFile(
mode="w+", prefix="pipenv", suffix="constraints.txt", delete=False
) as constraints_file:
constraints_file.write(str("\n".join(constraints)))
cmd.append("--constraints-file")
cmd.append(constraints_file.name)
st.console.print("Resolving dependencies...")
c = resolve(cmd, st, project=project)
if c.returncode == 0:
st.console.print(environments.PIPENV_SPINNER_OK_TEXT.format("Success!"))
if not project.s.is_verbose() and c.stderr.strip():
click.echo(click.style(f"Warning: {c.stderr.strip()}"), err=True)
else:
try:
results = resolve_packages(
pre,
clear,
project.s.is_verbose(),
allow_global,
req_dir,
deps,
category=category,
constraints=constraints,
)
if results:
st.console.print(
environments.PIPENV_SPINNER_OK_TEXT.format("Success!")
)
except Exception:
st.console.print(
environments.PIPENV_SPINNER_FAIL_TEXT.format("Locking Failed!")
)
click.echo(f"Output: {c.stdout.strip()}", err=True)
click.echo(f"Error: {c.stderr.strip()}", err=True)
try:
with open(target_file.name) as fh:
results = json.load(fh)
except (IndexError, json.JSONDecodeError):
click.echo(c.stdout.strip(), err=True)
click.echo(c.stderr.strip(), err=True)
if os.path.exists(target_file.name):
os.unlink(target_file.name)
raise RuntimeError("There was a problem with locking.")
if os.path.exists(target_file.name):
os.unlink(target_file.name)
raise RuntimeError("There was a problem with locking.")
if lockfile_section not in lockfile:
lockfile[lockfile_section] = {}
return prepare_lockfile(results, pipfile, lockfile[lockfile_section])
@@ -1159,7 +1126,6 @@ def resolve_deps(
if not deps:
return results, resolver
# First (proper) attempt:
req_dir = req_dir if req_dir else os.environ.get("req_dir", None)
if not req_dir:
req_dir = create_tracked_tempdir(prefix="pipenv-", suffix="-requirements")
with HackedPythonVersion(python_path=project.python(system=allow_global)):
-11
View File
@@ -243,17 +243,6 @@ class Lockfile(ReqLibBaseModel):
]
return []
def as_requirements(self, category: str, include_hashes: bool = False) -> List[str]:
lines = []
section = list(self.get_requirements(categories=[category]))
for req in section:
kwargs = {"include_hashes": include_hashes}
if req.editable:
kwargs["include_markers"] = False
r = req.as_line(**kwargs)
lines.append(r.strip())
return lines
def write(self) -> None:
self.projectfile.model = copy.deepcopy(self.lockfile)
self.projectfile.write()
-8
View File
@@ -1386,12 +1386,6 @@ class SetupInfo(ReqLibBaseModel):
self.populate_metadata(dist)
self._ran_setup = True
@property
def pep517_config(self) -> Dict[str, Any]:
config = {}
config.setdefault("--global-option", [])
return config
def build_wheel(self) -> str:
need_delete = False
if not self.pyproject.exists():
@@ -1422,7 +1416,6 @@ build-backend = "{1}"
result = build_pep517(
directory,
self.extra_kwargs["build_dir"],
config_settings=self.pep517_config,
dist_type="wheel",
)
if need_delete:
@@ -1454,7 +1447,6 @@ build-backend = "{1}"
result = build_pep517(
self.base_dir,
self.extra_kwargs["build_dir"],
config_settings=self.pep517_config,
dist_type="sdist",
)
if need_delete:
+4 -4
View File
@@ -92,18 +92,18 @@ tablib = "*"
@pytest.mark.basic
@pytest.mark.install
def test_install_with_version_req_default_operator(pipenv_instance_pypi):
def test_install_with_version_req_default_operator(pipenv_instance_private_pypi):
"""Ensure that running `pipenv install` work when spec is package = "X.Y.Z". """
with pipenv_instance_pypi(chdir=True) as p:
with pipenv_instance_private_pypi(chdir=True) as p:
with open(p.pipfile_path, "w") as f:
contents = """
[packages]
fastapi = "0.95.0"
six = "1.12.0"
""".strip()
f.write(contents)
c = p.pipenv("install")
assert c.returncode == 0
assert "fastapi" in p.pipfile["packages"]
assert "six" in p.pipfile["packages"]
@pytest.mark.basic