事情的起因是感觉目前项目中的参数校验方法写的太简单了,很多时候需要在server层再if else处理,于是就动手准备写一个好用一点的,可以自定义校验参数规则的参数校验器,考虑到要可以灵活的配置就萌生了大概的印象:

  1. 使用map - 参数A:ruleA,参数B-ruleB..等等,对参数进行规则绑定
  2. Python装饰器
  3. 可扩展,可以自定义校验规则

于是第一个版本实现如下:

版本1


# -*- coding:utf-8 -*-
__author__ = "aleimu"
__date__ = "2018-12-6"
__doc__ = "一个实用的入参校验装饰器--针对目前,前端 url?&a=1&b=2或-d'a=1&b=2c=qwe'形式的非json(所有参数都是str类型)" \"入参的校验"import copy
import traceback
from collections import OrderedDict
from functools import wraps
from flask import Flask, json, jsonify, requestapp = Flask(__name__)def verify_args(need=None, length=None, check=None, strip=True, default=(False, None), diy_func=None, release=False):"""约束:1. 简化了传参校验,使用位置传参或者关键词传参(一个参数对应一个参数),不允许使用one to list等python高级传参特性2. 所有的参数都是str/unicode类型的,前端没有使用json带参数类型的入参方式:param need: 必须参数,且不能为None或者"":param length: 参数长度范围:param check:  str的常用类方法/属性如下:isalnum 判断字符串中只能由字母和数字的组合,不能有特殊符号isalpha 字符串里面都是字母,并且至少是一个字母,结果就为真,(汉字也可以)其他情况为假isdigit 函数判断是否全为数字:param strip:对字段进行前后过滤空格:param default:将"" 装换成None:param diy_func:自定义的对某一参数的校验函数格式: {key:func},类似check, diy_func={"a": lambda x: x + "aa"}):param release:发生参数校验异常后是否依然让参数进入主流程函数:return:"""def wraps_1(f):@wraps(f)def wraps_2(*args, **kwargs):if release:args_bak = args[:]kwargs_bak = copy.deepcopy(kwargs)  # 下面流程异常时,是否直接使用 原参数传入f todoprint ("in", args, kwargs)args_template = f.func_code.co_varnamesprint("args_template:", args_template)args_dict = OrderedDict()req_args_need_list = []req_args_types_list = []try:for i, x in enumerate(args):args_dict[args_template[i]] = xsorted_kwargs = sort_by_co_varnames(args_template, kwargs)args_dict.update(sorted_kwargs)print("args_dict:", args_dict)# needif need:for k in need:if k not in args_dict:req_args_need_list.append(k)else:if args_dict[k] == None or args_dict[k] == "":req_args_need_list.append(k)if req_args_need_list:return False, "%s is in need" % req_args_need_list# stripif strip:for k in args_dict:if args_dict[k]:args_dict[k] = args_dict[k].strip()# lengthif length:for k in args_dict:if k in length:if not (len(args_dict[k]) >= length[k][0] and len(args_dict[k]) <= length[k][1]):return False, "%s length err" % k# default:if default[0]:for x in args_dict:if args_dict[x] == "":args_dict[x] = default[1]# checkif check:for k in check:check_func = getattr(type(args_dict[k]), check[k], None)if not (k in args_dict and check_func and check_func(args_dict[k])):req_args_types_list.append(k)if req_args_types_list:return False, "%s type err" % req_args_types_list# diy_funcif diy_func:for k in args_dict:if k in diy_func:args_dict[k] = diy_func[k](args_dict[k])except Exception as e:print("verify_args catch err: ", traceback.format_exc())if release:return f(*args_bak, **kwargs_bak)else:return False, str(e)return f(*args_dict.values())return wraps_2return wraps_1def sort_by_co_varnames(all_args, kwargs):new_ordered = OrderedDict()for x in all_args:if x in kwargs:new_ordered[x] = kwargs[x]return new_ordered@app.route("/", methods=["GET", "POST", "PUT"])
def index():a = request.values.get("a")b = request.values.get("b")c = request.values.get("c")d = request.values.get("d")e = request.values.get("e")f = request.values.get("f")g = request.values.get("g")status, data = todo(a, b, c, d, e=e, f=f, g=g)if status:return jsonify({"code": 200, "data": data, "err": None})else:return jsonify({"code": 500, "data": None, "err": data})@verify_args(need=['a', 'b', 'c'], length={"a": (6, 50)}, strip=True,check={"b": 'isdigit', "c": "isalnum"},default=(True, None),diy_func={"a": lambda x: x + "aa"})
def todo(a, b, c, d, e='  1  ', f='2    ', g=''):return True, {"a": a, "b": b, "c": c, "d": d, "e": e, "f": f, "g": g}if __name__ == "__main__":app.run(host='0.0.0.0', port=6000, debug=True)"""
# curl "http://127.0.0.1:6000/" -d "pwd=123&a=1111111&b=2&c=3&d=d&e=eeeeee&f=12345&g="
{"code": 200,"data": {"a": "1111111aa","b": "2","c": "3","d": "d","e": "eeeeee","f": "12345","g": null},"err": null
}# curl "http://127.0.0.1:6000/" -d "pwd=123&a=1111111&b=2&c=3346()*&d=d&e=eeeeee&f=12345&g="
{"code": 500,"data": null,"err": "['c'] type err"
}# curl "http://127.0.0.1:6000/" -d "pwd=123&a=1111111&b=2&c=&d=d&e=eeeeee&f=12345&g="
{                                                                                        "code": 500,                                                                           "data": null,                                                                          "err": "['c'] is in need"
}   # curl "http://127.0.0.1:6000/" -d "pwd=123&a=1111111&b=2&c=  1  &d=d&e=eeeeee&f=12345&g="
{                                                                                           "code": 200,                                                                              "data": {                                                                                 "a": "1111111aa",                                                                       "b": "2",                                                                               "c": "1",                                                                               "d": "d",                                                                               "e": "eeeeee",                                                                          "f": "12345",                                                                           "g": null                                                                               },                                                                                        "err": null
}
"""

