好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

使用jsonschema通过装饰器优雅地对Python函数参数进行校验

通常,我们在开发Web应用时,会使用jsonschema对Request入参进行校验。

那么如果仅是常规的Python函数,又该如何进行校验。

我们希望能够做到:

对业务代码侵入性小; 校验代码与业务代码通过约定进行匹配,可以独立调整; 代码阅读流畅。

自然地我想到使用Python的装饰器来实现。

先放出装饰器代码

 #!/usr/bin/python
# -*- coding: UTF-8 -*-
import functools
import inspect
from typing import Callable

from jsonschema import validate, exceptions


def validated(validator, role: str = 'FUNC'):
    def decorator(func: Callable):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            args_spec = inspect.getfullargspec(func)

            instance = (args_spec.kwonlydefaults or {}).copy()  # 取func定义的缺省值
            instance.update(kwargs)  # 使用实际的kw参数更新默认值(如果有的话)
            if args_spec.args is not None:  # 更新实际传入的args
                instance.update({arg_name: args[index] for index, arg_name in enumerate(args_spec.args) if arg_name != 'self'})
            schema = validator.get_schema(role=role)
            schema['additionalProperties'] = True
            try:
                validate(instance, schema)
            except exceptions.ValidationError as ex:
                arg_name = ex.relative_path[-1]
                raise ValueError('Parameter {0!r} - {1}'.format(arg_name, ex.message)) from None
            else:
                return func(*args, **kwargs)
        return wrapper
    return decorator
 

代码逻辑说明:

通过inspect获取函数的反射信息 尝试构造一个待审查的json实例instance 2.1 instance初始化为func的缺省参数(带参数值) 2.2 使用调用func时的实际关键字参数kwargs更新instance 2.3 如果func存在args,使用func中定义的{参数名:实际参数值}更新instance 从装饰器传入的validator(这里采用jsl包中的jsl.Document)取出校验schma 使用jsonschema.validate执行校验,成功的话执行原func,否则抛出首个不匹配的参数及错误详情

来一个简单的使用代码示例:

 # schema.py
...
class CreateAccountOfKeyPairSchema(Document):
    app = StringField(required=True, pattern=r'^[^!#$%&*,]*$')
    subject = StringField(required=True, pattern=r'^[^!#$%&*,]*$')
    custom = StringField(required=True, pattern=r'^[^!#$%&*,]*$')
    data = StringField(required=False)
...

# service.py
...
    @validated(CreateAccountOfKeyPairSchema)
    def create_account(self, app: str, subject: str, custom: str, data: str) -> str:
        biz = TenAccountInfo()
        biz.id = self._account_info_id_generator.next()
        biz.app = app
        biz.subject = subject
        biz.custom = custom
        biz.data = data
        biz.invalid = 0
        biz.version = self._version_generator.next()

        ret = biz.id
        self._account_info_dao.save(biz)
        return ret
...
 

查看更多关于使用jsonschema通过装饰器优雅地对Python函数参数进行校验的详细内容...

  阅读:33次