A form has a field. The field has a validator. The validator calls a parser. If and only if the parsing succeeds, the validation is successful.
However, later I have to call the parser again to get the parsed value. How can I avoid this duplication? Should I attach the result to the field object as a new attribute?
Simplified Flask code:
def data_validator(form, field):
try:
# first parser call, but the result will be lost
result = parse(field.data)
except Exception as err:
raise ValidationError(f"Invalid data: {err}") from None
class DataForm(FlaskForm):
datafield = wtf.fields.StringField('Enter data', validators=[data_validator])
...
@app.post('/some/url')
def view_function():
form = DataForm()
if form.validate_on_submit():
# second parser call
result = parse(form.datafield.data)
...
In the name of "(...) Although practicality beats purity." (one of Python's guiding motos when you tipe import this
- I'd say, yes, just attach the parser result to the field instance itself:
def data_validator(form, field):
try:
result = parse(field.data) # <-- first parser call, but the result will be lost
except Exception as err:
raise ValidationError(f"Invalid data: {err}") from None
field.cooked = result
And go on with life. This is one of the advantages of Python being a dynamic language.
However, if you are using static type annotation in this project, the type checker tool will complain about this: static type checking is fundamentally incompatible with dynamic aspects of the language. The easiest way to overcome that is telling the static checker to ignore this assignment and subsequent reading from this attribute.
You may want to make use of typing.Cast
when reading the attribute back, or even having a separate annotated field class you can cast the field too, so that the static checker is aware of the new parameter (.cooked
in the example above).
Other ways to do that would be: mark the parse
function as cached with functools.cache
- that way, you'd have to write the call to parse
, but the results wouldn't be re-calculated. I am not sure that would work because I think it'd require field
to be hashable.
Another way would be creating a cache yourself, preferably using contextvars.Contextvar
: this would be more complex, but under certain points of view, it would be more "correct" than adding the value as a new attribute on the field instance.