Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 91143de

Browse files
committedJan 8, 2022
DRAFT - installation report
1 parent 2922e34 commit 91143de

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed
 

‎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.check import ConflictDetails, check_install_conflicts
2527
from pip._internal.req import install_given_reqs
2628
from pip._internal.req.req_install import InstallRequirement
@@ -223,6 +225,19 @@ def add_options(self) -> None:
223225
help="Do not warn about broken dependencies",
224226
)
225227

228+
self.cmd_opts.add_option(
229+
"--report",
230+
dest="json_report_file",
231+
metavar="file",
232+
default=None,
233+
help=(
234+
"Generate a JSON file describing what pip did to install "
235+
"the provided requirements. "
236+
"Can be used in combination with --dry-run and --ignore-installed "
237+
"to 'resolve' the requirements."
238+
),
239+
)
240+
226241
self.cmd_opts.add_option(cmdoptions.no_binary())
227242
self.cmd_opts.add_option(cmdoptions.only_binary())
228243
self.cmd_opts.add_option(cmdoptions.prefer_binary())
@@ -338,6 +353,10 @@ def run(self, options: Values, args: List[str]) -> int:
338353
requirement_set = resolver.resolve(
339354
reqs, check_supported_wheels=not options.target_dir
340355
)
356+
if options.json_report_file:
357+
report = InstallationReport.from_requirement_set(requirement_set)
358+
with open(options.json_report_file, "w") as f:
359+
json.dump(report.to_json(), f)
341360

342361
try:
343362
pip_req = requirement_set.get_requirement("pip")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
from pip._internal.utils.direct_url_helpers import (
6+
direct_url_for_editable,
7+
direct_url_from_link,
8+
)
9+
10+
11+
class InstallationReportItem:
12+
def __init__(self, install_req: InstallRequirement):
13+
self._install_req = install_req
14+
15+
def to_json(self) -> Dict[str, Any]:
16+
if self._install_req.editable:
17+
is_direct = True
18+
direct_url = direct_url_for_editable(
19+
self._install_req.unpacked_source_directory
20+
)
21+
elif self._install_req.original_link:
22+
is_direct = True
23+
direct_url = direct_url_from_link(
24+
self._install_req.original_link,
25+
self._install_req.source_dir,
26+
self._install_req.original_link_is_in_wheel_cache,
27+
)
28+
else:
29+
assert self._install_req.link
30+
is_direct = False
31+
direct_url = direct_url_from_link(self._install_req.link)
32+
res = {
33+
# is_direct is true if requirement came from a direct URL reference (which
34+
# includes editable requirements), and false if the requirement was
35+
# downloaded from a PEP 503 index or --find-links.
36+
"is_direct": is_direct,
37+
# PEP 610 json for the download URL
38+
"download_info": direct_url.to_dict(),
39+
# PEP 566 json encoding for metadata
40+
# https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
41+
# TODO (MVP) self._install_req.metadata.to_json()
42+
"metadata": {},
43+
}
44+
if self._install_req.user_supplied:
45+
# TODO (MVP) investigate why this does not reproduce the user supplied URL
46+
# in case of direct requirements
47+
res["requested"] = str(self._install_req.req)
48+
# TODO (LATER) information about the index for find-links for non-direct reqs
49+
# TODO (LATER) information about pip install options
50+
# TODO (MVP?) platform information (python version, etc)
51+
return res
52+
53+
54+
class InstallationReport:
55+
def __init__(self, items: Dict[str, InstallationReportItem]):
56+
self._items = items
57+
58+
@classmethod
59+
def from_requirement_set(
60+
cls, requirement_set: RequirementSet
61+
) -> "InstallationReport":
62+
items = {}
63+
for name, requirement in requirement_set.requirements.items():
64+
item = InstallationReportItem(requirement)
65+
items[name] = item
66+
return InstallationReport(items)
67+
68+
def to_json(self) -> Dict[str, Any]:
69+
return {
70+
"installed": {name: item.to_json() for name, item in self._items.items()}
71+
}

0 commit comments

Comments
 (0)
Please sign in to comment.