def union(self, other: "VersionTypes") -> "VersionTypes": from .version import Version if isinstance(other, Version): if self.allows(other): return self if other == self.min: return VersionRange(self.min, self.max, include_min=True, include_max=self.include_max) if other == self.max: return VersionRange(self.min, self.max, include_min=self.include_min, include_max=True) return VersionUnion.of(self, other) if isinstance(other, VersionRangeConstraint): # If the two ranges don't overlap, we won't be able to create a single # VersionRange for both of them. edges_touch = (self.max == other.min and (self.include_max or other.include_min)) or ( self.min == other.max and (self.include_min or other.include_max)) if not edges_touch and not self.allows_any(other): return VersionUnion.of(self, other) if self.allows_lower(other): union_min = self.min union_include_min = self.include_min else: union_min = other.min union_include_min = other.include_min if self.allows_higher(other): union_max = self.max union_include_max = self.include_max else: union_max = other.max union_include_max = other.include_max return VersionRange( union_min, union_max, include_min=union_include_min, include_max=union_include_max, ) return VersionUnion.of(self, other)
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_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 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)
"== 3.8", VersionRange( min=Version.from_parts(3, 8), max=Version.from_parts(3, 8), include_min=True, include_max=True, ), ), ( "~2.7 || ~3.8", VersionUnion( VersionRange( min=Version.from_parts(2, 7), max=Version.from_parts(2, 8), include_min=True, ), VersionRange( min=Version.from_parts(3, 8), max=Version.from_parts(3, 9), include_min=True, ), ), ), ( "~2.7||~3.8", VersionUnion( VersionRange( min=Version.from_parts(2, 7), max=Version.from_parts(2, 8), include_min=True, ), VersionRange(
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}")
def difference(self, other: "VersionTypes") -> "VersionTypes": from .version import Version if other.is_empty(): return self if isinstance(other, Version): if not self.allows(other): return self if other == self.min: if not self.include_min: return self return VersionRange(self.min, self.max, False, self.include_max) if other == self.max: if not self.include_max: return self return VersionRange(self.min, self.max, self.include_min, False) return VersionUnion.of( VersionRange(self.min, other, self.include_min, False), VersionRange(other, self.max, False, self.include_max), ) elif isinstance(other, VersionRange): if not self.allows_any(other): return self if not self.allows_lower(other): before = None elif self.min == other.min: before = self.min else: before = VersionRange( self.min, other.min, self.include_min, not other.include_min ) if not self.allows_higher(other): after = None elif self.max == other.max: after = self.max else: after = VersionRange( other.max, self.max, not other.include_max, self.include_max ) if before is None and after is None: return EmptyConstraint() if before is None: return after if after is None: return before return VersionUnion.of(before, after) elif isinstance(other, VersionUnion): ranges: List[VersionRange] = [] current = self for range in other.ranges: # Skip any ranges that are strictly lower than [current]. if range.is_strictly_lower(current): continue # If we reach a range strictly higher than [current], no more ranges # will be relevant so we can bail early. if range.is_strictly_higher(current): break difference = current.difference(range) if difference.is_empty(): return EmptyConstraint() elif isinstance(difference, VersionUnion): # If [range] split [current] in half, we only need to continue # checking future ranges against the latter half. ranges.append(difference.ranges[0]) current = difference.ranges[-1] else: current = difference if not ranges: return current return VersionUnion.of(*(ranges + [current])) raise ValueError("Unknown VersionConstraint type {}.".format(other))