Skip to content

Commit 65680b4

Browse files
authored
Merge pull request #11096 from sbidoul/install-dry-run-sbi
Add --dry-run option to pip install
2 parents 25dd005 + 701a5d6 commit 65680b4

File tree

5 files changed

+59
-1
lines changed

5 files changed

+59
-1
lines changed

news/11096.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``--dry-run`` option to ``pip install``, to let it print what it would install but
2+
not actually change anything in the target environment.

src/pip/_internal/commands/install.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ def add_options(self) -> None:
8585
self.cmd_opts.add_option(cmdoptions.pre())
8686

8787
self.cmd_opts.add_option(cmdoptions.editable())
88+
self.cmd_opts.add_option(
89+
"--dry-run",
90+
action="store_true",
91+
dest="dry_run",
92+
default=False,
93+
help=(
94+
"Don't actually install anything, just print what would be. "
95+
"Can be used in combination with --ignore-installed "
96+
"to 'resolve' the requirements."
97+
),
98+
)
8899
self.cmd_opts.add_option(
89100
"-t",
90101
"--target",
@@ -342,6 +353,18 @@ def run(self, options: Values, args: List[str]) -> int:
342353
reqs, check_supported_wheels=not options.target_dir
343354
)
344355

356+
if options.dry_run:
357+
would_install_items = sorted(
358+
(r.metadata["name"], r.metadata["version"])
359+
for r in requirement_set.requirements_to_install
360+
)
361+
if would_install_items:
362+
write_output(
363+
"Would install %s",
364+
" ".join("-".join(item) for item in would_install_items),
365+
)
366+
return SUCCESS
367+
345368
try:
346369
pip_req = requirement_set.get_requirement("pip")
347370
except KeyError:

src/pip/_internal/req/req_install.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
BaseDistribution,
2626
get_default_environment,
2727
get_directory_distribution,
28+
get_wheel_distribution,
2829
)
30+
from pip._internal.metadata.base import FilesystemWheel
2931
from pip._internal.models.direct_url import DirectUrl
3032
from pip._internal.models.link import Link
3133
from pip._internal.operations.build.metadata import generate_metadata
@@ -558,7 +560,16 @@ def metadata(self) -> Any:
558560
return self._metadata
559561

560562
def get_dist(self) -> BaseDistribution:
561-
return get_directory_distribution(self.metadata_directory)
563+
if self.metadata_directory:
564+
return get_directory_distribution(self.metadata_directory)
565+
elif self.local_file_path and self.is_wheel:
566+
return get_wheel_distribution(
567+
FilesystemWheel(self.local_file_path), canonicalize_name(self.name)
568+
)
569+
raise AssertionError(
570+
f"InstallRequirement {self} has no metadata directory and no wheel: "
571+
f"can't make a distribution."
572+
)
562573

563574
def assert_source_matches_version(self) -> None:
564575
assert self.source_dir

src/pip/_internal/req/req_set.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,16 @@ def get_requirement(self, name: str) -> InstallRequirement:
6767
@property
6868
def all_requirements(self) -> List[InstallRequirement]:
6969
return self.unnamed_requirements + list(self.requirements.values())
70+
71+
@property
72+
def requirements_to_install(self) -> List[InstallRequirement]:
73+
"""Return the list of requirements that need to be installed.
74+
75+
TODO remove this property together with the legacy resolver, since the new
76+
resolver only returns requirements that need to be installed.
77+
"""
78+
return [
79+
install_req
80+
for install_req in self.all_requirements
81+
if not install_req.constraint and not install_req.satisfied_by
82+
]

tests/functional/test_install.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2238,3 +2238,12 @@ def test_install_logs_pip_version_in_debug(
22382238
result = script.pip("install", "-v", fake_package)
22392239
pattern = "Using pip .* from .*"
22402240
assert_re_match(pattern, result.stdout)
2241+
2242+
2243+
def test_install_dry_run(script: PipTestEnvironment, data: TestData) -> None:
2244+
"""Test that pip install --dry-run logs what it would install."""
2245+
result = script.pip(
2246+
"install", "--dry-run", "--find-links", data.find_links, "simple"
2247+
)
2248+
assert "Would install simple-3.0" in result.stdout
2249+
assert "Successfully installed" not in result.stdout

0 commit comments

Comments
 (0)