0

I'm using formencode 1.3.0a1 (and turbogeras 2.3.4) and run into a problem with the validator OneOf.

I want to validate some input according to a list in the Database. Here is my validation schema and the method for getting the list:

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

The method "getActiveCodes" is executed just once (I guess during schema init or something like that).

I need it to run every time when I want to check my user input for "code", how can I do that?

Thanks for the help

Martin Thorsen Ranang
  • 2,394
  • 1
  • 28
  • 43
A-Palgy
  • 1,291
  • 2
  • 14
  • 30

2 Answers2

1

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)})
Martin Thorsen Ranang
  • 2,394
  • 1
  • 28
  • 43
  • Thanks for the detailed answer. I ended up writing a FacnyValidator instead of using one of validator – A-Palgy Nov 08 '15 at 10:31
0

In the end I worte a FancyValidtor instead of using OneOf, here is my code:

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
A-Palgy
  • 1,291
  • 2
  • 14
  • 30