# -*- coding: UTF-8 -*-
'''
@summary:验证器
该模块提供了一个装饰器用于验证参数是否合法。使用方法为:
from validator import validParam,nullOk,multiType
@validParam(i=int)
def foo(i):
return i+1
编写验证器:
1. 仅验证类型:
@validParam(type, ...)
例如:
检查第一个位置的参数是否为int类型:
@validParam(int)
检查名为x的参数是否为int类型:
@validParam(x=int)
验证多个参数:
@validParam(int, int)
指定参数名验证:
@validParam(int, s=str)
针对*和**参数编写的验证器将验证这些参数实际包含的每个元素:
@validParam(varargs=int)
def foo(*varargs): pass
@validParam(kws=int)
def foo7(s, **kws): pass
2. 带有条件的验证:
@validParam((type, condition), ...)
其中,condition是一个表达式字符串,使用x引用待验证的对象;
根据bool(表达式的值)判断是否通过验证,若计算表达式时抛出异常,视为失败。
例如:
验证一个10到20之间的整数:
@validParam(i=(int, '10<x<20'))
验证一个长度小于20的字符串:
@validParam(s=(str, 'len(x)<20'))
验证一个年龄小于20的学生:
@validParam(stu=(Student, 'x.age<20'))
另外,如果类型是字符串,condition还可以使用斜杠开头和结尾表示正则表达式匹配。
验证一个由数字组成的字符串:
@validParam(s=(str, '/^\d*$/'))
3. 以上验证方式默认为当值是None时验证失败。如果None是合法的参数,可以使用nullOk()。
nullOk()接受一个验证条件作为参数。
例如:
@validParam(i=nullOk(int))
@validParam(i=nullOk((int, '10<x<20')))
也可以简写为:
@validParam(i=nullOk(int, '10<x<20'))
4. 如果参数有多个合法的类型,可以使用multiType()。
multiType()可接受多个参数,每个参数都是一个验证条件。
例如:
@validParam(s=multiType(int, str))
@validParam(s=multiType((int, 'x>20'), nullOk(str, '/^\d+$/')))
5. 如果有更复杂的验证需求,还可以编写一个函数作为验证函数传入。
这个函数接收待验证的对象作为参数,根据bool(返回值)判断是否通过验证,抛出异常视为失败。
例如:
def validFunction(x):
return isinstance(x, int) and x>0
@validParam(i=validFunction)
def foo(i): pass
这个验证函数等价于:
@validParam(i=(int, 'x>0'))
def foo(i): pass
@author: HUXI
@since: 2011-3-22
@change:
'''
import
inspect
import
re
class
ValidateException(Exception):
pass
def
validParam(
*
varargs,
*
*
keywords):
'''验证参数的装饰器。'''
varargs
=
map
(_toStardardCondition, varargs)
keywords
=
dict
((k, _toStardardCondition(keywords[k]))
for
k
in
keywords)
def
generator(func):
args, varargname, kwname
=
inspect.getargspec(func)[:
3
]
dctValidator
=
_getcallargs(args, varargname, kwname,
varargs, keywords)
def
wrapper(
*
callvarargs,
*
*
callkeywords):
dctCallArgs
=
_getcallargs(args, varargname, kwname,
callvarargs, callkeywords)
k, item
=
None
,
None
try
:
for
k
in
dctValidator:
if
k
=
=
varargname:
for
item
in
dctCallArgs[k]:
assert
dctValidator[k](item)
elif
k
=
=
kwname:
for
item
in
dctCallArgs[k].values():
assert
dctValidator[k](item)
else
:
item
=
dctCallArgs[k]
assert
dctValidator[k](item)
except
:
raise
ValidateException,\
(
'%s() parameter validation fails, param: %s, value: %s(%s)'
%
(func.func_name, k, item, item.__class__.__name__))
return
func(
*
callvarargs,
*
*
callkeywords)
wrapper
=
_wrapps(wrapper, func)
return
wrapper
return
generator
def
_toStardardCondition(condition):
'''将各种格式的检查条件转换为检查函数'''
if
inspect.isclass(condition):
return
lambda
x:
isinstance
(x, condition)
if
isinstance
(condition, (
tuple
,
list
)):
cls
, condition
=
condition[:
2
]
if
condition
is
None
:
return
_toStardardCondition(
cls
)
if
cls
in
(
str
,
unicode
)
and
condition[
0
]
=
=
condition[
-
1
]
=
=
'/'
:
return
lambda
x: (
isinstance
(x,
cls
)
and
re.match(condition[
1
:
-
1
], x)
is
not
None
)
return
lambda
x:
isinstance
(x,
cls
)
and
eval
(condition)
return
condition
def
nullOk(
cls
, condition
=
None
):
'''这个函数指定的检查条件可以接受None值'''
return
lambda
x: x
is
None
or
_toStardardCondition((
cls
, condition))(x)
def
multiType(
*
conditions):
'''这个函数指定的检查条件只需要有一个通过'''
lstValidator
=
map
(_toStardardCondition, conditions)
def
validate(x):
for
v
in
lstValidator:
if
v(x):
return
True
return
validate
def
_getcallargs(args, varargname, kwname, varargs, keywords):
'''获取调用时的各参数名-值的字典'''
dctArgs
=
{}
varargs
=
tuple
(varargs)
keywords
=
dict
(keywords)
argcount
=
len
(args)
varcount
=
len
(varargs)
callvarargs
=
None
if
argcount <
=
varcount:
for
n, argname
in
enumerate
(args):
dctArgs[argname]
=
varargs[n]
callvarargs
=
varargs[
-
(varcount
-
argcount):]
else
:
for
n, var
in
enumerate
(varargs):
dctArgs[args[n]]
=
var
for
argname
in
args[
-
(argcount
-
varcount):]:
if
argname
in
keywords:
dctArgs[argname]
=
keywords.pop(argname)
callvarargs
=
()
if
varargname
is
not
None
:
dctArgs[varargname]
=
callvarargs
if
kwname
is
not
None
:
dctArgs[kwname]
=
keywords
dctArgs.update(keywords)
return
dctArgs
def
_wrapps(wrapper, wrapped):
'''复制元数据'''
for
attr
in
(
'__module__'
,
'__name__'
,
'__doc__'
):
setattr
(wrapper, attr,
getattr
(wrapped, attr))
for
attr
in
(
'__dict__'
,):
getattr
(wrapper, attr).update(
getattr
(wrapped, attr, {}))
return
wrapper
#===============================================================================
# 测试
#===============================================================================
def
_unittest(func,
*
cases):
for
case
in
cases:
_functest(func,
*
case)
def
_functest(func, isCkPass,
*
args,
*
*
kws):
if
isCkPass:
func(
*
args,
*
*
kws)
else
:
try
:
func(
*
args,
*
*
kws)
assert
False
except
ValidateException:
pass
def
_test1_simple():
#检查第一个位置的参数是否为int类型:
@validParam
(
int
)
def
foo1(i):
pass
_unittest(foo1,
(
True
,
1
),
(
False
,
's'
),
(
False
,
None
))
#检查名为x的参数是否为int类型:
@validParam
(x
=
int
)
def
foo2(s, x):
pass
_unittest(foo2,
(
True
,
1
,
2
),
(
False
,
's'
,
's'
))
#验证多个参数:
@validParam
(
int
,
int
)
def
foo3(s, x):
pass
_unittest(foo3,
(
True
,
1
,
2
),
(
False
,
's'
,
2
))
#指定参数名验证:
@validParam
(
int
, s
=
str
)
def
foo4(i, s):
pass
_unittest(foo4,
(
True
,
1
,
'a'
),
(
False
,
's'
,
1
))
#针对*和**参数编写的验证器将验证这些参数包含的每个元素:
@validParam
(varargs
=
int
)
def
foo5(
*
varargs):
pass
_unittest(foo5,
(
True
,
1
,
2
,
3
,
4
,
5
),
(
False
,
'a'
,
1
))
@validParam
(kws
=
int
)
def
foo6(
*
*
kws):
pass
_functest(foo6,
True
, a
=
1
, b
=
2
)
_functest(foo6,
False
, a
=
'a'
, b
=
2
)
@validParam
(kws
=
int
)
def
foo7(s,
*
*
kws):
pass
_functest(foo7,
True
, s
=
'a'
, a
=
1
, b
=
2
)
def
_test2_condition():
#验证一个10到20之间的整数:
@validParam
(i
=
(
int
,
'10<x<20'
))
def
foo1(x, i):
pass
_unittest(foo1,
(
True
,
1
,
11
),
(
False
,
1
,
'a'
),
(
False
,
1
,
1
))
#验证一个长度小于20的字符串:
@validParam
(s
=
(
str
,
'len(x)<20'
))
def
foo2(a, s):
pass
_unittest(foo2,
(
True
,
1
,
'a'
),
(
False
,
1
,
1
),
(
False
,
1
,
'a'
*
20
))
#验证一个年龄小于20的学生:
class
Student(
object
):
def
__init__(
self
, age):
self
.age
=
age
@validParam
(stu
=
(Student,
'x.age<20'
))
def
foo3(stu):
pass
_unittest(foo3,
(
True
, Student(
18
)),
(
False
,
1
),
(
False
, Student(
20
)))
#验证一个由数字组成的字符串:
@validParam
(s
=
(
str
, r
'/^\d*$/'
))
def
foo4(s):
pass
_unittest(foo4,
(
True
,
'1234'
),
(
False
,
1
),
(
False
,
'a1234'
))
def
_test3_nullok():
@validParam
(i
=
nullOk(
int
))
def
foo1(i):
pass
_unittest(foo1,
(
True
,
1
),
(
False
,
'a'
),
(
True
,
None
))
@validParam
(i
=
nullOk(
int
,
'10<x<20'
))
def
foo2(i):
pass
_unittest(foo2,
(
True
,
11
),
(
False
,
'a'
),
(
True
,
None
),
(
False
,
1
))
def
_test4_multitype():
@validParam
(s
=
multiType(
int
,
str
))
def
foo1(s):
pass
_unittest(foo1,
(
True
,
1
),
(
True
,
'a'
),
(
False
,
None
),
(
False
,
1.1
))
@validParam
(s
=
multiType((
int
,
'x>20'
), nullOk(
str
,
'/^\d+$/'
)))
def
foo2(s):
pass
_unittest(foo2,
(
False
,
1
),
(
False
,
'a'
),
(
True
,
None
),
(
False
,
1.1
),
(
True
,
21
),
(
True
,
'21'
))
def
_main():
d
=
globals
()
from
types
import
FunctionType
print
for
f
in
d:
if
f.startswith(
'_test'
):
f
=
d[f]
if
isinstance
(f, FunctionType):
f()
if
__name__
=
=
'__main__'
:
_main()