diff --git a/docs/html/reference/installation-report.md b/docs/html/reference/installation-report.md
index 5823205f9..e0cfcd97e 100644
--- a/docs/html/reference/installation-report.md
+++ b/docs/html/reference/installation-report.md
@@ -56,6 +56,9 @@ package with the following properties:
URL reference. `false` if the requirements was provided as a name and version
specifier.
+- `is_yanked`: `true` if the requirement was yanked from the index, but was still
+ selected by pip conform to [PEP 592](https://peps.python.org/pep-0592/#installers).
+
- `download_info`: Information about the artifact (to be) downloaded for installation,
using the [direct URL data
structure](https://packaging.python.org/en/latest/specifications/direct-url-data-structure/).
@@ -106,6 +109,7 @@ will produce an output similar to this (metadata abriged for brevity):
}
},
"is_direct": false,
+ "is_yanked": false,
"requested": true,
"metadata": {
"name": "pydantic",
@@ -133,6 +137,7 @@ will produce an output similar to this (metadata abriged for brevity):
}
},
"is_direct": true,
+ "is_yanked": false,
"requested": true,
"metadata": {
"name": "packaging",
diff --git a/news/12224.feature.rst b/news/12224.feature.rst
new file mode 100644
index 000000000..d87426578
--- /dev/null
+++ b/news/12224.feature.rst
@@ -0,0 +1 @@
+Add ``is_yanked`` boolean entry to the installation report (``--report``) to indicate whether the requirement was yanked from the index, but was still selected by pip conform to PEP 592.
diff --git a/src/pip/_internal/models/installation_report.py b/src/pip/_internal/models/installation_report.py
index 7f001f35e..e38e8f1c0 100644
--- a/src/pip/_internal/models/installation_report.py
+++ b/src/pip/_internal/models/installation_report.py
@@ -23,6 +23,9 @@ class InstallationReport:
# includes editable requirements), and false if the requirement was
# downloaded from a PEP 503 index or --find-links.
"is_direct": ireq.is_direct,
+ # is_yanked is true if the requirement was yanked from the index, but
+ # was still selected by pip to conform to PEP 592.
+ "is_yanked": ireq.link.is_yanked if ireq.link else False,
# 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
diff --git a/tests/functional/test_install_report.py b/tests/functional/test_install_report.py
index 003b29d38..a0f855978 100644
--- a/tests/functional/test_install_report.py
+++ b/tests/functional/test_install_report.py
@@ -64,6 +64,59 @@ def test_install_report_dep(
assert _install_dict(report)["simple"]["requested"] is False
+def test_yanked_version(
+ script: PipTestEnvironment, data: TestData, tmp_path: Path
+) -> None:
+ """
+ Test is_yanked is True when explicitly requesting a yanked package.
+ Yanked files are always ignored, unless they are the only file that
+ matches a version specifier that "pins" to an exact version (PEP 592).
+ """
+ report_path = tmp_path / "report.json"
+ script.pip(
+ "install",
+ "simple==3.0",
+ "--index-url",
+ data.index_url("yanked"),
+ "--dry-run",
+ "--report",
+ str(report_path),
+ allow_stderr_warning=True,
+ )
+ report = json.loads(report_path.read_text())
+ simple_report = _install_dict(report)["simple"]
+ assert simple_report["requested"] is True
+ assert simple_report["is_direct"] is False
+ assert simple_report["is_yanked"] is True
+ assert simple_report["metadata"]["version"] == "3.0"
+
+
+def test_skipped_yanked_version(
+ script: PipTestEnvironment, data: TestData, tmp_path: Path
+) -> None:
+ """
+ Test is_yanked is False when not explicitly requesting a yanked package.
+ Yanked files are always ignored, unless they are the only file that
+ matches a version specifier that "pins" to an exact version (PEP 592).
+ """
+ report_path = tmp_path / "report.json"
+ script.pip(
+ "install",
+ "simple",
+ "--index-url",
+ data.index_url("yanked"),
+ "--dry-run",
+ "--report",
+ str(report_path),
+ )
+ report = json.loads(report_path.read_text())
+ simple_report = _install_dict(report)["simple"]
+ assert simple_report["requested"] is True
+ assert simple_report["is_direct"] is False
+ assert simple_report["is_yanked"] is False
+ assert simple_report["metadata"]["version"] == "2.0"
+
+
@pytest.mark.network
def test_install_report_index(script: PipTestEnvironment, tmp_path: Path) -> None:
"""Test report for sdist obtained from index."""