Flask Python Model Validation Flask Python Model Validation flask flask

Flask Python Model Validation


Have you considered doing the validation in the Model layer...

This would allow you to have a perfectly DRY solution as validation would be automatically triggered whether the update source is data sent by the user, or whether it is a component of your application which is updating the model as part of an indirect update. In short, you could also reuse this solution in your front-end with WTForms, and have only one place where you do your validation for both your API and your front-end.

See this answer for more pros of doing the validation in the model.


...using tools provided by SQLAlchemy?

1. The validates() decorator for simple validation:

Using this decorator is pretty straightforward: just apply it to the fields you want to validate:

from sqlalchemy.orm import validatesclass EmailAddress(Base):    __tablename__ = 'address'    id = Column(Integer, primary_key=True)    email = Column(String)    @validates('email')    def validate_email(self, key, address):        assert '@' in address        return address

2. ORM Events for complex business rules:

You can use attribute events to perform complex validation directly when one of the attributes of an instance of a model is changed. The advantage of using attribute events is that you are guaranteed that the data in the session (the objects in-memory) are in a validated state.

Here is an example (a simple one, but you should think complex rules here) from the docs:

def validate_phone(target, value, oldvalue, initiator):    "Strip non-numeric characters from a phone number"    return re.sub(r'(?![0-9])', '', value)# setup listener on UserContact.phone attribute, instructing# it to use the return valuelisten(UserContact.phone, 'set', validate_phone, retval=True)

You could also use Mapper Events such as before_insert to postpone validation to the session.add() call, or even use Session Events to intercept commits... But you lose the integrity guarantee of the data in the session...


I'm writing a library for this, called Flask-Inputs.

Similar to Colander, you define schemas and validate your inputs against them. Like @Sean Vieira's suggestion, it relies on WTForms for validation.

Internally, it converts all request input data to MultiDicts. Just like WTForms, you can define custom validators (one built-in custom validator is for request.json data, it uses jsonschema for validation).

Since it sounds like you're running validation on data posted to a public API, here's an example for API key and posted JSON validation.

from flask_inputs import Inputsfrom flask_inputs.validators import JsonSchemaschema = {    'type': 'object',    'properties': {        'name': {'type': 'string'}    }}class ApiInputs(Inputs):    headers = {        'Authorization': [DataRequired(), valid_api_key]    }    json = [JsonSchema(schema=schema)]

Then in your route:

@app.route('/api/<version>/endpoint')def endpoint():    inputs = ApiInputs(request)    if not inputs.validate():        return jsonify(success=False, errors=inputs.errors)

The biggest benefits I've found (using it in production) is surfacing all errors in one place. Good validators covering all incoming data prevent a lot of unexpected/undefined behavior in production.


As long as the data coming in can be read in a Multi-Dict like format there is no reason why you can't still use WTForms for the validation (albeit, it is a little more awkward than using Colander).

So for a hypothetical API that produces and consumes JSON you might do something like this:

class MyDataStructure(Form):    widget = TextField("Widget", validators=[Required()])    quantity = IntegerField("Quantity", validators=[Required()])@app.route("/api/v1/widgets", methods=["POST"])def widgets():    try:        new_widget_info = json.loads(request.form.data)    except KeyError:        return jsonify(error="Must provide widget JSON in data param")    except ValueError:        return jsonify(error="Invalid JSON Provided")    data = MyDataStructure(**new_widget_info)    if not data.validate():        return jsonify(error="Missing or invalid data",                           error_details=data.errors)    else:        # Create a new widget