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__)))