Versioning in Pleiades scripts

In preparing for a few rounds of large Pleiades coordinate updates in the next few weeks, I've been rediscovering zopectl's run command. It lets you run a script in the context of your database:

$ zopectl run script.py

much like psql (for example) lets you run a script on your Postgres database:

$ psql -f script.sql

The main differences are that with zopectl your script is Python, not SQL, and that the data is a arbitrarily large and nested mapping bound to the name app instead of tables. Imagine your dataset is a giant JSON structure: that covers much of what developing with the ZODB (Zope object database) is like. The ZODB is stored as an append-only B-Tree and enjoys many of the benefits explained in the CouchDB Guide. The essential similarities and differences between ZODB and CouchDB were summarized two years ago by Chris McDonough here.

A key feature of Pleiades is resource versioning. There's a repository tool in which we track changes to places, names, and locations; it even provides diffs between versions. I found out before the holiday that a Plone developer (Pleiades uses Plone 3.2, and your mileage may vary) has to take a few extra steps to setup supporting utilities for the repository tool in the case of an offline script.

from Products.CMFCore.utils import getToolByName
from Products.CMFUid.interfaces import (
    IUniqueIdGenerator, IUniqueIdAnnotationManagement, IUniqueIdHandler)

from zope.component import provideUtility

def setup_cmfuid(site):
    provideUtility(
        getToolByName(site, 'portal_uidgenerator'), IUniqueIdGenerator)
    provideUtility(
        getToolByName(site, 'portal_uidannotation'),
        IUniqueIdAnnotationManagement)
    provideUtility(
        getToolByName(site, 'portal_uidhandler'), IUniqueIdHandler)

Now I call this function in my script and the repository tool is ready for use. The login() function makes sure that all changes are made as the specified user (me, in this case) and bulk_update_locations() updates the coordinates from a number of CSV records within a single transaction, annotating each version change with a message.

from pleiades.bulkup import login, setup_cmfuid

site = app['plone']
setup_cmfuid(site)
login(site, user)
bulk_update_locations(site, reader, columns, message)

Mikko Ohtamaa's Command-line interaction and scripting helped me learn how to set the effective user (or login), but didn't explain why the 3 Plone tools in my case weren't being automatically registered.