第一个版本切合了当前项目中经常遇到的校验问题,实现起来较简单,基本满足要求.
想要更通用点,更多校验规则一些,就需要每次为verify_args添加参数写if else了,嗯.....有点不优雅啊,于是去看github上有啥好的实现.
找到了如下几个项目:

  1. https://github.com/keleshev/s... 嗯,1.6K的star,思路一致,实现的优雅,但是不好扩展啊....
  2. https://github.com/kvesteri/v... 额,Python Data Validation for Humans™. not for me....
  3. https://github.com/mansam/val... 嗯,思路一致,实现也简单,挺好扩展的,就用它了!

这里说说validator.py ,给个例子


from validator import Required, Not, Truthy, Blank, Range, Equals, In, validate# let's say that my dictionary needs to meet the following rules...
rules = {"foo": [Required, Equals(123)],"bar": [Required, Truthy()],"baz": [In(["spam", "eggs", "bacon"])],"qux": [Not(Range(1, 100))] # by default, Range is inclusive
}# then this following dict would pass:
passes = {"foo": 123,"bar": True, # or a non-empty string, or a non-zero int, etc..."baz": "spam","qux": 101
}
print validate(rules, passes)
# (True, {})# but this one would fail
fails = {"foo": 321,"bar": False, # or 0, or [], or an empty string, etc..."baz": "barf","qux": 99
}
print validate(rules, fails)
# (False,
#  {
#  'foo': ["must be equal to '123'"],
#  'bar': ['must be True-equivalent value'],
#  'baz': ["must be one of ['spam', 'eggs', 'bacon']"],
#  'qux': ['must not fall between 1 and 100']
#  })

嗯,使用第一个版本封装一下validator.py就好了!考虑到需要写个dome来试试,就选了flask,嗯,对了,先去github 上搜一下 flask validator 没准已经有现成的呢,实现思路基本一致,但是......前几个star多的都不令人满意,还是自己造轮子吧.
先实现常见的在route上加装饰器版本,这样的话,就可以直接接收request收到的参数,然后直接校验了,有问题就直接返回错误给调用者,于是有了版本2

版本2


