[Checkins] SVN: zodbupdate/trunk/ - Add loading of renames via entry points.
Christian Theune
ct at gocept.com
Tue Jun 23 03:18:11 EDT 2009
Log message for revision 101242:
- Add loading of renames via entry points.
- Extend tests to ensure found renames can be saved
- Ensure that no empty transactions are written.
- Ensure that loaded rename rules override automatically detected ones.
- Ensure that loaded rename rules override missing classes.
Changed:
D zodbupdate/trunk/TODO.txt
U zodbupdate/trunk/src/zodbupdate/main.py
U zodbupdate/trunk/src/zodbupdate/tests.py
U zodbupdate/trunk/src/zodbupdate/update.py
-=-
Deleted: zodbupdate/trunk/TODO.txt
===================================================================
--- zodbupdate/trunk/TODO.txt 2009-06-23 06:52:27 UTC (rev 101241)
+++ zodbupdate/trunk/TODO.txt 2009-06-23 07:18:10 UTC (rev 101242)
@@ -1,12 +0,0 @@
-TODO
-====
-
-- Write out rewritten class names to file that can be read for offline
- updating.
-
-- Provide "offline" updating: read a list of old/new pairs and update pickles
- directly without needing the old code. Use setuptools entry points for
- discovering those mappings?
-
-- Review and accordingly improve logging.
-
Modified: zodbupdate/trunk/src/zodbupdate/main.py
===================================================================
--- zodbupdate/trunk/src/zodbupdate/main.py 2009-06-23 06:52:27 UTC (rev 101241)
+++ zodbupdate/trunk/src/zodbupdate/main.py 2009-06-23 07:18:10 UTC (rev 101242)
@@ -16,6 +16,8 @@
import ZODB.FileStorage
import logging
import optparse
+import pkg_resources
+import pprint
import sys
import zodbupdate.update
@@ -77,8 +79,17 @@
raise SystemExit(
'Exactly one of --file or --config must be given.')
+ rename_rules = {}
+ for entry_point in pkg_resources.iter_entry_points('zodbupdate'):
+ rules = entry_point.load()
+ rename_rules.update(rules)
+ logging.debug('Loaded %s rules from %s:%s' %
+ (len(rules), entry_point.module_name, entry_point.name))
+
updater = zodbupdate.update.Updater(
- storage, dry=options.dry_run, ignore_missing=options.ignore_missing)
+ storage, dry=options.dry_run,
+ ignore_missing=options.ignore_missing,
+ renames=rename_rules)
try:
updater()
except Exception, e:
@@ -88,6 +99,5 @@
if options.save_renames:
f = open(options.save_renames, 'w')
- for key, value in sorted(updater.renames.items()):
- f.write('%s,%s\n' % (key, value))
+ f.write('renames = %s' % pprint.pformat(updater.renames))
f.close()
Modified: zodbupdate/trunk/src/zodbupdate/tests.py
===================================================================
--- zodbupdate/trunk/src/zodbupdate/tests.py 2009-06-23 06:52:27 UTC (rev 101241)
+++ zodbupdate/trunk/src/zodbupdate/tests.py 2009-06-23 07:18:10 UTC (rev 101242)
@@ -29,7 +29,7 @@
class IgnoringFilter(object):
-
+ # Do not spit out any logging during testing.
def filter(self, record):
return False
@@ -94,14 +94,14 @@
transaction.commit()
del sys.modules['module1'].Factory
- self.update(ignore_missing=True)
+ updater = self.update(ignore_missing=True)
self.assertEquals('cmodule1\nFactory\nq\x01.}q\x02.',
self.storage.load(self.root['test']._p_oid, '')[0])
self.assert_(isinstance(self.root['test'],
ZODB.broken.PersistentBroken))
+ self.assertEquals({}, updater.renames)
-
def test_factory_renamed(self):
# Create a ZODB with an object referencing a factory, then
# rename the the factory but keep a reference from the old name in
@@ -109,37 +109,36 @@
# then still be able to access the object.
self.root['test'] = sys.modules['module1'].Factory()
transaction.commit()
- self.db.close()
sys.modules['module1'].NewFactory = sys.modules['module1'].Factory
sys.modules['module1'].NewFactory.__name__ = 'NewFactory'
- self.update()
+ updater = self.update()
self.assertEquals('cmodule1\nNewFactory\nq\x01.}q\x02.',
self.storage.load(self.root['test']._p_oid, '')[0])
self.assertEquals('module1', self.root['test'].__class__.__module__)
self.assertEquals('NewFactory', self.root['test'].__class__.__name__)
+ self.assertEquals({'module1 Factory': 'module1 NewFactory'}, updater.renames)
def test_factory_renamed_dryrun(self):
# Run an update with "dy run" option and see that the pickle is
# not updated.
self.root['test'] = sys.modules['module1'].Factory()
transaction.commit()
- self.db.close()
sys.modules['module1'].NewFactory = sys.modules['module1'].Factory
sys.modules['module1'].NewFactory.__name__ = 'NewFactory'
- self.update(dry=True)
-
+ updater = self.update(dry=True)
self.assertEquals('cmodule1\nFactory\nq\x01.}q\x02.',
self.storage.load(self.root['test']._p_oid, '')[0])
+ self.assertEquals({'module1 Factory': 'module1 NewFactory'}, updater.renames)
- self.update(dry=False)
-
+ updater = self.update(dry=False)
self.assertEquals('cmodule1\nNewFactory\nq\x01.}q\x02.',
self.storage.load(self.root['test']._p_oid, '')[0])
+ self.assertEquals({'module1 Factory': 'module1 NewFactory'}, updater.renames)
def test_factory_registered_with_copy_reg(self):
# Factories registered with copy_reg.pickle loose their __name__.
@@ -162,12 +161,53 @@
self.root['test'] = sys.modules['module1'].Anonymous
transaction.commit()
- self.update()
+ updater = self.update()
self.assertEquals('module1', self.root['test'].__class__.__module__)
self.assertEquals('AnonymousFactory', self.root['test'].__class__.__name__)
+ self.assertEquals({}, updater.renames)
+ def test_no_transaction_if_no_changes(self):
+ # If an update run doesn't produce any changes it won't commit the
+ # transaction to avoid superfluous clutter in the DB.
+ last = self.storage.lastTransaction()
+ updater = self.update()
+ self.assertEquals(0, updater.changes)
+ self.assertEquals(last, self.storage.lastTransaction())
+ self.assertEquals({}, updater.renames)
+ def test_loaded_renames_override_automatic(self):
+ # Same as test_factory_renamed, but provide a pre-defined rename
+ # dictionary whose rules will result in a different class being picked
+ # than what automatic detection would have done.
+ self.root['test'] = sys.modules['module1'].Factory()
+ transaction.commit()
+
+ sys.modules['module1'].NewFactory = sys.modules['module1'].Factory
+ sys.modules['module1'].NewFactory.__name__ = 'NewFactory'
+
+ updater = self.update(renames={'module1 Factory': 'module2 OtherFactory'})
+
+ self.assertEquals('cmodule2\nOtherFactory\nq\x01.}q\x02.',
+ self.storage.load(self.root['test']._p_oid, '')[0])
+ self.assertEquals({'module1 Factory': 'module2 OtherFactory'}, updater.renames)
+
+
+ def test_loaded_renames_override_missing(self):
+ # Same as test_factory_missing, but provide a pre-defined rename
+ # dictionary whose rules will result in a different class being picked
+ # than what automatic detection would have done.
+ self.root['test'] = sys.modules['module1'].Factory()
+ transaction.commit()
+
+ del sys.modules['module1'].Factory
+ updater = self.update(renames={'module1 Factory': 'module2 OtherFactory'})
+
+ self.assertEquals('cmodule2\nOtherFactory\nq\x01.}q\x02.',
+ self.storage.load(self.root['test']._p_oid, '')[0])
+ self.assertEquals({'module1 Factory': 'module2 OtherFactory'}, updater.renames)
+
+
class PickleFilterTests(unittest.TestCase):
# Tests the pickle filter for re-pickling op-codes
Modified: zodbupdate/trunk/src/zodbupdate/update.py
===================================================================
--- zodbupdate/trunk/src/zodbupdate/update.py 2009-06-23 06:52:27 UTC (rev 101241)
+++ zodbupdate/trunk/src/zodbupdate/update.py 2009-06-23 07:18:10 UTC (rev 101242)
@@ -29,13 +29,15 @@
class Updater(object):
"""Update class references for all current objects in a storage."""
- def __init__(self, storage, dry=False, ignore_missing=False):
+ def __init__(self, storage, dry=False, ignore_missing=False, renames=None):
self.ignore_missing = ignore_missing
self.dry = dry
self.storage = storage
self.missing = set()
- self.renames = {}
+ self.renames = renames or {}
+ self.changes = 0
+
def __call__(self):
t = transaction.Transaction()
self.storage.tpc_begin(t)
@@ -43,14 +45,18 @@
for oid, serial, current in self.records:
new = self.update_record(current)
- if new == current:
+ if new == current.getvalue():
continue
logger.debug('Updated %s' % ZODB.utils.oid_repr(oid))
self.storage.store(oid, serial, new, '', t)
+ self.changes += 1
if self.dry:
logger.info('Dry run selected, aborting transaction.')
self.storage.tpc_abort(t)
+ elif not self.changes:
+ logger.info('No changes, aborting transaction.')
+ self.storage.tpc_abort(t)
else:
logger.info('Committing changes.')
self.storage.tpc_vote(t)
@@ -85,7 +91,6 @@
return
if arg in self.renames:
- # XXX missing testcase
return code, self.renames[arg]
factory_module, factory_name = arg.split(' ')
More information about the Checkins
mailing list