Skip to content

Commit c55b985

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: pypa#7839
1 parent 64d8938 commit c55b985

File tree

12 files changed

+62
-6
lines changed

12 files changed

+62
-6
lines changed

news/7839.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
@@ -415,6 +415,19 @@ def constraints() -> Option:
415415
)
416416

417417

418+
def ignored_constraints() -> Option:
419+
return Option(
420+
"--ignore-constraint",
421+
dest="ignored_constraints",
422+
action="append",
423+
default=[],
424+
metavar="package",
425+
help="Ignore constraints for given package. This is commonly used "
426+
"during development of a package when using a common constraints "
427+
"file. This option be used multiple times.",
428+
)
429+
430+
418431
def requirements() -> Option:
419432
return Option(
420433
"-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
@@ -37,6 +37,7 @@ class DownloadCommand(RequirementCommand):
3737

3838
def add_options(self) -> None:
3939
self.cmd_opts.add_option(cmdoptions.constraints())
40+
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
4041
self.cmd_opts.add_option(cmdoptions.requirements())
4142
self.cmd_opts.add_option(cmdoptions.no_deps())
4243
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
@@ -72,6 +72,7 @@ class InstallCommand(RequirementCommand):
7272
def add_options(self) -> None:
7373
self.cmd_opts.add_option(cmdoptions.requirements())
7474
self.cmd_opts.add_option(cmdoptions.constraints())
75+
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
7576
self.cmd_opts.add_option(cmdoptions.no_deps())
7677
self.cmd_opts.add_option(cmdoptions.pre())
7778

src/pip/_internal/commands/wheel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def add_options(self) -> None:
6161
self.cmd_opts.add_option(cmdoptions.no_use_pep517())
6262
self.cmd_opts.add_option(cmdoptions.check_build_deps())
6363
self.cmd_opts.add_option(cmdoptions.constraints())
64+
self.cmd_opts.add_option(cmdoptions.ignored_constraints())
6465
self.cmd_opts.add_option(cmdoptions.editable())
6566
self.cmd_opts.add_option(cmdoptions.requirements())
6667
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:
@@ -223,11 +227,15 @@ def _eligible_for_upgrade(identifier: str) -> bool:
223227
return user_order is not None
224228
return False
225229

226-
constraint = _get_with_identifier(
227-
self._constraints,
228-
identifier,
229-
default=Constraint.empty(),
230-
)
230+
if identifier not in self._ignored_constraints:
231+
constraint = _get_with_identifier(
232+
self._constraints,
233+
identifier,
234+
default=Constraint.empty(),
235+
)
236+
else:
237+
constraint = Constraint.empty()
238+
231239
return self._factory.find_candidates(
232240
identifier=identifier,
233241
requirements=requirements,

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

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

77
from pip._vendor.packaging.utils import canonicalize_name
88
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
@@ -50,6 +50,7 @@ def __init__(
5050
ignore_requires_python: bool,
5151
force_reinstall: bool,
5252
upgrade_strategy: str,
53+
ignored_constraints: Sequence[str],
5354
py_version_info: Optional[Tuple[int, ...]] = None,
5455
):
5556
super().__init__()
@@ -68,6 +69,7 @@ def __init__(
6869
)
6970
self.ignore_dependencies = ignore_dependencies
7071
self.upgrade_strategy = upgrade_strategy
72+
self.ignored_constraints = ignored_constraints
7173
self._result: Optional[Result] = None
7274

7375
def resolve(
@@ -80,6 +82,7 @@ def resolve(
8082
ignore_dependencies=self.ignore_dependencies,
8183
upgrade_strategy=self.upgrade_strategy,
8284
user_requested=collected.user_requested,
85+
ignored_constraints=self.ignored_constraints,
8386
)
8487
if "PIP_RESOLVER_DEBUG" in os.environ:
8588
reporter: BaseReporter = PipDebuggingReporter()

tests/functional/test_new_resolver.py

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

683683

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