目录
- 一、前置说明
- 1、总体目录
- 2、相关回顾
- 3、本节目标
- 二、操作步骤
- 1、项目目录
- 2、代码实现
- 3、测试代码
- 4、日志输出
- 三、后置说明
- 1、要点小结
- 2、下节准备
一、前置说明
1、总体目录
- 《 pyparamvalidate 参数校验器,从编码到发布全过程》
2、相关回顾
- python 装饰器基础
- Validator 类的简单实现
3、本节目标
- 了解装饰器的基本原理和使用。
- 使用
raise_exception
装饰器,简化if not ... raise ...
抛出异常的过程。
二、操作步骤
1、项目目录
atme
:@me
用于存放临时的代码片断或其它内容。pyparamvalidate
: 新建一个与项目名称同名的package,为了方便发布至pypi
。core
: 用于存放核心代码。tests
: 用于存放测试代码。utils
: 用于存放一些工具类或方法。
2、代码实现
atme/demo/validator_v2/validator.py
import functools
import inspect
def _error_prompt(value, exception_msg=None, rule_des=None, field=None):
"""
优先使用校验方法中的错误提示, 如果方法中没有错误提示,则使用"字段规则描述"代替错误提示
拼接出:name error: "123" is invalid. due to: name must be string.
"""
default = f'"{value}" is invalid.'
prompt = exception_msg or rule_des
prompt = f'{default} due to: {prompt}' if prompt else default
prompt = f'{field} error: {prompt}' if field else prompt
return prompt
# python 装饰器基础, 请参考:https://kindtester.blog.csdn.net/article/details/135550880
def raise_exception(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
# 将 "参数名" 和 "参数值" 绑定为键值对
bound_args = inspect.signature(func).bind(self, *args, **kwargs).arguments
# 如果 参数名 在 kwargs 中,则优先从 kwargs 中获取,对应: 以关键字参数进行传值的情形, 如 is_string('Jane', exception_msg='name must be string')
# 否则,从 bound_args 中获取,对应: 以位置参数进行传值的情形,如 is_string('Jane', 'name must be string')
exception_msg = kwargs.get('exception_msg', None) or bound_args.get('exception_msg', None)
error_prompt = _error_prompt(self.value, exception_msg, self._rule_des, self._field)
result = func(self, *args, **kwargs)
if not result:
raise ValueError(error_prompt)
return self
return wrapper
class Validator:
def __init__(self, value, field=None, rule_des=None):
self.value = value
self._field = field
self._rule_des = rule_des
@raise_exception
def is_string(self, exception_msg=None):
"""
exception_msg 会在 raise_exception 装饰器中隐式调用。
"""
return isinstance(self.value, str)
'''
@raise_exception
def is_string(self, **kwargs):
"""
虽然这种方式能实现同样的效果,但是不推荐使用,理由是:
1. pycharm 无法智能提示;
2. 必须要有明确的文档说明,告知用户要传递的参数是什么;
3.使用 exception_msg=None 作为关键字参数可以解决这两个问题;
"""
return isinstance(self.value, str)
'''
@raise_exception
def is_not_empty(self, exception_msg=None):
return bool(self.value)
3、测试代码
atme/demo/validator_v2/test_validator.py
import pytest
from atme.demo.validator_v2.validator import Validator
def test_validator_01():
"""
在 Validator 实例化时,不给 field、rule_des 传值; 在校验方法中,不给 exception_msg 传值
"""
validator = Validator('Jane')
assert validator.is_string().is_not_empty()
with pytest.raises(ValueError) as exc_info:
validator = Validator(123)
validator.is_string().is_not_empty()
assert 'invalid' in str(exc_info.value)
print(exc_info.value) # 输出: "123" is invalid.
def test_validator_02():
"""
在 Validator 实例化时,给 field、rule_des 传值
"""
validator = Validator('Jane', field='name', rule_des='name must be string from rule des.')
assert validator.is_string().is_not_empty()
with pytest.raises(ValueError) as exc_info:
validator = Validator(123, field='name', rule_des='name must be string from rule des.')
validator.is_string().is_not_empty()
assert 'name must be string from rule des.' in str(exc_info.value)
print(exc_info.value) # 输出: name error: "123" is invalid. due to: name must be string from rule des.
def test_validator_03():
"""
在 Validator 实例化时,给 field、rule_des 传值; 在校验方法中,给 exception_msg 传值
"""
validator = Validator('Jane', field='name', rule_des='name must be string from rule des.')
assert validator.is_string().is_not_empty()
with pytest.raises(ValueError) as exc_info:
validator = Validator(123, field='name', rule_des='name must be string from rule des.')
validator.is_string('name must be string from method exception msg.').is_not_empty()
assert 'name must be string from method exception msg.' in str(exc_info.value)
print(exc_info.value) # 输出: "123" is invalid due to "name error: name must be string from method exception msg."
def test_validator_04():
"""
field_name 为空
"""
validator = Validator('Jane', rule_des='name must be string from rule des.')
assert validator.is_string().is_not_empty()
with pytest.raises(ValueError) as exc_info:
validator = Validator(123, rule_des='name must be string from rule des.')
validator.is_string('name must be string from method exception msg.').is_not_empty()
assert 'name must be string from method exception msg.' in str(exc_info.value)
print(exc_info.value) # 输出: "123" is invalid due to "name must be string from method exception msg."
4、日志输出
执行 test
的日志如下,验证通过:
============================= test session starts =============================
collecting ... collected 4 items
test_validator.py::test_validator_01 PASSED [ 25%]"123" is invalid.
test_validator.py::test_validator_02 PASSED [ 50%]name error: "123" is invalid. due to: name must be string from rule des.
test_validator.py::test_validator_03 PASSED [ 75%]name error: "123" is invalid. due to: name must be string from method exception msg.
test_validator.py::test_validator_04 PASSED [100%]"123" is invalid. due to: name must be string from method exception msg.
============================== 4 passed in 0.01s ==============================
三、后置说明
1、要点小结
- 使用
inspect.signature(func).bind(*args, **kwargs).arguments
获取方法中的参数名和对应的参数值; - 因为用户有可能使用两种方式给
exception_msg
传值,如:is_string('Jane', exception_msg='name must be string')
、is_string('Jane', 'name must be string')
,所以使用kwargs.get('exception_msg', None) or bound_args.get('exception_msg', None)
来获取exception_msg
的值; - 显示优于隐式,为了方便用户知道传递什么参数,在校验方法中显示指定关键字参数
exception_msg=None
, 不要使用**kwargs
这种写法。 - 每个方法中都使用了
@raise_exception
装饰器,如果校验方法越来越多,类中的所有方法都会被加上装饰器,重复会很明显,可以继续优化。
2、下节准备
- 使用
RaiseExceptionMeta
元类隐式装饰Validator
类中的所有校验方法
点击返回主目录