def test_parse_constraint_multi_wilcard(input): assert parse_constraint(input) == VersionUnion( VersionRange( Version.from_parts(2, 7, 0), Version.from_parts(3, 0, 0), True, False ), VersionRange(Version.from_parts(3, 2, 0), None, True, False), )
def test_allows_any(): v = Version.parse("1.2.3") assert v.allows_any(v) assert not v.allows_any(Version.parse("0.0.3")) assert v.allows_any(VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))) assert v.allows_any(VersionRange()) assert not v.allows_any(EmptyConstraint())
def test_intersect() -> None: v = Version.parse("1.2.3") assert v.intersect(v) == v assert v.intersect(Version.parse("1.1.4")).is_empty() assert (v.intersect( VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))) == v) assert (Version.parse("1.1.4").intersect( VersionRange(v, Version.parse("1.2.4"))).is_empty())
def test_difference() -> None: v = Version.parse("1.2.3") assert v.difference(v).is_empty() assert v.difference(Version.parse("0.8.0")) == v assert v.difference( VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))).is_empty() assert (v.difference( VersionRange(Version.parse("1.4.0"), Version.parse("3.0.0"))) == v)
def test_allows_post_releases_explicit_with_min(base, one, two): range = VersionRange(min=one, include_min=True) assert not range.allows(base) assert range.allows(two) range = VersionRange(min=two, include_min=True) assert not range.allows(base) assert not range.allows(one)
def test_parse_constraint_multi(input: str) -> None: assert parse_constraint(input) == VersionRange( Version.from_parts(2, 0, 0), Version.from_parts(3, 0, 0), include_min=False, include_max=True, )
def __str__(self) -> str: from .version_range import VersionRange if self.excludes_single_version(): return "!={}".format(VersionRange().difference(self)) return " || ".join([str(r) for r in self._ranges])
def parse_constraint(constraints: str) -> VersionConstraint: if constraints == "*": from poetry.core.semver.version_range import VersionRange return VersionRange() or_constraints = re.split(r"\s*\|\|?\s*", constraints.strip()) or_groups = [] for constraints in or_constraints: and_constraints = re.split( "(?<!^)(?<![~=>< ,]) *(?<!-)[, ](?!-) *(?!,|$)", constraints) constraint_objects = [] if len(and_constraints) > 1: for constraint in and_constraints: constraint_objects.append(parse_single_constraint(constraint)) else: constraint_objects.append( parse_single_constraint(and_constraints[0])) if len(constraint_objects) == 1: constraint = constraint_objects[0] else: constraint = constraint_objects[0] for next_constraint in constraint_objects[1:]: constraint = constraint.intersect(next_constraint) or_groups.append(constraint) if len(or_groups) == 1: return or_groups[0] else: from poetry.core.semver.version_union import VersionUnion return VersionUnion.of(*or_groups)
def test_allows_post_releases_explicit_with_max( base: Version, one: Version, two: Version ) -> None: range = VersionRange(max=one, include_max=True) assert range.allows(base) assert not range.allows(two) range = VersionRange(max=two, include_max=True) assert range.allows(base) assert range.allows(one)
def test_allows_all_bordering_range_not_more_inclusive(v010, v250): # Allows bordering range that is not more inclusive exclusive = VersionRange(v010, v250) inclusive = VersionRange(v010, v250, True, True) assert inclusive.allows_all(exclusive) assert inclusive.allows_all(inclusive) assert not exclusive.allows_all(inclusive) assert exclusive.allows_all(exclusive)
def python_versions(self, value: Union[str, "VersionTypes"]) -> None: from poetry.core.semver.version_range import VersionRange self._python_versions = value if value == "*" or value == VersionRange(): value = "~2.7 || >=3.4" self._python_constraint = parse_constraint(value) self._python_marker = parse_marker( create_nested_marker("python_version", self._python_constraint))
def test_allows_all(v123, v124, v140, v250, v300): assert VersionRange(v123, v250).allows_all(EmptyConstraint()) range = VersionRange(v123, v250, include_max=True) assert not range.allows_all(v123) assert range.allows_all(v124) assert range.allows_all(v250) assert not range.allows_all(v300)
def test_union() -> None: v = Version.parse("1.2.3") assert v.union(v) == v result = v.union(Version.parse("0.8.0")) assert result.allows(v) assert result.allows(Version.parse("0.8.0")) assert not result.allows(Version.parse("1.1.4")) range = VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4")) assert v.union(range) == range union = Version.parse("1.1.4").union( VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"))) assert union == VersionRange(Version.parse("1.1.4"), Version.parse("1.2.4"), include_min=True) result = v.union( VersionRange(Version.parse("0.0.3"), Version.parse("1.1.4"))) assert result.allows(v) assert result.allows(Version.parse("0.1.0"))
def union(self, other: VersionConstraint) -> VersionConstraint: from poetry.core.semver.version_range import VersionRange if other.allows(self): return other if isinstance(other, VersionRangeConstraint): if self.allows(other.min): return VersionRange( other.min, other.max, include_min=True, include_max=other.include_max, ) if self.allows(other.max): return VersionRange( other.min, other.max, include_min=other.include_min, include_max=True, ) return VersionUnion.of(self, other)
def test_allows_all_contained_unions( v010: Version, v114: Version, v123: Version, v124: Version, v140: Version, v200: Version, v234: Version, ) -> None: # Allows unions that are completely contained range = VersionRange(v114, v200) assert range.allows_all(VersionRange(v123, v124).union(v140)) assert not range.allows_all(VersionRange(v010, v124).union(v140)) assert not range.allows_all(VersionRange(v123, v234).union(v140))
def of(cls, *ranges: VersionConstraint) -> VersionConstraint: from poetry.core.semver.version_range import VersionRange flattened: list[VersionRangeConstraint] = [] for constraint in ranges: if constraint.is_empty(): continue if isinstance(constraint, VersionUnion): flattened += constraint.ranges continue assert isinstance(constraint, VersionRangeConstraint) flattened.append(constraint) if not flattened: return EmptyConstraint() if any([constraint.is_any() for constraint in flattened]): return VersionRange() # Only allow Versions and VersionRanges here so we can more easily reason # about everything in flattened. _EmptyVersions and VersionUnions are # filtered out above. for constraint in flattened: if not isinstance(constraint, VersionRangeConstraint): raise ValueError(f"Unknown VersionConstraint type {constraint}.") flattened.sort() merged: list[VersionRangeConstraint] = [] for constraint in flattened: # Merge this constraint with the previous one, but only if they touch. if not merged or ( not merged[-1].allows_any(constraint) and not merged[-1].is_adjacent_to(constraint) ): merged.append(constraint) else: new_constraint = merged[-1].union(constraint) assert isinstance(new_constraint, VersionRangeConstraint) merged[-1] = new_constraint if len(merged) == 1: return merged[0] return VersionUnion(*merged)
def excludes_single_wildcard_range(self) -> bool: from poetry.core.semver.version_range import VersionRange if len(self._ranges) != 2: return False idx_order = (0, 1) if self._ranges[0].max else (1, 0) one = self._ranges[idx_order[0]] two = self._ranges[idx_order[1]] is_range_exclusion = ( one.max and not one.include_max and two.min and two.include_min ) if not is_range_exclusion: return False if not self._excludes_single_wildcard_range_check_is_valid_range(one, two): return False return isinstance(VersionRange().difference(self), VersionRange)
def get_python_constraint_from_marker( marker: "BaseMarker", ) -> "VersionTypes": from poetry.core.semver.empty_constraint import EmptyConstraint from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange # noqa python_marker = marker.only("python_version", "python_full_version") if python_marker.is_any(): return VersionRange() if python_marker.is_empty(): return EmptyConstraint() markers = convert_markers(marker) ors = [] for or_ in markers["python_version"]: ands = [] for op, version in or_: # Expand python version if op == "==": version = "~" + version op = "" elif op == "!=": version += ".*" elif op in ("<=", ">"): parsed_version = Version.parse(version) if parsed_version.precision == 1: if op == "<=": op = "<" version = parsed_version.next_major().text elif op == ">": op = ">=" version = parsed_version.next_major().text elif parsed_version.precision == 2: if op == "<=": op = "<" version = parsed_version.next_minor().text elif op == ">": op = ">=" version = parsed_version.next_minor().text elif op in ("in", "not in"): versions = [] for v in re.split("[ ,]+", version): split = v.split(".") if len(split) in [1, 2]: split.append("*") op_ = "" if op == "in" else "!=" else: op_ = "==" if op == "in" else "!=" versions.append(op_ + ".".join(split)) glue = " || " if op == "in" else ", " if versions: ands.append(glue.join(versions)) continue ands.append("{}{}".format(op, version)) ors.append(" ".join(ands)) return parse_constraint(" || ".join(ors))
import pytest from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange from poetry.core.semver.version_union import VersionUnion @pytest.mark.parametrize( "constraint,version", [ ( "~=3.8", VersionRange( min=Version.from_parts(3, 8), max=Version.from_parts(4, 0), include_min=True, ), ), ( "== 3.8.*", VersionRange( min=Version.from_parts(3, 8), max=Version.from_parts(3, 9, 0), include_min=True, ), ), ( "~= 3.8", VersionRange( min=Version.from_parts(3, 8),
def parse_single_constraint(constraint: str) -> VersionConstraint: from poetry.core.semver.patterns import BASIC_CONSTRAINT from poetry.core.semver.patterns import CARET_CONSTRAINT from poetry.core.semver.patterns import TILDE_CONSTRAINT from poetry.core.semver.patterns import TILDE_PEP440_CONSTRAINT from poetry.core.semver.patterns import X_CONSTRAINT from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange from poetry.core.semver.version_union import VersionUnion m = re.match(r"(?i)^v?[xX*](\.[xX*])*$", constraint) if m: return VersionRange() # Tilde range m = TILDE_CONSTRAINT.match(constraint) if m: version = Version.parse(m.group("version")) high = version.stable.next_minor() if version.release.precision == 1: high = version.stable.next_major() return VersionRange(version, high, include_min=True) # PEP 440 Tilde range (~=) m = TILDE_PEP440_CONSTRAINT.match(constraint) if m: version = Version.parse(m.group("version")) if version.release.precision == 2: high = version.stable.next_major() else: high = version.stable.next_minor() return VersionRange(version, high, include_min=True) # Caret range m = CARET_CONSTRAINT.match(constraint) if m: version = Version.parse(m.group("version")) return VersionRange(version, version.next_breaking(), include_min=True) # X Range m = X_CONSTRAINT.match(constraint) if m: op = m.group("op") major = int(m.group(2)) minor = m.group(3) if minor is not None: version = Version.from_parts(major, int(minor), 0) result: VersionConstraint = VersionRange(version, version.next_minor(), include_min=True) else: if major == 0: result = VersionRange(max=Version.from_parts(1, 0, 0)) else: version = Version.from_parts(major, 0, 0) result = VersionRange(version, version.next_major(), include_min=True) if op == "!=": result = VersionRange().difference(result) return result # Basic comparator m = BASIC_CONSTRAINT.match(constraint) if m: op = m.group("op") version_string = m.group("version") if version_string == "dev": version_string = "0.0-dev" try: version = Version.parse(version_string) except ValueError: raise ValueError( f"Could not parse version constraint: {constraint}") if op == "<": return VersionRange(max=version) if op == "<=": return VersionRange(max=version, include_max=True) if op == ">": return VersionRange(min=version) if op == ">=": return VersionRange(min=version, include_min=True) if op == "!=": return VersionUnion(VersionRange(max=version), VersionRange(min=version)) return version from poetry.core.semver.exceptions import ParseConstraintError raise ParseConstraintError( f"Could not parse version constraint: {constraint}")
from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange from poetry.core.semver.version_union import VersionUnion from poetry.core.version.pep440 import ReleaseTag @pytest.mark.parametrize( "constraint,version", [ ( "~=3.8", VersionRange( min=Version.from_parts(3, 8), max=Version.from_parts(4, 0), include_min=True, ), ), ( "== 3.8.*", VersionRange( min=Version.from_parts(3, 8), max=Version.from_parts(3, 9, 0), include_min=True, ), ), ( "~= 3.8", VersionRange( min=Version.from_parts(3, 8),
from typing import cast import pytest from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange from poetry.core.semver.version_union import VersionUnion from poetry.core.version.pep440 import ReleaseTag @pytest.mark.parametrize( "input,constraint", [ ("*", VersionRange()), ("*.*", VersionRange()), ("v*.*", VersionRange()), ("*.x.*", VersionRange()), ("x.X.x.*", VersionRange()), (">1.0.0", VersionRange(min=Version.from_parts(1, 0, 0))), ("<1.2.3", VersionRange(max=Version.from_parts(1, 2, 3))), ("<=1.2.3", VersionRange(max=Version.from_parts(1, 2, 3), include_max=True)), (">=1.2.3", VersionRange(min=Version.from_parts(1, 2, 3), include_min=True)), ("=1.2.3", Version.from_parts(1, 2, 3)), ("1.2.3", Version.from_parts(1, 2, 3)), ("1!2.3.4", Version.from_parts(2, 3, 4, epoch=1)), ("=1.0", Version.from_parts(1, 0, 0)), ("1.2.3b5", Version.from_parts(1, 2, 3, pre=ReleaseTag("beta", 5))),
def test_parse_constraint_multi(input): assert parse_constraint(input) == VersionRange( Version(2, 0, 0), Version(3, 0, 0), include_min=False, include_max=True )
def test_intersect(v114, v123, v124, v200, v250, v300): # two overlapping ranges assert VersionRange(v123, v250).intersect(VersionRange( v200, v300)) == VersionRange(v200, v250) # a non-overlapping range allows no versions a = VersionRange(v114, v124) b = VersionRange(v200, v250) assert a.intersect(b).is_empty() # adjacent ranges allow no versions if exclusive a = VersionRange(v114, v124) b = VersionRange(v124, v200) assert a.intersect(b).is_empty() # adjacent ranges allow version if inclusive a = VersionRange(v114, v124, include_max=True) b = VersionRange(v124, v200, include_min=True) assert a.intersect(b) == v124 # with an open range open = VersionRange() a = VersionRange(v114, v124) assert open.intersect(open) == open assert open.intersect(a) == a # returns the version if the range allows it assert VersionRange(v114, v124).intersect(v123) == v123 assert VersionRange(v123, v124).intersect(v114).is_empty()
def test_union(v003, v010, v072, v080, v114, v123, v124, v130, v140, v200, v234, v250, v300): # with a version returns the range if it contains the version range = VersionRange(v114, v124) assert range.union(v123) == range # with a version on the edge of the range, expands the range range = VersionRange(v114, v124) assert range.union(v124) == VersionRange(v114, v124, include_max=True) assert range.union(v114) == VersionRange(v114, v124, include_min=True) # with a version allows both the range and the version if the range # doesn't contain the version result = VersionRange(v003, v114).union(v124) assert result.allows(v010) assert not result.allows(v123) assert result.allows(v124) # returns a VersionUnion for a disjoint range result = VersionRange(v003, v114).union(VersionRange(v130, v200)) assert result.allows(v080) assert not result.allows(v123) assert result.allows(v140) # considers open ranges disjoint result = VersionRange(v003, v114).union(VersionRange(v114, v200)) assert result.allows(v080) assert not result.allows(v114) assert result.allows(v140) result = VersionRange(v114, v200).union(VersionRange(v003, v114)) assert result.allows(v080) assert not result.allows(v114) assert result.allows(v140) # returns a merged range for an overlapping range result = VersionRange(v003, v114).union(VersionRange(v080, v200)) assert result == VersionRange(v003, v200) # considers closed ranges overlapping result = VersionRange(v003, v114, include_max=True).union(VersionRange(v114, v200)) assert result == VersionRange(v003, v200) result = VersionRange(v003, v114).union( VersionRange(v114, v200, include_min=True)) assert result == VersionRange(v003, v200)
def excludes_single_version(self) -> bool: from .version import Version from .version_range import VersionRange return isinstance(VersionRange().difference(self), Version)
def test_include_max_prerelease(v200, v300, v300b1): result = VersionRange(v200, v300) assert not result.allows(v300b1) assert not result.allows_any(VersionRange(v300b1)) assert not result.allows_all(VersionRange(v200, v300b1)) result = VersionRange(v200, v300, always_include_max_prerelease=True) assert result.allows(v300b1) assert result.allows_any(VersionRange(v300b1)) assert result.allows_all(VersionRange(v200, v300b1))
def test_allows_all(v003, v010, v080, v114, v123, v124, v140, v200, v234, v250, v300): assert VersionRange(v123, v250).allows_all(EmptyConstraint()) range = VersionRange(v123, v250, include_max=True) assert not range.allows_all(v123) assert range.allows_all(v124) assert range.allows_all(v250) assert not range.allows_all(v300) # with no min range = VersionRange(max=v250) assert range.allows_all(VersionRange(v080, v140)) assert not range.allows_all(VersionRange(v080, v300)) assert range.allows_all(VersionRange(max=v140)) assert not range.allows_all(VersionRange(max=v300)) assert range.allows_all(range) assert not range.allows_all(VersionRange()) # with no max range = VersionRange(min=v010) assert range.allows_all(VersionRange(v080, v140)) assert not range.allows_all(VersionRange(v003, v140)) assert range.allows_all(VersionRange(v080)) assert not range.allows_all(VersionRange(v003)) assert range.allows_all(range) assert not range.allows_all(VersionRange()) # Allows bordering range that is not more inclusive exclusive = VersionRange(v010, v250) inclusive = VersionRange(v010, v250, True, True) assert inclusive.allows_all(exclusive) assert inclusive.allows_all(inclusive) assert not exclusive.allows_all(inclusive) assert exclusive.allows_all(exclusive) # Allows unions that are completely contained range = VersionRange(v114, v200) assert range.allows_all(VersionRange(v123, v124).union(v140)) assert not range.allows_all(VersionRange(v010, v124).union(v140)) assert not range.allows_all(VersionRange(v123, v234).union(v140))
def get_python_constraint_from_marker( marker: BaseMarker, ) -> VersionConstraint: from poetry.core.semver.empty_constraint import EmptyConstraint from poetry.core.semver.helpers import parse_constraint from poetry.core.semver.version import Version from poetry.core.semver.version_range import VersionRange python_marker = marker.only("python_version", "python_full_version") if python_marker.is_any(): return VersionRange() if python_marker.is_empty(): return EmptyConstraint() markers = convert_markers(marker) if contains_group_without_marker(markers, "python_version"): # groups are in disjunctive normal form (DNF), # an empty group means that python_version does not appear in this group, # which means that python_version is arbitrary for this group return VersionRange() ors = [] for or_ in markers["python_version"]: ands = [] for op, version in or_: # Expand python version if op == "==": if "*" not in version: version = "~" + version op = "" elif op == "!=": if "*" not in version: version += ".*" elif op in ("<=", ">"): parsed_version = Version.parse(version) if parsed_version.precision == 1: if op == "<=": op = "<" version = parsed_version.next_major().text elif op == ">": op = ">=" version = parsed_version.next_major().text elif parsed_version.precision == 2: if op == "<=": op = "<" version = parsed_version.next_minor().text elif op == ">": op = ">=" version = parsed_version.next_minor().text elif op in ("in", "not in"): versions = [] for v in re.split("[ ,]+", version): split = v.split(".") if len(split) in [1, 2]: split.append("*") op_ = "" if op == "in" else "!=" else: op_ = "==" if op == "in" else "!=" versions.append(op_ + ".".join(split)) glue = " || " if op == "in" else ", " if versions: ands.append(glue.join(versions)) continue ands.append(f"{op}{version}") ors.append(" ".join(ands)) return parse_constraint(" || ".join(ors))
def test_allows_any(v003, v010, v072, v080, v114, v123, v124, v140, v200, v234, v250, v300): # disallows an empty constraint assert not VersionRange(v123, v250).allows_any(EmptyConstraint()) # allows allowed versions range = VersionRange(v123, v250, include_max=True) assert not range.allows_any(v123) assert range.allows_any(v124) assert range.allows_any(v250) assert not range.allows_any(v300) # with no min range = VersionRange(max=v200) assert range.allows_any(VersionRange(v140, v300)) assert not range.allows_any(VersionRange(v234, v300)) assert range.allows_any(VersionRange(v140)) assert not range.allows_any(VersionRange(v234)) assert range.allows_any(range) # with no max range = VersionRange(min=v072) assert range.allows_any(VersionRange(v003, v140)) assert not range.allows_any(VersionRange(v003, v010)) assert range.allows_any(VersionRange(max=v080)) assert not range.allows_any(VersionRange(max=v003)) assert range.allows_any(range) # with min and max range = VersionRange(v072, v200) assert range.allows_any(VersionRange(v003, v140)) assert range.allows_any(VersionRange(v140, v300)) assert not range.allows_any(VersionRange(v003, v010)) assert not range.allows_any(VersionRange(v234, v300)) assert not range.allows_any(VersionRange(max=v010)) assert not range.allows_any(VersionRange(v234)) assert range.allows_any(range) # allows a bordering range when both are inclusive assert not VersionRange(max=v250).allows_any(VersionRange(min=v250)) assert not VersionRange(max=v250, include_max=True).allows_any( VersionRange(min=v250)) assert not VersionRange(max=v250).allows_any( VersionRange(min=v250, include_min=True)) assert not VersionRange(min=v250).allows_any(VersionRange(max=v250)) assert VersionRange(max=v250, include_max=True).allows_any( VersionRange(min=v250, include_min=True)) # allows unions that are partially contained' range = VersionRange(v114, v200) assert range.allows_any(VersionRange(v010, v080).union(v140)) assert range.allows_any(VersionRange(v123, v234).union(v300)) assert not range.allows_any(VersionRange(v234, v300).union(v010)) # pre-release min does not allow lesser than itself range = VersionRange(Version.parse("1.9b1"), include_min=True) assert not range.allows_any( VersionRange( Version.parse("1.8.0"), Version.parse("1.9.0"), include_min=True))