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?
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.
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
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...
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 Inputs
from flask_inputs.validators import JsonSchema
schema = {
'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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With