Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions news/11316.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Report installation errors and dependency resolution failures in JSON format when using ``--report``.
23 changes: 23 additions & 0 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ def run(self, options: Values, args: list[str]) -> int:
globally_managed=True,
)

requirement_set = None
try:
reqs = self.get_requirements(args, options, finder, session)

Expand Down Expand Up @@ -580,6 +581,28 @@ def run(self, options: Values, args: list[str]) -> int:
)
if summary := installed_packages_summary(installed, env):
write_output(summary)
except (InstallationError, CommandError, InstallWheelBuildError) as e:
# If a JSON report is requested, write the installation failure details
# in JSON format before re-raising the exception.
if options.json_report_file:
reqs_to_report = (
requirement_set.requirements_to_install
if requirement_set is not None
and hasattr(requirement_set, "requirements_to_install")
else []
)
report = InstallationReport(reqs_to_report)
report_dict = report.to_dict()
report_dict["error"] = {
"code": e.__class__.__name__,
"description": str(e),
}
if options.json_report_file == "-":
print_json(data=report_dict)
else:
with open(options.json_report_file, "w", encoding="utf-8") as f:
json.dump(report_dict, f, indent=2, ensure_ascii=False)
raise e
except OSError as error:
show_traceback = self.verbosity >= 1

Expand Down
37 changes: 37 additions & 0 deletions tests/functional/test_install_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,40 @@ def test_install_report_to_stdout(
report = json.loads(result.stdout)
assert "install" in report
assert len(report["install"]) == 1


def test_install_report_error(script: PipTestEnvironment, tmp_path: Path) -> None:
report_path = tmp_path / "report.json"
script.pip(
"install",
"nonexistent-package-name-xyz",
"--no-index",
"--report",
str(report_path),
expect_error=True,
)
report = json.loads(report_path.read_text())
assert "error" in report
assert report["error"]["code"] == "DistributionNotFound"
assert (
"No matching distribution found for nonexistent-package-name-xyz"
in report["error"]["description"]
)


def test_install_report_error_stdout(script: PipTestEnvironment) -> None:
result = script.pip(
"install",
"nonexistent-package-name-xyz",
"--no-index",
"--report",
"-",
expect_error=True,
)
report = json.loads(result.stdout)
assert "error" in report
assert report["error"]["code"] == "DistributionNotFound"
assert (
"No matching distribution found for nonexistent-package-name-xyz"
in report["error"]["description"]
)
Loading