Flask Python Model Validation


Question

Coming from a php background, I am learning python through Flask. I have used WTForms for the client, and this handles validation nicely.

However, one of the things that I would like to use flask for is a public API, in which case I would like all validation to be run on my models. I thought that SQLAlchemy would include validation functionality, but this doesn't seem to be the case.

I have come across Colander, which looks nice, but I am kinda surprised that there are not more ubiquitous validation libraries. And even more surprised that SQLAlchemy doesn't do this natively.

What are the options here? Perhaps I am missing something, but how can I easily validate model data?

1
11
7/20/2012 3:27:27 PM

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 validates

class 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 value
listen(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...

20
5/23/2017 12:17:58 PM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon