Python

wtforms-tornado: WTForms extensions for Tornado

Earlier this year I took up the development of a simple CMS for blogging, this project was just an attempt to play with Tornado and MongoDB, and one issue that was bugging me was the validation of the arguments of the HTTP request, I had to do nested and gigantic conditions, it was something really annoying. I started with something like this:

import tornado.web
import tornado.ioloop

class RegisterHandler(tornado.web.RequestHandler):

    def post(self):
        username = self.get_argument('username', None)
        password = self.get_argument('password', None)
        if username and password:
            self.write('Ok!')
        else:
            self.write('Invalid request!')

if __name__ == '__main__':
    tornado.web.Application([('/', RegisterHandler)]).listen(8888)
    tornado.ioloop.IOLoop.instance().start()

And as you can see this is quite basic, and what about if I want to validate data types, length, a regular expression, choices, etc, etc, etc, it would be really extensive.

The first thing I thought as a solution for this was WTForms, WTForms would help me to avoid these conditions and form fields rendering in templates. But what happened when I started to use it?

import tornado.web
import tornado.ioloop
import wtforms

from wtforms.fields import StringField, PasswordField
from wtforms.validators import Required

class RegisterForm(wtforms.Form):

    username = StringField(validators=[Required()])
    password = PasswordField(validators=[Required()])

class RegisterHandler(tornado.web.RequestHandler):

    def post(self):
        form = RegisterForm(self.request.arguments)
        if form.validate():
            self.write('Ok!')
        else:
            self.write('Invalid request!')

if __name__ == '__main__':
    tornado.web.Application([('/', RegisterHandler)]).listen(8888)
    tornado.ioloop.IOLoop.instance().start()

It looks very simple to do but the arguments attribute of the request is a naive dictionary and is not what WTForms expects to access the data. The exception and traceback obtained by invoking the validate method were:

ERROR:tornado.application:Uncaught exception POST / (127.0.0.1)
HTTPRequest(protocol='http', host='localhost:8888', method='POST', uri='/', version='HTTP/1.1', remote_ip='127.0.0.1', headers={'Host': 'localhost:8888', 'Accept': '*/*', 'User-Agent': 'curl/7.29.0'})
Traceback (most recent call last):
  File "/home/puentesarrin/virtualenvs/wtforms-tornado/local/lib/python2.7/site-packages/tornado/web.py", line 1141, in _when_complete
    callback()
  File "/home/puentesarrin/virtualenvs/wtforms-tornado/local/lib/python2.7/site-packages/tornado/web.py", line 1162, in _execute_method
    self._when_complete(method(*self.path_args, **self.path_kwargs),
  File "wtforms-tornado.py", line 18, in post
    form = RegisterForm(self.request.arguments)
  File "/home/puentesarrin/virtualenvs/wtforms-tornado/local/lib/python2.7/site-packages/wtforms/form.py", line 178, in __call__
    return type.__call__(cls, *args, **kwargs)
  File "/home/puentesarrin/virtualenvs/wtforms-tornado/local/lib/python2.7/site-packages/wtforms/form.py", line 233, in __init__
    self.process(formdata, obj, **kwargs)
  File "/home/puentesarrin/virtualenvs/wtforms-tornado/local/lib/python2.7/site-packages/wtforms/form.py", line 102, in process
    raise TypeError("formdata should be a multidict-type wrapper that supports the 'getlist' method")
TypeError: formdata should be a multidict-type wrapper that supports the 'getlist' method
ERROR:tornado.access:500 POST / (127.0.0.1) 15.63ms

Well, arguments doesn’t implement the “getlist” method, which is expected to return a list of values ​​for a given key. So I sent a pull request two weeks ago, for adding an extension to the core of WTForms.

But unfortunately the WTForms team is interested on removing the extensions, and after recommendation from @crast I created the module wtforms-tornado which can be located on PyPI and GitHub. All suggestions are welcome!

Standard