Best way to validate your Database model in Python

Validate your models with code that is elegant and easy to maintain

Alfonsocv12
3 min readNov 18, 2020

On this example i will show how to validate faster and cleaner with orator bottle and Python, you could use flask or Django but in the example I’m using BottlePy.

When I started using Python for the backend of my projects, I landed into a problem that the phoenix framework for elixir and mongoose for nodeJS had solved a long time ago.

It’s a lot better to validate your request data for a new insert on the database from the model instead of the controller so I built a simple library that along orator creates and easy way to maintain your validations with a clean syntax.

from orator import Model, SoftDeletes
from orator_validator import Validator
class User(Model, SoftDeletes, Validator): __table__ = 'users'
__connection__ = 'local'
__fillable__ = [
'name', 'last_name', 'email', 'password'
]
__guarded__ = ['id', 'password']
__hidden__ = ['password', 'created_at', 'updated_at']
def token_keys(self):
return ['id', 'name', 'email']
class ValidateClass(object): def saving(self, user):
user.validate('name', require=True)
user.validate('last_name', require=True)
user.validate(
'email', require=True, regex="your_email_regex"
)
# Minimum six characters
user.validate(
'password',
require=True,
regex="^.{6,}$"
)
user.errors()
user.password = bcrypt.hashpw(user.password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
User.observe(ValidateClass())

On this simple example I validate that the user send name, last_name, if they didn’t and error will raise letting you know that it’s required, also you can’t use regex type of validation, because we all can’t agree to what is the best regex for email validates. Just put the one you prefer.

This leaves you with a fast to program environment for the controllers i just do this to handle the errors and create a new user.

from bottle import (
request, abort, error
)
class ExampleController(object): def create(self, request_json = None):
'''
Function decicated to create new restaurant admin users
return: user
rtype: json string
'''
if not request_json: request_json = request.json
try:
return User.create(request_json).to_json()
except Exception as e:
self.handle_exceptions(e)
def handle_exceptions(self, error):
'''
Function dedicated to handle error with querys
param: error: type Exception
return: abort_error
'''
if str(type(error)) == "<class 'orator_validator.ValidatorError'>":
self.abort_error(
error.status_code,
json.loads(error.body)
)
else:
self.abort_error(500, str(error))
def abort_error(self, status_code, msg=None, dict_error=None):
'''
This is the abort_error function use to finish the request
param: int status_code: This is the http error
param: str msg: This is the msg
param: dict dict_error: If you return multiple errors
return: Fucntion abort: finish the request
'''
if not dict_error:
dict_error = dict()
dict_error["code"] = status_code
dict_error["msg"] = msg
abort(status_code, str(json.dumps(dict_error)))

The problem that I encounter after using this new library was, that the updates where taking more time, than the inserts so I updated the library to validate on updates to.

from orator import Model, SoftDeletes
from orator_validator import Validator
class ItemData(Model, SoftDeletes, Validator): __table__ = 'item_data'
__connection__ = 'local'
__fillable__ = [
'humidity', 'quality', 'weight', 'stacks',
'sample', 'item_id', 'entry_date', 'lot',
'amount_paid', 'exchange_rate', 'liquidated'
]
class ValidateClass(object): def saving(self, item_data):
item_data.validate('item_id', require=True)
item_data.errors()
def updating(self, item_data):
item_data.validate_update('id', guarded=True)
item_data.validate_update('item_id', guarded=True)
item_data.validate_update('entry_date', guarded=True)
item_data.validate_update(
'amount_paid',
function_callback=self._set_liquidated_bool,
item_data=item_data
)
item_data.errors()
def _set_liquidated_bool(self, item_data):
'''
Here you can add code to omit the need for cron jobs or
triggers
param: item_data
ptype: object
'''
pass
ItemData.observe(ValidateClass())

On the validate Update function I have the same options as validate for create, but the main difference between them it’s that here I have a guarded parameter, when you activate the flag, if the user tries to update that part of the model it will raise and error because it’s forbidden.

Also the function_callback works like a trigger if the user sends the value it will trigger the callback the good part about this is that you have all the power of Python, and in most cases you will not required to use cron jobs if the api is well thought out.

The update controller look like this

def update_one(self, item_data_id):
'''
This function is dedicated to update the item_data to match some
other data
param: item_id
ptype: integer
return: updated_item_data
rtype: json dumps
'''
try:
item_data = ItemData.where('id', item_data_id).first()
item_data.update(request.json)
return item_data.to_json(default=self.datetime_json)
except Exception as e:
self.handle_exceptions(e)

To install the library just

$ pip install orator-validator

The github repo if you want to report and issue or make a pr is.

--

--