Skip to content

Commit

Permalink
Consider specifiers for equality operator to pin a dependency in make…
Browse files Browse the repository at this point in the history
…_install_requirement (#1323)
  • Loading branch information
IceTDrinker committed Feb 25, 2021
1 parent 1931e21 commit 4e78766
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 10 deletions.
4 changes: 1 addition & 3 deletions piptools/repositories/local.py
Expand Up @@ -63,9 +63,7 @@ def find_best_match(self, ireq, prereleases=None):
existing_pin = self.existing_pins.get(key)
if existing_pin and ireq_satisfied_by_existing_pin(ireq, existing_pin):
project, version, _ = as_tuple(existing_pin)
return make_install_requirement(
project, version, ireq.extras, constraint=ireq.constraint
)
return make_install_requirement(project, version, ireq)
else:
return self.repository.find_best_match(ireq, prereleases)

Expand Down
3 changes: 1 addition & 2 deletions piptools/repositories/pypi.py
Expand Up @@ -159,8 +159,7 @@ def find_best_match(self, ireq, prereleases=None):
return make_install_requirement(
best_candidate.name,
best_candidate.version,
ireq.extras,
constraint=ireq.constraint,
ireq,
)

def resolve_reqs(self, download_dir, ireq, wheel_cache):
Expand Down
14 changes: 12 additions & 2 deletions piptools/utils.py
Expand Up @@ -21,6 +21,7 @@
from pip._internal.vcs import is_url
from pip._vendor.packaging.markers import Marker
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.packaging.version import Version

_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
Expand Down Expand Up @@ -66,16 +67,25 @@ def comment(text: str) -> str:


def make_install_requirement(
name: str, version: str, extras: Iterable[str], constraint: bool = False
name: str, version: Union[str, Version], ireq: InstallRequirement
) -> InstallRequirement:
# If no extras are specified, the extras string is blank
extras_string = ""
extras = ireq.extras
if extras:
# Sort extras for stability
extras_string = f"[{','.join(sorted(extras))}]"

version_pin_operator = "=="
version_as_str = str(version)
for specifier in ireq.specifier:
if specifier.operator == "===" and specifier.version == version_as_str:
version_pin_operator = "==="
break

return install_req_from_line(
str(f"{name}{extras_string}=={version}"), constraint=constraint
str(f"{name}{extras_string}{version_pin_operator}{version}"),
constraint=ireq.constraint,
)


Expand Down
4 changes: 1 addition & 3 deletions tests/conftest.py
Expand Up @@ -68,9 +68,7 @@ def find_best_match(self, ireq, prereleases=False):
]
raise NoCandidateFound(ireq, tried_versions, ["https://fake.url.foo"])
best_version = max(versions, key=Version)
return make_install_requirement(
key_from_ireq(ireq), best_version, ireq.extras, constraint=ireq.constraint
)
return make_install_requirement(key_from_ireq(ireq), best_version, ireq)

def get_dependencies(self, ireq):
if ireq.editable or is_url_requirement(ireq):
Expand Down
71 changes: 71 additions & 0 deletions tests/test_cli_compile.py
Expand Up @@ -1555,3 +1555,74 @@ def test_duplicate_reqs_combined(
assert out.exit_code == 0, out
assert str(test_package_2) in out.stderr
assert "test-package-1==0.1" in out.stderr


@pytest.mark.parametrize(
("pkg2_install_requires", "req_in_content", "out_expected_content"),
(
pytest.param(
"",
["test-package-1===0.1.0\n"],
["test-package-1===0.1.0"],
id="pin package with ===",
),
pytest.param(
"",
["test-package-1==0.1.0\n"],
["test-package-1==0.1.0"],
id="pin package with ==",
),
pytest.param(
"test-package-1==0.1.0",
["test-package-1===0.1.0\n", "test-package-2==0.1.0\n"],
["test-package-1===0.1.0", "test-package-2==0.1.0"],
id="dep === pin preferred over == pin, main package == pin",
),
pytest.param(
"test-package-1==0.1.0",
["test-package-1===0.1.0\n", "test-package-2===0.1.0\n"],
["test-package-1===0.1.0", "test-package-2===0.1.0"],
id="dep === pin preferred over == pin, main package === pin",
),
pytest.param(
"test-package-1==0.1.0",
["test-package-2===0.1.0\n"],
["test-package-1==0.1.0", "test-package-2===0.1.0"],
id="dep == pin conserved, main package === pin",
),
),
)
def test_triple_equal_pinned_dependency_is_used(
runner,
make_package,
make_wheel,
tmpdir,
pkg2_install_requires,
req_in_content,
out_expected_content,
):
"""
Test that pip-compile properly emits the pinned requirement with ===
torchvision 0.8.2 requires torch==1.7.1 which can resolve to versions with
patches (e.g. torch 1.7.1+cu110), we want torch===1.7.1 without patches
"""

dists_dir = tmpdir / "dists"

test_package_1 = make_package("test_package_1", version="0.1.0")
make_wheel(test_package_1, dists_dir)

test_package_2 = make_package(
"test_package_2", version="0.1.0", install_requires=[pkg2_install_requires]
)
make_wheel(test_package_2, dists_dir)

with open("requirements.in", "w") as reqs_in:
for line in req_in_content:
reqs_in.write(line)

out = runner.invoke(cli, ["--find-links", str(dists_dir)])

assert out.exit_code == 0, out
for line in out_expected_content:
assert line in out.stderr

0 comments on commit 4e78766

Please sign in to comment.