I don't know any way to make formencode do what you ask. However, since this is Python, there are few limits to what we can do.
You can solve this by wrapping the call to getActiveCodes
in a purpose-built class. The wrapper class, RefreshBeforeContainsCheck
, will implement the special methods __iter__
and __contains__
to provide the necessary interface to be used as a iterable object:
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
I've added print statements to make it clearer how it behaves during run-time. The RefreshBeforeContainsCheck
class can then be used like
class ItemsEditSchema(Schema):
code = validators.OneOf(RefreshBeforeContainsCheck(getActiveCodes))
allow_extra_fields = True
in the validator schema.
The way it is implemented above, the getActiveCodes
function will be called each time the OneOf
validator performs an item in list
test (where our class acts as the list
), because that will cause RefreshBeforeContainsCheck.__contains__
to be called. Now, if the validation fails, the OneOf
validator generates an error message listing all the elements of list
; that case is handled by our __iter__
implementation. To avoid calling the database twice in case of validation errors, I've chosen to cache the "database" result list as self._current_list
, but whether or not that's appropriate depends on your needs.
I've created a gist for this: https://gist.github.com/mtr/9719d08f1bbace9ebdf6, basically creating an example of using the above code with the following code.
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'})
The output of the gist is:
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)})