iro/validate.py
changeset 302 3f4bdea2abbf
parent 294 0e75bd39767d
child 312 42fd5075a5d1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/iro/validate.py	Thu Sep 27 17:15:46 2012 +0200
@@ -0,0 +1,258 @@
+# Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
+# 
+# This file is part of Iro.
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
+# #Software, and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from twisted.internet import defer
+
+import re
+from decorator import decorator
+try:
+    from inspect import getcallargs
+except ImportError:
+    from .inspect_getcallargs 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
+