Skip to content

Commit

Permalink
Add --report option to pip install
Browse files Browse the repository at this point in the history
  • Loading branch information
sbidoul committed May 29, 2022
1 parent d869fc4 commit 1682a1e
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 0 deletions.
3 changes: 3 additions & 0 deletions news/53.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add ``--report`` to the install command to generate a json report of what was installed.
In combination with ``--dry-run`` and ``--ignore-installed`` it can be used to resolve
the requirements.
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.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 @@ -250,6 +252,19 @@ def add_options(self) -> None:
self.parser.insert_option_group(0, index_opts)
self.parser.insert_option_group(0, self.cmd_opts)

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."
),
)

@with_cleanup
def run(self, options: Values, args: List[str]) -> int:
if options.use_user_site and options.target_dir is not None:
Expand Down Expand Up @@ -352,6 +367,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(requirement_set)
with open(options.json_report_file, "w") as f:
json.dump(report.to_dict(), f)

if options.dry_run:
items = [
Expand Down
37 changes: 37 additions & 0 deletions src/pip/_internal/models/installation_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Any, Dict

from pip._internal.req.req_install import InstallRequirement
from pip._internal.req.req_set import RequirementSet


class InstallationReport:
def __init__(self, req_set: RequirementSet):
self._req_set = req_set

@classmethod
def _install_req_to_dict(cls, ireq: InstallRequirement) -> Dict[str, Any]:
assert ireq.download_info, f"No download_info for {ireq}"
res = {
# PEP 610 json for the download URL
"download_info": ireq.download_info.to_dict(),
# is_direct is true if the requirement was 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": bool(ireq.original_link),
# requested is true if the requirement was specified by the user (aka
# top level requirement), and false if it was installed as a dependency of a
# requirement. https://peps.python.org/pep-0376/#requested
"requested": ireq.user_supplied,
# PEP 566 json encoding for metadata
# https://www.python.org/dev/peps/pep-0566/#json-compatible-metadata
"metadata": ireq.get_dist().json_metadata,
}
return res

def to_dict(self) -> Dict[str, Any]:
return {
"install": {
name: self._install_req_to_dict(ireq)
for name, ireq in self._req_set.requirements.items()
}
}

0 comments on commit 1682a1e

Please sign in to comment.