rules_example = {"a": [Required, Equals("123")],  # foo must be exactly equal to 123"b": [Required, Truthy()],  # bar must be equivalent to True"c": [In(["spam", "eggs", "bacon"])],  # baz must be one of these options"d": [Not(Range(1, 100))],  # qux must not be a number between 1 and 100 inclusive"e": [Length(0, maximum=5)],"f": [Required, InstanceOf(str)],"g": [Required, Not(In(["spam", "eggs", "bacon"]))],"h": [Required, Pattern("\d\d\%")],"i": [Required, GreaterThan(1, reverse=True, auto=True)],  # auto 自动转换成float类型来做比较"j": [lambda x: x == "bar"],"k": [Required, Isalnum()],  # 判断字符串中只能由字母和数字的组合,不能有特殊符号"l": [Required, Isalpha()],  # 字符串里面都是字母,并且至少是一个字母,结果就为真,(汉字也可以)其他情况为假"m": [Required, Isdigit()],  # 判断字符串是否全为数字
}def validator_wrap(rules, strip=True, diy_func=None):"""装饰器版 - 只能检测是否符合规则,不能修改参数:param rules:参数的校验规则,map:param strip:对字段进行前后空格检测:param diy_func:自定义的对某一参数的校验函数格式: {key:func},类似check, diy_func={"a": lambda x: x=="aa"})"""def decorator(f):@wraps(f)def decorated_func(*args, **kwargs):try:args_dict = OrderedDict()if request.values:args_dict.update(request.values)if request.json:args_dict.update(request.json)# stripif strip:for k in args_dict:if args_dict[k] and isstr(args_dict[k]):if args_dict[k][0] == " " or args_dict[k][-1] == " ":return jsonify({"code": 500, "data": None, "err": "%s should not contain spaces" % k})# diy_funcif diy_func:for k in args_dict:if k in diy_func:args_dict[k] = diy_func[k](args_dict[k])# rulesif rules:result, err = validate(rules, args_dict)if not result:return jsonify({"code": 500, "data": None, "err": err})except Exception as e:print("verify_args catch err: ", traceback.format_exc())return jsonify({"code": 500, "data": None, "err": str(e)})return f(*args, **kwargs)return decorated_funcreturn decorator@app.route("/wrap", methods=["GET", "POST", "PUT"])
@validator_wrap(rules=rules_example, strip=True)  # 姿势 1:只能检测是否符合规则,不能修改参数,不符合就会直接返回json给调用者
def wrap_example():a = request.values.get("a")b = request.values.get("b")c = request.values.get("c")d = request.values.get("d")e = request.values.get("e")f = request.values.get("f")g = request.values.get("g")h = request.values.get("h")i = request.values.get("i")j = request.values.get("j")k = request.values.get("k")l = request.values.get("l")m = request.values.get("m")status, data = todo(a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h, i=i, j=j, k=k, l=l, m=m)if status:return jsonify({"code": 200, "data": data, "err": None})else:return jsonify({"code": 500, "data": None, "err": data})

好像挺好的,基本满足要求了,但是再route上加装饰器,那就改变不了参数的值了,虽然有些参数不一定符合要求,但是简单修补一下还是可以用的,还得继续寻找能够改变入参的方式,第一反应是在装饰器中修改request.values或者request.json的值,让进入到主函数后获取更新后的值,上下求索未得门径,request.value.update方法是被禁用的,继续看源码,后面的实现使用了dict的复杂封装,不好改啊,这样太绕了,还是直接调用函数吧,不玩装饰器了.于是又了版本3

版本3


