在核心,我要做的是使用一些函数,这些函数看起来像未修饰的验证函数:
def f(k: bool):
def g(n):
# check that n is valid
return n
return g
并使它们看起来像装饰的验证函数:
@k
def f():
def g(n):
# check that n is valid
return n
return g
这里的想法是k
在所有实现功能中描述相同的功能。
具体地说,这些函数都是返回"验证"函数,以便与性感的验证框架一起使用。因此,类型为f()
的所有函数都返回一个稍后由Schema()
执行的函数。k
实际上是allow_none
,也就是说,一个确定None
值是否正确的标志。一个非常简单的例子可能是这个示例使用代码:
x = "Some input value."
y = None
input_validator = Schema(f(allow_none=True))
x = input_validator(x) # succeeds, returning x
y = input_validator(y) # succeeds, returning None
input_validator_no_none = Schema(f(allow_none=False))
x = input_validator(x) # succeeds, returning x
y = input_validator(y) # raises an Invalid
在不更改示例使用代码的情况下,我试图通过将未修饰的验证函数更改为修饰的验证功能来实现相同的结果。举一个具体的例子,改变这个:
def valid_identifier(allow_none: bool=True):
min_range = Range(min=1)
validator = Any(All(int, min_range), All(Coerce(int), min_range))
return Any(validator, None) if allow_none else validator
对此:
@allow_none(default=True)
def valid_identifier():
min_range = Range(min=1)
return Any(All(int, min_range), All(Coerce(int), min_range))
从这两者返回的函数应该是等效的
我试图写的是,利用decorator
库:
from decorator import decorator
@decorator
def allow_none(default: bool=True):
def decorate_validator(wrapped_validator, allow_none: bool=default):
@wraps(wrapped_validator)
def validator_allowing_none(*args, **kwargs):
if allow_none:
return Any(None, wrapped_validator)
else:
return wrapped_validator(*args, **kwargs)
return validator_allowing_none
return decorate_validator
我有一个unittest.TestCase
来测试它是否如预期那样工作:
@allow_none()
def test_wrapped_func():
return Schema(str)
class TestAllowNone(unittest.TestCase):
def test_allow_none__success(self):
test_string = "blah"
validation_function = test_wrapped_func(allow_none=False)
self.assertEqual(test_string, validation_function(test_string))
self.assertEqual(None, validation_function(None))
但我的测试返回以下失败:
def validate_callable(path, data):
try:
> return schema(data)
E TypeError: test_wrapped_func() takes 0 positional arguments but 1 was given
我试着调试它,但无法让调试器真正进入装饰。我怀疑,由于命名问题,比如在这篇(非常长的)博客文章系列中提出的问题,test_wrapped_func
没有正确设置其参数列表,因此装饰器甚至从未被执行,但它也可能完全是其他东西。
我尝试了其他一些变体。通过从@allow_none
:中删除函数括号
@allow_none
def test_wrapped_func():
return Schema(str)
我得到一个不同的错误:
> validation_function = test_wrapped_func(allow_none=False)
E TypeError: test_wrapped_func() got an unexpected keyword argument 'allow_none'
丢弃@decorator
失败,带有:
> validation_function = test_wrapped_func(allow_none=False)
E TypeError: decorate_validator() missing 1 required positional argument: 'wrapped_validator'
这是有道理的,因为@allow_none
需要一个参数,因此在逻辑上需要括号。替换它们会产生原始错误。
装饰是微妙的,我显然错过了一些东西。这类似于对函数进行currying,但它并不完全起作用。关于如何实施,我缺少什么?
我认为您将allow_none=default
参数放在了错误的嵌套级别。它应该在最里面的函数(包装器)上,而不是装饰器(中间级别)上。
试试这样的东西:
def allow_none(default=True): # this is the decorator factory
def decorator(validator): # this is the decorator
@wraps(validator)
def wrapper(*args, allow_none=default, **kwargs): # this is the wrapper
if allow_none:
return Any(None, validator)
else:
return validator(*args, **kwargs)
return wrapper
return decorator
如果你不需要默认值是可设置的,你可以去掉最外层的嵌套,只需在包装函数中使默认值为常量(或者如果你的调用方总是传递一个值,则省略它)。注意,正如我在上面所写的,包装器的allow_none
参数是一个仅限关键字的参数。如果要将其作为位置参数传递,可以将其移动到*args
之前,但这需要它是第一个位置参数,从API的角度来看,这可能是不可取的。更复杂的解决方案可能是可能的,但对于这个答案来说,这太过分了。