Skip to content

Commit

Permalink
Add '--ignore-constraint' option
Browse files Browse the repository at this point in the history
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#11718
  • Loading branch information
stephenfin committed Jan 12, 2023
1 parent 07a360d commit 5b5f762
Show file tree
Hide file tree
Showing 12 changed files with 62 additions and 6 deletions.
3 changes: 3 additions & 0 deletions news/11718.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 @@ -394,6 +394,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 @@ -40,6 +40,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 @@ -94,6 +94,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 @@ -64,6 +64,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 @@ -216,11 +220,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
@@ -1,7 +1,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 @@ -47,6 +47,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 @@ -65,6 +66,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 @@ -77,6 +79,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 @@ -679,6 +679,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

0 comments on commit 5b5f762

Please sign in to comment.