Skip to content

Commit

Permalink
add "--allow-conflicts" option (proof-of-concept), jazzband#561
Browse files Browse the repository at this point in the history
By default we get an error if there is version conflict, e.g.:

  "Could not find a version that matches requests==2.18.4,==2.7.0"

With this option the constraints defined in the spec file
(requirements.in) take precedence, so the installation can continue,
and a warning is printed, e.g.:

  "Conflicting versions found: requests==2.18.4,==2.7.0, using our
   constraints: requests==2.18.4"

This makes behavior similar to ``pip`` and is a workaround when
libraries are too strict in specifying their dependencies.
  • Loading branch information
jcerjak committed Apr 19, 2018
1 parent 3d9e0ec commit 3fc0ca0
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 5 deletions.
33 changes: 30 additions & 3 deletions piptools/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from . import click
from .cache import DependencyCache
from .exceptions import UnsupportedConstraint
from .exceptions import NoCandidateFound, UnsupportedConstraint
from .logging import log
from .utils import (format_requirement, format_specifier, full_groupby,
is_pinned_requirement, key_from_ireq, key_from_req, UNSAFE_PACKAGES)
Expand Down Expand Up @@ -42,7 +42,8 @@ def __str__(self):


class Resolver(object):
def __init__(self, constraints, repository, cache=None, prereleases=False, clear_caches=False, allow_unsafe=False):
def __init__(self, constraints, repository, cache=None, prereleases=False,
clear_caches=False, allow_unsafe=False, allow_conflicts=False):
"""
This class resolves a given set of constraints (a collection of
InstallRequirement objects) by consulting the given Repository and the
Expand All @@ -58,6 +59,7 @@ def __init__(self, constraints, repository, cache=None, prereleases=False, clear
self.clear_caches = clear_caches
self.allow_unsafe = allow_unsafe
self.unsafe_constraints = set()
self.allow_conflicts = allow_conflicts

@property
def constraints(self):
Expand Down Expand Up @@ -243,6 +245,10 @@ def get_best_match(self, ireq):
Flask==0.10.1 => Flask==0.10.1
If a match cannot be found, ``NoCandidateFound`` exception will be
raised. If ``self.allow_conflicts`` option is set to True, then we try
to find a match by using our constraints in the spec file
(requirements.in).
"""
if ireq.editable:
# NOTE: it's much quicker to immediately return instead of
Expand All @@ -253,7 +259,28 @@ def get_best_match(self, ireq):
# hitting the index server
best_match = ireq
else:
best_match = self.repository.find_best_match(ireq, prereleases=self.prereleases)
try:
best_match = self.repository.find_best_match(ireq, prereleases=self.prereleases)
except NoCandidateFound:
if not self.allow_conflicts:
raise

# maybe we got a version conflict, try finding a match using our
# contraints for this requirement
for constraint in self.our_constraints:
if constraint.req.name == ireq.req.name:
new_req = constraint.req
original_req = ireq.req
# TODO: not sure if we should mutate ireq or create
# a new object
ireq.req = new_req

best_match = self.repository.find_best_match(ireq, prereleases=self.prereleases)
log.warning('Conflicting versions found: {}, using our constraints: {}'.format(
original_req, new_req))
break
else:
raise

# Format the best match
log.debug(' found candidate {} (constraint was {})'.format(format_requirement(best_match),
Expand Down
8 changes: 6 additions & 2 deletions piptools/scripts/compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,14 @@ class PipCommand(pip.basecommand.Command):
help="Generate pip 8 style hashes in the resulting requirements file.")
@click.option('--max-rounds', default=10,
help="Maximum number of rounds before resolving the requirements aborts.")
@click.option('--allow-conflicts', is_flag=True, default=False,
help=("Do not raise an error in case of version conflicts, use "
"the version defined in the spec file (requirements.in)."))
@click.argument('src_files', nargs=-1, type=click.Path(exists=True, allow_dash=True))
def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,
cert, client_cert, trusted_host, header, index, emit_trusted_host, annotate,
upgrade, upgrade_packages, output_file, allow_unsafe, generate_hashes,
src_files, max_rounds):
src_files, max_rounds, allow_conflicts):
"""Compiles requirements.txt from requirements.in specs."""
log.verbose = verbose

Expand Down Expand Up @@ -183,7 +186,8 @@ def cli(verbose, dry_run, pre, rebuild, find_links, index_url, extra_index_url,

try:
resolver = Resolver(constraints, repository, prereleases=pre,
clear_caches=rebuild, allow_unsafe=allow_unsafe)
clear_caches=rebuild, allow_unsafe=allow_unsafe,
allow_conflicts=allow_conflicts)
results = resolver.resolve(max_rounds=max_rounds)
if generate_hashes:
hashes = resolver.resolve_hashes(results)
Expand Down

0 comments on commit 3fc0ca0

Please sign in to comment.