Tornado + WTForms = TypeError: formdata should be a multidict type wrapper ...

Posted on Apr 25, 2013

If you’re here from some search engine, that means you’ve been working with Tornado and WTForms, and you’ve faced the same error -

(Pdb) form = SomeForm(self.request.arguments)
*** TypeError: formdata should be a multidict-type wrapper that supports the 'getlist' method

The thing is, Tornado does provide a dictionary that contains the data like a MultiDict should do for GET/POST/others request arguments -

{'_xsrf': ['b2c0d0bebbf84516a8aa0462bb41ea0f'], 'password': ['password'], 'email': ['mail@gmail.com']}

But it sends it as a dictionary -

(Pdb) type(self.request.arguments)
<type 'dict'>

And Python dictionaries don’t have a getlist() method. And WTForms requires the arguments object to have that method.

Workaround?

class SimpleMultiDict(dict):
    def getlist(self, key):
        return self[key]

    def __repr__(self):
        return type(self).__name__ + '(' + dict.__repr__(self) + ')'

This simply makes a subclass of dict and adds the getlist() method. So, What did we gain?

(Pdb) SimpleMultiDict(self.request.arguments)
SimpleMultiDict({'_xsrf': ['b2c0d0bebbf84516a8aa0462bb41ea0f'], 'password': ['password'], 'email': ['mail@gmail.com']})
(Pdb) arguments = SimpleMultiDict(self.request.arguments)
(Pdb) dir(arguments)
['__class__', (skipping some default methods) , 'clear', 'copy', 'fromkeys', 'get', 'getlist', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values', 'viewitems', 'viewkeys', 'viewvalues']

# Notice the getlist() method

(Pdb) arguments.getlist('email')
['mail@gmail.com']
(Pdb) form = LoginForm(arguments)
(Pdb) form.email.data
'mail@gmail.com'

Now there is a point to note here. Tornado sends the arguments data in a simple dictionary that has each of the values inside a list. Here is a pointer if you’re using some framework that sends a simple dictionary, with the arguments not in list, and that also doesn’t have a getlist() method, just change the getlist() method to this -

class SimpleMultiDict(dict):
    def getlist(self, key):
        return self[key] if type(self[key]) == list else [self[key]]

    def __repr__(self):
        return type(self).__name__ + '(' + dict.__repr__(self) + ')'

This just turns the simgle values to a list, just what getlist() is supposed to do.