""" Implementation of a subset of PEP440.
Implementation adapted from the distlib.version package
"""
import re
from ..errors import InvalidPEP440Version
PEP440_VERSION_RE = re.compile(r'^v?(\d+!)?(\d+(\.\d+)*)((a|b|rc)(\d+))?'
r'(\.(post)(\d+))?(\.(dev)(\d+))?'
r'(\+([a-zA-Z\d]+(\.[a-zA-Z\d]+)?))?$')
class _Min(object):
def __hash__(self):
return hash(self.__class__)
def __eq__(self, other):
return other.__class__ == self.__class__
def __ne__(self, other):
return other.__class__ != self.__class__
def __lt__(self, other):
return other.__class__ != self.__class__
def __le__(self, other):
return True
def __ge__(self, other):
return other.__class__ == self.__class__
def __gt__(self, other):
return False
class _Max(object):
def __hash__(self):
return hash(self.__class__)
def __eq__(self, other):
return other.__class__ == self.__class__
def __ne__(self, other):
return other.__class__ != self.__class__
def __lt__(self, other):
return False
def __le__(self, other):
return other.__class__ == self.__class__
def __ge__(self, other):
return True
def __gt__(self, other):
return other.__class__ != self.__class__
_MIN = _Min()
_MAX = _Max()
[docs]class PEP440Version(object):
""" A PEP440-compliant version object.
Note: replacements are not supported yet.
"""
@classmethod
def from_string(cls, s):
m = PEP440_VERSION_RE.match(s)
if not m:
raise InvalidPEP440Version(s)
groups = m.groups()
nums = tuple(int(v) for v in groups[1].split('.'))
if not groups[0]:
epoch = 0
else:
epoch = int(groups[0][:-1])
pre = groups[4:6]
post = groups[7:9]
dev = groups[10:12]
local = groups[13]
if pre == (None, None):
pre = ()
else:
pre = pre[0], int(pre[1])
if post == (None, None):
post = ()
else:
post = post[0], int(post[1])
if dev == (None, None):
dev = ()
else:
dev = dev[0], int(dev[1])
if local is None:
local = ()
else:
parts = []
for part in local.split('.'):
# to ensure that numeric compares as > lexicographic, avoid
# comparing them directly, but encode a tuple which ensures
# correct sorting
if part.isdigit():
part = (1, int(part))
else:
part = (0, part)
parts.append(part)
local = tuple(parts)
if not pre:
if not post and dev:
# before pre-release
pre = (_MIN,)
else:
pre = (_MAX,)
if not post:
post = (_MIN,)
if not dev:
dev = (_MAX,)
return cls(epoch, nums, pre, post, dev, local)
def __init__(self, epoch, nums, pre, post, dev, local):
self._release_clause = nums
nums = _strip_trailing_zeros(nums)
self._parts = (epoch, nums, pre, post, dev, local)
# Caching of the corresponding properties
self._normalized_string = None
self._string = None
@property
def normalized_string(self):
""" 'normalized' string, i.e. 0 in the numerical part are stripped.
"""
if self._normalized_string is None:
self._normalized_string = self._compute_string(*self._parts)
return self._normalized_string
def __hash__(self):
return hash(self._parts)
def _ensure_compatible(self, other):
if type(self) != type(other):
raise TypeError('cannot compare %r and %r' % (self, other))
def _compute_string(self, epoch, nums, pre, post, dev, local):
s = ""
if epoch:
s += str(epoch) + "!"
s += ".".join(str(i) for i in nums)
if pre and pre[0] not in (_MIN, _MAX):
s += pre[0] + str(pre[1])
if post and post[0] not in (_MIN, _MAX):
s += ".post" + str(post[1])
if dev and dev[0] not in (_MIN, _MAX):
s += ".dev" + str(dev[1])
if local:
s += "+" + ".".join(str(part[1]) for part in local)
return s
def __eq__(self, other):
self._ensure_compatible(other)
return self._parts == other._parts
def __ne__(self, other):
self._ensure_compatible(other)
return self._parts != other._parts
def __lt__(self, other):
self._ensure_compatible(other)
return self._parts < other._parts
def __le__(self, other):
self._ensure_compatible(other)
return self._parts <= other._parts
def __ge__(self, other):
self._ensure_compatible(other)
return self._parts >= other._parts
def __gt__(self, other):
self._ensure_compatible(other)
return self._parts > other._parts
def __repr__(self):
return "{0}('{1}')".format(self.__class__.__name__, str(self))
def __str__(self):
if self._string is None:
nums = self._release_clause
epoch, _, pre, post, dev, local = self._parts
self._string = self._compute_string(
epoch, nums, pre, post, dev, local
)
return self._string
def _strip_trailing_zeros(nums):
while len(nums) > 1 and nums[-1] == 0:
nums = nums[:-1]
return nums