From 0a7e040bb58fe7c7f5cccd0f1e6d3011416a35b4 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Mon, 11 Mar 2024 23:59:57 -0400 Subject: [PATCH 1/3] show: populate missing home-page from project-urls if possible --- news/11221.feature.rst | 4 ++++ src/pip/_internal/commands/show.py | 31 ++++++++++++++++++++++++------ tests/functional/test_show.py | 22 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 news/11221.feature.rst diff --git a/news/11221.feature.rst b/news/11221.feature.rst new file mode 100644 index 00000000000..3d2cf7583ca --- /dev/null +++ b/news/11221.feature.rst @@ -0,0 +1,4 @@ +Fallback to displaying "Home-page" Project-URL when the Home-Page metadata field is not set in ``pip show``. + +The Project-URL fallback is case-insensitive and ignores any dashes or +underscores that may be present. diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index cfd67420d0a..a92ce604409 100644 --- a/src/pip/_internal/commands/show.py +++ b/src/pip/_internal/commands/show.py @@ -1,6 +1,6 @@ import logging from optparse import Values -from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional +from typing import Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional from pip._vendor.packaging.utils import canonicalize_name @@ -61,7 +61,7 @@ class _PackageInfo(NamedTuple): classifiers: List[str] summary: str homepage: str - project_urls: List[str] + project_urls: Dict[str, str] author: str author_email: str license: str @@ -121,6 +121,25 @@ def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]: metadata = dist.metadata + project_urls = {} + for url in metadata.get_all("Project-URL", []): + url_label, url = url.split(",", maxsplit=1) + project_urls[url_label.strip()] = url.strip() + + homepage = metadata.get("Home-page", "") + if not homepage: + # It's common that there is a "homepage" Project-URL, but Home-page + # remains unset (especially as PEP 621 doesn't surface the field). + # + # This logic was taken from PyPI's codebase. + for url_label, url in project_urls.items(): + normalized_label = ( + url_label.casefold().replace("-", "").replace("_", "") + ) + if normalized_label == "homepage": + homepage = url + break + yield _PackageInfo( name=dist.raw_name, version=str(dist.version), @@ -132,8 +151,8 @@ def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]: metadata_version=dist.metadata_version or "", classifiers=metadata.get_all("Classifier", []), summary=metadata.get("Summary", ""), - homepage=metadata.get("Home-page", ""), - project_urls=metadata.get_all("Project-URL", []), + homepage=homepage, + project_urls=project_urls, author=metadata.get("Author", ""), author_email=metadata.get("Author-email", ""), license=metadata.get("License", ""), @@ -181,8 +200,8 @@ def print_results( for entry in dist.entry_points: write_output(" %s", entry.strip()) write_output("Project-URLs:") - for project_url in dist.project_urls: - write_output(" %s", project_url) + for label, url in dist.project_urls.items(): + write_output(f" {label}, {url}") if list_files: write_output("Files:") if dist.files is None: diff --git a/tests/functional/test_show.py b/tests/functional/test_show.py index ad139240b77..7797de9e992 100644 --- a/tests/functional/test_show.py +++ b/tests/functional/test_show.py @@ -3,6 +3,8 @@ import re import textwrap +import pytest + from pip import __version__ from pip._internal.commands.show import search_packages_info from pip._internal.utils.unpacking import untar_file @@ -387,3 +389,23 @@ def test_show_deduplicate_requirements(script: PipTestEnvironment) -> None: result = script.pip("show", "simple", cwd=pkg_path) lines = result.stdout.splitlines() assert "Requires: pip" in lines + + +@pytest.mark.parametrize( + "project_url", ["Home-page", "home-page", "Homepage", "homepage"] +) +def test_show_populate_homepage_from_project_urls( + script: PipTestEnvironment, project_url: str +) -> None: + pkg_path = create_test_package_with_setup( + script, + name="simple", + version="1.0", + project_urls={project_url: "https://example.com"}, + ) + script.run("python", "setup.py", "egg_info", expect_stderr=True, cwd=pkg_path) + script.environ.update({"PYTHONPATH": pkg_path}) + + result = script.pip("show", "simple", cwd=pkg_path) + lines = result.stdout.splitlines() + assert "Home-page: https://example.com" in lines From 64f09b216698e0290a69152d997958247d5007b3 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Tue, 12 Mar 2024 14:42:46 -0400 Subject: [PATCH 2/3] =?UTF-8?q?Update=20=F0=9F=93=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tzu-ping Chung --- news/11221.feature.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/news/11221.feature.rst b/news/11221.feature.rst index 3d2cf7583ca..319cc02c4a5 100644 --- a/news/11221.feature.rst +++ b/news/11221.feature.rst @@ -1,4 +1,3 @@ -Fallback to displaying "Home-page" Project-URL when the Home-Page metadata field is not set in ``pip show``. +Display the Project-URL value under key "Home-page" in ``pip show`` when the Home-Page metadata field is not set. -The Project-URL fallback is case-insensitive and ignores any dashes or -underscores that may be present. +The Project-URL key detection is case-insensitive, and ignores any dashes and underscores. From e0cddaa454657c884817ea3a2642692cb2317b90 Mon Sep 17 00:00:00 2001 From: Richard Si Date: Tue, 12 Mar 2024 14:43:06 -0400 Subject: [PATCH 3/3] Simplify logic --- src/pip/_internal/commands/show.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/pip/_internal/commands/show.py b/src/pip/_internal/commands/show.py index a92ce604409..b7894ce1f9c 100644 --- a/src/pip/_internal/commands/show.py +++ b/src/pip/_internal/commands/show.py @@ -1,6 +1,6 @@ import logging from optparse import Values -from typing import Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional +from typing import Generator, Iterable, Iterator, List, NamedTuple, Optional from pip._vendor.packaging.utils import canonicalize_name @@ -61,7 +61,7 @@ class _PackageInfo(NamedTuple): classifiers: List[str] summary: str homepage: str - project_urls: Dict[str, str] + project_urls: List[str] author: str author_email: str license: str @@ -121,23 +121,20 @@ def _get_requiring_packages(current_dist: BaseDistribution) -> Iterator[str]: metadata = dist.metadata - project_urls = {} - for url in metadata.get_all("Project-URL", []): - url_label, url = url.split(",", maxsplit=1) - project_urls[url_label.strip()] = url.strip() - + project_urls = metadata.get_all("Project-URL", []) homepage = metadata.get("Home-page", "") if not homepage: # It's common that there is a "homepage" Project-URL, but Home-page # remains unset (especially as PEP 621 doesn't surface the field). # # This logic was taken from PyPI's codebase. - for url_label, url in project_urls.items(): + for url in project_urls: + url_label, url = url.split(",", maxsplit=1) normalized_label = ( - url_label.casefold().replace("-", "").replace("_", "") + url_label.casefold().replace("-", "").replace("_", "").strip() ) if normalized_label == "homepage": - homepage = url + homepage = url.strip() break yield _PackageInfo( @@ -200,8 +197,8 @@ def print_results( for entry in dist.entry_points: write_output(" %s", entry.strip()) write_output("Project-URLs:") - for label, url in dist.project_urls.items(): - write_output(f" {label}, {url}") + for project_url in dist.project_urls: + write_output(" %s", project_url) if list_files: write_output("Files:") if dist.files is None: