Skip to content

Commit 1682a1e

Browse files
committed
Add --report option to pip install
1 parent d869fc4 commit 1682a1e

File tree

3 files changed

+59
-0
lines changed

3 files changed

+59
-0
lines changed

news/53.feature.rst

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add ``--report`` to the install command to generate a json report of what was installed.
2+
In combination with ``--dry-run`` and ``--ignore-installed`` it can be used to resolve
3+
the requirements.

src/pip/_internal/commands/install.py

+19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import errno
2+
import json
23
import operator
34
import os
45
import shutil
@@ -21,6 +22,7 @@
2122
from pip._internal.locations import get_scheme
2223
from pip._internal.metadata import get_environment
2324
from pip._internal.models.format_control import FormatControl
25+
from pip._internal.models.installation_report import InstallationReport
2426
from pip._internal.operations.build.build_tracker import get_build_tracker
2527
from pip._internal.operations.check import ConflictDetails, check_install_conflicts
2628
from pip._internal.req import install_given_reqs
@@ -250,6 +252,19 @@ def add_options(self) -> None:
250252
self.parser.insert_option_group(0, index_opts)
251253
self.parser.insert_option_group(0, self.cmd_opts)
252254

255+
self.cmd_opts.add_option(
256+
"--report",
257+
dest="json_report_file",
258+
metavar="file",
259+
default=None,
260+
help=(
261+
"Generate a JSON file describing what pip did to install "
262+
"the provided requirements. "
263+
"Can be used in combination with --dry-run and --ignore-installed "
264+
"to 'resolve' the requirements."
265+
),
266+
)
267+
253268
@with_cleanup
254269
def run(self, options: Values, args: List[str]) -> int:
255270
if options.use_user_site and options.target_dir is not None:
@@ -352,6 +367,10 @@ def run(self, options: Values, args: List[str]) -> int:
352367
requirement_set = resolver.resolve(
353368
reqs, check_supported_wheels=not options.target_dir
354369
)
370+
if options.json_report_file:
371+
report = InstallationReport(requirement_set)
372+
with open(options.json_report_file, "w") as f:
373+
json.dump(report.to_dict(), f)
355374

356375
if options.dry_run:
357376
items = [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import Any, Dict
2+
3+
from pip._internal.req.req_install import InstallRequirement
4+
from pip._internal.req.req_set import RequirementSet
5+
6+
7+
class InstallationReport:
8+
def __init__(self, req_set: RequirementSet):
9+
self._req_set = req_set
10+
11+
@classmethod
12+
def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
13+
assert ireq.download_info, f"No download_info for {ireq}"
14+
res = {
15+
# PEP 610 json for the download URL
16+
"download_info": ireq.download_info.to_dict(),
17+
# is_direct is true if the requirement was a direct URL reference (which
18+
# includes editable requirements), and false if the requirement was
19+
# downloaded from a PEP 503 index or --find-links.
20+
"is_direct": bool(ireq.original_link),
21+
# requested is true if the requirement was specified by the user (aka
22+
# top level requirement), and false if it was installed as a dependency of a
23+
# requirement. https://peps.python.org/pep-0376/#requested
24+
"requested": ireq.user_supplied,
25+
# PEP 566 json encoding for metadata
26+
# https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
27+
"metadata": ireq.get_dist().json_metadata,
28+
}
29+
return res
30+
31+
def to_dict(self) -> Dict[str, Any]:
32+
return {
33+
"install": {
34+
name: self._install_req_to_dict(ireq)
35+
for name, ireq in self._req_set.requirements.items()
36+
}
37+
}

0 commit comments

Comments
 (0)