Working with data
An important aspect of most web applications is the abitlity to access and process data, whether they be client or server side applications.
There are a myriad of database engines available these days and it is left to the developer to chose their preferred database engine for data storage and retrieval.
In order to provide maximum flexibility regarding database choice, Websaw ships with the voodoodal application which extends the standard PyDAL library to give additonal functionality and flexiblity when it comes to the DAL.
What is a DAL?
The DAL or Data Access Layer dynamically generates SQL in real time using the specified dialect for the database back end, so that you do not have to write SQL code or learn different SQL dialects.
The term SQL is used generically and the application will be portable between different types of databases.
Supported database engines
Here is a list of the database engines that Websaw supporsts as standard:
Database |
Drivers (source) |
|---|---|
SQLite |
sqlite3 or pysqlite2 or zxJDBC (on Jython) |
PostgreSQL |
psycopg2 or zxJDBC (on Jython) |
MySQL |
pymysql or MySQLdb |
Oracle |
cx_Oracle |
MSSQL |
pyodbc or pypyodbc |
FireBird |
kinterbasdb or fdb or pyodbc |
DB2 |
pyodbc |
Informix |
informixdb |
Ingres |
ingresdbi |
Cubrid |
cubriddb |
Sybase |
Sybase |
Teradata |
pyodbc |
SAPDB |
sapdb |
MongoDB |
pymongo |
IMAP |
imaplib |
Important
Do make sure that the correct driver for you preferred db engine is installed before tyring to connecto to the db.
So why voodoodal?
Voodoodal allows us to extend our DRY coding philosophy to the DAL. We define a model once and can use it in all applications.
What is more we can merge multiple models to create fully functional databases simply by importing the models we are interested in and intialising the new database.
Voodoodal is designed to work with mixins to provide complete functionality and flexibitlty for our applications.
Voodoodal in action
In order to better understand the power and flexibility of voodoodal lets take a look at it in action.
Before we look at some real life examples lets take a look at the structure of a typical model in detail.
Model definition
Lets create a demo model in demo_model.py
# demo_model.py
from voodoodal import Table, Field
from pydal import DAL
import datetime
now = datetime.datetime.now()
class sign_created(Table):
created = Field('datetime', default=now)
created_by = Field('reference person')
class sign_updated(Table):
updated = Field('datetime', default=now)
updated_by = Field('reference person')
class Model(DAL):
__config__ = {
# prefixes `rname` of all tables
'prefix': 'test_',
# add `primarykey = ['id']` if there is `id`-field with type != 'id'
# see `color`-table below
'auto_pk': True,
}
class person(Table):
name = Field('string', required=True)
class color(Table):
id = Field('integer')
name = Field('string', required=True)
# to inject signature(s) just specify them as base class(es)
class thing(sign_created, sign_updated):
owner = Field('reference person', required=True)
name = Field('string', required=True)
@property
def owner_id(row):
"""Define another virtual field."""
return row.thing.owner
@property
def owner_thing_name(row):
"""Define virtual field."""
return [row.thing.owner, row.thing.name]
def owner_name_meth(row):
"""Define method field."""
return [row.thing.owner, row.thing.name]
@classmethod
def get_like(self, patt):
"""Define table-method.
This will turn into `db.thing.get_like(<pattern>)`-method.
"""
db = self._db
assert self is db.thing
return db(self.name.like(patt)).select()
# hooks goes as is
def before_insert(args):
print('before_insert', args)
def before_update(s, args):
print('before_update', s, args)
def after_update(s, args):
print('after_insert', s, args)
@classmethod
def _on_define(cls, t: Table):
"""Postprocessing hook."""
print(f"_on_define: table '{t}' created")
__extra__ = ['whatever']
# special hooks
def on_action(tbl, hook, *args):
"""Convenient common hook for all before/after_insert/update/delete actions."""
print('on_action', tbl, hook, args)
@classmethod
def on_define_table(cls, tcls, t):
"""Postprocessing hook, invoked for each table."""
print(f"on_define_table: table '{t}' created from {tcls}")
@classmethod
def on_define_model(cls, db: DAL, extras: dict):
"""Postprocessing hook."""
print('on_define_model', db, extras)
As we can see the above is an example showing the ability of voodoodal. The code itself should be self explanetory and the most important thing to note is that this is purely the model definition and can be used in any db as we will see next.
Creating the database
The database initilisation and creation is normally done in a seperate module which allows us to store our model definitions seperately (normally in a common folder), to make them available to other applications that may wish to use them.
# demo_test.py
from voodoodal import ModelBuilder
from pydal import DAL
import os
from demo_model import Model
_db = DAL(
folder=f'{os.path.dirname(__file__)}/db_test'
)
# All magic goes here
@ModelBuilder(_db)
class db(Model):
pass
assert db is _db
db.commit()
Important
As the DAL supports a number of database engines as standard, the same database dfinition (model) can be used irrecpective of database engine. The only thing that needs to change will be the connection string to your database engine of choice.
As our Model is engine agnostic is is common practice to develop our apps using one db enngine then switch engines once we move to production. As can be seen from the examples we are typically using SQLite as our dev engine but can easily switch to any of the supported db engines using the same models.
Thats it. We can of course run other checks and tests and even insert data to make sure the db was created correctly etc but in essense we have created and connected to our database of choice so lets run a few tests.
# test.py
...
# check signatures
assert {db.thing.created, db.thing.created_by, db.thing.updated, db.thing.updated_by}.issubset({*db.thing})
# check rname prefix
assert all(t._rname == f'test_{t._tablename}' for t in db)
# check auto_pk
assert db.color._primarykey == ['id']
john = db.person.insert(name='John')
db.thing.insert(owner=john, name='ball')
assert db.thing.get_like('ball%')[0].name == 'ball'
db.thing(1).update_record(name='big ball')
row: Model.thing = db(db.thing).select().first()
assert row.owner_thing_name == [row.owner, row.name]
assert row.owner_id == row.owner
assert row.owner_name_meth() == [row.owner, row.name]
assert db.thing.get_like('big%')[0].name == 'big ball'
Using multiple models
In order to use multiple models in our database we simply import them and add them to the class db(…) as such.
...
@ModelBuilder(_db)
class db(Model1, Model2, Model3, ...):
pass
...
Important
Models with the same table names will be merged in order to create a unified db.
Data naming conventions should be followed in order to ensure the required results.
Now that we have seen how to define and create our database the next section will cover the standard Websaw grid classes which are the typical way for displaying and sorting dtat from our tables.