基于装饰器的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')
posted @ 2022-04-18 09:56  Aloe_n  阅读(36)  评论(0编辑  收藏  举报