def validator_func(rules, strip=True, default=(False, None), diy_func=None, release=False):"""函数版-返回dict,代替request.values/request.json:param rules:参数的校验规则,map:param strip:对字段进行前后过滤空格:param default:将"" 装换成None:param diy_func:自定义的对某一参数的校验函数格式: {key:func},类似check, diy_func={"a": lambda x: x + "aa"}):param release:发生参数校验异常后是否依然让参数进入主流程函数"""args_dict = OrderedDict()try:if request.values:args_dict.update(request.values)if request.json:args_dict.update(request.json)if release:args_dict_copy = copy.deepcopy(args_dict)  # 下面流程异常时,是否直接使用 原参数传入f # fixme# stripif strip:for k in args_dict:if isstr(args_dict[k]):args_dict[k] = args_dict[k].strip()# defaultif default[0]:for x in args_dict:if args_dict[x] == "":args_dict[x] = default[1]# diy_funcif diy_func:for k in args_dict:if k in diy_func:args_dict[k] = diy_func[k](args_dict[k])# rulesif rules:result, err = validate(rules, args_dict)if not result:return False, errexcept Exception as e:print("verify_args catch err: ", traceback.format_exc())  # TODOif release:return True, args_dict_copyelse:return False, str(e)return True, args_dict@app.route("/func", methods=["GET", "POST", "PUT"])
def func_example():result, request_args = validator_func(rules=rules_example, strip=True)  # 姿势 2if not result:return jsonify({"code": 500, "data": None, "err": request_args})a = request_args.get("a")b = request_args.get("b")c = request_args.get("c")d = request_args.get("d")e = request_args.get("e")f = request_args.get("f")g = request_args.get("g")h = request_args.get("h")i = request_args.get("i")j = request_args.get("j")k = request_args.get("k")l = request_args.get("l")m = request_args.get("m")status, data = todo(a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h, i=i, j=j, k=k, l=l, m=m)if status:return jsonify({"code": 200, "data": data, "err": None})else:return jsonify({"code": 500, "data": None, "err": data})

嗯,还行吧,就是不怎么优雅,还是有点喜欢装饰器版本,但是苦于能力有限,不想看ImmutableMultiDict,MultiDict的实现,还是将第一个版本融合一下吧,装饰route不行,装饰todo还不行吗.于是有了版本4

版本4


def validator_args(rules, strip=True, default=(False, None), diy_func=None, release=False):"""针对普通函数的参数校验的装饰器:param rules:参数的校验规则,map:param strip:对字段进行前后过滤空格:param default:将"" 装换成None:param diy_func:自定义的对某一参数的校验函数格式: {key:func},类似check, diy_func={"a": lambda x: x + "aa"}):param release:发生参数校验异常后是否依然让参数进入主流程函数"""def decorator(f):@wraps(f)def decorated_func(*args, **kwargs):if release:args_bak = args[:]kwargs_bak = copy.deepcopy(kwargs)  # 下面流程异常时,是否直接使用 原参数传入f # fixmetry:args_template = f.func_code.co_varnamesexcept:args_template = f.__code__.co_varnamesargs_dict = OrderedDict()try:for i, x in enumerate(args):args_dict[args_template[i]] = xsorted_kwargs = sort_by_co_varnames(args_template, kwargs)args_dict.update(sorted_kwargs)# stripif strip:for k in args_dict:if isstr(args_dict[k]):args_dict[k] = args_dict[k].strip()# defaultif default[0]:for x in args_dict:if args_dict[x] == "":args_dict[x] = default[1]# diy_funcif diy_func:for k in args_dict:if k in diy_func:args_dict[k] = diy_func[k](args_dict[k])# rulesif rules:result, err = validate(rules, args_dict)if not result:return False, errexcept Exception as e:print("verify_args catch err: ", traceback.format_exc())if release:return f(*args_bak, **kwargs_bak)else:return False, str(e)return f(*args_dict.values())return decorated_funcreturn decorator@validator_args(rules=rules_example, strip=True)  # 姿势 3
def todo(a, b, c, d, e, f, g, h, i, j, k, l, m):return True, {"a": a, "b": b, "c": c, "d": d, "e": e, "f": f, "g": g, "h": h, "i": i, "j": j, "k": k, "l": l,"m": m}

哎,就这样吧,打包一下,随便选吧,爱用哪个用哪个,反正我都写出来了.简单说就是:

  1. validator_func 针对flask的request.json/requests.values的参数校验以及修改,修改的方式有限,可以自己控制
  2. validator_wrap 是针对flask route的装饰器,针对request.json/requests.values的参数校验,只是校验,当然校验的方式可以自己写扩展
  3. validator_args 针对普通函数的参数校验以及修改,注意不要使用python传参的高级特性(一个参数对应多个值),这个方法可以脱离flask使用,所以如果需要就直接copy过去吧.

嗯,最后还是分享一下到git上吧, https://github.com/aleimu/flask-validator 喜欢的点个star.

