I continue to be (generally) impressed by SQLAlchemy. After a couple of days worth of work, digging around the guts of Zope transactions and thread management along with SQLAlchemy’s guts of similar nature, I think I have a system in place that will work fine with Zope 3. Zope 3 uses a two phase commit transaction management system, whereas SQLAlchemy generally supports the basic ‘begin / commit / abort’ cycle. However, by taking advantage of SQLAlchemy’s nesting of transactions, I essentially have the following model in place:
- Zope transaction begin—SQLAlchemy engine begin (starts a fresh outer scope and calls ‘begin’ on the database connection itself)
- On Zope commit start (first phase of commit)—SQLAlchemy objectstore commit (sends changes managed in SQLAlchemy’s unit of work / object graph over the wire)
- This is of interest because objectstore commit also calls
engine.begin()
andengine.commit()
. SQLAlchemy is designed for ‘reentrance’, so that multiple calls to something likeengine.begin()
are OK – as long as they’re matched by an equal number of finishing calls likeengine.commit()
. When the nesting count is back to zero,engine.commit()
saves. - In two phase commits, it’s allowable for an error to happen here. It’s probably not preferred, but it’s OK.
- This is of interest because objectstore commit also calls
- On Zope commit finish (final phase of commit)—SQLAlchemy engine commit. This ‘commit’ matches to the ‘begin’ at the start of the Zope transaction, and sends the ‘COMMIT’ statement to the target database.
Beyond that, I’m not doing too much work to track changes in either environment (SQLALchemy, ZODB) in this current setup. ZODB’s persistence manages the loading and unloading of its objects, and SQLAlchemy manages its own too. It’s not that I’m trying to mix them – it’s just that Zope really really really wants to run with the ZODB, so even if it’s not used, it’s probably going to be there.
If this system ends up actually doing its job, I’ll share the code. I’m trying to write it separate from the application I’m developing.
Also: for kicks, I’ve toyed with how I might get ActiveRecord style sytax (which I like) using Python and SQLAlchemy. This mostly works as it stands right now:
from ruts.storage.base import Base from ruts.storage import act import db.tables class Venue(Base): act.fromTable(db.tables.venues) act.hasMany('Event', 'events', order_by='start_date') class Presenter(Base): act.fromTable(db.tables.presenters) act.hasMany('Event', 'events', order_by='start_date') class Event(Base): act.fromTable(db.tables.events) act.hasOne(Presenter) act.hasOne(Venue)
>>> v = Venue.get(1) >>> v.events [<exmple.model.Event object at 0x104a930>] >>> v.events[0].venue.title 'Testy McVenueVille' >>> v.events.append(Event(title="Jackson")) >>> v.commit() >>> v.events [<example.model.Event object at 0x104a930>, <example.model.Event object at 0x2b2c090>] >>> e2 = Event.get(2) >>> e = Event.get(1) >>> from datetime import timedelta >>> e2.start_date = e.start_date + timedelta(1) >>> e2.end_date = e.end_date + timedelta(1) >>> e.end_date datetime.datetime(2006, 4, 8, 23, 0) >>> e2.end_date datetime.datetime(2006, 4, 9, 23, 0) >>> v.events[1].end_date datetime.datetime(2006, 4, 9, 23, 0) >>> v.events[1].title 'Jackson'