5 min read Author: klaas

Validating models with cerberus

All pow models (SQL or NoSQL) use a cerberus schema as definition.

In PythonOnWheels you define your models using a cerberus schema. This doesn't only give you the benefit of using the same model definition schema for NoSQL and all SQL Databases but it also means that you have validation on board for every model and you can easily switch from SQL to NoSQL

That's pretty handy. We will focus on model validation in this hands-on documentation. If you don't know how to generate models just read the short models intro Or go to the in-depth model documentation.

For this tutorial I generated a TinyDB (NoSQL DB) model like this:

python generate_model.py -n todo -t tinydb

And this is how the generated model schema looks like:

# TinyDB Model: Todo
from testapp.models.tinydb.tinymodel import TinyModel
class Todo(TinyModel):
# Use the cerberus schema style
# which offer you immediate validation with cerberus
# http://docs.python-cerberus.org/en/stable/validation-rules.html
# types: http://docs.python-cerberus.org/en/stable/validation-rules.html#type
schema = {
'title' : { 'type' : 'string', 'maxlength' : 35 },
'text' : { 'type' : 'string' },
'tags' : { 'type' : 'list', "default" : [] },
"votes" : { "type" : "integer", "default" : 0 }

A PythonOnWheels SQL model would use the exact same definition syntax. Which is a cerberus schema for all PythonOnWheels models. Cerberus is a lightweight and extensible validation library for python. It's purpose is to define datatypes and constraints by defining a schema. It also offers validation for the actual models against the constraints in the schema. 

So with our schema we can:

  • define your attributes and their datatypes 
  • define the attributes constraints and validation rules
  • all in a concise and proven manner using a widely used python library.
  • and use the same model definition schema for SQL and NoSQL models.

What PythonOnWheels does behind the scenes

PythonOnWheels generates the actual model implementations from the cerberus schemas for you. So when you define a model like the above. PoW generates a mongoDB, tinyDB, sqlalchemy schema in the background that is actually used to work with the database of your choice. 

So let's use the validation 

Let's look again at our model's schema definition, because the validation uses our definition to check if an actual model instance is correct or not.

Adapt the schema to look like this:

schema = {         
'title' : { 'type' : 'string', 'maxlength' : 35 },
'text' : { 'type' : 'string' },
"status": { "type" : "string", "default" : "new", "allowed" : ["new", "wip", "done", "hold"]},
"votes": { "type" : "integer", "default" : 0 }

To ease this just copy and replace the whole schema.

But we just rename tags to status and add the "allowed" : ["wip", "done", "hold"] definition.

Attribute Types

You can see that we defined four attributes title, text, status and votes and we also defined their types. Which are all strings, only the points attribute is of type integer.  This is the first part that will be checked. Are the types of a model instance correct ? 

Attribute constraints

For some of the attributes, in this case title and status we also defined constraints. Constraints just narrow the allowed values for an attribute type. For the title attribute the type has to be a string with the constraint that it may not be longer than 35 characters. The status attribute has to be of type string as well, but it also only validates if the string is one of the allowed: "new", "wip", "done" or "hold". Since we use cerberus you can rely on a lot of more possible constraint definitions. Just read the full validation rules docs

Hands-on tutorial part

Fire up your python interpreter and import your model. If you don't know how to generate a PythonOnWheels app read the 2 Minute intro. See above how to generate a model.

>>> from testapp.models.tinydb.todo import Todo

Create an instance

>>> t=Todo()

Let us review the schema for the status attribute:

 "status":   { "type" : "string", "default" : "new", "allowed" : ["new", "wip", "done", "hold"]}, 

As you know, this defines status to be of type string AND only accepts the allowed values which are in this case: new, wip, done and hold. Any other value will be validated as False.

Set some attributes

>>> t.title="test" 
>>> t.status="yihaa"

Validate the model to see if it's accepted

>>> r=t.validate()

Check the validation result

>>> r 
>>> (False, <cerberus.validator.Validator object at 0x10c7b2160>) 

As you can see, the validate() method returns a 2-tuple. 

  • The first attribute is always a boolean representing the validation result.
  • The second attribute is a cerberus validator if the validation failed or None if the validation was ok.

We can use the cerberus.validator to dive deeper into the validation errors.

The most common case is to check the current errors attribute.

>>> r[1].errors 
{'status': ['unallowed value yihaa']}

Ah, ok, "yihaa" is not allowed. So let's set an allowed value, like "done".

>>> t.status="done"

Let's check it again

>>> r=t.validate() 
>>> r
(True, None)

And now our todo passes the validation. 

I hope you agree that using cerberus schemas makes it really easy not only to define database models in an elegant way but also gives us a lot of benefit with validation as well. The schema syntax is very straight forward using the datatypes available in python as well and easy to remember constraints like "allowed", "maxlength", "anyof" and so on.

Hope you enjoy PythonOnWheels. If you have any questions or remarks or errors you can open an issue on github or tweet to @pythononwheels.