Sign in
Log inSign up
For the want of an int...

For the want of an int...

Hippo's photo
Hippo
·Apr 14, 2016

I am building an app which generates "notes" with an ID and unlock-code (password). These can be printed and given out to people, who can later add them to their online account by entering the codes. Kind of like discount-coupons.

Anyway. I was using WTForms (along with the Flask web framework) to design the forms. The user should be able to add many notes at a time, so I used two useful WTForms features: the FormField and the FieldList.

For those who don't know WTForms: basically, it lets you create Python classes that later render to HTML forms. For example, here's a basic login form:

class UsernamePasswordForm(Form):
    username = TextField('Username')
    password = PasswordField('Password')

FormField is a helper that lets you encapsulate a form inside another form. The example from the docs is if you want to have a ContactForm with telephone numbers:

class TelephoneForm(Form):
    country_code = IntegerField('Country Code', [validators.required()])
    area_code    = IntegerField('Area Code/Exchange', [validators.required()])
    number       = StringField('Number')

class ContactForm(Form):
    first_name   = StringField()
    last_name    = StringField()
    mobile_phone = FormField(TelephoneForm)
    office_phone = FormField(TelephoneForm)

This way, the telephone validation and other stuff can be handled in TelephoneForm, instead of doing the same things twice over in the ContactForm.

FieldList is another useful helper. It lets you have a list of fields, rather than just one. For example:

keywords = FieldList(StringField(), min_entries=2)

This will automatically generate HTML code of the form

<input id="keywords-0" name="keywords-0" type="text" value="">
<input id="keywords-1" name="keywords-1" type="text" value="">

min_entries decides the minimum number of such entries to have—if there are not enough keywords provided, then FieldList will automatically add blank entries to the form to make sure there are at least as many as specified by min_entries

Now, what I wanted, was a FieldList of FormFields. Which is also documented. This is what I did:

class NoteUnlockForm(Form):
    note_id = TextField('Note ID', validators=[Required()])
    unlock_code = TextField('Unlock Code', validators=[Required()])

class PortalNotesForm(Form):
    portal = SelectField('Portal', validators=[Required()])
    notes = FieldList(FormField(NoteUnlockForm, 'Note'), min_entries='6')

Then I added the templates, started up the dev server, and loaded the page in the browser to see how it looked. And then...

...my computer crashed.

Suddenly, everything started responding very slowly—it was almost as if I was on Windows (no offense, Windows users)! I quickly switched to a text console and killed the large apps I could think of: Firefox, and the Thunderbird. No change. So I finally logged out and back in again that solved the problem.

This shouldn't be happening, I thought. My computer is a relatively new one, and while its motherboard does start acting up at times, it definitely shouldn't be getting so slow! Anyway, I started the development server and resumed work. I opened Firefox, and tried to load localhost:5000 again. It said it was loading, but there was no response from the server.

I thought perhaps my app's sqlite database had gotten corrupted on the last crash, so I opened a Python console and tried inspecting the database directly. All OK. I Ctrl+Ced the development server and switched to a different web project. I tried to run that project, but it said that the port :5000 was already in use. Strange, because I had closed that server! I changed the default port in the settings and tried a couple of other projects. All of the loaded fine. So I went back to the main project and then...

...the computer crashed again.

OK. Then it was definitely the app which was the culprit. But why? How could such a small change crash the whole system? I played around on the Python console, going over my code. Everything was fine, until I ran

>>> f = PortalNotesForm()

The prompt never returned! After a few seconds, I quickly Ctrl+Ced the process. Then I tried typing out the code by hand, without the min_entries option. This time it worked!

I had finally narrowed down to the problem: A FieldList of FormFields with a min_entries was too much for WTForms! It was probably already a reported bug, so I did a quick search online and on StackOverflow. But...no results!

Then I looked back at the code and...OMG why hadn't I thought of that before what was wrong with me?!

Here's the line that was causing the crash:

notes = FieldList(FormField(NoteUnlockForm, 'Note'), min_entries='6')

As expected. But the error? It was in the snippet

min_entries='6'

Not 6, but '6'!

FieldList was continually adding fields till the number became greater than or equal to '6'—which it never did, because, in Python, strings are always greater than integers. So the list just kept growing and growing until it filled up the memory and slowed down the whole system!

A quick confirmation:

>>> '6' > 6
True

I've made some pretty silly mistakes in the past, but I think this one was the silliest yet.