Magazine Informatique

Un petit décorateur validateur python qui casse pas trois pattes à un canard mais presque.

Publié le 22 janvier 2013 par Mikebrant

Il n’est pas vraiment fini (fait dans le train) mais il devrait fonctionner correctement.
Il n’est pas super factorisé (j’ai essayé un petit peu cf le commentaire de la fonction `gen()`) mais ce n’était pas le but premier ;
qui était de faire un validateur simple et lisible.

Pour l’utiliser ( cf la doc de la fonction `param_validators`):

on décore la fonction à vérifier du décorateur validate().
validate() prend le même nombre de paramètre que la fonction.
Si dans la fonction les paramètres sont nommés, il faut également
les nommer dans validate().

Les paramètres *validateurs* sont des chaines contenant les types que doivent avoir les paramètres à valider.

Elles peuvent aussi contenir un pipe ‘|’.
Dans ce cas, à droite du pipe on peut mettre une petite expression python du genre : ‘x > 3′ ou ‘id(x)==1111′.   Si vous n’avez pas compris, regardez l’exemple.

Voici le code (j’ai du me taper l’indentation à la main, donc j’espère pas que j’en ai zappé):

#-*- coding:utf-8 -*-

class TypeDoesNotMatch(Exception):
    pass

class ValueDoesNotMatch(Exception):
    pass

#def gen(total_args, iterator, args, kwargs, key_to_add):
#   """
#   Fonction générique pour ajouter des clefs à total_args.
#   Mais en fait il faut faire des conditions sur `key_to_add`.
#   Donc est-ce que ça vaut le coup ?
#
#   #total_args = {}
#   #gen(total_args, func.__code__.co_varnames, args, kwargs, 'value')
#   #gen(total_args, total_args, vargs, kwwargs, 'isinstance')
#   #gen(total_args, total_args, vargs, kwwargs, 'evaluation')
#   """
#   i = 0
#   for arg in iterator:
#   if arg in kwargs:
#   total_args.setdefault(arg,{})[key_to_add] = kwargs[arg]
#   elif i < len(args):
#   total_args.setdefault(arg,{})[key_to_add] = args[i]
#   i += 1
#   return total_args

def split_validator(validator, sep='|'):
    """
    Split the validator into two elements.

    validator   -- (str) the validator.
    sep   -- (str) the separator.
    """
    if '|' in validator:
        return validator.split('|')[:2]
    return validator, None

def param_to_dict(varnames, args, kwargs):
    """
    Retrieve the parameters names and values from the function,
    and add them into a new dictionnary.

    `varnames`  -- (dict) names of the function params.
    `args`   -- (tuple) params values.
    `kwargs`   -- (dict) named params values.
    """
    total_args = {}
    i = 0
    for arg in varnames:
        if arg in kwargs:
            total_args[arg] = {'value':kwargs[arg]}
        elif i < len(args):
            total_args[arg] = {'value':args[i]}
            i += 1
    return total_args

def param_validators(total_args, args, kwargs):
    """
    Add the params validators.
    `total_args`   -- (dict) contains all the params names&values.
    `args`   -- (tuple) contains params validator.
    `kwargs`   -- (dict) contains named params validator.

    The validator can contains a pipe '|'.
    The left value is the type, whereas the right
    is a little expression like: 'x > 3' or 'id(x)==1111'.
    It will be concatened with 'lambda x :'.
    """
    i = 0
    for key,val in total_args.items():
        if key in kwargs:
            instance, evaluation = split_validator(kwargs[key])
            val['isinstance'] = eval(instance)
            if evaluation:
                val['evaluation'] = eval('lambda x : {0}'.format(evaluation))
        elif i < len(args):
            instance, evaluation = split_validator(args[i])
            val['isinstance'] = eval(instance)
            if evaluation:
                val['evaluation'] = eval('lambda x : {0}'.format(evaluation))
            i += 1
    return total_args

def param_verification(args):
    """
    Verify all the parameters requirements.
    `args`  -- (dict) contains all the parameters names, values, requirements.

     Errors:
    `TypesDoesNotMatch`
    `ValueDoesNotMatch`
    """
    for arg in args.values():
        if not isinstance(arg['value'], arg['isinstance']):
            raise TypeDoesNotMatch(arg['value'], arg['isinstance'])
        if arg.has_key('evaluation') and not arg['evaluation'](arg['value']):
            raise ValueDoesNotMatch(arg['value'], arg['evaluation'])

def validate(*vargs, **kwvargs):
    def decorator(func):
        def wrapper(*args, **kwargs):
            total_args = param_to_dict(func.__code__.co_varnames, args, kwargs)
            param_validators(total_args, vargs, kwvargs)
            param_verification(total_args)
            return func(*args, **kwargs)
        return wrapper
    return decorator

@validate('str|len(x)>=2', deux='float', trois='basestring')
def titi(un, deux=3.0, trois='tres'):
    pass

print "titi('to', deux=4.2, trois='trois' )"
titi('to', deux=4.2, trois='trois')
print "titi('to')"
titi('to')
print "titi('to', 12.)"
titi('to', 12.)
print "titi('to', trois='tres')"
titi('to', trois='tres')

Retour à La Une de Logo Paperblog

A propos de l’auteur


Mikebrant 9 partages Voir son profil
Voir son blog

l'auteur n'a pas encore renseigné son compte l'auteur n'a pas encore renseigné son compte