mr.igor
Today I released mr.igor, a utility for helping you write Python faster by filling in missing imports based on where you’ve imported the names from before.
Here’s a one-minute screencast showing how it works.
Today I released mr.igor, a utility for helping you write Python faster by filling in missing imports based on where you’ve imported the names from before.
Here’s a one-minute screencast showing how it works.
I recently faced the task of joining back together a Plone site composed of 4 ZODB filestorages that had been (mostly through cavalier naïveté on my part) split asunder some time ago.
Normally I would probably just do a ZEXP export of each of the folders that lived in its own mountpoint, then remove the mountpoints and reimport the ZEXP files into the main database. However, that wasn’t going to work in this case because the database included some cross-database references.
Some background: Normally in Zope, mountpoints are the only place where one filestorage references another one, but the ZODB has some support for *any* object to link to any other object in any other database, and this can happen within Zope if you copy an object from one filestorage to another. This is generally bad, since the ZODB’s support for cross-database references is partial — when you pack one filestorage, the garbage collection routine doesn’t know about the cross-database references (unless you use zc.zodbdgc), so an object might get removed even if some other filestorage still refers to it, and you’ll get POSKeyErrors. Also, in ZODB 3.7.x, the code that handles packing doesn’t know about cross-database references, so you’ll get KeyError: ‘m’ or KeyError: ‘n’ while packing.
Well, this is what had happened to my multi-database, and I wanted to keep those cross-database references intact while I merged the site back into one monolithic filestorage. So I ended up adapting the ZEXP export code to:
Here is the script I ended up with. If you need to use it, you should:
"""Support for export of multidatabases."""
##############################################################################
#
# Based on the ZODB import/export code.
# Copyright (c) 2009 David Glick.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
import logging
import cPickle, cStringIO
from ZODB.utils import p64, u64
from ZODB.ExportImport import export_end_marker
from ZODB.DemoStorage import DemoStorage
logger = logging.getLogger('multiexport')
def export_zexp(self, fname):
context = self
f = open(fname, 'wb')
f.write('ZEXP')
for oid, p in flatten_multidatabase(context):
f.writelines((oid, p64(len(p)), p))
f.write(export_end_marker)
f.close()
def flatten_multidatabase(context):
"""Walk a multidatabase and yield rewritten pickles with oids for a single database"""
base_oid = context._p_oid
base_conn = context._p_jar
dbs = base_conn.connections
dummy_storage = DemoStorage()
oids = [(base_conn._db.database_name, base_oid)]
done_oids = {}
# table to keep track of mapping old oids to new oids
ooid_to_oid = {oids[0]: dummy_storage.new_oid()}
while oids:
# loop while references remain to objects we haven't exported yet
(dbname, ooid) = oids.pop(0)
if (dbname, ooid) in done_oids:
continue
done_oids[(dbname, ooid)] = True
db = dbs[dbname]
try:
# get pickle
p, serial = db._storage.load(ooid, db._version)
except:
logger.debug("broken reference for db %s, oid %s", (dbname, repr(ooid)),
exc_info=True)
else:
def persistent_load(ref):
""" Remap a persistent id to a new ID and create a ghost for it.
This is called by the unpickler for each reference found.
"""
# resolve the reference to a database name and oid
if isinstance(ref, tuple):
rdbname, roid = (dbname, ref[0])
elif isinstance(ref, str):
rdbname, roid = (dbname, ref)
else:
try:
ref_type, args = ref
except ValueError:
# weakref
return
else:
if ref_type in ('m', 'n'):
rdbname, roid = (args[0], args[1])
else:
return
# traverse Products.ZODBMountpoint mountpoints to the mounted location
rdb = dbs[rdbname]
p, serial = rdb._storage.load(roid, rdb._version)
klass = p.split()[0]
if 'MountedObject' in klass:
mountpoint = rdb.get(roid)
# get the object with the root as a parent, then unwrap,
# since there's no API to get the unwrapped object
mounted = mountpoint._getOrOpenObject(app).aq_base
rdbname = mounted._p_jar._db.database_name
roid = mounted._p_oid
if roid:
print '%s:%s -> %s:%s' % (dbname, u64(ooid), rdbname, u64(roid))
oids.append((rdbname, roid))
try:
oid = ooid_to_oid[(rdbname, roid)]
except KeyError:
# generate a new oid and associate it with this old db/oid
ooid_to_oid[(rdbname, roid)] = oid = dummy_storage.new_oid()
return Ghost(oid)
# do the repickling dance to rewrite references
pfile = cStringIO.StringIO(p)
unpickler = cPickle.Unpickler(pfile)
unpickler.persistent_load = persistent_load
newp = cStringIO.StringIO()
pickler = cPickle.Pickler(newp, 1)
pickler.persistent_id = persistent_id
pickler.dump(unpickler.load())
pickler.dump(unpickler.load())
p = newp.getvalue()
yield ooid_to_oid[(dbname, ooid)], p
class Ghost(object):
__slots__ = ("oid",)
def __init__(self, oid):
self.oid = oid
def persistent_id(obj):
if isinstance(obj, Ghost):
return obj.oid
export_zexp(app.mysite, '/tmp/mysite.zexp')
I’ve used this script with apparent success, but it has not been extensively tested and your mileage may of course vary.