基于装饰器的validator
实现效果
@pv.validate(
a=required(),
b=t_int(),
c=iin(["M", "F"])
)
def foo(a, b, c):
print(a, b, c)
@pv.validate(
a=pv.t_list(pv.t_int()),
b=pv.t_list(pv.like({'name': pv.required()}))
)
def tt(a, b):
print(locals())
@pv.validate(
vip=pv.required(),
vport=pv.t_int(),
protocol=pv.iin(['TCP', 'UDP']),
real_servers=pv.t_list_n_empty(pv.like({'ip': pv.t_str(), 'weight': pv.t_int(0, 100)}))
)
async def foo(self, vip, vport, protocol, real_servers):
...
方案
需要解决的问题:
- 如何获取调用参数;
- 如何将参数和形参名对应;
def decorator(func):
def inner(*args, **kwargs):
params = kwargs
# 获取参数名
args_names = inspect.getargs(func.__code__).args
# 将参数名和参数进行对应
for ind, item in enumerate(args):
params[args_names[ind]] = item
# todo: 加验证逻辑
# 将参数结构,传给func
return func(**params)
return inner
@decorator
def foo(a, b, k1=1, k2=2):
print(a, b, k1, k2)
foo(1,2)
foo(1, 2, 3, 4)
加上验证逻辑后完整代码如下:
def validate(**cond):
"""
装饰器,校验函数参数
"""
def outer(func):
def inner(*args, **kwargs):
co_varnames = inspect.getargs(func.__code__).args
print(args, kwargs, co_varnames)
for k, cond_func in cond.items():
v = kwargs.get(k)
if not v:
if k not in co_varnames:
raise Exception(f'unexpect validate key "{k}"')
v = args[co_varnames.index(k)]
cond_func(k, v)
return func(*args, **kwargs)
return inner
return outer
def required():
def inner(key, value):
if value is None:
raise Exception(f'{key} is requires but get {value}')
return inner
def iin(list):
def inner(key, value):
if value not in list:
raise Exception(f'{key} must be in {list} but get {value}')
return inner
def is_type(ty):
def inner(key, value):
if not isinstance(value, ty):
raise Exception(
f'{key} must be {ty} type but get {type(value)} type {value}')
return inner
def t_int(start=-float('inf'), end=float('inf'), fallback=(float('inf'), -float('inf'))):
def inner(key, value):
is_type(int)(key, value)
# 满足任一范围即可
if fallback[0] <= value <= fallback[1]:
return
if not (start <= value <= end):
tip_start = f'>={start}' if start != -float('inf') else ''
tip_end = f'<={end}' if end != float('inf') else ''
fallback_tip = f" or between ({fallback[0]},{fallback[1]})" if fallback else ''
raise Exception(
f'{key} must {" and ".join([tip_start, tip_end])}{fallback_tip} but get {value}')
return inner
def t_str():
return is_type(str)
def t_str_not_empty():
def inner(key, value):
is_type(str)(key, value)
if not value.strip():
raise Exception(f'{key} cannot be empty')
return inner
def t_list(v_f=None):
def inner(key, value):
if not isinstance(value, list):
raise Exception(
f'{key} must be list type but get {type(value)} type {value}')
if v_f:
for i in value:
v_f(key, i)
return inner
def t_list_n_empty(v_f=None):
def inner(key, value):
if not isinstance(value, list):
raise Exception(
f'{key} must be list type but get {type(value)} type {value}')
if len(value) == 0:
raise Exception(f'{key} cannot be empty')
if v_f:
for i in value:
v_f(key, i)
return inner
def like(obj):
def inner(key, value):
if not isinstance(value, dict):
raise Exception(f'{key} must be liken {obj} type but get {value}')
for k, v_f in obj.items():
v_f(f'{key}.{k}', value.get(k))
return inner
# test
@validate(
a=required(),
b=t_int(),
c=iin(["M", "F"])
)
def foo(a, b, c):
print(a, b, c)
foo(1, 'hello', 'M')