Skip to content

Commit

Permalink
DRAFT - installation report
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed Jan 8, 2022
1 parent 2922e34 commit 91143de
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
19 changes: 19 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.check import ConflictDetails, check_install_conflicts
from pip._internal.req import install_given_reqs
from pip._internal.req.req_install import InstallRequirement
Expand Down Expand Up @@ -223,6 +225,19 @@ def add_options(self) -> None:
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 @@ -338,6 +353,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
71 changes: 71 additions & 0 deletions src/pip/_internal/models/installation_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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,
direct_url_from_link,
)


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

def to_json(self) -> Dict[str, Any]:
if self._install_req.editable:
is_direct = True
direct_url = direct_url_for_editable(
self._install_req.unpacked_source_directory
)
elif self._install_req.original_link:
is_direct = True
direct_url = direct_url_from_link(
self._install_req.original_link,
self._install_req.source_dir,
self._install_req.original_link_is_in_wheel_cache,
)
else:
assert self._install_req.link
is_direct = False
direct_url = direct_url_from_link(self._install_req.link)
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": direct_url.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": {},
}
if self._install_req.user_supplied:
# TODO (MVP) investigate why this does not reproduce the user supplied URL
# in case of direct requirements
res["requested"] = str(self._install_req.req)
# TODO (LATER) information about the index for find-links for non-direct reqs
# TODO (LATER) information about pip install options
# TODO (MVP?) platform information (python version, etc)
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]:
return {
"installed": {name: item.to_json() for name, item in self._items.items()}
}

0 comments on commit 91143de

Please sign in to comment.