Skip to content

Commit 5b5f762

Browse files
committed
Add '--ignore-constraint' option
Constraint files are often shared across an organization. When developing a package included in such a constraint file, it is not possible to install the package with constraints since the constraint on the package prevents us installing a development version. ❯ cd my-amazing-package ❯ cat constraints.txt my-amazing-package==1.2.3 Jinja2==3.1.2 iso8601==1.1.0 msgpack==1.0.4 ❯ pip install -c constraints.txt . Processing /dev/my-amazing-package Preparing metadata (setup.py) ... done ERROR: Cannot install my-amazing-package 1.2.4.dev1 (from /dev/my-amazing-package) because these package versions have conflicting dependencies. The conflict is caused by: The user requested my-amazing-package 1.2.4.dev1 (from /dev/my-amazing-package) The user requested (constraint) my-amazing-package===1.2.4.dev1 To fix this you could try to: 1. loosen the range of package versions you've specified 2. remove package versions to allow pip attempt to solve the dependency conflict ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflicts Resolve this by allowing users to opt out of individual constraints to the 'install', 'wheel', and 'download' subcommands. This is rather manual but it's expected that tools like tox could automatically generate a value for this option when invoking 'pip install' command. ❯ pip install -c constraints.txt --ignore-constraint my-amazing-package . ❯ pip wheel -c constraints.txt --ignore-constraint my-amazing-package . ❯ pip download -c constraints.txt --ignore-constraint my-amazing-package . This is only added for the '2020-resolver' resolver, not the 'legacy-resolver' resolver, given the latter is deprecated for removal. Signed-off-by: Stephen Finucane <[email protected]> Fixes: #11718
1 parent 07a360d commit 5b5f762

File tree

12 files changed

+62
-6
lines changed

12 files changed

+62
-6
lines changed

news/11718.feature.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add the ``--ignore-constraint`` option to ``pip install``, ``pip download``
2+
and ``pip wheel`` commands to ignore an individual constraint from a
3+
constraints file.

src/pip/_internal/cli/cmdoptions.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,19 @@ def constraints() -> Option:
394394
)
395395

396396