Python 参数校验的进化相关推荐

  1. Python参数校验工具:validate.py

    一直都在找一个比较好用的参数校验工具包(Python),这次终于找到了就做个记录. 包名:validate.py 安装方式:(pip已收入, 当前最新版本是1.3.0) pip install val ...

  2. SpringMVC + Hibernate-Validator 参数校验

    2019独角兽企业重金招聘Python工程师标准>>> 前言: Web开发中,最为常见的场景就是前端表单数据.Json数据与后端实体类的绑定,即使JS能校验并阻止大部分的必填漏填的风 ...

  3. 2,Spring-mvc添加参数校验:避免参数格式不正确导致的400

    2019独角兽企业重金招聘Python工程师标准>>> 在有参数校验的基础上,新建异常处理类:(@ControllerAdvice("com.store.score.rpc ...

  4. 收货地址参数校验:收货人、邮编、地址、手机、固话等

    2019独角兽企业重金招聘Python工程师标准>>> 收货地址参数校验:收货人.邮编.地址.手机.固话等. 收货人: function checkshr() {var shr = ...

  5. 服务端json参数校验神器Json Schema

    目录 目录 json简介 服务端校验参数需求分析 json参数检验简单而繁琐方式 Json Schema Json Schema 入门 Json Schema 表达式 string Numeric t ...

  6. Spring Validation最佳实践及其实现原理,参数校验没那么简单!

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者:六点半起床 juejin.im/post/685654110 ...

  7. SpringBoot实现通用的接口参数校验

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:cipher juejin.im/post/5af3c25b ...

  8. 二胖写参数校验的坎坷之路

    背景 最近端午好久没有和二胖聚一聚了,于是约了二胖到人民广场去宰他一顿,正好最近他跳槽加薪了. 我:二胖听说你最近跳槽了,并且还是从传统软件公司跳到了互联网公司,工资是不是涨了一点啊,今天你请客哈. ...

  9. java 接口参数验证_SpringBoot实现通用的接口参数校验

    作者:cipher 来源:http://39sd.cn/560BA 本文介绍基于Spring Boot和JDK8编写一个AOP,结合自定义注解实现通用的接口参数校验. 缘由 目前参数校验常用的方法是在 ...

最新文章

  1. 去除RNA-seq数据批次效应
  2. 4.1 Qt绘图原理
  3. java实现二叉树遍历
  4. List(JDK1.7)(2)
  5. 特斯拉,谁给你的勇气在中国玩双标
  6. 线接触和面接触的区别_接触器是啥?跟继电器有啥区别,6大常见故障怎么处理...
  7. Android 输入法问题 解决三星s5830i或华为低端机输入法崩溃问题
  8. 为何数据视觉化越来越火
  9. 基于python的酒店管理系统_基于Web酒店管理系统的设计与实现
  10. 用python画简单的猴子画法_10种可爱的小猴子简笔画合集,分分钟被萌翻,一看就会画...
  11. 杂志订阅系统c语言,杂志订阅管理系统
  12. Linux系统基础命令详细总结,不定期更新,建议收藏
  13. 面对阿里云、腾讯云、百度云、青云等云计算平台,大家选择的时候更看重什么?
  14. Java之下载word文档,linux视频监控
  15. 手机微信群控源码二次开发
  16. Java之于Javascript就好比Car(汽车)之于Carpet(地毯)。
  17. 零件加工 贪心 题解
  18. 华三服务器怎样用hdm安装linux,H3C服务器配置HDM用户指南
  19. Fedora 20 安装
  20. chart控件两个Y轴

热门文章

  1. 山东大学2021级数据结构实验全集代码
  2. 调研分析-全球与中国电动垂直起降飞行器市场现状及未来发展趋势
  3. 【华为OD机试】消消乐游戏【2023 B卷|100分】
  4. 按字寻址与按字节寻址的区别
  5. webstorm解决svn冲突
  6. xdp原理分析及支持的驱动分析
  7. linux 用户和组之间的关系
  8. JavaScript 的三种书写位置,注释形式,变量的使用叙述
  9. OSI七层模型个人理解
  10. vs code 设置 保存自动格式化vue代码