diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py index 37dc17188..658420433 100644 --- a/src/pip/_internal/cli/base_command.py +++ b/src/pip/_internal/cli/base_command.py @@ -25,6 +25,7 @@ from pip._internal.exceptions import ( ) from pip._internal.index import PackageFinder from pip._internal.locations import running_under_virtualenv +from pip._internal.models.selection_prefs import SelectionPreferences from pip._internal.models.target_python import TargetPython from pip._internal.req.constructors import ( install_req_from_editable, install_req_from_line, @@ -337,6 +338,13 @@ class RequirementCommand(Command): "Requires-Python" values in links. Defaults to False. """ search_scope = make_search_scope(options) + selection_prefs = SelectionPreferences( + allow_yanked=True, + format_control=options.format_control, + allow_all_prereleases=options.pre, + prefer_binary=options.prefer_binary, + ignore_requires_python=ignore_requires_python, + ) target_python = TargetPython( platform=platform, @@ -347,12 +355,8 @@ class RequirementCommand(Command): return PackageFinder.create( search_scope=search_scope, - allow_yanked=True, - format_control=options.format_control, + selection_prefs=selection_prefs, trusted_hosts=options.trusted_hosts, - allow_all_prereleases=options.pre, session=session, target_python=target_python, - prefer_binary=options.prefer_binary, - ignore_requires_python=ignore_requires_python, ) diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py index a9ff88eb1..8a373ef08 100644 --- a/src/pip/_internal/commands/list.py +++ b/src/pip/_internal/commands/list.py @@ -11,6 +11,7 @@ from pip._internal.cli.base_command import Command from pip._internal.cli.cmdoptions import make_search_scope from pip._internal.exceptions import CommandError from pip._internal.index import PackageFinder +from pip._internal.models.selection_prefs import SelectionPreferences from pip._internal.utils.misc import ( dist_is_editable, get_installed_distributions, ) @@ -117,10 +118,14 @@ class ListCommand(Command): search_scope = make_search_scope(options) # Pass allow_yanked=False to ignore yanked versions. - return PackageFinder.create( - search_scope=search_scope, + selection_prefs = SelectionPreferences( allow_yanked=False, allow_all_prereleases=options.pre, + ) + + return PackageFinder.create( + search_scope=search_scope, + selection_prefs=selection_prefs, trusted_hosts=options.trusted_hosts, session=session, ) diff --git a/src/pip/_internal/index.py b/src/pip/_internal/index.py index 5dafd1c7e..a5a6f295a 100644 --- a/src/pip/_internal/index.py +++ b/src/pip/_internal/index.py @@ -26,6 +26,7 @@ from pip._internal.exceptions import ( from pip._internal.models.candidate import InstallationCandidate from pip._internal.models.format_control import FormatControl from pip._internal.models.link import Link +from pip._internal.models.selection_prefs import SelectionPreferences from pip._internal.models.target_python import TargetPython from pip._internal.utils.compat import ipaddress from pip._internal.utils.logging import indent_log @@ -649,31 +650,20 @@ class PackageFinder(object): def create( cls, search_scope, # type: SearchScope - allow_yanked, # type: bool - allow_all_prereleases=False, # type: bool + selection_prefs, # type: SelectionPreferences trusted_hosts=None, # type: Optional[List[str]] - session=None, # type: Optional[PipSession] - format_control=None, # type: Optional[FormatControl] + session=None, # type: Optional[PipSession] target_python=None, # type: Optional[TargetPython] - prefer_binary=False, # type: bool - ignore_requires_python=None, # type: Optional[bool] ): # 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 selection_prefs: The candidate selection preferences, as a + SelectionPreferences object. :param trusted_hosts: Domains not to emit warnings for when not using HTTPS. :param session: The Session to use to make requests. - :param format_control: A FormatControl object or None. Used to control - the selection of source packages / binary packages when consulting - the index and links. :param target_python: The target Python interpreter. - :param prefer_binary: Whether to prefer an old, but valid, binary - dist over a new source dist. - :param ignore_requires_python: Whether to ignore incompatible - "Requires-Python" values in links. Defaults to False. """ if session is None: raise TypeError( @@ -682,18 +672,18 @@ class PackageFinder(object): ) candidate_evaluator = CandidateEvaluator( - allow_yanked=allow_yanked, + allow_yanked=selection_prefs.allow_yanked, target_python=target_python, - prefer_binary=prefer_binary, - allow_all_prereleases=allow_all_prereleases, - ignore_requires_python=ignore_requires_python, + prefer_binary=selection_prefs.prefer_binary, + allow_all_prereleases=selection_prefs.allow_all_prereleases, + ignore_requires_python=selection_prefs.ignore_requires_python, ) return cls( candidate_evaluator=candidate_evaluator, search_scope=search_scope, session=session, - format_control=format_control, + format_control=selection_prefs.format_control, trusted_hosts=trusted_hosts, ) diff --git a/src/pip/_internal/models/selection_prefs.py b/src/pip/_internal/models/selection_prefs.py new file mode 100644 index 000000000..f58fdce9c --- /dev/null +++ b/src/pip/_internal/models/selection_prefs.py @@ -0,0 +1,47 @@ +from pip._internal.utils.typing import MYPY_CHECK_RUNNING + +if MYPY_CHECK_RUNNING: + from typing import Optional + from pip._internal.models.format_control import FormatControl + + +class SelectionPreferences(object): + + """ + Encapsulates the candidate selection preferences for downloading + and installing files. + """ + + # 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 + allow_all_prereleases=False, # type: bool + format_control=None, # type: Optional[FormatControl] + prefer_binary=False, # type: bool + ignore_requires_python=None, # type: Optional[bool] + ): + # type: (...) -> None + """Create a SelectionPreferences object. + + :param allow_yanked: Whether files marked as yanked (in the sense + of PEP 592) are permitted to be candidates for install. + :param format_control: A FormatControl object or None. Used to control + the selection of source packages / binary packages when consulting + the index and links. + :param prefer_binary: Whether to prefer an old, but valid, binary + dist over a new source dist. + :param ignore_requires_python: Whether to ignore incompatible + "Requires-Python" values in links. Defaults to False. + """ + if ignore_requires_python is None: + ignore_requires_python = False + + self.allow_yanked = allow_yanked + self.allow_all_prereleases = allow_all_prereleases + self.format_control = format_control + self.prefer_binary = prefer_binary + self.ignore_requires_python = ignore_requires_python diff --git a/src/pip/_internal/utils/outdated.py b/src/pip/_internal/utils/outdated.py index 8088cda78..2b10aeff6 100644 --- a/src/pip/_internal/utils/outdated.py +++ b/src/pip/_internal/utils/outdated.py @@ -11,6 +11,7 @@ from pip._vendor.packaging import version as packaging_version from pip._internal.cli.cmdoptions import make_search_scope from pip._internal.index import PackageFinder +from pip._internal.models.selection_prefs import SelectionPreferences from pip._internal.utils.compat import WINDOWS from pip._internal.utils.filesystem import check_path_owner from pip._internal.utils.misc import ensure_dir, get_installed_version @@ -127,10 +128,14 @@ def pip_version_check(session, options): # Pass allow_yanked=False so we don't suggest upgrading to a # yanked version. - finder = PackageFinder.create( - search_scope=search_scope, + selection_prefs = SelectionPreferences( allow_yanked=False, allow_all_prereleases=False, # Explicitly set to False + ) + + finder = PackageFinder.create( + search_scope=search_scope, + selection_prefs=selection_prefs, trusted_hosts=options.trusted_hosts, session=session, ) diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index 0e9cd9c92..348ad9214 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -13,10 +13,11 @@ import subprocess import pytest from scripttest import FoundDir, TestFileEnvironment -from pip._internal.models.search_scope import SearchScope -from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX from pip._internal.download import PipSession from pip._internal.index import PackageFinder +from pip._internal.models.search_scope import SearchScope +from pip._internal.models.selection_prefs import SelectionPreferences +from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX from pip._internal.utils.typing import MYPY_CHECK_RUNNING from tests.lib.path import Path, curdir @@ -101,11 +102,14 @@ def make_test_finder( find_links=find_links, index_urls=index_urls, ) + selection_prefs = SelectionPreferences( + allow_yanked=True, + allow_all_prereleases=allow_all_prereleases, + ) return PackageFinder.create( search_scope=search_scope, - allow_yanked=True, - allow_all_prereleases=allow_all_prereleases, + selection_prefs=selection_prefs, trusted_hosts=trusted_hosts, session=session, target_python=target_python, diff --git a/tests/unit/test_build_env.py b/tests/unit/test_build_env.py index dc1bb187d..16f0ad438 100644 --- a/tests/unit/test_build_env.py +++ b/tests/unit/test_build_env.py @@ -25,11 +25,17 @@ def run_with_build_env(script, setup_script_contents, from pip._internal.download import PipSession from pip._internal.index import PackageFinder from pip._internal.models.search_scope import SearchScope + from pip._internal.models.selection_prefs import ( + SelectionPreferences + ) search_scope = SearchScope.create([%r], []) + selection_prefs = SelectionPreferences( + allow_yanked=True, + ) finder = PackageFinder.create( search_scope, - allow_yanked=True, + selection_prefs=selection_prefs, session=PipSession(), ) build_env = BuildEnvironment() diff --git a/tests/unit/test_index.py b/tests/unit/test_index.py index 83531d4d2..46da51eda 100644 --- a/tests/unit/test_index.py +++ b/tests/unit/test_index.py @@ -13,6 +13,7 @@ from pip._internal.index import ( ) from pip._internal.models.candidate import InstallationCandidate from pip._internal.models.search_scope import SearchScope +from pip._internal.models.selection_prefs import SelectionPreferences from pip._internal.models.target_python import TargetPython from tests.lib import CURRENT_PY_VERSION_INFO, make_test_finder @@ -307,9 +308,12 @@ class TestPackageFinder: Test that allow_yanked is passed to CandidateEvaluator. """ search_scope = SearchScope([], []) + selection_prefs = SelectionPreferences( + allow_yanked=allow_yanked, + ) finder = PackageFinder.create( search_scope=search_scope, - allow_yanked=allow_yanked, + selection_prefs=selection_prefs, session=object(), ) evaluator = finder.candidate_evaluator @@ -320,10 +324,13 @@ class TestPackageFinder: Test that target_python is passed to CandidateEvaluator as is. """ search_scope = SearchScope([], []) + selection_prefs = SelectionPreferences( + allow_yanked=True, + ) target_python = TargetPython(py_version_info=(3, 7, 3)) finder = PackageFinder.create( search_scope=search_scope, - allow_yanked=True, + selection_prefs=selection_prefs, session=object(), target_python=target_python, ) @@ -407,9 +414,12 @@ class TestPackageFinder: # Use PackageFinder.create() rather than make_test_finder() # to make sure we're really passing trusted_hosts=None. search_scope = SearchScope([], []) + selection_prefs = SelectionPreferences( + allow_yanked=True, + ) finder = PackageFinder.create( search_scope=search_scope, - allow_yanked=True, + selection_prefs=selection_prefs, trusted_hosts=None, session=object(), )