"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "poetry/mixology/incompatibility.py" between
poetry-1.1.15.tar.gz and poetry-1.2.0.tar.gz

About: Poetry is a tool for dependency management and packaging in Python.

incompatibility.py  (poetry-1.1.15):incompatibility.py  (poetry-1.2.0)
from typing import Dict from __future__ import annotations
from typing import Generator
from typing import List from typing import TYPE_CHECKING
from .incompatibility_cause import ConflictCause from poetry.mixology.incompatibility_cause import ConflictCause
from .incompatibility_cause import DependencyCause from poetry.mixology.incompatibility_cause import DependencyCause
from .incompatibility_cause import IncompatibilityCause from poetry.mixology.incompatibility_cause import NoVersionsCause
from .incompatibility_cause import NoVersionsCause from poetry.mixology.incompatibility_cause import PackageNotFoundCause
from .incompatibility_cause import PackageNotFoundCause from poetry.mixology.incompatibility_cause import PlatformCause
from .incompatibility_cause import PlatformCause from poetry.mixology.incompatibility_cause import PythonCause
from .incompatibility_cause import PythonCause from poetry.mixology.incompatibility_cause import RootCause
from .incompatibility_cause import RootCause
from .term import Term if TYPE_CHECKING:
from collections.abc import Callable
from collections.abc import Iterator
from poetry.mixology.incompatibility_cause import IncompatibilityCause
from poetry.mixology.term import Term
class Incompatibility: class Incompatibility:
def __init__( def __init__(self, terms: list[Term], cause: IncompatibilityCause) -> None:
self, terms, cause
): # type: (List[Term], IncompatibilityCause) -> None
# Remove the root package from generated incompatibilities, since it wil l # Remove the root package from generated incompatibilities, since it wil l
# always be satisfied. This makes error reporting clearer, and may also # always be satisfied. This makes error reporting clearer, and may also
# make solving more efficient. # make solving more efficient.
if ( if (
len(terms) != 1 len(terms) != 1
and isinstance(cause, ConflictCause) and isinstance(cause, ConflictCause)
and any([term.is_positive() and term.dependency.is_root for term in terms]) and any(term.is_positive() and term.dependency.is_root for term in t erms)
): ):
terms = [ terms = [
term term
for term in terms for term in terms
if not term.is_positive() or not term.dependency.is_root if not term.is_positive() or not term.dependency.is_root
] ]
if ( if len(terms) != 1 and (
len(terms) == 1
# Short-circuit in the common case of a two-term incompatibility wit h # Short-circuit in the common case of a two-term incompatibility wit h
# two different packages (for example, a dependency). # two different packages (for example, a dependency).
or len(terms) == 2 len(terms) != 2
and terms[0].dependency.complete_name != terms[-1].dependency.comple or terms[0].dependency.complete_name == terms[-1].dependency.complet
te_name e_name
): ):
pass
else:
# Coalesce multiple terms about the same package if possible. # Coalesce multiple terms about the same package if possible.
by_name = {} # type: Dict[str, Dict[str, Term]] by_name: dict[str, dict[str, Term]] = {}
for term in terms: for term in terms:
if term.dependency.complete_name not in by_name: if term.dependency.complete_name not in by_name:
by_name[term.dependency.complete_name] = {} by_name[term.dependency.complete_name] = {}
by_ref = by_name[term.dependency.complete_name] by_ref = by_name[term.dependency.complete_name]
ref = term.dependency.complete_name ref = term.dependency.complete_name
if ref in by_ref: if ref in by_ref:
by_ref[ref] = by_ref[ref].intersect(term) value = by_ref[ref].intersect(term)
# If we have two terms that refer to the same package but ha # If we have two terms that refer to the same package but ha
ve a null ve a
# intersection, they're mutually exclusive, making this inco # null intersection, they're mutually exclusive, making this
mpatibility # incompatibility irrelevant, since we already know that mut
# irrelevant, since we already know that mutually exclusive ually
version # exclusive version ranges are incompatible. We should never
# ranges are incompatible. We should never derive an irrelev derive
ant # an irrelevant incompatibility.
# incompatibility. err_msg = f"Package '{ref}' is listed as a dependency of its
assert by_ref[ref] is not None elf."
assert value is not None, err_msg
by_ref[ref] = value
else: else:
by_ref[ref] = term by_ref[ref] = term
new_terms = [] new_terms = []
for by_ref in by_name.values(): for by_ref in by_name.values():
positive_terms = [ positive_terms = [
term for term in by_ref.values() if term.is_positive() term for term in by_ref.values() if term.is_positive()
] ]
if positive_terms: if positive_terms:
new_terms += positive_terms new_terms += positive_terms
continue continue
new_terms += list(by_ref.values()) new_terms += list(by_ref.values())
terms = new_terms terms = new_terms
self._terms = terms self._terms = terms
self._cause = cause self._cause = cause
@property @property
def terms(self): # type: () -> List[Term] def terms(self) -> list[Term]:
return self._terms return self._terms
@property @property
def cause(self): # type: () -> IncompatibilityCause def cause(self) -> IncompatibilityCause:
return self._cause return self._cause
@property @property
def external_incompatibilities(self): # type: () -> Generator[Incompatibili def external_incompatibilities(
ty] self,
) -> Iterator[Incompatibility]:
""" """
Returns all external incompatibilities in this incompatibility's Returns all external incompatibilities in this incompatibility's
derivation graph. derivation graph.
""" """
if isinstance(self._cause, ConflictCause): if isinstance(self._cause, ConflictCause):
cause = self._cause # type: ConflictCause cause: ConflictCause = self._cause
for incompatibility in cause.conflict.external_incompatibilities: yield from cause.conflict.external_incompatibilities
yield incompatibility
for incompatibility in cause.other.external_incompatibilities: yield from cause.other.external_incompatibilities
yield incompatibility
else: else:
yield self yield self
def is_failure(self): # type: () -> bool def is_failure(self) -> bool:
return len(self._terms) == 0 or ( return len(self._terms) == 0 or (
len(self._terms) == 1 and self._terms[0].dependency.is_root len(self._terms) == 1 and self._terms[0].dependency.is_root
) )
def __str__(self): def __str__(self) -> str:
if isinstance(self._cause, DependencyCause): if isinstance(self._cause, DependencyCause):
assert len(self._terms) == 2 assert len(self._terms) == 2
depender = self._terms[0] depender = self._terms[0]
dependee = self._terms[1] dependee = self._terms[1]
assert depender.is_positive() assert depender.is_positive()
assert not dependee.is_positive() assert not dependee.is_positive()
return "{} depends on {}".format( return (
self._terse(depender, allow_every=True), self._terse(dependee) f"{self._terse(depender, allow_every=True)} depends on"
f" {self._terse(dependee)}"
) )
elif isinstance(self._cause, PythonCause): elif isinstance(self._cause, PythonCause):
assert len(self._terms) == 1 assert len(self._terms) == 1
assert self._terms[0].is_positive() assert self._terms[0].is_positive()
cause = self._cause # type: PythonCause text = f"{self._terse(self._terms[0], allow_every=True)} requires "
text = "{} requires ".format(self._terse(self._terms[0], allow_every text += f"Python {self._cause.python_version}"
=True))
text += "Python {}".format(cause.python_version)
return text return text
elif isinstance(self._cause, PlatformCause): elif isinstance(self._cause, PlatformCause):
assert len(self._terms) == 1 assert len(self._terms) == 1
assert self._terms[0].is_positive() assert self._terms[0].is_positive()
cause = self._cause # type: PlatformCause text = f"{self._terse(self._terms[0], allow_every=True)} requires "
text = "{} requires ".format(self._terse(self._terms[0], allow_every text += f"platform {self._cause.platform}"
=True))
text += "platform {}".format(cause.platform)
return text return text
elif isinstance(self._cause, NoVersionsCause): elif isinstance(self._cause, NoVersionsCause):
assert len(self._terms) == 1 assert len(self._terms) == 1
assert self._terms[0].is_positive() assert self._terms[0].is_positive()
return "no versions of {} match {}".format( return (
self._terms[0].dependency.name, self._terms[0].constraint f"no versions of {self._terms[0].dependency.name} match"
f" {self._terms[0].constraint}"
) )
elif isinstance(self._cause, PackageNotFoundCause): elif isinstance(self._cause, PackageNotFoundCause):
assert len(self._terms) == 1 assert len(self._terms) == 1
assert self._terms[0].is_positive() assert self._terms[0].is_positive()
return "{} doesn't exist".format(self._terms[0].dependency.name) return f"{self._terms[0].dependency.name} doesn't exist"
elif isinstance(self._cause, RootCause): elif isinstance(self._cause, RootCause):
assert len(self._terms) == 1 assert len(self._terms) == 1
assert not self._terms[0].is_positive() assert not self._terms[0].is_positive()
assert self._terms[0].dependency.is_root assert self._terms[0].dependency.is_root
return "{} is {}".format( return (
self._terms[0].dependency.name, self._terms[0].dependency.constr f"{self._terms[0].dependency.name} is"
aint f" {self._terms[0].dependency.constraint}"
) )
elif self.is_failure(): elif self.is_failure():
return "version solving failed" return "version solving failed"
if len(self._terms) == 1: if len(self._terms) == 1:
term = self._terms[0] term = self._terms[0]
if term.constraint.is_any(): verb = "forbidden" if term.is_positive() else "required"
return "{} is {}".format( return f"{term.dependency.name} is {verb}"
term.dependency.name,
"forbidden" if term.is_positive() else "required",
)
else:
return "{} is {}".format(
term.dependency.name,
"forbidden" if term.is_positive() else "required",
)
if len(self._terms) == 2: if len(self._terms) == 2:
term1 = self._terms[0] term1 = self._terms[0]
term2 = self._terms[1] term2 = self._terms[1]
if term1.is_positive() == term2.is_positive(): if term1.is_positive() == term2.is_positive():
if term1.is_positive(): if not term1.is_positive():
package1 = ( return f"either {self._terse(term1)} or {self._terse(term2)}
term1.dependency.name "
if term1.constraint.is_any()
else self._terse(term1)
)
package2 = (
term2.dependency.name
if term2.constraint.is_any()
else self._terse(term2)
)
return "{} is incompatible with {}".format(package1, package package1 = (
2) term1.dependency.name
else: if term1.constraint.is_any()
return "either {} or {}".format( else self._terse(term1)
self._terse(term1), self._terse(term2) )
) package2 = (
term2.dependency.name
if term2.constraint.is_any()
else self._terse(term2)
)
return f"{package1} is incompatible with {package2}"
positive = [] positive = []
negative = [] negative = []
for term in self._terms: for term in self._terms:
if term.is_positive(): if term.is_positive():
positive.append(self._terse(term)) positive.append(self._terse(term))
else: else:
negative.append(self._terse(term)) negative.append(self._terse(term))
if positive and negative: if positive and negative:
if len(positive) == 1: if len(positive) != 1:
positive_term = [term for term in self._terms if term.is_positiv return f"if {' and '.join(positive)} then {' or '.join(negative)
e()][0] }"
return "{} requires {}".format( positive_term = [term for term in self._terms if term.is_positive()]
self._terse(positive_term, allow_every=True), " or ".join(ne [0]
gative) return (
) f"{self._terse(positive_term, allow_every=True)} requires"
else: f" {' or '.join(negative)}"
return "if {} then {}".format( )
" and ".join(positive), " or ".join(negative)
)
elif positive: elif positive:
return "one of {} must be false".format(" or ".join(positive)) return f"one of {' or '.join(positive)} must be false"
else: else:
return "one of {} must be true".format(" or ".join(negative)) return f"one of {' or '.join(negative)} must be true"
def and_to_string( def and_to_string(
self, other, details, this_line, other_line self,
): # type: (Incompatibility, dict, int, int) -> str other: Incompatibility,
requires_both = self._try_requires_both(other, details, this_line, other this_line: int | None,
_line) other_line: int | None,
) -> str:
requires_both = self._try_requires_both(other, this_line, other_line)
if requires_both is not None: if requires_both is not None:
return requires_both return requires_both
requires_through = self._try_requires_through( requires_through = self._try_requires_through(other, this_line, other_li
other, details, this_line, other_line ne)
)
if requires_through is not None: if requires_through is not None:
return requires_through return requires_through
requires_forbidden = self._try_requires_forbidden( requires_forbidden = self._try_requires_forbidden(other, this_line, othe
other, details, this_line, other_line r_line)
)
if requires_forbidden is not None: if requires_forbidden is not None:
return requires_forbidden return requires_forbidden
buffer = [str(self)] buffer = [str(self)]
if this_line is not None: if this_line is not None:
buffer.append(" " + str(this_line)) buffer.append(f" {this_line!s}")
buffer.append(" and {}".format(str(other))) buffer.append(f" and {other!s}")
if other_line is not None: if other_line is not None:
buffer.append(" " + str(other_line)) buffer.append(f" {other_line!s}")
return "\n".join(buffer) return "\n".join(buffer)
def _try_requires_both( def _try_requires_both(
self, other, details, this_line, other_line self,
): # type: (Incompatibility, dict, int, int) -> str other: Incompatibility,
this_line: int | None,
other_line: int | None,
) -> str | None:
if len(self._terms) == 1 or len(other.terms) == 1: if len(self._terms) == 1 or len(other.terms) == 1:
return return None
this_positive = self._single_term_where(lambda term: term.is_positive()) this_positive = self._single_term_where(lambda term: term.is_positive())
if this_positive is None: if this_positive is None:
return return None
other_positive = other._single_term_where(lambda term: term.is_positive( )) other_positive = other._single_term_where(lambda term: term.is_positive( ))
if other_positive is None: if other_positive is None:
return return None
if this_positive.dependency != other_positive.dependency: if this_positive.dependency != other_positive.dependency:
return return None
this_negatives = " or ".join( this_negatives = " or ".join(
[self._terse(term) for term in self._terms if not term.is_positive() ] [self._terse(term) for term in self._terms if not term.is_positive() ]
) )
other_negatives = " or ".join( other_negatives = " or ".join(
[self._terse(term) for term in other.terms if not term.is_positive() ] [self._terse(term) for term in other.terms if not term.is_positive() ]
) )
buffer = [self._terse(this_positive, allow_every=True) + " "] buffer = [self._terse(this_positive, allow_every=True) + " "]
is_dependency = isinstance(self.cause, DependencyCause) and isinstance( is_dependency = isinstance(self.cause, DependencyCause) and isinstance(
other.cause, DependencyCause other.cause, DependencyCause
) )
if is_dependency: if is_dependency:
buffer.append("depends on") buffer.append("depends on")
else: else:
buffer.append("requires") buffer.append("requires")
buffer.append(" both {}".format(this_negatives)) buffer.append(f" both {this_negatives}")
if this_line is not None: if this_line is not None:
buffer.append(" ({})".format(this_line)) buffer.append(f" ({this_line})")
buffer.append(" and {}".format(other_negatives)) buffer.append(f" and {other_negatives}")
if other_line is not None: if other_line is not None:
buffer.append(" ({})".format(other_line)) buffer.append(f" ({other_line})")
return "".join(buffer) return "".join(buffer)
def _try_requires_through( def _try_requires_through(
self, other, details, this_line, other_line self,
): # type: (Incompatibility, dict, int, int) -> str other: Incompatibility,
this_line: int | None,
other_line: int | None,
) -> str | None:
if len(self._terms) == 1 or len(other.terms) == 1: if len(self._terms) == 1 or len(other.terms) == 1:
return return None
this_negative = self._single_term_where(lambda term: not term.is_positiv e()) this_negative = self._single_term_where(lambda term: not term.is_positiv e())
other_negative = other._single_term_where(lambda term: not term.is_posit ive()) other_negative = other._single_term_where(lambda term: not term.is_posit ive())
if this_negative is None and other_negative is None: if this_negative is None and other_negative is None:
return return None
this_positive = self._single_term_where(lambda term: term.is_positive()) this_positive = self._single_term_where(lambda term: term.is_positive())
other_positive = self._single_term_where(lambda term: term.is_positive() ) other_positive = self._single_term_where(lambda term: term.is_positive() )
if ( if (
this_negative is not None this_negative is not None
and other_positive is not None and other_positive is not None
and this_negative.dependency.name == other_positive.dependency.name and this_negative.dependency.name == other_positive.dependency.name
and this_negative.inverse.satisfies(other_positive) and this_negative.inverse.satisfies(other_positive)
): ):
skipping to change at line 336 skipping to change at line 332
and this_positive is not None and this_positive is not None
and other_negative.dependency.name == this_positive.dependency.name and other_negative.dependency.name == this_positive.dependency.name
and other_negative.inverse.satisfies(this_positive) and other_negative.inverse.satisfies(this_positive)
): ):
prior = other prior = other
prior_negative = other_negative prior_negative = other_negative
prior_line = other_line prior_line = other_line
latter = self latter = self
latter_line = this_line latter_line = this_line
else: else:
return return None
prior_positives = [term for term in prior.terms if term.is_positive()] prior_positives = [term for term in prior.terms if term.is_positive()]
buffer = [] buffer = []
if len(prior_positives) > 1: if len(prior_positives) > 1:
prior_string = " or ".join([self._terse(term) for term in prior_posi tives]) prior_string = " or ".join([self._terse(term) for term in prior_posi tives])
buffer.append("if {} then ".format(prior_string)) buffer.append(f"if {prior_string} then ")
else: else:
if isinstance(prior.cause, DependencyCause): if isinstance(prior.cause, DependencyCause):
verb = "depends on" verb = "depends on"
else: else:
verb = "requires" verb = "requires"
buffer.append( buffer.append(
"{} {} ".format(self._terse(prior_positives[0], allow_every=True ), verb) f"{self._terse(prior_positives[0], allow_every=True)} {verb} "
) )
buffer.append(self._terse(prior_negative)) buffer.append(self._terse(prior_negative))
if prior_line is not None: if prior_line is not None:
buffer.append(" ({})".format(prior_line)) buffer.append(f" ({prior_line})")
buffer.append(" which ") buffer.append(" which ")
if isinstance(latter.cause, DependencyCause): if isinstance(latter.cause, DependencyCause):
buffer.append("depends on ") buffer.append("depends on ")
else: else:
buffer.append("requires ") buffer.append("requires ")
buffer.append( buffer.append(
" or ".join( " or ".join(
[self._terse(term) for term in latter.terms if not term.is_posit ive()] [self._terse(term) for term in latter.terms if not term.is_posit ive()]
) )
) )
if latter_line is not None: if latter_line is not None:
buffer.append(" ({})".format(latter_line)) buffer.append(f" ({latter_line})")
return "".join(buffer) return "".join(buffer)
def _try_requires_forbidden( def _try_requires_forbidden(
self, other, details, this_line, other_line self,
): # type: (Incompatibility, dict, int, int) -> str other: Incompatibility,
this_line: int | None,
other_line: int | None,
) -> str | None:
if len(self._terms) != 1 and len(other.terms) != 1: if len(self._terms) != 1 and len(other.terms) != 1:
return None return None
if len(self.terms) == 1: if len(self.terms) == 1:
prior = other prior = other
latter = self latter = self
prior_line = other_line prior_line = other_line
latter_line = this_line latter_line = this_line
else: else:
prior = self prior = self
latter = other latter = other
prior_line = this_line prior_line = this_line
latter_line = other_line latter_line = other_line
negative = prior._single_term_where(lambda term: not term.is_positive()) negative = prior._single_term_where(lambda term: not term.is_positive())
if negative is None: if negative is None:
return return None
if not negative.inverse.satisfies(latter.terms[0]): if not negative.inverse.satisfies(latter.terms[0]):
return return None
positives = [t for t in prior.terms if t.is_positive()] positives = [t for t in prior.terms if t.is_positive()]
buffer = [] buffer = []
if len(positives) > 1: if len(positives) > 1:
prior_string = " or ".join([self._terse(term) for term in positives] ) prior_string = " or ".join([self._terse(term) for term in positives] )
buffer.append("if {} then ".format(prior_string)) buffer.append(f"if {prior_string} then ")
else: else:
buffer.append(self._terse(positives[0], allow_every=True)) buffer.append(self._terse(positives[0], allow_every=True))
if isinstance(prior.cause, DependencyCause): if isinstance(prior.cause, DependencyCause):
buffer.append(" depends on ") buffer.append(" depends on ")
else: else:
buffer.append(" requires ") buffer.append(" requires ")
buffer.append(self._terse(latter.terms[0]) + " ") buffer.append(self._terse(latter.terms[0]) + " ")
if prior_line is not None: if prior_line is not None:
buffer.append("({}) ".format(prior_line)) buffer.append(f"({prior_line}) ")
if isinstance(latter.cause, PythonCause): if isinstance(latter.cause, PythonCause):
cause = latter.cause # type: PythonCause cause: PythonCause = latter.cause
buffer.append("which requires Python {}".format(cause.python_version buffer.append(f"which requires Python {cause.python_version}")
))
elif isinstance(latter.cause, NoVersionsCause): elif isinstance(latter.cause, NoVersionsCause):
buffer.append("which doesn't match any versions") buffer.append("which doesn't match any versions")
elif isinstance(latter.cause, PackageNotFoundCause): elif isinstance(latter.cause, PackageNotFoundCause):
buffer.append("which doesn't exist") buffer.append("which doesn't exist")
else: else:
buffer.append("which is forbidden") buffer.append("which is forbidden")
if latter_line is not None: if latter_line is not None:
buffer.append(" ({})".format(latter_line)) buffer.append(f" ({latter_line})")
return "".join(buffer) return "".join(buffer)
def _terse(self, term, allow_every=False): def _terse(self, term: Term, allow_every: bool = False) -> str:
if allow_every and term.constraint.is_any(): if allow_every and term.constraint.is_any():
return "every version of {}".format(term.dependency.complete_name) return f"every version of {term.dependency.complete_name}"
if term.dependency.is_root: if term.dependency.is_root:
return term.dependency.pretty_name pretty_name: str = term.dependency.pretty_name
return pretty_name
return "{} ({})".format( if term.dependency.source_type:
term.dependency.pretty_name, term.dependency.pretty_constraint return str(term.dependency)
) return f"{term.dependency.pretty_name} ({term.dependency.pretty_constrai
nt})"
def _single_term_where(self, callable): # type: (callable) -> Term def _single_term_where(self, callable: Callable[[Term], bool]) -> Term | Non e:
found = None found = None
for term in self._terms: for term in self._terms:
if not callable(term): if not callable(term):
continue continue
if found is not None: if found is not None:
return return None
found = term found = term
return found return found
def __repr__(self): def __repr__(self) -> str:
return "<Incompatibility {}>".format(str(self)) return f"<Incompatibility {self!s}>"
 End of changes. 66 change blocks. 
153 lines changed or deleted 150 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)