iro/validate.py
author Sandro Knauß <knauss@netzguerilla.net>
Wed, 25 Apr 2012 00:06:27 +0200
branchdevel
changeset 276 4841b443f1fd
parent 267 ef2df3f23cb1
child 282 50cc13814bfb
permissions -rw-r--r--
validate: adding vInteger

from twisted.internet import defer

import re
from decorator import decorator
from inspect import getcallargs
import types

from .error import ValidateException, InvalidTel, InvalidMail
from .telnumber import Telnumber

def vBool(value, field):
    '''Validate function  for boolean values
    
    :return: **value**
    :raises: :exc:`iro.error.ValidateException`
    '''
    t=[True, 1, "true", "True", "TRUE"]
    f=[False, 0, "false", "False", "FALSE"]
    if value in t:
        return True
    elif value in f:
        return False
    else:
        raise ValidateException(field=field, msg='%s is not boolean' % field)

def vNumber(value,field, nval,  minv=None, maxv=None, none_allowed=False):
    """validate function for integer values.

    :param integer minv: minimum value
    :param integer maxv: maximum value
    :param func nval: function that give back a number
    :param boolean none_allowed: is None or empty string allowed
    :return: **value**
    :raises: :exc:`iro.error.ValidateException`
    """
    if none_allowed and value in [None,'']:
        return None

    try:
        ret = nval(value)
    except ValueError:
        raise ValidateException(field=field)
    except TypeError:
        raise ValidateException(field=field)

    if minv is not None and ret < minv:
        raise ValidateException(field=field)

    if maxv is not None and ret > maxv:
        raise ValidateException(field=field)

    return ret

def vInteger(value, field, minv=None, maxv=None, none_allowed=False):
    """validate function for integer values.

    :param integer minv: minimum value
    :param integer maxv: maximum value
    :param boolean none_allowed: is None or empty string allowed
    :return: **value**
    :raises: :exc:`iro.error.ValidateException`

    see also :func:vNumber
    """
    return vNumber(value, field, int, minv, maxv, none_allowed)

def vFloat(value, field, minv=None, maxv=None, none_allowed=False):
    """validate function for float values.

    :param integer minv: minimum value
    :param integer maxv: maximum value
    :param boolean none_allowed: is None or empty string allowed
    :return: **value**
    :raises: :exc:`iro.error.ValidateException`

    see also :func:vNumber
    """
    return vNumber(value, field, float, minv, maxv, none_allowed)



def vHash(value,field,minlength=None,maxlength=None):
    '''Validate function for hash values
    
    :param integer minlength: minimum length of value string
    :param integer maxlength: maximum length of value string
    :return: **value**
    :raises: :exc:`iro.error.ValidateException`
    '''
    if not re.match(r'^[a-f0-9]*$', value.lower()):
        raise ValidateException(field=field)
    if minlength and len(value)<minlength:
        raise ValidateException(field=field)
    if maxlength and len(value)>maxlength:
        raise ValidateException(field=field)
    return value.lower()

def vTel(value,field):
    '''Validator for telefon numbers
    :return: **value**
    :raises: :exc:`iro.error.InvalidTel`
    '''

    ret = []
    for v in value:
        try:
            tel=Telnumber(v)
            if tel not in ret:
                ret.append(tel)
        except InvalidTel, e:
            e.field=field
            raise e
    return ret

def vEmail(value, field, allowString=True, allowList=True):
    '''validator for emailadresses (see wikipeda for strange mailadresses and RFC3696)
    
    valid:

    - "very.(),:;<>[]\\".VERY.\\"very@\\\ \\"very\\".unusual"@strange.example.com
    - ""@example.org
    - "very.unusual.@.unusual.com"@example.com'

    not valid:
    
    - Abc.@example.com
    - Abc..123@example.com
    - thisis."notallowed@example.com
    - this\\ still\\"not\\allowed@example.com
   
    :param boolean allowString: value can be a string -> a string is returned
    :param boolean allowList: value is a a list -> a list is returned
    :return: **value**
    :raises: :exc:`iro.error.ValidateException`, :exc:`iro.error.InvalidMail`
    '''
    ret = []
    str_=False
    if type(value) is types.StringType:
        if not allowString:
            raise ValidateException('%s must be a list of email addresses.'%field)
        str_=True
        value=[value]
    elif not allowList:
        raise ValidateException('%s must be a email address - No list of email addresses.'%field)
    for v in value:
        parts= re.match(r'^(.*)@(.+?)$',v)
        if not parts:
            raise InvalidMail(v,field)
        local=parts.group(1)
        domain=parts.group(2)
        
        if not re.match(r'^(\[[0-9\.]{7,16}\]|\[[0-9a-f:]{3,}\]|([a-z0-9+\-%_]+\.)+[a-z]{2,6})$',domain.lower()):
            raise InvalidMail(v,field)
       
        if local == "":
            ret.append(v)
            continue
    
        if local.startswith(".") or local.endswith("."):
            raise InvalidMail(v,field)
        unquote = True
        parts = local.split('"')
        c=0
        i=0
        for part in parts:
            if unquote and part != "":                     #unquoted is not allowd so much
                if not re.match(r'^[^\\,\[\];\(\)@<>: ]+$',part) or ".." in part:
                    raise InvalidMail(v,field)
            if i == 0:
                if unquote and part != "" and len(parts) > 1 and part[-1] != '.': #quoted parts must be seperated by a dot
                    raise InvalidMail(v,field)
                unquote = not unquote
                c+=1
            elif part == ''  or part[-1] != "\\":
                if unquote and part != "":         #quoted parts must be seperated by a dot
                    if part[0] != ".":
                        raise InvalidMail(v,field)
                    if i < len(parts)-1 and part[-1] != '.':
                        raise InvalidMail(v,field)
                unquote = not unquote
                c += 1
            i += 1
        if c%2 == 0 and c > 1:                        #no single quote allowed
            raise InvalidMail(v,field)
        if v not in ret:
            ret.append(v)
    if str_:
        ret=ret[0]
    return ret

def validate(kwd,func, need=True,*args,**kargs):
    '''validate decorator.

    :param string kwd: keyword to validate
    :param func func: validate function
    :param boolean need: ``False`` -- ``None`` is a valid value for kwd
    :params args: arguments for validate function
    :params kargs: keyword arguments for validate function

    .. note:: this decorator can handle function that returns a defer object.
    
    use it like this::

      @validate(kwd=userhash, func=vuserhash)
      f(userhash)
    
    that will validate ``userhash`` with the function **vuserhash**.
    Every validate function should raise an Exception, if the the value is not valid.
    All **args** and **kargs** are used to call the validate function.
    if **need** is True, the kwd can't be `None`.
    '''
    @decorator
    def v(f,*a,**k):
        kp=getcallargs(f,*a,**k)
        def dfunc(*x,**y):
            return None
        try:
            if kp[kwd] is not None:
                dfunc=func
            elif need:
                raise ValidateException(field=kwd,msg="%s is nessasary"%kwd)
        except KeyError:
            if need:
                raise ValidateException(field=kwd,msg="%s is nessasary"%kwd)
            kp[kwd] = None

        def _gotResult(value):
            kp[kwd] = value
            e = defer.maybeDeferred(f,**kp)
            return e
        d = defer.maybeDeferred(dfunc, kp[kwd],kwd,*args,**kargs)
        return d.addCallback(_gotResult)
    return v