For the decorators: I'm not sure if this is overkill: But in Python2.5
there is the "functools.wraps"-decorator, that takes care that the wrapped
function looks the same like the original one

...
This would look like:
def decorator (f):
@wraps(f)
def func_proxy(...):
# something
return func_proxy
And when we are not using 2.5, your current approach could be used (though
enhanced a bit to copy more data of the wrapped func):
def wraps (f):
"""Emulate functools.wraps for <python-2.5"""
def w_deco(func):
func.__doc__ = f.__doc__
func.__dict__ = f.__dict__
func.__name__ = f.__name__
return func
return w_deco
On Thu, 24 Jul 2008 03:12:50 +0300, Ali Polatel <hawking@gentoo.org> wrote:
> Thanks for the comments.
>
> Version 2, updates:
> * Use "import sys" instead of "from sys import hexversion"
> * Use super() to call functions from parent class.
> * Fix initialization, used to pass m.groups() to parent object which is a
> leftover from older versions where I used to subclass list instead of
str.
> Removed __str__ functions and __str which are useless as well.
> * Added readonly attributes cvs, main_version and revision to version, pv
> and
> cpv, package to pv, cpv and category to cpv. I think this is a better way
> than
> making __parts available so it's guaranteed that there'll be no unwanted
> change
> in internal state.
> * version uses __init__ now. pv and cpv still use __new__ because to fit
> code
> easily they return None when the argument isn't a valid PV or CPV instead
> of
> raising TypeError.
>
> Please comment.
>
> ---
> pym/portage/versions.py | 332
> +++++++++++++++++++++++++++++++++++++++++-----
> 1 files changed, 296 insertions(+), 36 deletions(-)
>
> diff --git a/pym/portage/versions.py b/pym/portage/versions.py
> index 261fa9d..507d7a1 100644
> --- a/pym/portage/versions.py
> +++ b/pym/portage/versions.py
> @@ -4,6 +4,7 @@
> # $Id$
>
> import re
> +import warnings
>
> ver_regexp =
>
re.compile("^(cvs.)?(d+)((.d+)*)([a-z]?)((_(pre|p|beta|alpha|rc)d*)*)(-r(d+))?$")
> suffix_regexp = re.compile("^(alpha|beta|rc|pre|p)(d*)$")
> @@ -12,6 +13,245 @@ endversion_keys = ["pre", "p", "alpha", "beta", "rc"]
>
> from portage.exception import InvalidData
>
> +# builtin all() is new in Python-2.5
> +# TODO Move compatibility stuff to a new module portage.compat
> +# and import from it like from portage.compat import all
> +import sys
> +if sys.hexversion < 0x02050000:
> + def all(iterable):
> + for i in iterable:
> + if not bool(i):
> + return False
> + return True
> +
> +def needs_version(func):
> + """Decorator for functions that require non-keyword arguments of type
> version."""
> + def func_proxy(*args, **kwargs):
> + if not all([isinstance(arg, version) for arg in args]):
> + raise TypeError("Not all non-keyword arguments are of type version")
> + return func(*args, **kwargs)
> + func_proxy.__doc__ = func.__doc__
> + return func_proxy
> +
> +def needs_pv(func):
> + """Decorator for functions that require non-keyword arguments of type
> pv."""
> + def func_proxy(*args, **kwargs):
> + if not all([isinstance(arg, pv) for arg in args]):
> + raise TypeError("Not all non-keyword arguments are of type pv")
> + return func(*args, **kwargs)
> + func_proxy.__doc__ = func.__doc__
> + return func_proxy
> +
> +def needs_cpv(func):
> + """Decorator for functions that require non-keyword arguments of type
> cpv."""
> + def func_proxy(*args, **kwargs):
> + if not all([isinstance(arg, cpv) for arg in args]):
> + raise TypeError("Not all non-keyword arguments are of type cpv")
> + return func(*args, **kwargs)
> + func_proxy.__doc__ = func.__doc__
> + return func_proxy
> +
> +class version(str):
> + """Represents a package version"""
> +
> + __hash = None
> + __parts = ()
> +
> + def __init__(self, value):
> + m = ver_regexp.match(value)
> + if m is None:
> + raise TypeError("Syntax error in version: %s" % value)
> + else:
> + super(version, self).__init__(value)
> + self.__hash = hash(m.groups()) + hash(value)
> + self.__parts = m.groups()
> +
> + def __repr__(self):
> + return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
> + id(self), self)
> +
> + def __hash__(self):
> + return self.__hash
> +
> + def __getitem__(self, i):
> + return self.__parts[i]
> +
> + def __getslice__(self, i, j):
> + return self.__parts[i:j]
> +
> + def __len__(self):
> + return len(self.__parts)
> +
> + @needs_version
> + def __cmp__(self, y):
> + return vercmp(self, y)
> +
> + @needs_version
> + def __eq__(self, y):
> + return vercmp(self, y) == 0
> +
> + @needs_version
> + def __ne__(self, y):
> + return vercmp(self, y) != 0
> +
> + @needs_version
> + def __lt__(self, y):
> + return vercmp(self, y) < 0
> +
> + @needs_version
> + def __le__(self, y):
> + return vercmp(self, y) <= 0
> +
> + @needs_version
> + def __gt__(self, y):
> + return vercmp(self, y) > 0
> +
> + @needs_version
> + def __ge__(self, y):
> + return vercmp(self, y) >= 0
> +
> + @property
> + def cvs(self):
> + return self.__parts[0]
> +
> + @property
> + def main_version(self):
> + mv = self.__parts[1:3]
> + mv += self.__parts[4:6]
> + return "".join(mv)
> +
> + @property
> + def revision(self):
> + return self.__parts[8]
> +
> +class pv(str):
> + """Represents a pv"""
> +
> + __hash = None
> + __parts = ()
> +
> + def __new__(cls, value):
> + parts = pkgsplit(value)
> + if parts is None:
> + # Ideally a TypeError should be raised here.
> + # But to fit code using this easily, fail silently.
> + return None
> + else:
> + new_pv = super(pv, cls).__new__(cls, value)
> + new_pv.__hash = hash(parts) + hash(value)
> + new_pv.__parts = (parts[0], version("-".join(parts[1:])))
> +
> + return new_pv
> +
> + def __repr__(self):
> + return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
> + id(self), self)
> +
> + def __hash__(self):
> + return self.__hash
> +
> + def __getitem__(self, i):
> + return self.__parts[i]
> +
> + def __getslice__(self, i, j):
> + return self.__parts[i:j]
> +
> + def __len__(self):
> + return len(self.__parts)
> +
> + @needs_pv
> + def __cmp__(self, y):
> + if self.__parts[0] != y.__parts[0]:
> + return None
> + else:
> + return cmp(self[1], y[1])
> +
> + @property
> + def package(self):
> + return self.__parts[0]
> +
> + @property
> + def version(self):
> + return self.__parts[1]
> +
> + @property
> + def cvs(self):
> + return self.__parts[1].cvs
> +
> + @property
> + def main_version(self):
> + return self.__parts[1].main_version
> +
> + @property
> + def revision(self):
> + return self.__parts[1].revision
> +
> +class cpv(str):
> + """Represents a cpv"""
> +
> + __hash = None
> + __parts = ()
> +
> + def __new__(cls, value):
> + parts = catpkgsplit(value)
> + if parts is None:
> + # Ideally a TypeError should be raised here.
> + # But to fit code using this easily, fail silently.
> + return None
> + else:
> + new_cpv = super(cpv, cls).__new__(cls, value)
> + new_cpv.__hash = hash(parts) + hash(value)
> + new_cpv.__parts = (parts[0], pv("-".join(parts[1:])))
> +
> + return new_cpv
> +
> + def __repr__(self):
> + return "<%s object at 0x%x: %s>" % (self.__class__.__name__,
> + id(self), self)
> +
> + def __hash__(self):
> + return self.__hash
> +
> + def __getitem__(self, i):
> + return self.__parts[i]
> +
> + def __getslice__(self, i, j):
> + return self.__parts[i:j]
> +
> + def __len__(self):
> + return len(self.__parts)
> +
> + @needs_cpv
> + def __cmp__(self, y):
> + if self[0] != y[0]:
> + return None
> + else:
> + return cmp(self[1], y[1])
> +
> + @property
> + def category(self):
> + return self.__parts[0]
> +
> + @property
> + def package(self):
> + return self.__parts[1]
> +
> + @property
> + def version(self):
> + return self.__parts[1].version
> +
> + @property
> + def cvs(self):
> + return self.__parts[1].cvs
> +
> + @property
> + def main_version(self):
> + return self.__parts[1].main_version
> +
> + @property
> + def revision(self):
> + return self.__parts[1].revision
> +
> def ververify(myver, silent=1):
> if ver_regexp.match(myver):
> return 1
> @@ -45,43 +285,63 @@ def vercmp(ver1, ver2, silent=1):
> 4. None if ver1 or ver2 are invalid (see ver_regexp in
> portage.versions.py)
> """
>
> - if ver1 == ver2:
> - return 0
> - mykey=ver1+":"+ver2
> - try:
> - return vercmp_cache[mykey]
> - except KeyError:
> - pass
> - match1 = ver_regexp.match(ver1)
> - match2 = ver_regexp.match(ver2)
> -
> - # checking that the versions are valid
> - if not match1 or not match1.groups():
> - if not silent:
> - print "!!! syntax error in version: %s" % ver1
> - return None
> - if not match2 or not match2.groups():
> - if not silent:
> - print "!!! syntax error in version: %s" % ver2
> - return None
> + if isinstance(ver1, version) and isinstance(ver2, version):
> + if ver1._str == ver2._str:
> + return 0
> + mykey = ver1._str+":"+ver2._str
> + if mykey in vercmp_cache:
> + return vercmp_cache[mykey]
> +
> + group1 = ver1[:]
> + group2 = ver2[:]
> + elif isinstance(ver1, str) and isinstance(ver2, str):
> + ## Backwards compatibility
> + msg = "vercmp(str,str) is deprecated use portage.version object
> instead"
> + warnings.warn(msg, DeprecationWarning)
> +
> + if ver1 == ver2:
> + return 0
> + mykey=ver1+":"+ver2
> + try:
> + return vercmp_cache[mykey]
> + except KeyError:
> + pass
> + match1 = ver_regexp.match(ver1)
> + match2 = ver_regexp.match(ver2)
> +
> + # checking that the versions are valid
> + if not match1 or not match1.groups():
> + if not silent:
> + print "!!! syntax error in version: %s" % ver1
> + return None
> + if not match2 or not match2.groups():
> + if not silent:
> + print "!!! syntax error in version: %s" % ver2
> + return None
> +
> + group1 = match1.groups()
> + group2 = match2.groups()
> + else:
> + raise TypeError(
> + "Arguments aren't of type str,str or version,version")
>
> # shortcut for cvs ebuilds (new style)
> - if match1.group(1) and not match2.group(1):
> + if group1[0] and not group2[0]:
> vercmp_cache[mykey] = 1
> return 1
> - elif match2.group(1) and not match1.group(1):
> + elif group2[0] and not group1[0]:
> vercmp_cache[mykey] = -1
> return -1
>
> # building lists of the version parts before the suffix
> # first part is simple
> - list1 = [int(match1.group(2))]
> - list2 = [int(match2.group(2))]
> + list1 = [int(group1[1])]
> + list2 = [int(group2[1])]
>
> # this part would greatly benefit from a fixed-length version pattern
> - if len(match1.group(3)) or len(match2.group(3)):
> - vlist1 = match1.group(3)[1:].split(".")
> - vlist2 = match2.group(3)[1:].split(".")
> + if len(group1[2]) or len(group2[2]):
> + vlist1 = group1[2][1:].split(".")
> + vlist2 = group2[2][1:].split(".")
> for i in range(0, max(len(vlist1), len(vlist2))):
> # Implcit .0 is given a value of -1, so that 1.0.0 > 1.0, since it
> # would be ambiguous if two versions that aren't literally equal
> @@ -111,10 +371,10 @@ def vercmp(ver1, ver2, silent=1):
> list2.append(int(vlist2[i].ljust(max_len, "0")))
>
> # and now the final letter
> - if len(match1.group(5)):
> - list1.append(ord(match1.group(5)))
> - if len(match2.group(5)):
> - list2.append(ord(match2.group(5)))
> + if len(group1[4]):
> + list1.append(ord(group1[4]))
> + if len(group2[4]):
> + list2.append(ord(group2[4]))
>
> for i in range(0, max(len(list1), len(list2))):
> if len(list1) <= i:
> @@ -128,8 +388,8 @@ def vercmp(ver1, ver2, silent=1):
> return list1[i] - list2[i]
>
> # main version is equal, so now compare the _suffix part
> - list1 = match1.group(6).split("_")[1:]
> - list2 = match2.group(6).split("_")[1:]
> + list1 = group1[5].split("_")[1:]
> + list2 = group2[5].split("_")[1:]
>
> for i in range(0, max(len(list1), len(list2))):
> # Implicit _p0 is given a value of -1, so that 1 < 1_p0
> @@ -154,12 +414,12 @@ def vercmp(ver1, ver2, silent=1):
> return r1 - r2
>
> # the suffix part is equal to, so finally check the revision
> - if match1.group(10):
> - r1 = int(match1.group(10))
> + if group1[9]:
> + r1 = int(group1[9])
> else:
> r1 = 0
> - if match2.group(10):
> - r2 = int(match2.group(10))
> + if group2[9]:
> + r2 = int(group2[9])
> else:
> r2 = 0
> vercmp_cache[mykey] = r1 - r2
> --
> Regards,
> Ali Polatel