Commit aa193989 authored by Tim Peters's avatar Tim Peters

Change ConflictError constructor to stop importing app objects.

When the ConflictError constructor is passed a pickle, extract
the module and class names without loading the pickle.  A ZEO
server doesn't necessarily have the implementation code for
application classes, and when that's so the attempt to raise
ConflictError was itself dying with an ImportError.
parent a66a5acb
...@@ -17,6 +17,17 @@ impossible to overlook. In a release build, ``unghostify()`` raises ...@@ -17,6 +17,17 @@ impossible to overlook. In a release build, ``unghostify()`` raises
problem in a release build (``ghostify()`` is supposed to be so simple that problem in a release build (``ghostify()`` is supposed to be so simple that
it "can't fail"). it "can't fail").
ConflictError
-------------
New in 3.3, a ``ConflictError`` exception may attempt to insert the path to
the object's class in its message. However, a ZEO server may not have
access to application class implementations, and then the attempt by the
server to raise ``ConflictError`` could raise ``ImportError`` instead while
trying to determine the object's class path. This was confusing. The code
has been changed to obtain the class path from the object's pickle, without
trying to import application modules or classes.
Install Install
------- -------
......
...@@ -90,8 +90,8 @@ class ConflictError(TransactionError): ...@@ -90,8 +90,8 @@ class ConflictError(TransactionError):
if data is not None: if data is not None:
# avoid circular import chain # avoid circular import chain
from ZODB.serialize import SimpleObjectReader from ZODB.utils import get_pickle_metadata
self.class_name = SimpleObjectReader().getClassName(data) self.class_name = "%s.%s" % get_pickle_metadata(data)
## else: ## else:
## if message != "data read conflict error": ## if message != "data read conflict error":
## raise RuntimeError ## raise RuntimeError
......
...@@ -53,6 +53,41 @@ class TestUtils(unittest.TestCase): ...@@ -53,6 +53,41 @@ class TestUtils(unittest.TestCase):
writer = BaseObjectWriter(None) writer = BaseObjectWriter(None)
self.assertEqual(writer.persistent_id(P), None) self.assertEqual(writer.persistent_id(P), None)
# It's hard to know where to put this test. We're checking that the
# ConflictError constructor uses utils.py's get_pickle_metadata() to
# deduce the class path from a pickle, instead of actually loading
# the pickle (and so also trying to import application module and
# class objects, which isn't a good idea on a ZEO server when avoidable).
def checkConflictErrorDoesntImport(self):
from ZODB.serialize import BaseObjectWriter
from ZODB.POSException import ConflictError
from ZODB.tests.MinPO import MinPO
import cPickle as pickle
obj = MinPO()
data = BaseObjectWriter().serialize(obj)
# The pickle contains a GLOBAL ('c') opcode resolving to MinPO's
# module and class.
self.assert_('cZODB.tests.MinPO\nMinPO\n' in data)
# Fiddle the pickle so it points to something "impossible" instead.
data = data.replace('cZODB.tests.MinPO\nMinPO\n',
'cpath.that.does.not.exist\nlikewise.the.class\n')
# Pickle can't resolve that GLOBAL opcode -- gets ImportError.
self.assertRaises(ImportError, pickle.loads, data)
# Verify that building ConflictError doesn't get ImportError.
try:
raise ConflictError(object=obj, data=data)
except ConflictError, detail:
# And verify that the msg names the impossible path.
self.assert_('path.that.does.not.exist.likewise.the.class' in
str(detail))
else:
self.fail("expected ConflictError, but no exception raised")
def test_suite(): def test_suite():
return unittest.makeSuite(TestUtils, 'check') return unittest.makeSuite(TestUtils, 'check')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment