Since Twistar uses Twisted, most methods and function calls will return a twisted.internet.defer.Deferred
object. You must understand the proper use of these objects before most of this documentation will make sense.
Initialization
Before using Twistar a connection to the DB must be made. To connect to the database
use Twisted's twisted.enterprise.adapi
module and the ConnectionPool
object. The Registry
class is used to keep track of that pool. For instance, this is the
method to specify a connection to a MySQL database:
1 2 3 4
from twisted.enterprise import adbapi from twistar.registry import Registry Registry.DBPOOL = adbapi.ConnectionPool('MySQLdb', user="twistar", passwd="apass", db="twistar")
The modules implementing DBAPI that are supported by Twistar are:
Database Creation
Twistar does not provide DB creation / migration functionality beyond asynchronously making SQL queries. There are plenty of utilities in existance to provide these capabilities. If you need to create tables in an asynchronous manner, you can always execute the creation SQL directly.
Objects will assume that the plural version of the object's class name will be the table name. For instance,
if the class name is Person, then the tablename should be People. If it is Chicken, the tablename should be
Chickens. If you want to manually specify a tablename, you can do that with the
TABLENAME
class variable (see the
DBObject
definition).
Column names should have no spaces (with words separated by a _
) and should generally be lower case.
All object tables (that don't describe relationships) should have
an auto-incrementing integer column named id
.
Defining and Interacting With Objects
Objects representing rows in a database need only extend the
DBObject
class.
1 2 3 4
from twistar.dbobject import DBObject class User(DBObject): pass
Assuming that the users
table has some VARCHAR fields like first_name and last_name and, say,
and integer field named age (along with the required auto-incrementing integer column named
id
), then object properties can be assigned:
1 2 3 4 5 6 7 8 9 10 11 12
def done(user): print "A user has just been saved with id: %i" % user.id u = User() u.first_name = "John" u.last_name = "Smith" u.age = 25 u.save().addCallback(done) # The following is equivalent u = User(first_name="John", last_name="Smith", age=25) u.save().addCallback(done)
Any additional properties that are set that don't correspond to column names are ignored on the save. For instance,
had a middle_name
property been set for the user it would have simply been ignored when the object was saved.
Methods you would traditionally find in an active record style RDBMS are also available, for instance to test for existance and delete an object instance.
Finding Objects
To find an object, use the class's find
method. It accepts a number of
arguments to group, sort, and limit results. The simplest way to use the method is to get an object
instance by id. For instance, if we wanted to find the user with an id of 1:
1
User.find(1).addCallback(...)
Additional constriants can be specified using the where
paramter and others:
1
User.find(where=['first_name = ? AND last_name = ?', "John", "Smith"], limit=1).addCallback(...)
There are many more options, all of which are described on the
DBObject
API reference page.
One thing to note: if either limit
is set to 1 or the query
is by id
, then the resulting deferred will return either the
found object or None
if not found. Otherwise, an array is returned
containing the found objects.
Class Methods
Additional class methods (other than find) includes ones for getting an array of all objects
(using the all()
method), deleting all instances
(using the deleteAll()
method), getting a count of all instances
(using the count()
method), and determining whether or not
any particular objects exist (using the exists()
method). For instance:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
def printAll(users): for user in users: print "First: %s Last: %s" % (user.first_name, user.last_name) User.all().addCallback(printAll) def printExists(doesExist): if doesExist: print "User exists!" else: print "User does not exist." User.exists(['first_name = ?', "John"]).addCallback(printExists)
Validations
Twistar also supports validations. Validations describe constraints on an object's parameters. If those constraints are violated, then an object will not be saved (this includes both creating and updating). In such a case a special parameter in the object keeps track of the errors and the messages.
As an example, let's say that we want all users to have a first name set and a last
name that has a length between 1 and 256 characters. First, we describe the class
User
as above and then add restrictions on it.
1 2 3 4
class User(DBObject): pass User.validatesPresenceOf('first_name') User.validatesLengthOf('last_name', range=xrange(1,257))
If we look at an object without those parameters, it will be invalid:
1 2 3 4 5
def vcheck(isValid, object): if not isValid: print "Object not valid: %s" % str(u.errors) u = User() u.isValid().addCallback(vcheck, u)
If we try to save an invalid object, nothing happens:
1 2 3 4 5 6
def checkUser(user): print user.id # this will be None print user.errors.errorsFor('first_name') print user.errors.errorsFor('last_name') print "There were %d errors" % len(user.errors) User().save().addCallback(checkUser)
You can also create your own validity function. It should accept an object as
a parameter and then add errors as necessary. It can return a Deferred
if necessary; the return value from the function will be ignored.
1 2 3 4 5 6 7 8 9 10
def myValidator(user): if user.first_name != "fred": user.errors.add('first_name', "must be Fred!") User.addValidator(myValidator) def test(user): # This will print "First Name must be Fred!" print user.errors print user.id # None User(first_name='not fred').save().addCallback(test)
For more information see the Validator
and
Errors
classes.
DBAPI Column Objects
The DBAPI specification requires that
implementing modules define classes corresponding to certain column types (like
the class Date
). Twistar provides a driver agnostic method for
using those classes using the Registry
class:
1 2
Date = Registry.getDBAPIClass("Date") bob = User(first_name="Bob", dob=Date(1970, 1, 1))
Then, regardless of which DBAPI module you are using, your code will always be using the correct
Date
class.