Twisted Enterprise is a database interface module for Twisted that allows python code to communicate with a relational database. It is implemented as a "service" in the Twisted architecture and so is accessible by any both client and server type applications. Twisted Enterprise is database vendor neutral and currently has support for Sybase, Interbase and Postgres database servers. As it is implemented entirely in python, it runs on Windows and Unix with no code changes.
Twisted includes a utility for building servers called "mktap" in the Twisted/bin directory. This can be used to build an Enterprise server. The option to mktap for buliding an enterprise server are:
-s, --service database vendor service to load (example: postgres, sybase, oracle) -r, --server database server instance to connect to -d, --database database instance to connect to (example: twisted, template1,masterdb). Default is "twisted" -u, --username username to connect to the database -p, --password password to connect to the database -c, --connections number of connections (threads) to spawn --pbusername username to allow connections to this service with --pbpassword password to allow connections to this service with --pbport port to start pb service on
To build an Enterprise server to use Postgres:
% mktap enterprise -s postgres -d mydatabase --pbusername remoteuser --pbpassword remotepassword
This will create a file called "enterprise.tap" which is used to start the server. The "--pb" arguments specify the username and password that remote applications will use to connect to the db service.
Twisted includes a utility to run the configured server from the ".tap" file we just created called twistd also in the Twisted/bin directory. To start the server in the foreground use:
% twist -l - -n -f enterprise.tap 24/08/2001 12:54 [-] Log opened. 24/08/2001 12:54 [-] Loading enterprise.tap... 24/08/2001 12:54 [-] Loaded. 24/08/2001 12:54 [enterprise application] twisted.spread.pb.BrokerFactory starting on 8787 24/08/2001 12:54 [enterprise application] Starting db service
To use Twisted Enterprise a program must be connected to the server and be attached to the "twisted.enterprise.db" service. The dbclient.py file included in the enterprise demonstrates the connection setup. The steps involved are:
self.client = pb.Broker() tcp.Client(self.host, 8787, self.client) self.client.requestIdentity(\ "remoteuser",\ "remotepassword",\ callback = self.preConnected,\ errback = self.couldntConnect\ )
identity.attach(\ "twisted.enterprise.db",\ "twisted", None,\ pbcallback=self.gotConnection,\ pberrback=self.couldntConnect\ )
Once we are connected to the server, we can issue SQL requests using the builtin GenericRequest object. To issue a simple SQL command you can use the built in method "simpleSQL" and passing a SQL string to be executed and any arguements to it:
self.dbUser.simpleSQL("select * from accounts where name = ?", ["testuser"] , self)
Note that the method call is on the dbUser object which is a "Perspective" and that the parameter must be a tuple or list, not a single value. The third parameter is "self" because the the DbClient object derived from twisted.spread.pb.Referenced - this allows the server to call remote methods on it. In this case we know that the server will call the method "remote_simpleSQLResults" when the query is done and return the result set.
If there was an error, the simpleSQLError method would have been called.
The simpleSQL call can only be used for single SQL commands that involve no transaction semantics. This is fine for simple database applications and useful for rapid development as it requires no changes to the database server code to change the SQL, but for more complex behaviour you can define your own database Requests on the server.
The simple SQL query above used the GenericRequest object defined in twisted.enterprise.requests. It is derived from twisted.enterprise.manager.Request which is an abstract base class for database requests. Database Requests can contain any python code, and any amount of actual database commands in the "execute" method. They can be used to implement business logic in the server. Exceptions within database requests are caught be the server framework and error message are propogated back to the caller.
For our example we will create a database request that inserts a row into an orders table and updates an accounts table within a single transaction. To create a new Request, derive a class from the Request class in enterprise.manager and implement the methods __init__ and execute.
from twisted.enterprise import requests class OrderRequest(requests.Request): def __init__(self, accountName, itemID, quantity, callback, errback): requests.Request.__init__(self, callback, errback) self.accountName = accountName self.itemID = itemID self.quantity = quantity def execute(self, connection): # begin a new transaction cur = connection.cursor() cur.execute("begin transaction") # insert the new order cur.execute("INSERT INTO orders (accountName, itemID, quantity) VALUES (?, ?, ?)", (self.accountName, self.itemID, self.quantitiy) ) # update the account cur.execute("UPDATE accounts SET numItems = numItems + 1 WHERE accountName = ?", self.accountName) # commit the transaction cur.execute("commit transcation") cur.close() self.status = 1 def loadRequests(service): service.registerRequest("order", OrderRequest)
In addition to defining the class OrderRequest, the file that contains the class must implement the method "loadRequests" to register the new Request class with the enterprise service. When this file is imported, the loadRequests method will be invoked by the service and the Request will become available for client application to use.
Save the python code for the class in a file of the format "dbrequest*.py" where the * is a name of your choice. Then, put the file in the userRequests directory. It will then be loaded when the enterprise service starts up.
Once the Request class has been created and loaded into the server, it can be invoked by a client application. Again, the dbclient.py file has an example of doing this using the builtin callRequest method:
args = (accountName, itemID, quantity) self.dbUser.callRequest("order", args, self)
the callRequest method is used to invoke a database request by name. The args tuple will be passed to the request as parameters, and on completion either the requestResults or requestError method will be called on the reference object passed as the last argument ( in this case the dbclient object itself).