diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py
index 78a4e1160..37dc17188 100644
--- a/src/pip/_internal/cli/base_command.py
+++ b/src/pip/_internal/cli/base_command.py
@@ -347,6 +347,7 @@ class RequirementCommand(Command):
 
         return PackageFinder.create(
             search_scope=search_scope,
+            allow_yanked=True,
             format_control=options.format_control,
             trusted_hosts=options.trusted_hosts,
             allow_all_prereleases=options.pre,
diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py
index 67abee0fb..a9ff88eb1 100644
--- a/src/pip/_internal/commands/list.py
+++ b/src/pip/_internal/commands/list.py
@@ -116,8 +116,10 @@ class ListCommand(Command):
         """
         search_scope = make_search_scope(options)
 
+        # Pass allow_yanked=False to ignore yanked versions.
         return PackageFinder.create(
             search_scope=search_scope,
+            allow_yanked=False,
             allow_all_prereleases=options.pre,
             trusted_hosts=options.trusted_hosts,
             session=session,
@@ -183,10 +185,7 @@ class ListCommand(Command):
                                       if not candidate.version.is_prerelease]
 
                 evaluator = finder.candidate_evaluator
-                # Pass allow_yanked=False to ignore yanked versions.
-                best_candidate = evaluator.get_best_candidate(
-                    all_candidates, allow_yanked=False,
-                )
+                best_candidate = evaluator.get_best_candidate(all_candidates)
                 if best_candidate is None:
                     continue
 
diff --git a/src/pip/_internal/index.py b/src/pip/_internal/index.py
index 2acdd74b4..5dafd1c7e 100644
--- a/src/pip/_internal/index.py
+++ b/src/pip/_internal/index.py
@@ -41,7 +41,7 @@ if MYPY_CHECK_RUNNING:
     from logging import Logger
     from typing import (
         Any, Callable, Iterable, Iterator, List, MutableMapping, Optional,
-        Sequence, Set, Tuple, Union,
+        Sequence, Set, Text, Tuple, Union,
     )
     import xml.etree.ElementTree
     from pip._vendor.packaging.version import _BaseVersion
@@ -307,8 +307,13 @@ class CandidateEvaluator(object):
     on what tags are valid.
     """
 
+    # Don't include an allow_yanked default value to make sure each call
+    # site considers whether yanked releases are allowed. This also causes
+    # that decision to be made explicit in the calling code, which helps
+    # people when reading the code.
     def __init__(
         self,
+        allow_yanked,  # type: bool
         target_python=None,  # type: Optional[TargetPython]
         prefer_binary=False,   # type: bool
         allow_all_prereleases=False,  # type: bool
@@ -316,6 +321,8 @@ class CandidateEvaluator(object):
     ):
         # type: (...) -> None
         """
+        :param allow_yanked: Whether files marked as yanked (in the sense
+            of PEP 592) are permitted to be candidates for install.
         :param target_python: The target Python interpreter to use to check
             both the Python version embedded in the filename and the package's
             "Requires-Python" metadata. If None (the default), then a
@@ -329,6 +336,7 @@ class CandidateEvaluator(object):
         if ignore_requires_python is None:
             ignore_requires_python = False
 
+        self._allow_yanked = allow_yanked
         self._ignore_requires_python = ignore_requires_python
         self._prefer_binary = prefer_binary
         self._target_python = target_python
@@ -347,7 +355,7 @@ class CandidateEvaluator(object):
         return wheel.supported(valid_tags)
 
     def evaluate_link(self, link, search):
-        # type: (Link, Search) -> Tuple[bool, Optional[str]]
+        # type: (Link, Search) -> Tuple[bool, Optional[Text]]
         """
         Determine whether a link is a candidate for installation.
 
@@ -357,6 +365,13 @@ class CandidateEvaluator(object):
             the link fails to qualify.
         """
         version = None
+        if link.is_yanked and not self._allow_yanked:
+            reason = link.yanked_reason or '<none given>'
+            # Mark this as a unicode string to prevent "UnicodeEncodeError:
+            # 'ascii' codec can't encode character" in Python 2 when
+            # the reason contains non-ascii characters.
+            return (False, u'yanked for reason: {}'.format(reason))
+
         if link.egg_fragment:
             egg_info = link.egg_fragment
             ext = link.ext
@@ -507,23 +522,14 @@ class CandidateEvaluator(object):
             yank_value, binary_preference, candidate.version, build_tag, pri,
         )
 
-    # Don't include an allow_yanked default value to make sure each call
-    # site considers whether yanked releases are allowed. This also causes
-    # that decision to be made explicit in the calling code, which helps
-    # people when reading the code.
     def get_best_candidate(
         self,
         candidates,    # type: List[InstallationCandidate]
-        allow_yanked,  # type: bool
     ):
         # type: (...) -> Optional[InstallationCandidate]
         """
         Return the best candidate per the instance's sort order, or None if
         no candidate is acceptable.
-
-        :param allow_yanked: Whether to permit returning a yanked candidate
-            in the sense of PEP 592. If true, a yanked candidate will be
-            returned only if all candidates have been yanked.
         """
         if not candidates:
             return None
@@ -532,20 +538,17 @@ class CandidateEvaluator(object):
 
         # Log a warning per PEP 592 if necessary before returning.
         link = best_candidate.location
-        if not link.is_yanked:
-            return best_candidate
-
-        # Otherwise, all the candidates were yanked.
-        if not allow_yanked:
-            return None
-
-        reason = link.yanked_reason or '<none given>'
-        msg = (
-            'The candidate selected for download or install is a '
-            'yanked version: {candidate}\n'
-            'Reason for being yanked: {reason}'
-        ).format(candidate=best_candidate, reason=reason)
-        logger.warning(msg)
+        if link.is_yanked:
+            reason = link.yanked_reason or '<none given>'
+            msg = (
+                # Mark this as a unicode string to prevent
+                # "UnicodeEncodeError: 'ascii' codec can't encode character"
+                # in Python 2 when the reason contains non-ascii characters.
+                u'The candidate selected for download or install is a '
+                'yanked version: {candidate}\n'
+                'Reason for being yanked: {reason}'
+            ).format(candidate=best_candidate, reason=reason)
+            logger.warning(msg)
 
         return best_candidate
 
@@ -589,23 +592,13 @@ class FoundCandidates(object):
         # Again, converting version to str to deal with debundling.
         return (c for c in self.iter_all() if str(c.version) in self._versions)
 
-    # Don't include an allow_yanked default value to make sure each call
-    # site considers whether yanked releases are allowed. This also causes
-    # that decision to be made explicit in the calling code, which helps
-    # people when reading the code.
-    def get_best(self, allow_yanked):
-        # type: (bool) -> Optional[InstallationCandidate]
+    def get_best(self):
+        # type: () -> Optional[InstallationCandidate]
         """Return the best candidate available, or None if no applicable
         candidates are found.
-
-        :param allow_yanked: Whether to permit returning a yanked candidate
-            in the sense of PEP 592. If true, a yanked candidate will be
-            returned only if all candidates have been yanked.
         """
         candidates = list(self.iter_applicable())
-        return self._evaluator.get_best_candidate(
-            candidates, allow_yanked=allow_yanked,
-        )
+        return self._evaluator.get_best_candidate(candidates)
 
 
 class PackageFinder(object):
@@ -648,10 +641,15 @@ class PackageFinder(object):
         # These are boring links that have already been logged somehow.
         self._logged_links = set()  # type: Set[Link]
 
+    # Don't include an allow_yanked default value to make sure each call
+    # site considers whether yanked releases are allowed. This also causes
+    # that decision to be made explicit in the calling code, which helps
+    # people when reading the code.
     @classmethod
     def create(
         cls,
         search_scope,  # type: SearchScope
+        allow_yanked,  # type: bool
         allow_all_prereleases=False,  # type: bool
         trusted_hosts=None,  # type: Optional[List[str]]
         session=None,  # type: Optional[PipSession]
@@ -663,6 +661,8 @@ class PackageFinder(object):
         # type: (...) -> PackageFinder
         """Create a PackageFinder.
 
+        :param allow_yanked: Whether files marked as yanked (in the sense
+            of PEP 592) are permitted to be candidates for install.
         :param trusted_hosts: Domains not to emit warnings for when not using
             HTTPS.
         :param session: The Session to use to make requests.
@@ -682,6 +682,7 @@ class PackageFinder(object):
             )
 
         candidate_evaluator = CandidateEvaluator(
+            allow_yanked=allow_yanked,
             target_python=target_python,
             prefer_binary=prefer_binary,
             allow_all_prereleases=allow_all_prereleases,
@@ -969,7 +970,7 @@ class PackageFinder(object):
         Raises DistributionNotFound or BestVersionAlreadyInstalled otherwise
         """
         candidates = self.find_candidates(req.name, req.specifier)
-        best_candidate = candidates.get_best(allow_yanked=True)
+        best_candidate = candidates.get_best()
 
         installed_version = None    # type: Optional[_BaseVersion]
         if req.satisfied_by is not None:
@@ -1072,11 +1073,14 @@ class PackageFinder(object):
         return no_eggs + eggs
 
     def _log_skipped_link(self, link, reason):
-        # type: (Link, str) -> None
+        # type: (Link, Text) -> None
         if link not in self._logged_links:
-            # Put the link at the end so the reason is more visible and
-            # because the link string is usually very long.
-            logger.debug('Skipping link: %s: %s', reason, link)
+            # Mark this as a unicode string to prevent "UnicodeEncodeError:
+            # 'ascii' codec can't encode character" in Python 2 when
+            # the reason contains non-ascii characters.
+            #   Also, put the link at the end so the reason is more visible
+            # and because the link string is usually very long.
+            logger.debug(u'Skipping link: %s: %s', reason, link)
             self._logged_links.add(link)
 
     def get_install_candidate(self, link, search):
@@ -1094,7 +1098,9 @@ class PackageFinder(object):
             return None
 
         return InstallationCandidate(
-            search.supplied, location=link, version=result,
+            # Convert the Text result to str since InstallationCandidate
+            # accepts str.
+            search.supplied, location=link, version=str(result),
         )
 
     def _package_versions(
@@ -1229,6 +1235,7 @@ def _create_link_from_element(
 
     yanked_reason = anchor.get('data-yanked')
     if yanked_reason:
+        # This is a unicode string in Python 2 (and 3).
         yanked_reason = unescape(yanked_reason)
 
     link = Link(
diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py
index d07ed5ee2..54c97f526 100644
--- a/src/pip/_internal/models/link.py
+++ b/src/pip/_internal/models/link.py
@@ -11,7 +11,7 @@ from pip._internal.utils.models import KeyBasedCompareMixin
 from pip._internal.utils.typing import MYPY_CHECK_RUNNING
 
 if MYPY_CHECK_RUNNING:
-    from typing import Optional, Tuple, Union
+    from typing import Optional, Text, Tuple, Union
     from pip._internal.index import HTMLPage
 
 
@@ -24,7 +24,7 @@ class Link(KeyBasedCompareMixin):
         url,                   # type: str
         comes_from=None,       # type: Optional[Union[str, HTMLPage]]
         requires_python=None,  # type: Optional[str]
-        yanked_reason=None,    # type: Optional[str]
+        yanked_reason=None,    # type: Optional[Text]
     ):
         # type: (...) -> None
         """
diff --git a/src/pip/_internal/utils/outdated.py b/src/pip/_internal/utils/outdated.py
index 8ebb54d6b..8088cda78 100644
--- a/src/pip/_internal/utils/outdated.py
+++ b/src/pip/_internal/utils/outdated.py
@@ -125,17 +125,16 @@ def pip_version_check(session, options):
             # Lets use PackageFinder to see what the latest pip version is
             search_scope = make_search_scope(options, suppress_no_index=True)
 
+            # Pass allow_yanked=False so we don't suggest upgrading to a
+            # yanked version.
             finder = PackageFinder.create(
                 search_scope=search_scope,
+                allow_yanked=False,
                 allow_all_prereleases=False,  # Explicitly set to False
                 trusted_hosts=options.trusted_hosts,
                 session=session,
             )
-            # Pass allow_yanked=False so we don't suggest upgrading to a
-            # yanked version.
-            candidate = finder.find_candidates("pip").get_best(
-                allow_yanked=False,
-            )
+            candidate = finder.find_candidates("pip").get_best()
             if candidate is None:
                 return
             pypi_version = str(candidate.version)

Refactor: filter out "yanked" files before calling get_best_candidate()
This refactoring idea for the "yanked" file (PEP 592) code was suggested by @pradyunsg e.g. [here](https://github.com/pypa/pip/pull/6647#discussion_r297673433) in the discussion of PR #6647.
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
index ac706b4af..0e9cd9c92 100644
--- a/tests/lib/__init__.py
+++ b/tests/lib/__init__.py
@@ -104,6 +104,7 @@ def make_test_finder(
 
     return PackageFinder.create(
         search_scope=search_scope,
+        allow_yanked=True,
         allow_all_prereleases=allow_all_prereleases,
         trusted_hosts=trusted_hosts,
         session=session,
diff --git a/tests/unit/test_build_env.py b/tests/unit/test_build_env.py
index 4f279ff8f..dc1bb187d 100644
--- a/tests/unit/test_build_env.py
+++ b/tests/unit/test_build_env.py
@@ -27,7 +27,11 @@ def run_with_build_env(script, setup_script_contents,
             from pip._internal.models.search_scope import SearchScope
 
             search_scope = SearchScope.create([%r], [])
-            finder = PackageFinder.create(search_scope, session=PipSession())
+            finder = PackageFinder.create(
+                search_scope,
+                allow_yanked=True,
+                session=PipSession(),
+            )
             build_env = BuildEnvironment()
 
             try:
diff --git a/tests/unit/test_finder.py b/tests/unit/test_finder.py
index e5c0f29d4..7a79fb51f 100644
--- a/tests/unit/test_finder.py
+++ b/tests/unit/test_finder.py
@@ -218,7 +218,10 @@ class TestWheel:
         ]
         target_python = TargetPython()
         target_python._valid_tags = valid_tags
-        evaluator = CandidateEvaluator(target_python=target_python)
+        evaluator = CandidateEvaluator(
+            allow_yanked=True,
+            target_python=target_python,
+        )
         sort_key = evaluator._sort_key
         results = sorted(links, key=sort_key, reverse=True)
         results2 = sorted(reversed(links), key=sort_key, reverse=True)
@@ -426,7 +429,7 @@ class TestCandidateEvaluator(object):
     def setup(self):
         self.search_name = 'pytest'
         self.canonical_name = 'pytest'
-        self.evaluator = CandidateEvaluator()
+        self.evaluator = CandidateEvaluator(allow_yanked=True)
 
     @pytest.mark.parametrize('url, expected_version', [
         ('http:/yo/pytest-1.0.tar.gz', '1.0'),
diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py
index 26bc9e914..83531d4d2 100644
--- a/tests/unit/test_index.py
+++ b/tests/unit/test_index.py
@@ -90,7 +90,10 @@ class TestCandidateEvaluator:
         Test the target_python argument.
         """
         target_python = TargetPython(py_version_info=(3, 7, 3))
-        evaluator = CandidateEvaluator(target_python=target_python)
+        evaluator = CandidateEvaluator(
+            allow_yanked=True,
+            target_python=target_python,
+        )
         # The target_python attribute should be set as is.
         assert evaluator._target_python is target_python
 
@@ -98,7 +101,10 @@ class TestCandidateEvaluator:
         """
         Test passing None for the target_python argument.
         """
-        evaluator = CandidateEvaluator(target_python=None)
+        evaluator = CandidateEvaluator(
+            allow_yanked=True,
+            target_python=None,
+        )
         # Spot-check the default TargetPython object.
         actual_target_python = evaluator._target_python
         assert actual_target_python._given_py_version_info is None
@@ -118,6 +124,7 @@ class TestCandidateEvaluator:
     ):
         target_python = TargetPython(py_version_info=py_version_info)
         evaluator = CandidateEvaluator(
+            allow_yanked=True,
             target_python=target_python,
             ignore_requires_python=ignore_requires_python,
         )
@@ -131,6 +138,33 @@ class TestCandidateEvaluator:
         actual = evaluator.evaluate_link(link, search=search)
         assert actual == expected
 
+    @pytest.mark.parametrize('yanked_reason, allow_yanked, expected', [
+        (None, True, (True, '1.12')),
+        (None, False, (True, '1.12')),
+        ('', True, (True, '1.12')),
+        ('', False, (False, 'yanked for reason: <none given>')),
+        ('bad metadata', True, (True, '1.12')),
+        ('bad metadata', False,
+         (False, 'yanked for reason: bad metadata')),
+        # Test a unicode string with a non-ascii character.
+        (u'curly quote: \u2018', True, (True, '1.12')),
+        (u'curly quote: \u2018', False,
+         (False, u'yanked for reason: curly quote: \u2018')),
+    ])
+    def test_evaluate_link__allow_yanked(
+        self, yanked_reason, allow_yanked, expected,
+    ):
+        evaluator = CandidateEvaluator(allow_yanked=allow_yanked)
+        link = Link(
+            'https://example.com/#egg=twine-1.12',
+            yanked_reason=yanked_reason,
+        )
+        search = Search(
+            supplied='twine', canonical='twine', formats=['source'],
+        )
+        actual = evaluator.evaluate_link(link, search=search)
+        assert actual == expected
+
     def test_evaluate_link__incompatible_wheel(self):
         """
         Test an incompatible wheel.
@@ -138,7 +172,10 @@ class TestCandidateEvaluator:
         target_python = TargetPython(py_version_info=(3, 6, 4))
         # Set the valid tags to an empty list to make sure nothing matches.
         target_python._valid_tags = []
-        evaluator = CandidateEvaluator(target_python=target_python)
+        evaluator = CandidateEvaluator(
+            allow_yanked=True,
+            target_python=target_python,
+        )
         link = Link('https://example.com/sample-1.0-py2.py3-none-any.whl')
         search = Search(
             supplied='sample', canonical='sample', formats=['binary'],
@@ -163,7 +200,7 @@ class TestCandidateEvaluator:
         link = Link(url, yanked_reason=yanked_reason)
         candidate = InstallationCandidate('mypackage', '1.0', link)
 
-        evaluator = CandidateEvaluator()
+        evaluator = CandidateEvaluator(allow_yanked=True)
         sort_value = evaluator._sort_key(candidate)
         # Yanked / non-yanked is reflected in the first element of the tuple.
         actual = sort_value[0]
@@ -176,30 +213,17 @@ class TestCandidateEvaluator:
 
         return candidate
 
-    @pytest.mark.parametrize('allow_yanked', [True, False])
-    def test_get_best_candidate__no_candidates(self, allow_yanked):
+    def test_get_best_candidate__no_candidates(self):
         """
         Test passing an empty list.
         """
-        evaluator = CandidateEvaluator()
-        actual = evaluator.get_best_candidate([], allow_yanked=allow_yanked)
+        evaluator = CandidateEvaluator(allow_yanked=True)
+        actual = evaluator.get_best_candidate([])
         assert actual is None
 
-    def test_get_best_candidate__all_yanked__allow_yanked_false(self):
+    def test_get_best_candidate__all_yanked(self, caplog):
         """
-        Test all candidates yanked with allow_yanked=False.
-        """
-        candidates = [
-            self.make_mock_candidate('1.0', yanked_reason=''),
-            self.make_mock_candidate('2.0', yanked_reason=''),
-        ]
-        evaluator = CandidateEvaluator()
-        actual = evaluator.get_best_candidate(candidates, allow_yanked=False)
-        assert actual is None
-
-    def test_get_best_candidate__all_yanked__allow_yanked_true(self, caplog):
-        """
-        Test all candidates yanked with allow_yanked=True.
+        Test all candidates yanked.
         """
         candidates = [
             self.make_mock_candidate('1.0', yanked_reason='bad metadata #1'),
@@ -208,8 +232,8 @@ class TestCandidateEvaluator:
             self.make_mock_candidate('2.0', yanked_reason='bad metadata #2'),
         ]
         expected_best = candidates[1]
-        evaluator = CandidateEvaluator()
-        actual = evaluator.get_best_candidate(candidates, allow_yanked=True)
+        evaluator = CandidateEvaluator(allow_yanked=True)
+        actual = evaluator.get_best_candidate(candidates)
         assert actual is expected_best
         assert str(actual.version) == '3.0'
 
@@ -224,26 +248,35 @@ class TestCandidateEvaluator:
             'Reason for being yanked: bad metadata #3'
         )
 
-    def test_get_best_candidate__yanked_no_reason_given(self, caplog):
+    @pytest.mark.parametrize('yanked_reason, expected_reason', [
+        # Test no reason given.
+        ('', '<none given>'),
+        # Test a unicode string with a non-ascii character.
+        (u'curly quote: \u2018', u'curly quote: \u2018'),
+    ])
+    def test_get_best_candidate__yanked_reason(
+        self, caplog, yanked_reason, expected_reason,
+    ):
         """
-        Test the log message when no reason is given.
+        Test the log message with various reason strings.
         """
         candidates = [
-            self.make_mock_candidate('1.0', yanked_reason=''),
+            self.make_mock_candidate('1.0', yanked_reason=yanked_reason),
         ]
-        evaluator = CandidateEvaluator()
-        actual = evaluator.get_best_candidate(candidates, allow_yanked=True)
+        evaluator = CandidateEvaluator(allow_yanked=True)
+        actual = evaluator.get_best_candidate(candidates)
         assert str(actual.version) == '1.0'
 
         assert len(caplog.records) == 1
         record = caplog.records[0]
         assert record.levelname == 'WARNING'
-        assert record.message == (
+        expected_message = (
             'The candidate selected for download or install is a yanked '
             "version: 'mypackage' candidate "
             '(version 1.0 at https://example.com/pkg-1.0.tar.gz)\n'
-            'Reason for being yanked: <none given>'
-        )
+            'Reason for being yanked: '
+        ) + expected_reason
+        assert record.message == expected_message
 
     def test_get_best_candidate__best_yanked_but_not_all(self, caplog):
         """
@@ -257,8 +290,8 @@ class TestCandidateEvaluator:
             self.make_mock_candidate('1.0'),
         ]
         expected_best = candidates[1]
-        evaluator = CandidateEvaluator()
-        actual = evaluator.get_best_candidate(candidates, allow_yanked=True)
+        evaluator = CandidateEvaluator(allow_yanked=True)
+        actual = evaluator.get_best_candidate(candidates)
         assert actual is expected_best
         assert str(actual.version) == '2.0'
 
@@ -268,6 +301,20 @@ class TestCandidateEvaluator:
 
 class TestPackageFinder:
 
+    @pytest.mark.parametrize('allow_yanked', [False, True])
+    def test_create__allow_yanked(self, allow_yanked):
+        """
+        Test that allow_yanked is passed to CandidateEvaluator.
+        """
+        search_scope = SearchScope([], [])
+        finder = PackageFinder.create(
+            search_scope=search_scope,
+            allow_yanked=allow_yanked,
+            session=object(),
+        )
+        evaluator = finder.candidate_evaluator
+        assert evaluator._allow_yanked == allow_yanked
+
     def test_create__target_python(self):
         """
         Test that target_python is passed to CandidateEvaluator as is.
@@ -276,6 +323,7 @@ class TestPackageFinder:
         target_python = TargetPython(py_version_info=(3, 7, 3))
         finder = PackageFinder.create(
             search_scope=search_scope,
+            allow_yanked=True,
             session=object(),
             target_python=target_python,
         )
@@ -361,6 +409,7 @@ class TestPackageFinder:
         search_scope = SearchScope([], [])
         finder = PackageFinder.create(
             search_scope=search_scope,
+            allow_yanked=True,
             trusted_hosts=None,
             session=object(),
         )
@@ -656,10 +705,18 @@ class TestHTMLPage:
             # Test a value with an escaped character.
             ('<a href="/pkg4-1.0.tar.gz" data-yanked="version &lt 1"></a>',
                 'version < 1'),
+            # Test a yanked reason with a non-ascii character.
+            (u'<a href="/pkg-1.0.tar.gz" data-yanked="curlyquote \u2018"></a>',
+                u'curlyquote \u2018'),
         ]
     )
     def test_iter_links__yanked_reason(self, anchor_html, expected):
-        html = '<html><body>{}</body></html>'.format(anchor_html)
+        html = (
+            # Mark this as a unicode string for Python 2 since anchor_html
+            # can contain non-ascii.
+            u'<html><head><meta charset="utf-8"><head>'
+            '<body>{}</body></html>'
+        ).format(anchor_html)
         html_bytes = html.encode('utf-8')
         page = HTMLPage(html_bytes, url='https://example.com/simple/')
         links = list(page.iter_links())
diff --git a/tests/unit/test_unit_outdated.py b/tests/unit/test_unit_outdated.py
index f2b343f9f..5a8eb5c1e 100644
--- a/tests/unit/test_unit_outdated.py
+++ b/tests/unit/test_unit_outdated.py
@@ -16,7 +16,7 @@ class MockFoundCandidates(object):
     def __init__(self, best):
         self._best = best
 
-    def get_best(self, allow_yanked):
+    def get_best(self):
         return self._best
 
 

pip install -e .
parse_log_pytest
pytest
pytest pytest-cov pytest-rerunfailures pytest-timeout pytest-xdist freezegun mock pretend pyyaml setuptools>=39.2.0 scripttest virtualenv wheel
3.9
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
coverage==7.8.0
distlib==0.3.9
exceptiongroup==1.2.0
execnet==2.1.1
filelock==3.18.0
freezegun==1.5.1
iniconfig==1.1.1
mock==5.2.0
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
pretend==1.0.9
pytest==8.3.4
pytest-cov==6.0.0
pytest-rerunfailures==15.0
pytest-timeout==2.3.1
pytest-xdist==3.6.1
python-dateutil==2.9.0.post0
PyYAML==6.0.2
scripttest==2.0
six==1.17.0
tomli==2.0.1
virtualenv==20.29.3

name: pip
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - exceptiongroup=1.2.0=py39h06a4308_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - packaging=24.2=py39h06a4308_0
  - pluggy=1.5.0=py39h06a4308_0
  - pytest=8.3.4=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py39h06a4308_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - coverage==7.8.0
      - distlib==0.3.9
      - execnet==2.1.1
      - filelock==3.18.0
      - freezegun==1.5.1
      - mock==5.2.0
      - pip==19.2.dev0
      - platformdirs==4.3.7
      - pretend==1.0.9
      - pytest-cov==6.0.0
      - pytest-rerunfailures==15.0
      - pytest-timeout==2.3.1
      - pytest-xdist==3.6.1
      - python-dateutil==2.9.0.post0
      - pyyaml==6.0.2
      - scripttest==2.0
      - six==1.17.0
      - virtualenv==20.29.3
prefix: /opt/conda/envs/pip

tests/unit/test_finder.py::test_no_mpkg tests/unit/test_finder.py::test_no_partial_name_match tests/unit/test_finder.py::test_tilde tests/unit/test_finder.py::test_duplicates_sort_ok tests/unit/test_finder.py::test_finder_detects_latest_find_links tests/unit/test_finder.py::test_incorrect_case_file_index tests/unit/test_finder.py::test_finder_detects_latest_already_satisfied_find_links tests/unit/test_finder.py::test_finder_detects_latest_already_satisfied_pypi_links tests/unit/test_finder.py::TestWheel::test_skip_invalid_wheel_link tests/unit/test_finder.py::TestWheel::test_not_find_wheel_not_supported tests/unit/test_finder.py::TestWheel::test_find_wheel_supported tests/unit/test_finder.py::TestWheel::test_wheel_over_sdist_priority tests/unit/test_finder.py::TestWheel::test_existing_over_wheel_priority tests/unit/test_finder.py::TestWheel::test_link_sorting tests/unit/test_finder.py::TestWheel::test_link_sorting_wheels_with_build_tags tests/unit/test_finder.py::test_finder_priority_file_over_page tests/unit/test_finder.py::test_finder_priority_nonegg_over_eggfragments tests/unit/test_finder.py::test_finder_only_installs_stable_releases tests/unit/test_finder.py::test_finder_only_installs_data_require tests/unit/test_finder.py::test_finder_installs_pre_releases tests/unit/test_finder.py::test_finder_installs_dev_releases tests/unit/test_finder.py::test_finder_installs_pre_releases_with_version_spec tests/unit/test_finder.py::test_find_all_candidates_nothing tests/unit/test_finder.py::test_find_all_candidates_find_links tests/unit/test_finder.py::test_find_all_candidates_index tests/unit/test_finder.py::test_find_all_candidates_find_links_and_index tests/unit/test_index.py::TestCandidateEvaluator::test_init__target_python tests/unit/test_index.py::TestCandidateEvaluator::test_init__target_python_none tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link[py_version_info0-None-expected0] tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link[py_version_info1-None-expected1] tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link[py_version_info2-True-expected2] tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link__allow_yanked[None-True-expected0] tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link__allow_yanked[None-False-expected1] tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link__allow_yanked[-True-expected2] tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link__allow_yanked[-False-expected3] tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link__allow_yanked[bad tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link__allow_yanked[curly tests/unit/test_index.py::TestCandidateEvaluator::test_evaluate_link__incompatible_wheel tests/unit/test_index.py::TestCandidateEvaluator::test_sort_key__is_yanked[None-0] tests/unit/test_index.py::TestCandidateEvaluator::test_sort_key__is_yanked[bad tests/unit/test_index.py::TestCandidateEvaluator::test_get_best_candidate__no_candidates tests/unit/test_index.py::TestCandidateEvaluator::test_get_best_candidate__all_yanked tests/unit/test_index.py::TestCandidateEvaluator::test_get_best_candidate__yanked_reason[-<none tests/unit/test_index.py::TestCandidateEvaluator::test_get_best_candidate__yanked_reason[curly tests/unit/test_index.py::TestCandidateEvaluator::test_get_best_candidate__best_yanked_but_not_all tests/unit/test_index.py::TestPackageFinder::test_create__allow_yanked[False] tests/unit/test_index.py::TestPackageFinder::test_create__allow_yanked[True] tests/unit/test_index.py::TestPackageFinder::test_create__target_python tests/unit/test_index.py::TestPackageFinder::test_add_trusted_host tests/unit/test_index.py::TestPackageFinder::test_add_trusted_host__logging tests/unit/test_index.py::TestPackageFinder::test_iter_secure_origins tests/unit/test_index.py::TestPackageFinder::test_iter_secure_origins__none_trusted_hosts tests/unit/test_index.py::test_sort_locations_file_expand_dir tests/unit/test_index.py::test_sort_locations_file_not_find_link tests/unit/test_index.py::test_sort_locations_non_existing_path tests/unit/test_index.py::test_secure_origin[http://pypi.org/something-trusted0-True] tests/unit/test_index.py::test_secure_origin[https://pypi.org/something-trusted1-False] tests/unit/test_index.py::test_secure_origin[git+http://pypi.org/something-trusted2-True] tests/unit/test_index.py::test_secure_origin[git+https://pypi.org/something-trusted3-False] tests/unit/test_index.py::test_secure_origin[git+ssh://git@pypi.org/something-trusted4-False] tests/unit/test_index.py::test_secure_origin[http://localhost-trusted5-False] tests/unit/test_index.py::test_secure_origin[http://127.0.0.1-trusted6-False] tests/unit/test_index.py::test_secure_origin[http://example.com/something/-trusted7-True] tests/unit/test_index.py::test_secure_origin[http://example.com/something/-trusted8-False] tests/unit/test_index.py::test_secure_origin[http://eXample.com/something/-trusted9-False] tests/unit/test_unit_outdated.py::test_pip_version_check[1970-01-01T10:00:00Z-1.0-6.9.0-pip-True-True] tests/unit/test_unit_outdated.py::test_pip_version_check[1970-01-01T10:00:00Z-1.0-6.9.0-rpm-True-False]
tests/unit/test_finder.py::TestCandidateEvaluator::test_evaluate_link__match[http:/yo/pytest-1.0.tar.gz-1.0] tests/unit/test_finder.py::TestCandidateEvaluator::test_evaluate_link__match[http:/yo/pytest-1.0-py2.py3-none-any.whl-1.0] tests/unit/test_finder.py::TestCandidateEvaluator::test_evaluate_link__substring_fails[http:/yo/pytest2-1.0.tar.gz-Missing tests/unit/test_finder.py::TestCandidateEvaluator::test_evaluate_link__substring_fails[http:/yo/pytest_xdist-1.0-py2.py3-none-any.whl-wrong
tests/unit/test_build_env.py::test_build_env_allow_empty_requirements_install tests/unit/test_index.py::test_check_link_requires_python[== tests/unit/test_index.py::test_check_link_requires_python[invalid-True] tests/unit/test_index.py::test_check_link_requires_python__incompatible_python[None-expected0] tests/unit/test_index.py::test_check_link_requires_python__incompatible_python[True-expected1] tests/unit/test_index.py::test_check_link_requires_python__invalid_requires tests/unit/test_index.py::test_determine_base_url[<html></html>-https://example.com/-https://example.com/] tests/unit/test_index.py::test_determine_base_url[<html><head><base tests/unit/test_index.py::test_determine_base_url[<html><head><base><base tests/unit/test_index.py::test_find_name_version_sep[pip-18.0-pip-3] tests/unit/test_index.py::test_find_name_version_sep[zope-interface-4.5.0-zope-interface-14] tests/unit/test_index.py::test_find_name_version_sep[Jinja2-2.10-jinja2-6] tests/unit/test_index.py::test_find_name_version_sep[zope.interface-4.5.0-zope-interface-14] tests/unit/test_index.py::test_find_name_version_sep[zope_interface-4.5.0-zope-interface-14] tests/unit/test_index.py::test_find_name_version_sep[foo-2-2-foo-3] tests/unit/test_index.py::test_find_name_version_sep[foo-2-2-foo-2-5] tests/unit/test_index.py::test_find_name_version_sep[foo--bar-1.0-foo-bar-8] tests/unit/test_index.py::test_find_name_version_sep[foo-_bar-1.0-foo-bar-8] tests/unit/test_index.py::test_find_name_version_sep[zope.interface--4.5.0-zope-interface-14] tests/unit/test_index.py::test_find_name_version_sep[zope.interface---zope-interface-14] tests/unit/test_index.py::test_find_name_version_sep[zope.interface--zope-interface-14] tests/unit/test_index.py::test_find_name_version_sep_failure[zope.interface4.5.0-zope-interface] tests/unit/test_index.py::test_find_name_version_sep_failure[zope.interface.4.5.0-zope-interface] tests/unit/test_index.py::test_find_name_version_sep_failure[zope.interface.-4.5.0-zope-interface] tests/unit/test_index.py::test_find_name_version_sep_failure[zope.interface-zope-interface] tests/unit/test_index.py::test_egg_info_matches[pip-18.0-pip-18.0] tests/unit/test_index.py::test_egg_info_matches[zope-interface-4.5.0-zope-interface-4.5.0] tests/unit/test_index.py::test_egg_info_matches[Jinja2-2.10-jinja2-2.10] tests/unit/test_index.py::test_egg_info_matches[zope.interface-4.5.0-zope-interface-4.5.0] tests/unit/test_index.py::test_egg_info_matches[zope_interface-4.5.0-zope-interface-4.5.0] tests/unit/test_index.py::test_egg_info_matches[foo-2-2-foo-2-2_0] tests/unit/test_index.py::test_egg_info_matches[foo-2-2-foo-2-2_1] tests/unit/test_index.py::test_egg_info_matches[zope.interface--4.5.0-zope-interface--4.5.0] tests/unit/test_index.py::test_egg_info_matches[zope.interface---zope-interface--] tests/unit/test_index.py::test_egg_info_matches[foo--bar-1.0-foo-bar-1.0] tests/unit/test_index.py::test_egg_info_matches[foo-_bar-1.0-foo-bar-1.0] tests/unit/test_index.py::test_egg_info_matches[the-package-name-8.19-does-not-match-None] tests/unit/test_index.py::test_egg_info_matches[zope.interface.-4.5.0-zope.interface-None] tests/unit/test_index.py::test_egg_info_matches[zope.interface--zope-interface-None] tests/unit/test_index.py::test_egg_info_matches[zope.interface4.5.0-zope-interface-None] tests/unit/test_index.py::test_egg_info_matches[zope.interface.4.5.0-zope-interface-None] tests/unit/test_index.py::test_egg_info_matches[zope.interface.-4.5.0-zope-interface-None] tests/unit/test_index.py::test_egg_info_matches[zope.interface-zope-interface-None] tests/unit/test_index.py::test_request_http_error tests/unit/test_index.py::test_request_retries tests/unit/test_index.py::test_clean_link[https://localhost.localdomain:8181/path/with tests/unit/test_index.py::test_clean_link[https://localhost.localdomain:8181/path/with%20quoted%20space/-https://localhost.localdomain:8181/path/with%20quoted%20space/] tests/unit/test_index.py::test_clean_link[https://127.0.0.1:8181/path/with tests/unit/test_index.py::test_clean_link[https://[fd00:0:0:236::100]:8181/path/with tests/unit/test_index.py::test_clean_link[https://localhost.localdomain:8181/path/with/query?request=test-https://localhost.localdomain:8181/path/with/query?request=test] tests/unit/test_index.py::test_clean_link[https://localhost.localdomain:8181/path:/with:/colon-https://localhost.localdomain:8181/path%3A/with%3A/colon] tests/unit/test_index.py::test_clean_link[https://localhost.localdomain/T:/path/-https://localhost.localdomain/T%3A/path/] tests/unit/test_index.py::test_clean_link[git+ssh://example.com/path tests/unit/test_index.py::test_clean_link_non_windows[file:///T:/path/with tests/unit/test_index.py::TestHTMLPage::test_iter_links__yanked_reason[<a tests/unit/test_unit_outdated.py::test_pip_version_check[1970-01-01T10:00:00Z-None-1.0-pip-False-False] tests/unit/test_unit_outdated.py::test_pip_version_check[1970-01-9T10:00:00Z-6.9.0-6.9.0-pip-False-False] tests/unit/test_unit_outdated.py::test_self_check_state tests/unit/test_unit_outdated.py::test_self_check_state_no_cache_dir
src/pip/_internal/cli/base_command.py src/pip/_internal/utils/outdated.py src/pip/_internal/commands/list.py src/pip/_internal/index.py src/pip/_internal/models/link.py
src/pip/_internal/cli/base_command.py src/pip/_internal/utils/outdated.py src/pip/_internal/commands/list.py src/pip/_internal/index.py src/pip/_internal/models/link.py