事情的原由是感受目前項目中的參數校驗方法寫的太簡單了,不少時候須要在server層再if else處理,因而就動手準備寫一個好用一點的,能夠自定義校驗參數規則的參數校驗器,考慮到要能夠靈活的配置就萌生了大概的印象:前端
因而第一個版本實現以下:python
# -*- 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, request app = 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 todo print ("in", args, kwargs) args_template = f.func_code.co_varnames print("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]] = x sorted_kwargs = sort_by_co_varnames(args_template, kwargs) args_dict.update(sorted_kwargs) print("args_dict:", args_dict) # need if 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 # strip if strip: for k in args_dict: if args_dict[k]: args_dict[k] = args_dict[k].strip() # length if 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] # check if 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_func if 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_2 return wraps_1 def 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上有啥好的實現.
找到了以下幾個項目:git
這裏說說validator.py ,給個例子github
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收到的參數,而後直接校驗了,有問題就直接返回錯誤給調用者,因而有了版本2json
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) # strip if 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_func if diy_func: for k in args_dict: if k in diy_func: args_dict[k] = diy_func[k](args_dict[k]) # rules if 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_func return 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的複雜封裝,很差改啊,這樣太繞了,仍是直接調用函數吧,不玩裝飾器了.因而又了版本3flask
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 # strip if strip: for k in args_dict: if isstr(args_dict[k]): args_dict[k] = args_dict[k].strip() # default if default[0]: for x in args_dict: if args_dict[x] == "": args_dict[x] = default[1] # diy_func if diy_func: for k in args_dict: if k in diy_func: args_dict[k] = diy_func[k](args_dict[k]) # rules if rules: result, err = validate(rules, args_dict) if not result: return False, err except Exception as e: print("verify_args catch err: ", traceback.format_exc()) # TODO if release: return True, args_dict_copy else: 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) # 姿式 2 if 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還不行嗎.因而有了版本4app
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 # fixme try: args_template = f.func_code.co_varnames except: args_template = f.__code__.co_varnames args_dict = OrderedDict() try: for i, x in enumerate(args): args_dict[args_template[i]] = x sorted_kwargs = sort_by_co_varnames(args_template, kwargs) args_dict.update(sorted_kwargs) # strip if strip: for k in args_dict: if isstr(args_dict[k]): args_dict[k] = args_dict[k].strip() # default if default[0]: for x in args_dict: if args_dict[x] == "": args_dict[x] = default[1] # diy_func if diy_func: for k in args_dict: if k in diy_func: args_dict[k] = diy_func[k](args_dict[k]) # rules if rules: result, err = validate(rules, args_dict) if not result: return False, err 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 decorated_func return 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}
哎,就這樣吧,打包一下,隨便選吧,愛用哪一個用哪一個,反正我都寫出來了.簡單說就是:dom
嗯,最後仍是分享一下到git上吧, https://github.com/aleimu/flask-validator 喜歡的點個star.curl