397+
def ignored_constraints() -> Option:
398+
return Option(
399+
"--ignore-constraint",
400+
dest="ignored_constraints",
401+
action="append",
402+
default=[],
403+
metavar="package",
404+
help="Ignore constraints for given package. This is commonly used "
405+
"during development of a package when using a common constraints "
406+
"file. This option be used multiple times.",
407+
)
408+
409+
397410
def requirements() -> Option:
398411
return Option(
399412
"-r",

src/pip/_internal/cli/req_command.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,13 @@ def make_resolver(
363363
ignore_requires_python=ignore_requires_python,
364364
force_reinstall=force_reinstall,
365365
upgrade_strategy=upgrade_strategy,
366+
ignored_constraints=options.ignored_constraints,
366367
py_version_info=py_version_info,
367368
)
368369
import pip._internal.resolution.legacy.resolver
369370

371+
# we intentionally don't pass ignored_constraints to this since the
372+
# resolver is deprecated
370373
return pip._internal.resolution.legacy.resolver.Resolver(
371374
preparer=preparer,
372375
finder=finder,

src/pip/_internal/commands/download.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class DownloadCommand(RequirementCommand):
4040

4141
def add_options(self) -> None:
4242
self.cmd_opts.add_option(cmdoptions.constraints())
43+
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
4344
self.cmd_opts.add_option(cmdoptions.requirements())
4445
self.cmd_opts.add_option(cmdoptions.no_deps())
4546
self.cmd_opts.add_option(cmdoptions.global_options())

src/pip/_internal/commands/install.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class InstallCommand(RequirementCommand):
9494
def add_options(self) -> None:
9595
self.cmd_opts.add_option(cmdoptions.requirements())
9696
self.cmd_opts.add_option(cmdoptions.constraints())
97+
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
9798
self.cmd_opts.add_option(cmdoptions.no_deps())
9899
self.cmd_opts.add_option(cmdoptions.pre())
99100

src/pip/_internal/commands/wheel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ def add_options(self) -> None:
6464
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
6565
self.cmd_opts.add_option(cmdoptions.check_build_deps())
6666
self.cmd_opts.add_option(cmdoptions.constraints())
67+
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
6768
self.cmd_opts.add_option(cmdoptions.editable())
6869
self.cmd_opts.add_option(cmdoptions.requirements())
6970
self.cmd_opts.add_option(cmdoptions.src())

src/pip/_internal/resolution/resolvelib/provider.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class PipProvider(_ProviderBase):
8484
:params upgrade_strategy: The user-specified upgrade strategy.
8585
:params user_requested: A set of canonicalized package names that the user
8686
supplied for pip to install/upgrade.
87+
:params ignored_constraints: A list of canonicalized package names that the
88+
user has asked us to ignore constraints for.
8789
"""
8890

8991
def __init__(
@@ -93,12 +95,14 @@ def __init__(
9395
ignore_dependencies: bool,
9496
upgrade_strategy: str,
9597
user_requested: Dict[str, int],
98+
ignored_constraints: Sequence[str],
9699
) -> None:
97100
self._factory = factory
98101
self._constraints = constraints
99102
self._ignore_dependencies = ignore_dependencies
100103
self._upgrade_strategy = upgrade_strategy
101104
self._user_requested = user_requested
105+
self._ignored_constraints = ignored_constraints
102106
self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf)
103107

104108
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
@@ -216,11 +220,15 @@ def _eligible_for_upgrade(identifier: str) -> bool:
216220
return user_order is not None
217221
return False
218222

219-
constraint = _get_with_identifier(
220-
self._constraints,
221-
identifier,
222-
default=Constraint.empty(),
223-
)
223+
if identifier not in self._ignored_constraints:
224+
constraint = _get_with_identifier(
225+
self._constraints,
226+
identifier,
227+
default=Constraint.empty(),
228+
)
229+
else:
230+
constraint = Constraint.empty()
231+
224232
return self._factory.find_candidates(
225233
identifier=identifier,
226234
requirements=requirements,

src/pip/_internal/resolution/resolvelib/resolver.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import functools
22
import logging
33
import os
4-
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast
4+
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Set, Tuple, cast
55

66
from pip._vendor.packaging.utils import canonicalize_name
77
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
@@ -47,6 +47,7 @@ def __init__(
4747
ignore_requires_python: bool,
4848
force_reinstall: bool,
4949
upgrade_strategy: str,
50+
ignored_constraints: Sequence[str],
5051
py_version_info: Optional[Tuple[int, ...]] = None,
5152
):
5253
super().__init__()
@@ -65,6 +66,7 @@ def __init__(
6566
)
6667
self.ignore_dependencies = ignore_dependencies
6768
self.upgrade_strategy = upgrade_strategy
69+
self.ignored_constraints = ignored_constraints
6870
self._result: Optional[Result] = None
6971

7072
def resolve(
@@ -77,6 +79,7 @@ def resolve(
7779
ignore_dependencies=self.ignore_dependencies,
7880
upgrade_strategy=self.upgrade_strategy,
7981
user_requested=collected.user_requested,
82+
ignored_constraints=self.ignored_constraints,
8083
)
8184
if "PIP_RESOLVER_DEBUG" in os.environ:
8285
reporter: BaseReporter = PipDebuggingReporter()

tests/functional/test_new_resolver.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,26 @@ def test_new_resolver_constraints(
679679
script.assert_not_installed("constraint_only")
680680

681681

682+
def test_new_resolver_constraint_ignored(script: PipTestEnvironment) -> None:
683+
"We can ignore constraints. Useful when hacking on a constrained package."
684+
create_basic_wheel_for_package(script, "pkg", "1.1.dev1")
685+
constraints_file = script.scratch_path / "constraints.txt"
686+
constraints_file.write_text("pkg==1.0")
687+
script.pip(
688+
"install",
689+
"--no-cache-dir",
690+
"--no-index",
691+
"--find-links",
692+
script.scratch_path,
693+
"-c",
694+
constraints_file,
695+
"--ignore-constraint",
696+
"pkg",
697+
"pkg",
698+
)
699+
script.assert_installed(pkg="1.1.dev1")
700+
701+
682702
def test_new_resolver_constraint_no_specifier(script: PipTestEnvironment) -> None:
683703
"It's allowed (but useless...) for a constraint to have no specifier"
684704
create_basic_wheel_for_package(script, "pkg", "1.0")

tests/unit/resolution_resolvelib/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,5 @@ def provider(factory: Factory) -> Iterator[PipProvider]:
7575
ignore_dependencies=False,
7676
upgrade_strategy="to-satisfy-only",
7777
user_requested={},
78+
ignored_constraints=[],
7879
)

tests/unit/resolution_resolvelib/test_provider.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def test_provider_known_depths(factory: Factory) -> None:
3636
ignore_dependencies=False,
3737
upgrade_strategy="to-satisfy-only",
3838
user_requested={root_requirement_name: 0},
39+
ignored_constraints=[],
3940
)
4041

4142
root_requirement_information = build_requirement_information(

tests/unit/resolution_resolvelib/test_resolver.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def resolver(preparer: RequirementPreparer, finder: PackageFinder) -> Resolver:
2929
ignore_requires_python=False,
3030
force_reinstall=False,
3131
upgrade_strategy="to-satisfy-only",
32+
ignored_constraints=[],
3233
)
3334
return resolver
3435

0 commit comments

Comments
 (0)