Blob Blame History Raw
From 6817fbfb1fd389ad61009f0199db5670b146c8d3 Mon Sep 17 00:00:00 2001
From: Tzu-ping Chung <uranusjr@gmail.com>
Date: Sat, 6 Aug 2022 06:18:59 +0800
Subject: [PATCH] Skip dist if metadata does not have a valid name

---
 news/11352.bugfix.rst                           |  2 ++
 src/pip/_internal/metadata/importlib/_compat.py | 14 +++++++++++++-
 src/pip/_internal/metadata/importlib/_envs.py   | 14 +++++++++++---
 3 files changed, 26 insertions(+), 4 deletions(-)
 create mode 100644 news/11352.bugfix.rst

diff --git a/news/11352.bugfix.rst b/news/11352.bugfix.rst
new file mode 100644
index 00000000000..78016c912ef
--- /dev/null
+++ b/news/11352.bugfix.rst
@@ -0,0 +1,2 @@
+Ignore distributions with invalid ``Name`` in metadata instead of crashing, when
+using the ``importlib.metadata`` backend.
diff --git a/src/pip/_internal/metadata/importlib/_compat.py b/src/pip/_internal/metadata/importlib/_compat.py
index e0879807ab9..593bff23ede 100644
--- a/src/pip/_internal/metadata/importlib/_compat.py
+++ b/src/pip/_internal/metadata/importlib/_compat.py
@@ -2,6 +2,15 @@
 from typing import Any, Optional, Protocol, cast
 
 
+class BadMetadata(ValueError):
+    def __init__(self, dist: importlib.metadata.Distribution, *, reason: str) -> None:
+        self.dist = dist
+        self.reason = reason
+
+    def __str__(self) -> str:
+        return f"Bad metadata in {self.dist} ({self.reason})"
+
+
 class BasePath(Protocol):
     """A protocol that various path objects conform.
 
@@ -40,4 +49,7 @@ def get_dist_name(dist: importlib.metadata.Distribution) -> str:
     The ``name`` attribute is only available in Python 3.10 or later. We are
     targeting exactly that, but Mypy does not know this.
     """
-    return cast(Any, dist).name
+    name = cast(Any, dist).name
+    if not isinstance(name, str):
+        raise BadMetadata(dist, reason="invalid metadata entry 'name'")
+    return name
diff --git a/src/pip/_internal/metadata/importlib/_envs.py b/src/pip/_internal/metadata/importlib/_envs.py
index d5fcfdbfef2..cbec59e2c6d 100644
--- a/src/pip/_internal/metadata/importlib/_envs.py
+++ b/src/pip/_internal/metadata/importlib/_envs.py
@@ -1,5 +1,6 @@
 import functools
 import importlib.metadata
+import logging
 import os
 import pathlib
 import sys
@@ -14,9 +15,11 @@
 from pip._internal.utils.deprecation import deprecated
 from pip._internal.utils.filetypes import WHEEL_EXTENSION
 
-from ._compat import BasePath, get_dist_name, get_info_location
+from ._compat import BadMetadata, BasePath, get_dist_name, get_info_location
 from ._dists import Distribution
 
+logger = logging.getLogger(__name__)
+
 
 def _looks_like_wheel(location: str) -> bool:
     if not location.endswith(WHEEL_EXTENSION):
@@ -56,11 +59,16 @@ def _find_impl(self, location: str) -> Iterator[FoundResult]:
         # To know exactly where we find a distribution, we have to feed in the
         # paths one by one, instead of dumping the list to importlib.metadata.
         for dist in importlib.metadata.distributions(path=[location]):
-            normalized_name = canonicalize_name(get_dist_name(dist))
+            info_location = get_info_location(dist)
+            try:
+                raw_name = get_dist_name(dist)
+            except BadMetadata as e:
+                logger.warning("Skipping %s due to %s", info_location, e.reason)
+                continue
+            normalized_name = canonicalize_name(raw_name)
             if normalized_name in self._found_names:
                 continue
             self._found_names.add(normalized_name)
-            info_location = get_info_location(dist)
             yield dist, info_location
 
     def find(self, location: str) -> Iterator[BaseDistribution]: