Skip to content

Add '--ignore-constraint' option #11723

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions news/7839.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add the ``--ignore-constraint`` option to ``pip install``, ``pip download``
and ``pip wheel`` commands to ignore an individual constraint from a
constraints file.
13 changes: 13 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,19 @@ def constraints() -> Option:
)


def ignored_constraints() -> Option:
return Option(
"--ignore-constraint",
dest="ignored_constraints",
action="append",
default=[],
metavar="package",
help="Ignore constraints for given package. This is commonly used "
"during development of a package when using a common constraints "
"file. This option be used multiple times.",
)


def requirements() -> Option:
return Option(
"-r",
Expand Down
3 changes: 3 additions & 0 deletions src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,10 +363,13 @@ def make_resolver(
ignore_requires_python=ignore_requires_python,
force_reinstall=force_reinstall,
upgrade_strategy=upgrade_strategy,
ignored_constraints=options.ignored_constraints,
py_version_info=py_version_info,
)
import pip._internal.resolution.legacy.resolver

# we intentionally don't pass ignored_constraints to this since the
# resolver is deprecated
return pip._internal.resolution.legacy.resolver.Resolver(
preparer=preparer,
finder=finder,
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class DownloadCommand(RequirementCommand):

def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
self.cmd_opts.add_option(cmdoptions.requirements())
self.cmd_opts.add_option(cmdoptions.no_deps())
self.cmd_opts.add_option(cmdoptions.global_options())
Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class InstallCommand(RequirementCommand):
def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.requirements())
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
self.cmd_opts.add_option(cmdoptions.no_deps())
self.cmd_opts.add_option(cmdoptions.pre())

Expand Down
1 change: 1 addition & 0 deletions src/pip/_internal/commands/wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def add_options(self) -> None:
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
self.cmd_opts.add_option(cmdoptions.check_build_deps())
self.cmd_opts.add_option(cmdoptions.constraints())
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
self.cmd_opts.add_option(cmdoptions.editable())
self.cmd_opts.add_option(cmdoptions.requirements())
self.cmd_opts.add_option(cmdoptions.src())
Expand Down
18 changes: 13 additions & 5 deletions src/pip/_internal/resolution/resolvelib/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class PipProvider(_ProviderBase):
:params upgrade_strategy: The user-specified upgrade strategy.
:params user_requested: A set of canonicalized package names that the user
supplied for pip to install/upgrade.
:params ignored_constraints: A list of canonicalized package names that the
user has asked us to ignore constraints for.
"""

def __init__(
Expand All @@ -93,12 +95,14 @@ def __init__(
ignore_dependencies: bool,
upgrade_strategy: str,
user_requested: Dict[str, int],
ignored_constraints: Sequence[str],
) -> None:
self._factory = factory
self._constraints = constraints
self._ignore_dependencies = ignore_dependencies
self._upgrade_strategy = upgrade_strategy
self._user_requested = user_requested
self._ignored_constraints = ignored_constraints
self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf)

def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
Expand Down Expand Up @@ -223,11 +227,15 @@ def _eligible_for_upgrade(identifier: str) -> bool:
return user_order is not None
return False

constraint = _get_with_identifier(
self._constraints,
identifier,
default=Constraint.empty(),
)
if identifier not in self._ignored_constraints:
constraint = _get_with_identifier(
self._constraints,
identifier,
default=Constraint.empty(),
)
else:
constraint = Constraint.empty()

return self._factory.find_candidates(
identifier=identifier,
requirements=requirements,
Expand Down
5 changes: 4 additions & 1 deletion src/pip/_internal/resolution/resolvelib/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import functools
import logging
import os
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Set, Tuple, cast

from pip._vendor.packaging.utils import canonicalize_name
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
Expand Down Expand Up @@ -50,6 +50,7 @@ def __init__(
ignore_requires_python: bool,
force_reinstall: bool,
upgrade_strategy: str,
ignored_constraints: Sequence[str],
py_version_info: Optional[Tuple[int, ...]] = None,
):
super().__init__()
Expand All @@ -68,6 +69,7 @@ def __init__(
)
self.ignore_dependencies = ignore_dependencies
self.upgrade_strategy = upgrade_strategy
self.ignored_constraints = ignored_constraints
self._result: Optional[Result] = None

def resolve(
Expand All @@ -80,6 +82,7 @@ def resolve(
ignore_dependencies=self.ignore_dependencies,
upgrade_strategy=self.upgrade_strategy,
user_requested=collected.user_requested,
ignored_constraints=self.ignored_constraints,
)
if "PIP_RESOLVER_DEBUG" in os.environ:
reporter: BaseReporter = PipDebuggingReporter()
Expand Down
20 changes: 20 additions & 0 deletions tests/functional/test_new_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,26 @@ def test_new_resolver_constraints(
script.assert_not_installed("constraint_only")


def test_new_resolver_constraint_ignored(script: PipTestEnvironment) -> None:
"We can ignore constraints. Useful when hacking on a constrained package."
create_basic_wheel_for_package(script, "pkg", "1.1.dev1")
constraints_file = script.scratch_path / "constraints.txt"
constraints_file.write_text("pkg==1.0")
script.pip(
"install",
"--no-cache-dir",
"--no-index",
"--find-links",
script.scratch_path,
"-c",
constraints_file,
"--ignore-constraint",
"pkg",
"pkg",
)
script.assert_installed(pkg="1.1.dev1")


def test_new_resolver_constraint_no_specifier(script: PipTestEnvironment) -> None:
"It's allowed (but useless...) for a constraint to have no specifier"
create_basic_wheel_for_package(script, "pkg", "1.0")
Expand Down
1 change: 1 addition & 0 deletions tests/unit/resolution_resolvelib/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ def provider(factory: Factory) -> Iterator[PipProvider]:
ignore_dependencies=False,
upgrade_strategy="to-satisfy-only",
user_requested={},
ignored_constraints=[],
)
1 change: 1 addition & 0 deletions tests/unit/resolution_resolvelib/test_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def test_provider_known_depths(factory: Factory) -> None:
ignore_dependencies=False,
upgrade_strategy="to-satisfy-only",
user_requested={root_requirement_name: 0},
ignored_constraints=[],
)

root_requirement_information = build_requirement_information(
Expand Down
1 change: 1 addition & 0 deletions tests/unit/resolution_resolvelib/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def resolver(preparer: RequirementPreparer, finder: PackageFinder) -> Resolver:
ignore_requires_python=False,
force_reinstall=False,
upgrade_strategy="to-satisfy-only",
ignored_constraints=[],
)
return resolver

Expand Down