Basic Usage¶
This narrative documentation covers the use case of developing an
application from scratch that uses mortar_rdb
to interact with a
relational database through development and testing.
Development¶
For this narrative, we’ll assume we’re developing our application in a
python package called sample
that uses the following model:
sample/model.py
from mortar_rdb import declarative_base
from mortar_rdb.controlled import Config, scan
from sqlalchemy import Table, Column, Integer, String
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(20))
source = scan('sample')
config = Config(source)
There’s nothing particularly special about this model other than that
we’ve used mortar_rdb.declarative_base()
to obtain a declarative base rather
than calling sqlalchemy.ext.declarative.declarative_base()
. This
means that multiple python packages can all use the same declarative
base, without having to worry about which package first defines the
base.
This also means that all tables and models used in our application, regardless of the package they are defined in, can refer to each other.
To allow us to take advantage of the schema controls provided by
mortar_rdb
, we have also defined a Config
with a Source
returned from a
scan()
. The source is defined seperately to
the configuration for two reasons:
- it allows a configuration in another package to use the source defined here, which encapsulates all the tables managed by this package.
- it makes it easier to write tests for migration scripts for the tables managed by this package.
To use the above model, we have the following view code:
sample/views.py
from mortar_rdb import get_session
from sample.model import User
def add_user(name):
session = get_session()
session.add(User(name=name))
def edit_user(id,name):
session = get_session()
user = session.query(User).filter(User.id == id).one()
user.name = name
When using mortar_rdb
, the session is obtained by calling
mortar_rdb.get_session()
. This allows the provision of the session to
be independent of its use, which makes testing and deploying to
different environments easier.
It is also advised that application code does not manage committing or rollback of database transactions via the session unless absolutely necessary. These actions should be the responsibility of the framework running the application.
For the purposes of this narrative, we will use the following micro framework:
sample/run.py
from mortar_rdb import register_session
from sample import views
from sample.config import db_url
from sample.model import config
import sys
import transaction
def main():
register_session(db_url)
name = sys.argv[1]
args = sys.argv[2:]
with transaction.manager:
getattr(views, name)(*args)
print("Ran %r with %r" % (name, args))
if __name__=='__main__':
main()
Although there’s not much to it, the above framework shows the elements you will need to plug in to whatever framework you choose to use.
The main one of these is the call to register_session()
which sets up the components necessary for get_session()
to
return a Session
object.
The example framework is also shown to manage these sessions using the
transaction
package. Should your framework not use this
package, you are strongly suggested to read the documentation for
register_session()
in detail to make sure you pass the
correct parameters to get the behaviour required by your framework.
Testing¶
It’s alway a good idea to write automated tests, preferably before
writing the code under test. mortar_rdb
aids this by providing the
mortar_rdb.testing
module.
The following example shows how to provides minimal coverage using
mortar_rdb.testing.register_session()
and illustrates how the
abstraction of configuring a session from obtaining a session in
mortar_rdb
makes testing easier:
sample/tests.py
from mortar_rdb import get_session
from mortar_rdb.testing import register_session
from sample.model import User, config
from sample.views import add_user, edit_user
from unittest import TestCase
class Tests(TestCase):
def setUp(self):
self.session = register_session(config=config)
def tearDown(self):
self.session.rollback()
def test_add_user(self):
# code under test
add_user('Mr Test')
# checks
user = self.session.query(User).one()
self.assertEqual('Mr Test', user.name)
def test_edit_user(self):
# setup
self.session.add(User(id=1, name='Mr Chips'))
# code under test
edit_user('1', 'Mr Fish')
# checks
user = self.session.query(User).one()
self.assertEqual('Mr Fish', user.name)
If you wish to run these tests against a particular database, rather
than using the default in-memory SQLite database, then set the
DB_URL
enviroment variable to the SQLAlchemy url of the database
you’d like to use. For example, if you run your tests with nose
and are developing in a unix-like environment against a MySQL
database, you could do:
$ DB_URL=mysql://scott:tiger@localhost/test nosetests
Release¶
With the application developed and tested, it is now time to release
and deploy it. Users of mortar_rdb
are encouraged to create a small
database management script making use of
mortar_rdb.controlled.Scripts
.
Here’s is an example for the above model:
sample/db.py
from mortar_rdb.controlled import Scripts
from sample.config import db_url, is_production
from sample.model import config
scripts = Scripts(db_url, config, not is_production)
if __name__=='__main__':
scripts()
This script can be used to create all tables required by the
applications Config
as follows:
$ bin/db create
For database at sqlite:////test.db:
Creating the following tables:
user
Other commands are are provided by Scripts
and both the command line help, obtained with the --help
option to
either the script or any of its commands, and documentation are well
worth a read.
So, the view code, database model, tests and framework are all now ready and the database has been created. The framework is now ready to use:
$ bin/run add_user test
Ran 'add_user' with ['test']