Skip to content

Commit

Permalink
DRAFT - installation report
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed Apr 30, 2022
1 parent 828da47 commit 97b8b1a
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 0 deletions.
20 changes: 20 additions & 0 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import errno
import json
import operator
import os
import shutil
Expand All @@ -21,6 +22,7 @@
from pip._internal.locations import get_scheme
from pip._internal.metadata import get_environment
from pip._internal.models.format_control import FormatControl
from pip._internal.models.installation_report import InstallationReport
from pip._internal.operations.build.build_tracker import get_build_tracker
from pip._internal.operations.check import ConflictDetails, check_install_conflicts
from pip._internal.req import install_given_reqs
Expand Down Expand Up @@ -223,6 +225,20 @@ def add_options(self) -> None:
default=True,
help="Do not warn about broken dependencies",
)

self.cmd_opts.add_option(
"--report",
dest="json_report_file",
metavar="file",
default=None,
help=(
"Generate a JSON file describing what pip did to install "
"the provided requirements. "
"Can be used in combination with --dry-run and --ignore-installed "
"to 'resolve' the requirements."
),
)

self.cmd_opts.add_option(cmdoptions.no_binary())
self.cmd_opts.add_option(cmdoptions.only_binary())
self.cmd_opts.add_option(cmdoptions.prefer_binary())
Expand Down Expand Up @@ -340,6 +356,10 @@ def run(self, options: Values, args: List[str]) -> int:
requirement_set = resolver.resolve(
reqs, check_supported_wheels=not options.target_dir
)
if options.json_report_file:
report = InstallationReport.from_requirement_set(requirement_set)
with open(options.json_report_file, "w") as f:
json.dump(report.to_json(), f)

try:
pip_req = requirement_set.get_requirement("pip")
Expand Down
68 changes: 68 additions & 0 deletions src/pip/_internal/models/installation_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from typing import Any, Dict

from pip._internal.req.req_install import InstallRequirement
from pip._internal.req.req_set import RequirementSet
from pip._internal.utils.direct_url_helpers import direct_url_for_editable


class InstallationReportItem:
def __init__(self, install_req: InstallRequirement):
self._install_req = install_req

def to_json(self) -> Dict[str, Any]:
is_direct = bool(self._install_req.original_link)
assert (
self._install_req.download_info
), f"No download_info for {self._install_req}"
if self._install_req.editable:
download_info = direct_url_for_editable(
self._install_req.unpacked_source_directory
)
else:
download_info = self._install_req.download_info
res = {
# is_direct is true if requirement came from a direct URL reference (which
# includes editable requirements), and false if the requirement was
# downloaded from a PEP 503 index or --find-links.
"is_direct": is_direct,
# PEP 610 json for the download URL
"download_info": download_info.to_dict(),
# PEP 566 json encoding for metadata
# https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
# TODO (MVP?) self._install_req.metadata.to_json()
"metadata": {
"name": self._install_req.name,
"version": str(self._install_req.get_dist().version),
},
}
if self._install_req.user_supplied:
# TODO (MVP) investigate why self._install_req.req does not reproduce the
# user supplied URL in case of direct requirements
if self._install_req.original_link:
res["requested"] = str(self._install_req.original_link)
else:
res["requested"] = str(self._install_req.req)
# TODO (LATER) information about the index or find-links for non-direct reqs
# TODO (LATER) information about pip install options
return res


class InstallationReport:
def __init__(self, items: Dict[str, InstallationReportItem]):
self._items = items

@classmethod
def from_requirement_set(
cls, requirement_set: RequirementSet
) -> "InstallationReport":
items = {}
for name, requirement in requirement_set.requirements.items():
item = InstallationReportItem(requirement)
items[name] = item
return InstallationReport(items)

def to_json(self) -> Dict[str, Any]:
# TODO (MVP?) platform information (python version, etc)
return {
"installed": {name: item.to_json() for name, item in self._items.items()}
}

0 comments on commit 97b8b1a

Please sign in to comment.