0

I am trying to write a custom validator for my Flask Application (I am using wtforms). I need the user to input an array of float values. A valid input I would like to admit is, for example:

0.2, 0.35, 1, 2.0

or

12, 0.519, 8.7, 3, 9.999

Since the size of the array is not fixed, I thought about using a normal StringField and parse the input using , as a separator.

class PostForm(FlaskForm):
    # ...
    ar_params = StringField('AR parameters', validators=[DataRequired()])
    # ...
    submit = SubmitField('Generate Plot')

    def validate_ar_params(self, ar_params):
        # ... What should I write here?
        raise ValidationError('Invalid input. Please insert all coefficients separated by a , (e.g. 0.2, 0.35, 1, 2.0)')

How can I create this custom validator? I found Regexr but I did not understand how to use it in this situation... Also, is there maybe a way already implemented to get this input from the user rather than using StringField and parsing data?

Robb1
  • 4,587
  • 6
  • 31
  • 60

1 Answers1

0

You can process the content of the StringField in which ever way you want to ensure the input is valid and raise the ValidationError if the input is invalid. I implemented your idea of splitting the input using , as separator:

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, ValidationError


class PostForm(FlaskForm):
    # ...
    ar_params = StringField('AR parameters', validators=[DataRequired()])
    # ...
    submit = SubmitField('GeneratePlot')

    def validate_ar_params(self, ar_params):
        try:
            # try to convert each part of the input to a float 
            # see https://stackoverflow.com/a/736050/3388491
            [float(x) for x in ar_params.data.split(',')]
        except ValueError:
            raise ValidationError('Invalid input. Please...')

Take a look at the second-highest answer in the linked SO thread and consider unit testing. I added your code as a BluePrint to one of my projects and built upon my pytest setup (conftest.py):

import pytest

from app import create_app, db


class TestConfig(object):
    TESTING = True
    WTF_CSRF_ENABLED = False
    SECRET_KEY = 'test'
    SQLALCHEMY_DATABASE_URI = 'sqlite://'
    SQLALCHEMY_TRACK_MODIFICATIONS = False


@pytest.fixture
def app():
    """Create and configure a new app instance for each test."""
    app = create_app(TestConfig)

    with app.app_context():
        db.create_all()

        yield app

        db.session.remove()
        db.drop_all()


@pytest.fixture
def client(app):
    """A test client for the app."""
    return app.test_client()

With the config and fixtures in place you can write your tests (test_floatarray.py):

import pytest


def test_get_floatarray_shows_form(client):
    response = client.get('/floatarray')
    assert response.status_code == 200
    assert b'GeneratePlot' in response.data


@pytest.mark.parametrize('data', (
    '0.2',
    '0.2, 0.35, 1, 2.0',
    '12, 0.519, 8.7, 3, 9.999'
))
def test_valid_input_is_accepted(client, data):
    response = client.post('/floatarray', data={'ar_params': data})
    assert response.status_code == 200
    assert f'ar_params = {data}'.encode('utf-8') in response.data
    assert b'Invalid input' not in response.data


@pytest.mark.parametrize('data', (
    '0.a',
    'NULL',
    '-+1',
    '(1)',
    'abc'
))
def test_invalid_input_is_rejected(client, data):
    response = client.post('/floatarray', data={'ar_params': data})
    assert response.status_code == 200
    assert b'Invalid input' in response.data

And execute them via IDE or CLI:

(venv) $ python -m pytest tests/test_floatarray.py 
================================== test session starts ==================================
platform darwin -- Python 3.6.5, pytest-6.1.0, py-1.9.0, pluggy-0.13.1
rootdir: /Users/oschlueter/my-project
plugins: cov-2.10.1
collected 9 items
tests/test_floatarray.py .........                                                 [100%]

=================================== 9 passed in 1.04s ===================================
oschlueter
  • 2,596
  • 1
  • 23
  • 46