HiveNetCore.utils.validate_tool 源代码

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# Copyright 2018 黎慧剑
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
数据校验工具
@module validate_tool
@file validate_tool.py
"""

import sys
import os
import re
import datetime
import traceback
import copy
# 根据当前文件路径将包路径纳入, 在非安装的情况下可以引用到
sys.path.append(os.path.abspath(os.path.join(
    os.path.dirname(__file__), os.path.pardir, os.path.pardir)))
from HiveNetCore.generic import CResult


__MOUDLE__ = 'validate_tool'  # 模块名
__DESCRIPT__ = u'数据校验工具'  # 模块描述
__VERSION__ = '0.1.0'  # 版本
__AUTHOR__ = u'黎慧剑'  # 作者
__PUBLISH__ = '2019.11.15'  # 发布日期


# 标准验证错误信息, 可以在运行中改变成自定义的值
VALIDATE_ERR_MSG = {
    'str_not_null': '[$2] must not null',
    'str_len': 'Len of [$2] must between $4 and $5',
    'str_lenb': 'Bytes Len of [$2] must between $4 and $5',
    'str_is_int': '[$2]("$1") is not int number',
    'str_is_float': '[$2]("$1") is not float number',
    'str_is_email': '[$2]("$1") is not a Email address',
    'str_is_datetime': '[$2]("$1") is not match format "$4"',
    'str_check_date': '[$2]("$1") is not a legal date',
    'str_check_num_area': '[$2]("$1") must between $4 and $5',
    'str_check_float_size': 'The precision of [$2]("$1") not match: integer precision must less than $4 bits, decimal precision must less than $5 bits',
    'str_check_regex': '[$2]("$1") not match the regex "/$4/"',
    'check_fun': '[$2]("$1") validate failure by self define function',
    'not_null': '[$2] must not null',
    'DEFAULT_EXCEPT': '[$2]("$1") validate get exception: $4',
    'DEFAULT': '[$2]("$1") validate failure',
    'NOT_ENOUGH_VALUES': 'Object[$2] list has not enough values, at least $4 value',
    'OBJ_MUST_DICT': 'Object[$2] type must dict',
    'OBJ_MISS_KEY': '[$2] not found in object',

}

# 标识组合逻辑验证函数名清单(And/Or), 如果需要自定义组合函逻辑函数, 可修改该list
VALIDATE_LOGIC_FUN_NAME = [
    'And', 'Or', 'List'
]


[文档]class ValidateTool(object): """ 字符串基础校验函数 """ ############################# # 内部通用工具 #############################
[文档] @classmethod def datetime_fmt_to_py(cls, format_str): """ 将类Oracle格式的日期时间格式化字符串转换为Python格式 @param {string} format_str - 类Oracle格式的日期时间格式, 支持的格式如下: 'yyyy' : 四位年份 'yy' : 两位年份 'MM' : 两位月份, 0-9月前面需补0 'M' : 一位月份, 0-9月不能补0 'dd' : 两位日期, 这里限制到不能超过31 'd' : 一位日期, 这里限制到不能超过31 'hh' : 两位小时, 12小时制, 这里限制到不能超过12 'hh24' : 两位小时, 24小时制, 这里限制到不能超过23 'h' : 一位小时, 12小时制, 这里限制到不能超过12 'h24' : 一位小时, , 24小时制, 这里限制到不能超过23 'mm' : 两位分钟, 这里限制到不能超过60 'm' : 一位分钟, 这里限制到不能超过60 'ss' : 两位秒, 这里限制到不能超过60 's' : 一位秒, 这里限制到不能超过60 'p' : 早上, 下午标识, AM 或 PM @return {string} - 转换后的Python格式 %y 两位数的年份表示( 00-99) %Y 四位数的年份表示( 000-9999) %m 月份( 01-12) %d 月内中的一天( 0-31) %H 24小时制小时数( 0-23) %I 12小时制小时数( 01-12) %M 分钟数( 00=59) %S 秒( 00-59) %p 本地A.M.或P.M.的等价符 """ _format_str = format_str # 进行替换, 剔除有转义符的情况, 例如\y不转换 _format_str = re.sub(r'yyyy', '%Y', _format_str) # 四位年份 _format_str = re.sub(r'yy', '%y', _format_str) # 两位年份 _format_str = re.sub(r'MM', '%m', _format_str) # 月份 _format_str = re.sub(r'M', '%m', _format_str) # 月份 _format_str = re.sub(r'dd', '%d', _format_str) # 日期 _format_str = re.sub(r'(?<!%)d', '%d', _format_str) # 日期, 注意不会把%d重新替换了 _format_str = re.sub(r'hh24', '%H', _format_str) # 小时 _format_str = re.sub(r'h24', '%H', _format_str) # 小时 _format_str = re.sub(r'hh', '%I', _format_str) # 小时 _format_str = re.sub(r'h', '%I', _format_str) # 小时 _format_str = re.sub(r'(?<!%)mm', '%M', _format_str) # 分钟, 注意不会把%m重新替换了 _format_str = re.sub(r'(?<!%)m', '%M', _format_str) # 分钟, 注意不会把%m重新替换了 _format_str = re.sub(r'ss', '%S', _format_str) # 秒 _format_str = re.sub(r's', '%S', _format_str) # 秒 _format_str = re.sub(r'p', '%p', _format_str) # 早上, 下午标识, AM 或 PM # 返回结果 return _format_str
############################# # 字符串基础校验函数 #############################
[文档] @classmethod def str_not_null(cls, obj): """ 检查字符串是否非空 @param {string} obj - 要检查的字符串对象 @return {bool} - 校验结果, True-通过; False-不通过 """ return (obj is not None and obj != '' and type(obj) == str)
[文档] @classmethod def str_len(cls, obj, min_len, max_len): """ 检查字符串长度(包含两端的值) 注: 一个汉字长度为1 @param {string} obj - 要检查的字符串对象 @param {int} min_len - 最小长度 @param {int} max_len - 最大长度 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True if min_len is None: min_len = -1 if max_len is None: max_len = -1 _len = len(obj) return not (_len < min_len or (max_len > 0 and _len > max_len))
[文档] @classmethod def str_lenb(cls, obj, min_len, max_len, encoding='utf-8'): """ 检查字符串长度(包含两端的值) 注: gbk编码时一个汉字长度为2, utf-8编码时, 一个汉字长度为2-3 @param {string} obj - 要检查的字符串对象 @param {int} min_len - 最小长度 @param {int} max_len - 最大长度 @param {string} encoding='utf-8' - 字节编码 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True if min_len is None: min_len = -1 if max_len is None: max_len = -1 _len = len(obj.encode(encoding)) return not (_len < min_len or (max_len > 0 and _len > max_len))
[文档] @classmethod def str_is_int(cls, obj): """ 校验是否整数 @param {string} obj - 要检查的字符串对象 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True _rep = re.search('^[+-]{0,1}\\d{1,}$', obj) return False if _rep is None else True
[文档] @classmethod def str_is_float(cls, obj): """ 校验是否浮点数 @param {string} obj - 要检查的字符串对象 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True _rep = re.search('^[+-]{0,1}\\d{1,}\\.{0,1}\\d{0,}$', obj) return False if _rep is None else True
[文档] @classmethod def str_is_email(cls, obj): """ 校验是否Email @param {string} obj - 要检查的字符串对象 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True _rep = re.search('^[^@.]+@[^@]+$', obj) return False if _rep is None else True
[文档] @classmethod def str_is_datetime(cls, obj, format_str='yyyy-MM-dd'): """ 校验是否Email @param {string} obj - 要检查的字符串对象 @param {string} format_str='yyyy-MM-dd' - 时间日期格式, 支持的通配符包括: 'yyyy' : 四位年份 'yy' : 两位年份 'MM' : 两位月份, 0-9月前面需补0 'M' : 一位月份, 0-9月不能补0 'dd' : 两位日期, 这里限制到不能超过31 'd' : 一位日期, 这里限制到不能超过31 'hh' : 两位小时, 12小时制, 这里限制到不能超过12 'hh24' : 两位小时, 24小时制, 这里限制到不能超过23 'h' : 一位小时, 12小时制, 这里限制到不能超过12 'h24' : 一位小时, , 24小时制, 这里限制到不能超过23 'mm' : 两位分钟, 这里限制到不能超过60 'm' : 一位分钟, 这里限制到不能超过60 'ss' : 两位秒, 这里限制到不能超过60 's' : 一位秒, 这里限制到不能超过60 'p' : 早上, 下午标识, AM 或 PM @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True _regstr = format_str.replace('\\', '\\\\') # 将\替换为转义后的\\ # _regstr = format_str def re_group_replace(text): # 通过正则表达式替换匹配到的组的字符串, 增加转义符 return '\\%s' % text.group() # 替换转义符 _regstr = re.sub(r'[\$\(\)\*\+\.\[\]\?\/\^\{\}\|]', re_group_replace, _regstr) _regstr = re.sub(r'yyyy', '[0-9]{4,4}', _regstr) # 四位年份, 剔除有转义符的情况 _regstr = re.sub(r'yy', '[0-9]{2,2}', _regstr) # 两位年份, 剔除有转义符的情况 _regstr = re.sub(r'MM', '((0[0-9])|(1[012]))', _regstr) # 两位月份, 0-9月前面需补0 _regstr = re.sub(r'M', '([0-9]|(1[012]))', _regstr) # 一位月份, 0-9月不能补0 _regstr = re.sub(r'dd', '(([012][0-9])|(3[01]))', _regstr) # 两位日期, 这里限制到不能超过31 _regstr = re.sub(r'd', '(([12]{0,1}[0-9])|(3[01]))', _regstr) # 一位日期, 这里限制到不能超过31 _regstr = re.sub(r'hh24', '(([01][0-9])|(2[0-3]))', _regstr) # 两位小时, 这里限制到不能超过23 _regstr = re.sub(r'hh', '(([01][0-2]))', _regstr) # 两位小时, 这里限制到不能超过12 _regstr = re.sub(r'h24', '((1{0,1}[0-9])|(2[0-3]))', _regstr) # 一位小时, 这里限制到不能超过23 _regstr = re.sub(r'h', '(([0-9]|[1][0-2]))', _regstr) # 一位小时, 这里限制到不能超过12 _regstr = re.sub(r'mm', '(([0-5][0-9]))', _regstr) # 两位分钟, 这里限制到不能超过59 _regstr = re.sub(r'm', '(([0-9]|[1-5][0-9]))', _regstr) # 一位分钟, 这里限制到不能超过59 _regstr = re.sub(r'ss', '(([0-5][0-9]))', _regstr) # 两位分钟, 这里限制到不能超过59 _regstr = re.sub(r's', '(([0-9]|[1-5][0-9]))', _regstr) # 一位分钟, 这里限制到不能超过59 _regstr = re.sub(r'p', '(AM|PM)', _regstr) # 一位分钟, 这里限制到不能超过59 _regstr = '^%s$' % _regstr _rep = re.search(_regstr, obj) if _rep is None: return False else: try: # 尝试转换为日期格式 _format_str = cls.datetime_fmt_to_py(format_str) datetime.datetime.strptime(obj, _format_str) return True except: return False
[文档] @classmethod def str_check_date(cls, obj, format_str='yyyy-MM-dd'): """ 校验日期是否为有效日期 @param {string} obj - 要检查的字符串对象 @param {string} format_str='yyyy-MM-dd' - 日期时间格式 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True # 替换为标准转换格式 _format_str = cls.datetime_fmt_to_py(format_str) try: datetime.datetime.strptime(obj, _format_str) return True except: return False
[文档] @classmethod def str_check_num_area(cls, obj, min_val, max_val, is_eq_min=True, is_eq_max=True): """ 校验数字是否在取值范围内 @param {string} obj - 要检查的字符串对象 @param {float} min_val - 最小值, 如果不需要比较传入None @param {float}} max_val - 最大值, 如果不需要比较传入None @param {bool} is_eq_min=True - 是否可以等于最小值 @param {bool} is_eq_max=True - 是否可以等于最大值 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True _num = None try: _num = float(obj) except: # 转换出现异常 return False # 比较最小值 if min_val is not None: if not(_num > min_val or (_num == min_val and is_eq_min)): return False # 比较最大值 if max_val is not None: if not(_num < max_val or (_num == max_val and is_eq_max)): return False # 比较通过 return True
[文档] @classmethod def str_check_float_size(cls, obj, int_len, decimal_len): """ 校验数字的精度是否满足要求 @param {string} obj - 要检查的字符串对象 @param {int} int_len - 整数位 @param {int} decimal_len - 小数位 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True try: float(obj) except: # 转换失败说明不是数字 return False _strs = obj.split('.') if len(_strs[0]) > int_len: return False if len(_strs) > 1 and len(_strs[1]) > decimal_len: return False # 检查通过 return True
[文档] @classmethod def str_check_regex(cls, obj, regex_str, flags='0'): """ 根据传入的正则表达式进行判断 @param {string} obj - 要检查的字符串对象 @param {string} regex_str - 正则表达式 @param {string} flags='0' - 正则表达处理标记生成字符串, 可以多个标记组合, 例如're.I|re.L' 可支持的标记包括: re.I 忽略大小写 re.L 表示特殊字符集 \w, \W, \b, \B, \s, \S 依赖于当前环境 re.M 多行模式 re.S 即为 . 并且包括换行符在内的任意字符( . 不包括换行符) re.U 表示特殊字符集 \w, \W, \b, \B, \d, \D, \s, \S 依赖于 Unicode 字符属性数据库 re.X 为了增加可读性, 忽略空格和 # 后面的注释 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空字符串才继续校验后面的, 如果是空字符返回成功( 空字符不校验) if not cls.str_not_null(obj): return True _flags = eval(flags) _rep = re.search(regex_str, obj, flags=_flags) return False if _rep is None else True
############################# # 非字符串校验函数 #############################
[文档] @classmethod def check_fun(cls, obj, fun, *args, **kwargs): """ 根据传入的函数进行对象校验 注: 函数不会检查对象是否为None @param {string} obj - 要检查的字符串对象 @param {function} fun - 用于检查的函数, 定义如下: fun(obj, *args, **kwargs): return True/False @param {args} - 要传入的占位参数值 @param {kwargs} - 要传入校验函数的参数值 @return {bool} - 校验结果, True-通过; False-不通过 """ if not callable(fun): return False try: _rep = fun(obj, *args, **kwargs) if _rep: return True else: return False except: return False
[文档] @classmethod def not_null(cls, obj): """ 检查对象是否非空 @param {object} obj - 要检查的对象 @return {bool} - 校验结果, True-通过; False-不通过 """ return (obj is not None and obj != '')
[文档] @classmethod def is_type(cls, obj, data_type): """ 检查对象是否指定的数据类型 @param {object} obj - 要检查的对象 @param {type} data_type - 对象类型, 可以是基础类型如int, str, list等, 也可以是如VaildateTool等自定义类型 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空对象才继续校验后面的, 如果None返回校验通过 if obj is None: return True if type(obj) == data_type: return True else: return False
[文档] @classmethod def check_num_area(cls, obj, min_val, max_val, is_eq_min=True, is_eq_max=True): """ 校验数字是否在取值范围内 @param {object} obj - 要检查的字符串对象 @param {float} min_val - 最小值, 如果不需要比较传入None @param {float}} max_val - 最大值, 如果不需要比较传入None @param {bool} is_eq_min=True - 是否可以等于最小值 @param {bool} is_eq_max=True - 是否可以等于最大值 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空对象才继续校验后面的, 如果None返回校验通过 if obj is None: return True # 比较最小值 if min_val is not None: if not(obj > min_val or (obj == min_val and is_eq_min)): return False # 比较最大值 if max_val is not None: if not(obj < max_val or (obj == max_val and is_eq_max)): return False # 比较通过 return True
[文档] @classmethod def check_len(cls, obj, min_len, max_len): """ 检查对象长度(例如tuple、list) @param {obj} obj - 要检查的对象 @param {int} min_len - 最小长度 @param {int} max_len - 最大长度 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空对象才继续校验后面的, 如果None返回校验通过 if obj is None: return True if min_len is None: min_len = -1 if max_len is None: max_len = -1 _len = len(obj) return not (_len < min_len or (max_len > 0 and _len > max_len))
[文档] @classmethod def check_time_btween(cls, obj, min_date, max_date, format_str='yyyy-MM-dd', is_eq_min=True, is_eq_max=True): """ 送入的日期是否在两个日期之间 @param {string|datetime} obj - 要检查的日期对象 注: 如果传入的是string, 将自动通过format_str进行转换 @param {string|datetime} min_date - 比较的最小日期时间, None代表不比较 注: 如果传入的是string, 将自动通过format_str进行转换 @param {string|datetime} max_date - 比较的最大日期时间, None代表不比较 注: 如果传入的是string, 将自动通过format_str进行转换 @param {bool} is_eq_min=True - 是否可以等于最小值 @param {bool} is_eq_max=True - 是否可以等于最大值 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空对象才继续校验后面的, 如果None返回校验通过 if obj is None: return True try: _format_str = cls.datetime_fmt_to_py(format_str) _obj = obj if type(obj) == str: _obj = datetime.datetime.strptime(obj, _format_str) if min_date is not None: _min_date = min_date if type(min_date) == str: _min_date = datetime.datetime.strptime(min_date, _format_str) if not(_obj > _min_date or (_obj == _min_date and is_eq_min)): return False if max_date is not None: _max_date = max_date if type(max_date) == str: _max_date = datetime.datetime.strptime(max_date, _format_str) if not(_obj < _max_date or (_obj == _max_date and is_eq_max)): return False # 比较通过 return True except: return False
[文档] @classmethod def check_in_enum(cls, obj, enums): """ 检查对象是否在枚举值中 @param {object} obj - 要检查的对象 @param {list|tuple} enums - 枚举列表 @return {bool} - 校验结果, True-通过; False-不通过 """ # 只有非空对象才继续校验后面的, 如果None返回校验通过 if obj is None: return True if obj in enums: return True else: return False
############################# # 校验关系组合函数 #############################
[文档] @classmethod def And(cls, obj, rules, obj_id='object', ignore_list_miss_value=True, i18n_obj=None, is_use_pre_obj_id=True, pre_obj_id=''): """ 组合多个校验规则对同一个对象进行校验, 以'与'的关系返回检查结果 @param {object} obj - 要检查的对象 @param {list|tuple} rules - 校验规则列表, 格式为 (单个校验规则1, 单个校验规则2, ...) 或 [单个校验规则1, 单个校验规则2, ...], 格式参考单个校验规则 (参考_check_by_single_rule的定义) @param {string} obj_id='object' - 要检查对象的id, 用于在错误信息中显示 @param {bool} ignore_list_miss_value=True - 是否忽略列表中不足的对象 @param {SimpleI18N} i18n_obj=None - 国际化类的实例对象, 如不传入会尝试自动加载全局的国际化控件 @param {bool} is_use_pre_obj_id=True - 是否使用前置obj_id, 当指定为True时会根据dict的层级以及列表 中的位置修改obj_id的显示 @param {string} pre_obj_id='' 内部使用, 前置对象id @return {CResult} - 检查结果, '00000' - 检查成功, 其他 - 检查失败 """ for _rule in rules: _result = cls._check_by_rule_list( _rule, obj, obj_id=obj_id, ignore_list_miss_value=ignore_list_miss_value, i18n_obj=i18n_obj, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=pre_obj_id ) if not _result.is_success(): return _result # 全部检查通过 return CResult(code='00000', i18n_obj=i18n_obj)
[文档] @classmethod def Or(cls, obj, rules, obj_id='object', ignore_list_miss_value=True, i18n_obj=None, is_use_pre_obj_id=True, pre_obj_id=''): """ 组合多个校验规则对同一个对象进行校验, 以'或'的关系返回检查结果 @param {object} obj - 要检查的对象 @param {list|tuple} rules - 校验规则列表, 格式为 (单个校验规则1, 单个校验规则2, ...) 或 [单个校验规则1, 单个校验规则2, ...], 格式参考单个校验规则 (参考_check_by_single_rule的定义) @param {string} obj_id='object' - 要检查对象的id, 用于在错误信息中显示 @param {bool} ignore_list_miss_value=True - 是否忽略列表中不足的对象 @param {SimpleI18N} i18n_obj=None - 国际化类的实例对象, 如不传入会尝试自动加载全局的国际化控件 @param {bool} is_use_pre_obj_id=True - 是否使用前置obj_id, 当指定为True时会根据dict的层级以及列表 中的位置修改obj_id的显示 @param {string} pre_obj_id='' 内部使用, 前置对象id @return {CResult} - 检查结果, '00000' - 检查成功, 其他 - 检查失败 """ _result = None for _rule in rules: _result = cls._check_by_rule_list( _rule, obj, obj_id=obj_id, ignore_list_miss_value=ignore_list_miss_value, i18n_obj=i18n_obj, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=pre_obj_id ) if _result.is_success(): # 有一个通过 return _result # 执行完成, 最后一个reslut一定是不成功的 return _result
[文档] @classmethod def List(cls, obj, rule, obj_id='object', ignore_list_miss_value=True, i18n_obj=None, is_use_pre_obj_id=True, pre_obj_id=''): """ 按清单方式对传入的对象列表逐一处理 @param {tuple|list} obj - 要检查的对象列表, 本函数将会使用送入的规则(rule)逐一对列表中的对象进行校验 注: 如果传入的obj不是列表类型, 则函数会自动将其转为[obj, ]列表进行校验, 以兼容对单一对象进行校验的情况 @param {list|tuple|object} rule - 对对象列表的单一校验规则: type(rule) == list: 针对表格形式的数据进行验证, 校验规则格式为 [rule1, rule2, rule3, ...], 针对obj 的格式为 [ [data11, data12, data13, ...], [data21, data22, data23, ...], ... ] type(rule) != list: 视为单一校验规则, 格式参考单个校验规则 (参考_check_by_single_rule的定义) @param {string} obj_id='object' - 要检查对象的id, 用于在错误信息中显示 @param {bool} ignore_list_miss_value=True - 是否忽略列表中不足的对象 @param {SimpleI18N} i18n_obj=None - 国际化类的实例对象, 如不传入会尝试自动加载全局的国际化控件 @param {bool} is_use_pre_obj_id=True - 是否使用前置obj_id, 当指定为True时会根据dict的层级以及列表 中的位置修改obj_id的显示 @param {string} pre_obj_id='' 内部使用, 前置对象id @return {CResult} - 检查结果, '00000' - 检查成功, 其他 - 检查失败 """ _check_obj = obj _obj_type = type(obj) if _obj_type not in [tuple, list]: _check_obj = [obj, ] # 遍历对象用规则执行 _index = 0 for _item in _check_obj: _show_obj_id = obj_id if not is_use_pre_obj_id else ('%s~%d' % (obj_id, _index)) _result = cls._check_by_rule_list( rule, _item, obj_id=_show_obj_id, ignore_list_miss_value=ignore_list_miss_value, i18n_obj=i18n_obj, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=pre_obj_id ) if not _result.is_success(): return _result _index += 1 # 全部检查通过 return CResult(code='00000', i18n_obj=i18n_obj)
############################# # 通用校验函数 #############################
[文档] @classmethod def check_by_rule(cls, rule, obj, obj_id='object', ignore_list_miss_value=True, ignore_extra_keys=True, option_rule=None, i18n_obj=None, is_list_call=False, is_use_pre_obj_id=True, pre_obj_id=''): """ 按规则检查对象 @param {dict|list|tuple|object} rule - 检查规则, 不同类型的参数说明如下: dict : 实现按字典遍历检查, 检查对象也必须为对应格式的字典 其中key为对应关键字, value为检查的具体规则(rule) list : 按规则列表检查对应的对象列表, 注意对象也应为list对象 即: [rule1, rule2, rule3] <-> [obj1, obj2, obj3] tuple|object : 单个校验规则, 完整模式为 (校验函数, args参数, kwargs参数, 自定义错误信息) 1、完整模式传入的是一个tuple对象, 数组每个位置说明如下: 第1位: 校验函数, 传入检查函数对象(如: ValidateTool.str_len), 也可传入自定义的函数 自定义函数的格式为: my_fun(obj, *args, **kwargs) -> bool, 其中args和kwargs是示意, 除第一个入参必须固定为检查对象以外, 其他的入参按需设计 如果要使用的校验函数是ValidateTool的静态函数, 可以直接传函数名文本, 例如'str_len' 第2位: args参数, 调用校验函数所需传入的占位参数, 如果没有参数可传None; 该参数应为一个tuple对象, 例如: ('str_len', (3, 4), ) - 将用以下方式调用校验函数: str_len(obj, 3, 4) 注意: 如果args参数放置的对象不是tuple类型, 函数会自动把其加入到一个空数组中, 实现兼容处理, 例如: ('str_check_regex', '^[abc]+$', ) - 将用以下方式调用校验函数: str_check_regex(obj, ('^[abc]+$')) 第3位: kwargs参数, 调用校验函数所需传入kv模式的字典, 如果没有参数可传None; 该参数应为一个dict对象, 例如: ('str_is_datetime', None, {'format_str': 'yyyy/MM/dd'}) - 将用以下方式调用校验函数: str_is_datetime(obj, format_str='yyyy/MM/dd') 第4位: 自定义校验不通过时返回的CResult对象的错误信息, 如果不传, 将会根据函数名自动从字典变量'VALIDATE_ERR_MSG' 中获取对应函数名的错误信息, 当然也可以自己增加或修改'VALIDATE_ERR_MSG'的值来自定义错误信息 注意: 错误信息中可以通过'$1'、'$2'等占位符将校验本身的信息替换到错误信息中, 可送入的占位变量顺序如下: 校验对象本身(obj)、校验对象标识(obj_id)、校验函数名、args参数(按顺序拆开)、kwargs参数(按顺序拆开) 2、传入不完整的tuple对象, 可以只传前几个位置的参数, 例如: (校验函数, ) , (校验函数, None, kwargs参数, ) 程序会自动将该tuple对象补全, 缺失的后面几位会使用None进行补充, 例如: ('str_is_float', ) --> ('str_is_float', None, None, None) 3、仅传入函数名(string), 针对ValidateTool自带校验函数, 且无需其他入参的情况, 例如: 'str_is_float' -> (ValidateTool.str_is_float, None, None, None) 4、仅传入可执行函数, 针对自定义校验函数, 且无需其他入参的情况, 例如: ValidateTool.str_is_float -> (ValidateTool.str_is_float, None, None, None) 5、传入其他类型的对象, 程序将自动将对象转换为string去尝试对应回ValidateTool自带校验函数, 例如: object -> (str(object), None, None, None) @param {dict|object} obj - 要检查的对象, 根据校验规则的不同应按不同格式传入: rule为dict : 代表按字典进行校验, 传入的obj对象也应为dict rule为list : 代表按列表进行相应位置的数据校验, 传入的obj对象应为list或tuple格式 @param {string} obj_id='object' - 要检查对象的id, 用于在错误信息中显示 @param {bool} ignore_list_miss_value=True - 是否忽略列表中不足的对象 @param {bool} ignore_extra_keys=True - 当规则为dict时, 是否忽略数据中不存在的key @param {dict} option_rule=None - 指示字段是否可忽略, 只有当rule为dict的时候与rule配套使用, 字典结构与rule一致, key为要检查的字段, value为二元列表['M'或'O', {子字典}] @param {SimpleI18N} i18n_obj=None - 国际化类的实例对象, 如不传入会尝试自动加载全局的国际化控件 @param {bool} is_list_call=False - 内部针对dict的校验规则使用, 用于区分函数是否已进行过列表拆分处理 注: 指一个dict规则处理列表中的多个dict数据 @param {bool} is_use_pre_obj_id=True - 是否使用前置obj_id, 当指定为True时会根据dict的层级以及列表 中的位置修改obj_id的显示 @param {string} pre_obj_id='' 内部使用, 前置对象id @return {CResult} - 检查结果, '00000' - 检查成功, 其他 - 检查失败 """ if isinstance(rule, dict): # 按字典遍历进行检查 if not is_list_call and type(obj) in [list, tuple]: # 字典规则对列表时, 拆分列表检查, 相当于多行检查, 调用自己进行处理 _index = 0 for _obj_item in obj: _obj_id = obj_id if not is_use_pre_obj_id else ('%s~%d' % (obj_id, _index)) _result = cls.check_by_rule( rule, _obj_item, obj_id=_obj_id, ignore_list_miss_value=ignore_list_miss_value, ignore_extra_keys=ignore_extra_keys, i18n_obj=i18n_obj, is_list_call=True, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=pre_obj_id ) if not _result.is_success(): return _result _index += 1 # 全部检查通过, 返回成功 return CResult(code='00000', i18n_obj=i18n_obj) else: _pre_obj_id = '' if obj_id == '' else ('%s%s->' % (pre_obj_id, obj_id)) # 检查对象是否字典, 如果不是要返回失败 if not isinstance(obj, dict): _show_obj_id = obj_id if not is_use_pre_obj_id else (_pre_obj_id + obj_id) return CResult(code='19999', msg=VALIDATE_ERR_MSG['OBJ_MUST_DICT'], i18n_obj=i18n_obj, i18n_msg_paras=(obj, _show_obj_id, '')) # 遍历字典一一对应进行检查 for _key, _value in rule.items(): # 检查对象的key是不是对应存在 if _key not in obj.keys(): if ignore_extra_keys and (option_rule is None or _key not in option_rule.keys() or option_rule[_key][0] != 'M'): # 结合option_rule检查字段是否必填 continue else: # 检查对象不存在key _show_obj_id = _key if not is_use_pre_obj_id else (_pre_obj_id + _key) return CResult(code='19999', msg=VALIDATE_ERR_MSG['OBJ_MISS_KEY'], i18n_obj=i18n_obj, i18n_msg_paras=(obj, _show_obj_id, '')) # key存在 _result = None if isinstance(rule[_key], dict): # 下一级还是dict, 递归处理 _option_rule = None if option_rule is not None: _option_rule = option_rule[_key][1] _result = cls.check_by_rule( rule[_key], obj[_key], obj_id=_key, ignore_list_miss_value=ignore_list_miss_value, ignore_extra_keys=ignore_extra_keys, option_rule=_option_rule, i18n_obj=i18n_obj, is_list_call=False, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=_pre_obj_id ) else: # 执行检查 _result = cls._check_by_rule_list( rule[_key], obj[_key], obj_id=_key, ignore_list_miss_value=ignore_list_miss_value, i18n_obj=i18n_obj, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=_pre_obj_id ) # 判断检查结果 if not _result.is_success(): return _result # 遍历校验通过, 返回成功 return CResult(code='00000', i18n_obj=i18n_obj) else: # 按规则列表处理 return cls._check_by_rule_list( rule, obj, obj_id=obj_id, ignore_list_miss_value=ignore_list_miss_value, i18n_obj=i18n_obj, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=pre_obj_id )
@classmethod def _check_by_single_rule(cls, rule, obj, obj_id='object', ignore_list_miss_value=True, i18n_obj=None, is_use_pre_obj_id=True, pre_obj_id=''): """ 内部函数, 按一个规则校验一个对象 @param {tuple|object} rule - 单个校验规则, 完整模式为 (校验函数, args参数, kwargs参数, 自定义错误信息) 1、完整模式传入的是一个tuple对象, 数组每个位置说明如下: 第1位: 校验函数, 传入检查函数对象(如: ValidateTool.str_len), 也可传入自定义的函数 自定义函数的格式为: my_fun(obj, *args, **kwargs) -> bool, 其中args和kwargs是示意, 除第一个入参必须固定为检查对象以外, 其他的入参按需设计 如果要使用的校验函数是ValidateTool的静态函数, 可以直接传函数名文本, 例如'str_len' 第2位: args参数, 调用校验函数所需传入的占位参数, 如果没有参数可传None; 该参数应为一个tuple对象, 例如: ('str_len', (3, 4), ) - 将用以下方式调用校验函数: str_len(obj, 3, 4) 注意: 如果args参数放置的对象不是tuple类型, 函数会自动把其加入到一个空数组中, 实现兼容处理, 例如: ('str_check_regex', '^[abc]+$', ) - 将用以下方式调用校验函数: str_check_regex(obj, ('^[abc]+$')) 第3位: kwargs参数, 调用校验函数所需传入kv模式的字典, 如果没有参数可传None; 该参数应为一个dict对象, 例如: ('str_is_datetime', None, {'format_str': 'yyyy/MM/dd'}) - 将用以下方式调用校验函数: str_is_datetime(obj, format_str='yyyy/MM/dd') 第4位: 自定义校验不通过时返回的CResult对象的错误信息, 如果不传, 将会根据函数名自动从字典变量'VALIDATE_ERR_MSG' 中获取对应函数名的错误信息, 当然也可以自己增加或修改'VALIDATE_ERR_MSG'的值来自定义错误信息 注意: 错误信息中可以通过'$1'、'$2'等占位符将校验本身的信息替换到错误信息中, 可送入的占位变量顺序如下: 校验对象本身(obj)、校验对象标识(obj_id)、校验函数名、args参数(按顺序拆开)、kwargs参数(按顺序拆开) 2、传入不完整的tuple对象, 可以只传前几个位置的参数, 例如: (校验函数, ) , (校验函数, None, kwargs参数, ) 程序会自动将该tuple对象补全, 缺失的后面几位会使用None进行补充, 例如: ('str_is_float', ) --> ('str_is_float', None, None, None) 3、仅传入函数名(string), 针对ValidateTool自带校验函数, 且无需其他入参的情况, 例如: 'str_is_float' -> (ValidateTool.str_is_float, None, None, None) 4、仅传入可执行函数, 针对自定义校验函数, 且无需其他入参的情况, 例如: ValidateTool.str_is_float -> (ValidateTool.str_is_float, None, None, None) 5、传入其他类型的对象, 程序将自动将对象转换为string去尝试对应回ValidateTool自带校验函数, 例如: object -> (str(object), None, None, None) @param {object} obj - 要检查的对象( 单一对象) @param {string} obj_id='object' - 要检查对象的id, 用于在错误信息中显示 @param {bool} ignore_list_miss_value=True - 是否忽略列表中不足的对象 @param {SimpleI18N} i18n_obj=None - 国际化类的实例对象, 如不传入会尝试自动加载全局的国际化控件 @param {bool} is_use_pre_obj_id=True - 是否使用前置obj_id, 当指定为True时会根据dict的层级以及列表 中的位置修改obj_id的显示 @param {string} pre_obj_id='' 内部使用, 前置对象id @return {CResult} - 检查结果, '00000' - 检查成功, 其他 - 检查失败 """ if rule is None or rule == '': # 没有传规则进来, 直接返回成功 return CResult(code='00000', i18n_obj=i18n_obj) # 将校验规则标准化 _check_fun_str = '' # 校验函数名, 用来匹配固定错误信息 _check_fun = None # 校验函数对象 _args = None # 函数的args参数 _kwargs = None # 函数的kwargs参数 _err_msg = None # 错误信息 _rule_type = type(rule) if _rule_type == tuple: # 传入的是标准校验清单 _check_fun = rule[0] _args = None if len(rule) < 2 else rule[1] _kwargs = None if len(rule) < 3 else rule[2] _err_msg = None if len(rule) < 4 else str(rule[3]) # 支持传入一个参数时不用括号括上 if _args is not None and type(_args) != tuple: _args = (rule[1], ) elif _rule_type == str: _check_fun = rule elif callable(rule): _check_fun = rule else: # 不匹配的校验对象, 转换为str _check_fun = str(rule) if type(_check_fun) == str: # 转换为正确的检查函数 _check_fun_str = _check_fun _check_fun = eval('cls.' + _check_fun_str) else: # 从函数中获取名字 try: _check_fun_str = _check_fun.__name__ except: pass _show_obj_id = obj_id if not is_use_pre_obj_id else (pre_obj_id + obj_id) # 如果是组合逻辑函数, 需要修改入参 _is_logic_fun = (_check_fun_str in VALIDATE_LOGIC_FUN_NAME) if _is_logic_fun: if _kwargs is None or not isinstance(_kwargs, dict): _kwargs = dict() # 传入obj_id, i18n_obj _kwargs['obj_id'] = obj_id _kwargs['i18n_obj'] = i18n_obj _kwargs['ignore_list_miss_value'] = ignore_list_miss_value _kwargs['is_use_pre_obj_id'] = is_use_pre_obj_id _kwargs['pre_obj_id'] = pre_obj_id # 开始进行校验 _result = None _i18n_msg_paras = (obj, _show_obj_id, _check_fun_str) try: _rep = None if _args is None and _kwargs is None: _rep = _check_fun(obj) elif _args is None and _kwargs is not None: _rep = _check_fun(obj, **_kwargs) elif _args is not None and _kwargs is None: _rep = _check_fun(obj, *_args) else: _rep = _check_fun(obj, *_args, **_kwargs) # 处理返回值 if type(_rep) == CResult: # 如果是标准的返回值, 直接返回即可 return _rep elif _rep: _result = CResult(code='00000', i18n_obj=i18n_obj) else: # 错误信息处理(没有传入自定义错误信息) if _err_msg is None: if _check_fun_str in VALIDATE_ERR_MSG.keys(): _err_msg = VALIDATE_ERR_MSG[_check_fun_str] else: _err_msg = VALIDATE_ERR_MSG['DEFAULT'] # 错误信息的占位信息 if _args is not None: for _item in _args: _i18n_msg_paras = _i18n_msg_paras + (_item, ) if _kwargs is not None: for _item in _kwargs.values(): _i18n_msg_paras = _i18n_msg_paras + (_item, ) _result = CResult(code='19999', msg=_err_msg, i18n_obj=i18n_obj, i18n_msg_paras=_i18n_msg_paras) except: # 出现异常, 特殊处理 _error = str(sys.exc_info()[0]) _trace_str = traceback.format_exc() if _err_msg is None: _i18n_msg_paras = _i18n_msg_paras + (_error, _trace_str) _err_msg = VALIDATE_ERR_MSG['DEFAULT_EXCEPT'] else: # 自定义错误, 错误信息的占位信息 if _args is not None: for _item in _args: _i18n_msg_paras = _i18n_msg_paras + (_item, ) if _kwargs is not None: for _item in _kwargs.values(): _i18n_msg_paras = _i18n_msg_paras + (_item, ) # 组织错误信息 _result = CResult(code='29999', msg=_err_msg, i18n_obj=i18n_obj, error=_error, trace_str=_trace_str, i18n_msg_paras=_i18n_msg_paras) # 返回校验结果 return _result @classmethod def _check_by_rule_list(cls, rules, obj, obj_id='object', ignore_list_miss_value=True, i18n_obj=None, is_use_pre_obj_id=True, pre_obj_id=''): """ 内部函数, 按规则列表校验一个列表中对应位置的对象 注: 如果传入的规则对象(rules)不是列表(list), 则将其当作标准单一规则进行检查, 兼容普通模式 @param {list|object} rules - 规则列表, 根据传入对象的类型不同进行不同处理: type(rules) == list : 按规则列表检查对应的对象列表, 即: [rule1, rule2, rule3] <-> [obj1, obj2, obj3] type(rules) != list : 将rules整体视为单个规则对传入的对象进行检查, 即: rules <-> obj @param {list|tuple|object} obj - 要检查的对象列表, 规则如下: 1、如果rules为list, 则obj也应为list|tuple; 当obj不为list|tuple的情况, 会自动转换为[obj, ]进行兼容; 2、如果rules不为list, 则会将obj视为一个整体进行检查(不会按列表拆分) @param {string} obj_id='object' - 要检查对象的id, 用于在错误信息中显示 @param {bool} ignore_list_miss_value=True - 是否忽略列表中不足的对象 例如当为True时, [rule1, rule2, rule3] <-> [obj1, obj2, ] 不会报错 @param {SimpleI18N} i18n_obj=None - 国际化类的实例对象, 如不传入会尝试自动加载全局的国际化控件 @param {bool} is_use_pre_obj_id=True - 是否使用前置obj_id, 当指定为True时会根据dict的层级以及列表 中的位置修改obj_id的显示 @param {string} pre_obj_id='' 内部使用, 前置对象id @return {CResult} - 检查结果, '00000' - 检查成功, 其他 - 检查失败 """ if type(rules) != list: # 非列表, 就是单个检查 return cls._check_by_single_rule( rules, obj, obj_id=obj_id, ignore_list_miss_value=ignore_list_miss_value, i18n_obj=i18n_obj, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=pre_obj_id ) else: _check_obj = obj if type(obj) not in [list, tuple]: # 如果传入的不是一个列表, 当作一个对象的列表处理 _check_obj = [obj, ] _rules_len = len(rules) _obj_len = len(_check_obj) if not ignore_list_miss_value and _obj_len < _rules_len: # 不忽略不足对象时校验失败 return CResult(code='19999', msg=VALIDATE_ERR_MSG['NOT_ENOUGH_VALUES'], i18n_obj=i18n_obj, i18n_msg_paras=(obj, obj_id, '', _rules_len)) # 遍历进行检查 _index = 0 while _index < _obj_len: _show_obj_id = obj_id if not is_use_pre_obj_id else ('%s~%d' % (obj_id, _index)) _check_rule_fun = cls._check_by_single_rule if isinstance(rules[_index], dict): # 字典形式的校验 _check_rule_fun = cls.check_by_rule _result = _check_rule_fun( rules[_index], _check_obj[_index], obj_id=_show_obj_id, ignore_list_miss_value=ignore_list_miss_value, i18n_obj=i18n_obj, is_use_pre_obj_id=is_use_pre_obj_id, pre_obj_id=pre_obj_id ) if not _result.is_success(): # 校验失败 return _result _index += 1 # 全部校验成功 return CResult(code='00000', i18n_obj=i18n_obj) ############################# # 实例化对象的处理 #############################
[文档] def __init__(self, rule, ignore_list_miss_value=True, ignore_extra_keys=True, i18n_obj=None, is_use_pre_obj_id=True): """ 构造函数, 创建指定规则进行校验的实例对象 @param {dict|list|tuple|object} rule - 校验规则, 详细定义见check_by_rule @param {bool} ignore_list_miss_value=True - 是否忽略列表中不足的对象 @param {bool} ignore_extra_keys=True - 当规则为dict时, 是否忽略数据中不存在的key @param {SimpleI18N} i18n_obj=None - 国际化类的实例对象, 如不传入会尝试自动加载全局的国际化控件 @param {bool} is_use_pre_obj_id=True - 是否使用前置obj_id, 当指定为True时会根据dict的层级以及列表 中的位置修改obj_id的显示 """ self._rule = copy.deepcopy(rule) self._ignore_list_miss_value = ignore_list_miss_value self._ignore_extra_keys = ignore_extra_keys self._i18n_obj = i18n_obj self._is_use_pre_obj_id = is_use_pre_obj_id
[文档] def check(self, obj, obj_id='object'): """ 使用实例初始化的规则进行数据检查 @param {dict|object} obj - 要检查的对象, 根据校验规则的不同应按不同格式传入: rule为dict : 代表按字典进行校验, 传入的obj对象也应为dict rule为list : 代表按列表进行相应位置的数据校验, 传入的obj对象应为list或tuple格式 @param {string} obj_id='object' - 要检查对象的id, 用于在错误信息中显示 @return {CResult} - 检查结果, '00000' - 检查成功, 其他 - 检查失败 """ return self.check_by_rule( self._rule, obj, obj_id=obj_id, ignore_list_miss_value=self._ignore_list_miss_value, ignore_extra_keys=self._ignore_extra_keys, )
if __name__ == '__main__': # 当程序自己独立运行时执行的操作 # 打印版本信息 print(('模块名: %s - %s\n' '作者: %s\n' '发布日期: %s\n' '版本: %s' % (__MOUDLE__, __DESCRIPT__, __AUTHOR__, __PUBLISH__, __VERSION__)))