我不知道有什么方法可以让formencode按照你的要求去做。然而,由于这是 Python,我们可以做的几乎没有限制。
您可以通过将调用包装getActiveCodes
在一个专门构建的类中来解决这个问题。包装类 ,RefreshBeforeContainsCheck
将实现特殊方法__iter__
并__contains__
提供必要的接口以用作可迭代对象:
from formencode import Schema, validators, Invalid
class RefreshBeforeContainsCheck(object):
def __init__(self, func):
self._func = func
self._current_list = None
def __iter__(self):
print '__iter__ was called.'
#return iter(self._func()) # Could have refreshed here too, but ...
return iter(self._current_list)
def __contains__(self, item):
print '__contains__ was called.'
self._current_list = self._func() # Refresh list.
return item in self._current_list
我添加了打印语句,以使其在运行时的行为更加清晰。然后RefreshBeforeContainsCheck
可以像这样使用该类
class ItemsEditSchema(Schema):
code = validators.OneOf(RefreshBeforeContainsCheck(getActiveCodes))
allow_extra_fields = True
在验证器模式中。
按照上面的实现方式,getActiveCodes
每次OneOf
验证器执行item in list
测试时都会调用该函数(我们的类充当list
),因为这将导致RefreshBeforeContainsCheck.__contains__
被调用。现在,如果验证失败,OneOf
验证器会生成一条错误消息,列出 ; 的所有元素list
。这种情况由我们的__iter__
实施处理。为了避免在验证错误的情况下调用数据库两次,我选择将“数据库”结果列表缓存为self._current_list
,但这是否合适取决于您的需要。
我为此创建了一个要点:https ://gist.github.com/mtr/9719d08f1bbace9ebdf6 ,基本上创建了一个使用上述代码和以下代码的示例。
def getActiveCodes():
# This function could have performed a database lookup.
print 'getActivityCodes() was called.'
codes = map(str, [1, 2, 3, 4])
return codes
def validate_input(schema, value):
print 'Validating: value: {!r}'.format(value)
try:
schema.to_python(value)
except Invalid, error:
print 'error: {!r}'.format(error)
print
def main():
schema = ItemsEditSchema()
validate_input(schema, {'code': '3'})
validate_input(schema, {'code': '4'})
validate_input(schema, {'code': '5'})
要点的输出是:
Validating: value: {'code': '3'}
__contains__ was called.
getActivityCodes() was called.
Validating: value: {'code': '4'}
__contains__ was called.
getActivityCodes() was called.
Validating: value: {'code': '5'}
__contains__ was called.
getActivityCodes() was called.
__iter__ was called.
error: Invalid("code: Value must be one of: 1; 2; 3; 4 (not '5')",
{'code': '5'}, None, None,
{'code': Invalid(u"Value must be one of: 1; 2; 3; 4 (not '5')",
'5', None, None, None)})