Viewing file: version_range.py (12.87 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
from typing import List
import semver
from .empty_constraint import EmptyConstraint from .version_constraint import VersionConstraint from .version_union import VersionUnion
class VersionRange(VersionConstraint): def __init__( self, min=None, max=None, include_min=False, include_max=False, always_include_max_prerelease=False, ): full_max = max if ( always_include_max_prerelease and not include_max and not full_max.is_prerelease() and not full_max.build and (min is None or not min.is_prerelease() or not min.equals_without_prerelease(full_max)) ): full_max = full_max.first_prerelease
self._min = min self._max = max self._full_max = full_max self._include_min = include_min self._include_max = include_max
@property def min(self): return self._min
@property def max(self): return self._max
@property def full_max(self): return self._full_max
@property def include_min(self): return self._include_min
@property def include_max(self): return self._include_max
def is_empty(self): return False
def is_any(self): return self._min is None and self._max is None
def allows(self, other): # type: (semver.Version) -> bool if self._min is not None: if other < self._min: return False
if not self._include_min and other == self._min: return False
if self._max is not None: if other > self._max: return False
if not self._include_max and other == self._max: return False
return True
def allows_all(self, other): # type: (VersionConstraint) -> bool from .version import Version
if other.is_empty(): return True
if isinstance(other, Version): return self.allows(other)
if isinstance(other, VersionUnion): return all([self.allows_all(constraint) for constraint in other.ranges])
if isinstance(other, VersionRange): return not other.allows_lower(self) and not other.allows_higher(self)
raise ValueError("Unknown VersionConstraint type {}.".format(other))
def allows_any(self, other): # type: (VersionConstraint) -> bool from .version import Version
if other.is_empty(): return False
if isinstance(other, Version): return self.allows(other)
if isinstance(other, VersionUnion): return any([self.allows_any(constraint) for constraint in other.ranges])
if isinstance(other, VersionRange): return not other.is_strictly_lower(self) and not other.is_strictly_higher(self)
raise ValueError("Unknown VersionConstraint type {}.".format(other))
def intersect(self, other): # type: (VersionConstraint) -> VersionConstraint from .version import Version
if other.is_empty(): return other
if isinstance(other, VersionUnion): return other.intersect(self)
# A range and a Version just yields the version if it's in the range. if isinstance(other, Version): if self.allows(other): return other
return EmptyConstraint()
if not isinstance(other, VersionRange): raise ValueError("Unknown VersionConstraint type {}.".format(other))
if self.allows_lower(other): if self.is_strictly_lower(other): return EmptyConstraint()
intersect_min = other.min intersect_include_min = other.include_min else: if other.is_strictly_lower(self): return EmptyConstraint()
intersect_min = self._min intersect_include_min = self._include_min
if self.allows_higher(other): intersect_max = other.max intersect_include_max = other.include_max else: intersect_max = self._max intersect_include_max = self._include_max
if intersect_min is None and intersect_max is None: return VersionRange()
# If the range is just a single version. if intersect_min == intersect_max: # Because we already verified that the lower range isn't strictly # lower, there must be some overlap. assert intersect_include_min and intersect_include_max
return intersect_min
# If we got here, there is an actual range. return VersionRange(intersect_min, intersect_max, intersect_include_min, intersect_include_max)
def union(self, other): # type: (VersionConstraint) -> VersionConstraint 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, VersionRange): # 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 difference(self, other): # type: (VersionConstraint) -> VersionConstraint 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 = [] # type: 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))
def allows_lower(self, other): # type: (VersionRange) -> bool if self.min is None: return other.min is not None
if other.min is None: return False
if self.min < other.min: return True
if self.min > other.min: return False
return self.include_min and not other.include_min
def allows_higher(self, other): # type: (VersionRange) -> bool if self.max is None: return other.max is not None
if other.max is None: return False
if self.max < other.max: return False
if self.max > other.max: return True
return self.include_max and not other.include_max
def is_strictly_lower(self, other): # type: (VersionRange) -> bool if self.max is None or other.min is None: return False
if self.full_max < other.min: return True
if self.full_max > other.min: return False
return not self.include_max or not other.include_min
def is_strictly_higher(self, other): # type: (VersionRange) -> bool return other.is_strictly_lower(self)
def is_adjacent_to(self, other): # type: (VersionRange) -> bool if self.max != other.min: return False
return self.include_max and not other.include_min or not self.include_max and other.include_min
def __eq__(self, other): if not isinstance(other, VersionRange): return False
return ( self._min == other.min and self._max == other.max and self._include_min == other.include_min and self._include_max == other.include_max )
def __lt__(self, other): return self._cmp(other) < 0
def __le__(self, other): return self._cmp(other) <= 0
def __gt__(self, other): return self._cmp(other) > 0
def __ge__(self, other): return self._cmp(other) >= 0
def _cmp(self, other): # type: (VersionRange) -> int if self.min is None: if other.min is None: return self._compare_max(other)
return -1 elif other.min is None: return 1
result = self.min._cmp(other.min) if result != 0: return result
if self.include_min != other.include_min: return -1 if self.include_min else 1
return self._compare_max(other)
def _compare_max(self, other): # type: (VersionRange) -> int if self.max is None: if other.max is None: return 0
return 1 elif other.max is None: return -1
result = self.max._cmp(other.max) if result != 0: return result
if self.include_max != other.include_max: return 1 if self.include_max else -1
return 0
def __str__(self): text = ""
if self.min is not None: text += ">=" if self.include_min else ">" text += self.min.text
if self.max is not None: if self.min is not None: text += ","
text += "{}{}".format("<=" if self.include_max else "<", self.max.text)
if self.min is None and self.max is None: return "*"
return text
def __repr__(self): return "<VersionRange ({})>".format(str(self))
def __hash__(self): return hash((self.min, self.max, self.include_min, self.include_max))
|