[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