nutools/lib/nulib/python/nulib/base.py

506 lines
17 KiB
Python

# -*- coding: utf-8 -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
"""Fonctions de base
"""
__all__ = ('myself', 'mydir', 'myname',
'Undef',
'updated', 'updatem', 'odict', 'ncdict',
'required', 'nlistf', 'snlistf',
'isnum', 'isflt', 'isbool', 'isbytes', 'isunicode', 'isstr',
'isseq', 'seqof', 'listof', 'firstof',
'all_matches', 'one_match',
'strip_nl', 'norm_nl',
'make_getter', 'make_setter', 'make_deleter', 'make_prop',
'getattrs', 'setattrs', 'delattrs', 'make_delegate',
)
import os, sys, re
from os import path
from types import IntType, LongType, FloatType, BooleanType
from types import StringType, UnicodeType, StringTypes
# Enlever le répertoire courant de sys.path
try: from nutools_config import CLEAN_SYSPATH
except ImportError: CLEAN_SYSPATH = True
if CLEAN_SYSPATH:
def __clean_syspath():
cwd = os.getcwd()
sys.path = filter(lambda p: p not in ('', '.', cwd), sys.path)
__clean_syspath()
del __clean_syspath
# emplacement du script courant
myself = path.abspath(sys.argv[0])
mydir, myname = path.split(myself)
# Fonctions diverses
_undef = object()
class Undef(object):
def sa(self, value, kw, name, default=_undef):
"""si value est Undef, récupérer la valeur avec le nom court name dans kw
"""
if default is _undef: default = self
if value is self and name is not None: value = kw.pop(name, self)
if value is self: value = default
return value
def __nonzero__(self):
return False
def __len__(self):
return 0
def __lt__(self, other):
if other: return True
else: return False
def __le__(self, other):
return True
def __eq__(self, other):
if other: return False
else: return True
def __ne__(self, other):
if other: return True
else: return False
def __gt__(self, other):
if other: return False
else: return True
def __ge__(self, other):
return True
def __repr__(self):
return 'Undef'
def __call__(self):
"""créer une nouvelle instance de Undef, pratique pour un module qui veut
utiliser sa propre valeur différente de la valeur globale
"""
return self.__class__()
Undef = Undef()
def updated(dict=None, **kw):
"""Retourner une copie de dict mise à jour avec les éléments de kw
"""
if dict is None: dict = {}
else: dict = dict.copy()
dict.update(kw)
return dict
def updatem(dict=None, *dicts):
"""Mets à jour dict avec les dictionnaires dicts, et retourner dict
"""
if dict is None: dict = {}
for kw in dicts: dict.update(kw)
return dict
class odict(dict):
"""dictionnaire qui supporte aussi l'accès aux propriétés comme des attributs
"""
def __init__(self, dict=None, **kw):
super(odict, self).__init__(**updated(dict, **kw))
def __getattr__(self, name):
try: return self[name]
except KeyError: raise AttributeError(name)
def __setattr__(self, name, value):
if name in self.__dict__: self.__dict__[name] = value
else: self[name] = value
def __delattr__(self, name):
try: del self[name]
except KeyError: raise AttributeError(name)
def copy(self):
return self.__class__(super(odict, self).copy())
_none = object()
class ncdict(odict):
"""dictionnaire dont les clés sont insensibles à la casse
"""
def __init__(self, dict=None, **kw):
super(ncdict, self).__init__(**updated(dict, **kw))
def __getitem__(self, key):
if isstr(key): key = key.lower()
return super(ncdict, self).__getitem__(key)
def __setitem__(self, key, value):
if isstr(key): key = key.lower()
return super(ncdict, self).__setitem__(key, value)
def __delitem__(self, key):
if isstr(key): key = key.lower()
return super(ncdict, self).__delitem__(key)
def __getattr__(self, key):
if isstr(key): key = key.lower()
return super(ncdict, self).__getattr__(key)
def __setattr__(self, key, value):
if isstr(key): key = key.lower()
return super(ncdict, self).__setattr__(key, value)
def __delattr__(self, key):
if isstr(key): key = key.lower()
return super(ncdict, self).__delattr__(key)
def has_key(self, key):
if isstr(key): key = key.lower()
return super(ncdict, self).has_key(key)
def get(self, key, default=_none):
if isstr(key): key = key.lower()
if default is _none: return super(ncdict, self).get(key)
else: return super(ncdict, self).get(key, default)
def _itemprop(i, name):
def getter(self):
return self._values[i]
def setter(self, value):
validator = self.VALIDATORS.get(name, None)
if validator is not None: value = validator(value)
self._values[i] = value
return property(getter, setter)
def _fix_module(cls):
try: cls.__module__ = sys._getframe(1).f_globals.get('__name__', '__main__')
except (AttributeError, ValueError): pass
return cls
def required(validator, name=None):
if name is None: name = "The value"
def wrapper(value):
value = validator(value)
if value is None: raise ValueError("%s is required" % name)
else: return value
return wrapper
def nlistf(name, *attrs, **kw):
name = str(name)
# attributs
if len(attrs) == 1 and isstr(attrs[0]):
attrs = attrs[0].replace(',', ' ').split()
attrs = tuple(map(str, attrs))
# validateurs
validators = {}
for attr, validator in kw.iteritems():
if attr not in attrs:
raise ValueError("Invalid validator attribute: %s" % attr)
validators[attr] = validator
template = ["""class %(name)s(object):
__slots__ = ('_values')
ATTRS = None
VALIDATORS = None
def reset(self):
"Reinitialiser toutes les valeurs a None"
self._values = [None] * len(self.ATTRS)
return self
def replace(self, *values, **kw):
"Modifier des valeurs specifiques"
for i, attr in enumerate(self.ATTRS[:len(values)]): setattr(self, attr, values[i])
for attr, value in kw.iteritems(): setattr(self, attr, value)
return self
def init(self, *values, **kw):
"Modifier toutes les valeurs de cet objet. Les valeurs non specifiees recoivent None."
return self.reset().replace(*values, **kw)
def __init__(self, *values, **kw): self.init(*values, **kw)
def inito(self, o):
"Modifier toutes les valeurs de cet objet en les prenant depuis les attributs de l'objet o."
for attr in self.ATTRS: setattr(self, attr, getattr(o, attr, None))
def update(self, d):
"Mettre a jour le dictionnaire d avec les valeurs de cet objet"
for attr in self.ATTRS: d[attr] = getattr(self, attr)
def updateo(self, o):
"Mettre a jour les attributs de l'objet o avec les valeurs de cet objet."
for attr in self.ATTRS: setattr(o, attr, getattr(self, attr))
def asdict(self): return dict(zip(self.ATTRS, self._values))
def __repr__(self): return repr(self.asdict())
def __len__(self): return len(self._values)
def __getitem__(self, key): return self._values.__getitem__(key)
def __setitem__(self, key, value): self._values.__setitem__(key, value)
def __iter__(self): return self._values.__iter__()
def __contains__(self, item): return self._values.__contains__(item)"""]
for i, attr in enumerate(attrs):
template.append(" %s = itemprop(%i, '%s')" % (attr, i, attr))
template = "\n".join(template) % locals()
namespace = dict(itemprop=_itemprop)
try: exec template in namespace
except SyntaxError, e: raise SyntaxError('%s:\n%s' % (e.message, template))
cls = namespace[name]
cls.ATTRS = attrs
cls.VALIDATORS = validators
return _fix_module(cls)
def snlistf(base, name, *attrs, **kw):
name = str(name)
# attributs
if len(attrs) == 1 and isstr(attrs[0]):
attrs = attrs[0].replace(',', ' ').split()
attrs = tuple(map(str, attrs))
allattrs = base.ATTRS + attrs
# validateurs
validators = base.VALIDATORS.copy()
for attr, validator in kw.iteritems():
if attr not in allattrs:
raise ValueError("Invalid validator attribute: %s" % attr)
validators[attr] = validator
template = ["""class %(name)s(base):
__slots__ = ()
ATTRS = None
VALIDATORS = None"""]
basei = len(base.ATTRS)
for i, attr in enumerate(attrs):
template.append(" %s = itemprop(%i, '%s')" % (attr, basei + i, attr))
template = "\n".join(template) % locals()
namespace = dict(base=base, itemprop=_itemprop)
try: exec template in namespace
except SyntaxError, e: raise SyntaxError('%s:\n%s' % (e.message, template))
cls = namespace[name]
cls.ATTRS = allattrs
cls.VALIDATORS = validators
return _fix_module(cls)
def isnum(i):
"""Tester si i est une valeur numérique (int ou long)
"""
return type(i) in (IntType, LongType)
def isflt(f):
"""Tester si f est une valeur numérique flottante (float)
"""
return type(f) is FloatType
def isbool(b):
"""Tester si b est une valeur booléenne
"""
return type(b) is BooleanType
def isseq(t):
"""Tester si t est une séquence (list ou tuple)
"""
return isinstance(t, list) or isinstance(t, tuple)
def seqof(seq, ifNone=Undef, nocopy=False):
"""Retourner une séquence.
Si seq est une séquence, retourner une copie de l'objet si nocopy==False,
sinon l'objet lui-même.
Si seq==None: si ifNone est défini, retourner ifNone, sinon un tuple vide.
Sinon, retourner le tuple (seq,)
"""
if isseq(seq):
if nocopy: return seq
else: return seq[:]
elif seq is None:
if ifNone is Undef: return ()
else: return ifNone
else: return (seq,)
def listof(seq, ifNone=Undef):
"""Retourner une liste.
Si seq est une séquence, retourner la liste correspondante
Si seq==None: si ifNone est défini, retourner ifNone, sinon une liste vide.
Sinon, retourner la liste [seq]
"""
if seq is None:
if ifNone is Undef: return []
else: return ifNone
elif isseq(seq): return list(seq)
else: return [seq]
def firstof(seq):
"""Retourner le premier élément de la séquence.
Si seq n'est pas une séquence, retourner l'objet lui-même.
Si seq est une séquence vide, retourner None.
"""
if isseq(seq): return seq[0:1] and seq[0] or None
else: return seq
def isbytes(s):
"""Tester si s est une valeur chaine (str)
"""
return type(s) is StringType
def isunicode(s):
"""Tester si s est une valeur chaine (unicode)
"""
return type(s) is UnicodeType
def isstr(s):
"""Tester si s est une valeur chaine (str ou unicode)
"""
return type(s) in StringTypes
def all_matches(func, seq):
"""Tester si tous les éléments de seq sont matchés par la fonction func.
"""
for item in seqof(seq):
if not func(item): return False
return True
def one_match(func, seq):
"""Tester si au moins un des éléments de seq est matché par la fonction
func.
"""
for item in seqof(seq):
if func(item): return True
return False
def strip_nl(s):
"""Enlever le caractère de fin de ligne de s: soit \\n, soit \\r, soit \\r\\n
"""
if s is None: return None
elif s.endswith("\r\n"): s = s[: - 2]
elif s.endswith("\n"): s = s[: - 1]
elif s.endswith("\r"): s = s[: - 1]
return s
RE_NL = re.compile(r'(?:\r?\n|\r)')
def norm_nl(s, nl="\\n"):
"""Transformer tous les caractères de fin de ligne en \\n
"""
if s is None: return None
else: return RE_NL.sub(nl, s)
def make_getter(name):
return lambda self: getattr(self, name)
def make_setter(name, validator=None):
if validator is None:
return lambda self, value: setattr(self, name, value)
else:
return lambda self, value: setattr(self, name, validator(value))
def make_deleter(name):
return lambda self: delattr(self, name)
def make_prop(name, value=None, getter=True, setter=True, deleter=False, validator=None):
"""Retourne un tuple facilitant la création d'une propriété protégée par
des accesseurs.
Voici un exemple d'usage:
class C:
_name, name, get_name, set_name = make_prop('_name', 'Default value')
@return: (value, property, getter_func, setter_func, deleter_func)
"""
accessors = {}
if getter in (False, None): pass
elif getter is True: getter = make_getter(name)
if getter: accessors['fget'] = getter
if setter in (False, None): pass
elif setter is True: setter = make_setter(name, validator)
elif validator is not None:
_setter = setter
setter = lambda self, value: _setter(self, validator(value))
if setter: accessors['fset'] = setter
if deleter in (False, None): pass
elif deleter is True: deleter = make_deleter(name)
if deleter: accessors['fdel'] = deleter
result = [value, property(**accessors)]
if getter: result.append(accessors['fget'])
if setter: result.append(accessors['fset'])
if deleter: result.append(accessors['fdel'])
return tuple(result)
def __check_names(names):
if not names: raise AttributeError("The attribute name is required")
def getattrs(obj, names, strict=False):
u"""Soit un objet obj, et un nom de la forme "attr0.attr1....",
retourner l'objet obtenu avec l'expression obj.attr0.attr1....
@param strict: on requière que toute l'expression soit parcouru jusqu'à la
fin. Sinon, arrêter dès que le résultat de l'expression est None.
"""
if not names: return obj
if not isseq(names): names = names.split(".")
__check_names(names)
value = obj
for i in range(len(names)):
name = names[i]
if value is None:
if strict:
if i > 0: path = "obj." + ".".join(names[:i])
else: path = "None"
raise AttributeError("%s instance has no value '%s'" % (path, name))
else: break
value = getattr(value, name)
return value
def setattrs(obj, names, value):
u"""Soit un objet obj, et un nom de la forme "attr0.attr1....",
effectuer l'équivalent de l'opération:
obj.attr0.attr1.... = value
"""
if not isseq(names): names = names.split(".")
__check_names(names)
obj = getattrs(obj, names[:-1], True)
setattr(obj, names[-1], value)
def delattrs(obj, names):
u"""Soit un objet obj, et un nom de la forme "attr0.attr1....",
effectuer l'équivalent de l'opération:
del obj.attr0.attr1....
"""
if not isseq(names): names = names.split(".")
__check_names(names)
obj = getattrs(obj, names[:-1], True)
delattr(obj, names[-1])
def make_delegate(names, getter=True, setter=True, deleter=False):
if getter is True:
def getter(self):
return getattrs(self, names, True)
if setter is True:
def setter(self, value):
setattrs(self, names, value)
if deleter is True:
def deleter(self):
delattrs(self, names)
accessors = {}
if getter: accessors['fget'] = getter
if setter: accessors['fset'] = setter
if deleter: accessors['fdel'] = deleter
return property(**accessors)
def get__all__(module):
"""Retourner la valeur __all__ d'un module, ou la construire si cette
valeur n'est pas définie.
@rtype: tuple
"""
all = getattr(module, '__all__', None)
if all is None:
all = []
for key in module.__dict__.keys():
if key[0] != '_': all.append(key)
return tuple(all)
def import__module__(module_name, globals, locals=None, name=None):
"""Importer dans globals le module nommé module_name, en le nommant name.
Par défaut, name est le nom de base du module. par exemple, le module
"a.b.c" sera importé sous le nom "c"
"""
module = __import__(module_name, globals, locals)
basenames = module_name.split('.')
for basename in basenames[1:]:
module = getattr(module, basename)
if name is None: name = basenames[-1]
globals[name] = module
return [name]
def import__all__(module_name, globals, locals=None, *names):
"""Importer dans globals tous les objets du module nommé module_name
mentionnés dans names. Si names est vides, tous les objets sont importés
comme avec 'from module import *'
"""
module = __import__(module_name, globals, locals)
basenames = module_name.split('.')
for basename in basenames[1:]:
module = getattr(module, basename)
if not names: names = get__all__(module)
__all__ = []
for name in names:
globals[name] = getattr(module, name, None)
__all__.append(name)
return __all__