DEV Community

Cover image for Python: WTForms 3.0.1 IntegerField and InputRequired do not accept 0 as valid!
Be Hai Nguyen
Be Hai Nguyen

Posted on

Python: WTForms 3.0.1 IntegerField and InputRequired do not accept 0 as valid!

0 is a valid integer value. In the latest version of WTForms, version 3.0.1, IntegerField and InputRequired don't accept 0 as valid. This appears to be an ongoing issue dating back several years. I am proposing a patch, which seems to be working for me.

WTForms 3.0.1 is a great validation library. And I think it is also framework-independent: we can implement our own generic business rules classes, and use it as a basic data validation engine, we can then use these business rules classes in any framework of our own choosing.

I have recently found an issue with it, my form has an IntegerField, and the InputRequired validator, whereby 0 is an acceptable value.

-- But 0 gets rejected!

This issue has been reported over the years, but so far, it is still in this latest version:

Following is my attempt to reproduce this issue, and how to get around it. ❶ Create project virtual environment. ❷ Write a test script.

❶ Create project virtual environment. We will need Werkzeug and WTForms 3.0.1 packages.

The test project lives under /webwork/wtforms_test:

$ cd webwork/ $ mkdir wtforms_test $ cd wtforms_test/ behai@HP-Pavilion-15:~/webwork/wtforms_test$ virtualenv venv behai@HP-Pavilion-15:~/webwork/wtforms_test$ source venv/bin/activate (venv) behai@HP-Pavilion-15:~/webwork/wtforms_test$ ./venv/bin/pip install werkzeug WTForms 
Enter fullscreen mode Exit fullscreen mode

061-01.png

❷ Test script.

Content of /webwork/wtforms_test$/wtforms_bug.py 
Enter fullscreen mode Exit fullscreen mode
from pprint import pprint from werkzeug.datastructures import MultiDict from wtforms import ( Form, IntegerField, ) from wtforms.validators import ( InputRequired, NumberRange, ) BREAK_HOUR_01_MSG = "Break Hour must have a value." BREAK_HOUR_02_MSG = "Break Hour is between 0 and 23." class TestForm(Form): break_hour = IntegerField('Break Hour', validators=[InputRequired(BREAK_HOUR_01_MSG), NumberRange(0, 23, BREAK_HOUR_02_MSG)]) def validate(data: dict, form: Form) -> tuple(): form_data = MultiDict(mapping=data) f = form(form_data) res = f.validate() return res, f.errors print("\n--break_hour: -1") res, errors = validate({'break_hour': -1}, TestForm) print(res) pprint(errors) print("\n--break_hour: 'xx'") res, errors = validate({'break_hour': 'xx'}, TestForm) print(res) pprint(errors) print("\n--break_hour: 0") res, errors = validate({'break_hour': 0}, TestForm) print(res) pprint(errors) print("\n--break_hour: 1") res, errors = validate({'break_hour': 1}, TestForm) print(res) pprint(errors) 
Enter fullscreen mode Exit fullscreen mode

It is simple, the form has only a single integer field break_hour, it is a required field, and accepts any value in the range of 0 to 23 -- and follows by 4 ( four ) tests.

To run:

(venv) behai@HP-Pavilion-15:~/webwork/wtforms_test$ venv/bin/python wtforms_bug.py 
Enter fullscreen mode Exit fullscreen mode

Output:

--break_hour: -1 False {'break_hour': ['Break Hour is between 0 and 23.']} --break_hour: 'xx' False {'break_hour': ['Not a valid integer value.', 'Break Hour is between 0 and 23.']} --break_hour: 0 False {'break_hour': ['Break Hour must have a value.']} --break_hour: 1 True {} 
Enter fullscreen mode Exit fullscreen mode

061-02.png

-1 and 'xx' get rejected, which are correct. But 0 gets rejected is a bug: 0 is a valid value.

I traced the issue to InputRequired, I am printing the code for this class below:

InputRequired in ./venv/lib/python3.10/site-packages/wtforms/validators.py 
Enter fullscreen mode Exit fullscreen mode
class InputRequired: """ Validates that input was provided for this field. Note there is a distinction between this and DataRequired in that InputRequired looks that form-input data was provided, and DataRequired looks at the post-coercion data. Sets the `required` attribute on widgets. """ def __init__(self, message=None): self.message = message self.field_flags = {"required": True} def __call__(self, form, field): if field.raw_data and field.raw_data[0]: return if self.message is None: message = field.gettext("This field is required.") else: message = self.message field.errors[:] = [] raise StopValidation(message) 
Enter fullscreen mode Exit fullscreen mode

The issue is caused by the first line in def call(self, form, field):

 def __call__(self, form, field): if field.raw_data and field.raw_data[0]: return 
Enter fullscreen mode Exit fullscreen mode

field.raw_data[0] is evaluated to 0, and it is a False, causing the whole if statement to evaluate to False.

Change it to:

 if field.raw_data and len(field.raw_data): 
Enter fullscreen mode Exit fullscreen mode

And it will accept 0 as a value. The correct expected output is now:

--break_hour: -1 False {'break_hour': ['Break Hour is between 0 and 23.']} --break_hour: 'xx' False {'break_hour': ['Not a valid integer value.', 'Break Hour is between 0 and 23.']} --break_hour: 0 True {} --break_hour: 1 True {} 
Enter fullscreen mode Exit fullscreen mode

061-03.png

Right now, I am just having the change made locally. I am not sure what to do with it just yet. Thank you for reading. I hope this info is useful. Stay safe as always.

✿✿✿

Feature image sources:

Top comments (1)

Collapse
 
behainguyen profile image
Be Hai Nguyen

I ended up subclass InputRequired, and use InputRequiredEx in place of InputRequired:

class InputRequiredEx(InputRequired): def __call__(self, form, field): if field.raw_data and len(field.raw_data): return if self.message is None: message = field.gettext("This field is required.") else: message = self.message field.errors[:] = [] raise StopValidation(message) 
Enter fullscreen mode Exit fullscreen mode