Formencode OneOf 验证器与动态列表进行测试

Formencode OneOf validator with dynamic list to test against

我正在使用 formencode 1.3.0a1(和 turbogeras 2.3.4)和 运行 验证器 OneOf 出现问题。

我想根据数据库中的列表验证一些输入。 这是我的验证模式和获取列表的方法:

from formencode import Schema, validators

def getActiveCodes():
    codes = DBSession.query(SomeObject.code).all()
    codes = [str(x[0]) for x in codes]
    return codes

class itemsEditSchema(Schema):
    code = validators.OneOf(getActiveCodes())
    allow_extra_fields = True

方法"getActiveCodes"只执行一次(我猜是在架构初始化或类似的过程中)。

每次我想检查我的用户输入 "code" 时,我都需要它 运行,我该怎么做?

感谢帮助

我不知道有什么方法可以使 formencode 执行您要求的操作。然而,由于这是 Python,我们可以做的事情几乎没有限制。

您可以通过将对 getActiveCodes 的调用包装在一个专门构建的 class 中来解决这个问题。包装器 class、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 可以像

一样使用
class ItemsEditSchema(Schema):
    code = validators.OneOf(RefreshBeforeContainsCheck(getActiveCodes))
    allow_extra_fields = True

在验证器架构中。

按照上面的实现方式,每次 OneOf 验证器执行 item in list 测试时都会调用 getActiveCodes 函数(其中我们的 class 作为list),因为这会导致 RefreshBeforeContainsCheck.__contains__ 被调用。现在,如果验证失败,OneOf 验证器会生成一条错误消息,列出 list 的所有元素;该案例由我们的 __iter__ 实施处理。为了避免在验证错误的情况下调用数据库两次,我选择将 "database" 结果列表缓存为 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)})

最后我写了一个 Fancy Validator 而不是使用 OneOf,这是我的代码:

class codeCheck(FancyValidator):
    def to_python(self, value, state=None):
        if value==None:
            raise Invalid('missing a value', value, state)
        return super(codeCheck,self).to_python(value,state)

    def _validate_python(self, value, state):
        codes = DBSession.query(Code).all()
        if value not in codes:
            raise Invalid('wrong code',value, state)
        return value


class itemsEditSchema(Schema):
    code = codeCheck ()
    allow_extra_fields = True