Best way to validate your Database model in Python
Tags: Python, Validation, Bottle, Flask, Web Development

Best way to validate your Database model in Python

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.