[Erp5-report] r40827 nicolas.dumazet - in /erp5/trunk/products/ERP5Type: dynamic/ tests/
nobody at svn.erp5.org
nobody at svn.erp5.org
Mon Nov 29 06:49:08 CET 2010
Author: nicolas.dumazet
Date: Mon Nov 29 06:49:08 2010
New Revision: 40827
URL: http://svn.erp5.org?rev=40827&view=rev
Log:
Change ghost behavior so that a ghost always derives from the old bases.
When a portal type class is reset, the ghost that will be used should not
be a "basic" ghost (InitGhostBase) only deriving from Base, or we will have
a lot of code breaking. This ghost should of course have a __getattribute__
method making sure that we get out of ghost state as soon as possible, but most
of all, the __bases__ of this ghost should be the __bases__ of the old portal
type.
In this way, the class behavior before and after a restoreGhostState should not
change, pending un-ghostification. In particular, class attribute lookups will
still return the correct values. (only instance attribute lookups can trigger
a loadClass call)
Modified:
erp5/trunk/products/ERP5Type/dynamic/lazy_class.py
erp5/trunk/products/ERP5Type/tests/testDynamicClassGeneration.py
Modified: erp5/trunk/products/ERP5Type/dynamic/lazy_class.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/dynamic/lazy_class.py?rev=40827&r1=40826&r2=40827&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/dynamic/lazy_class.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/dynamic/lazy_class.py [utf8] Mon Nov 29 06:49:08 2010
@@ -18,8 +18,14 @@ ERP5BaseBroken = type('ERP5BaseBroken',
for x in PersistentBroken.__dict__.iteritems()
if x[0] not in ('__dict__', '__module__', '__weakref__')))
-class GhostPortalType(ERP5Base): #SimpleItem
+
+class GhostBaseMetaClass(ExtensionClass):
+ """
+ Generate classes that will be used as bases of portal types to
+ mark portal types as non-loaded and to force loading it.
"""
+
+ ghost_doc = """\
Ghost state for a portal type class that is not loaded.
When an instance of this portal type class is loaded (a new object is
@@ -31,33 +37,42 @@ class GhostPortalType(ERP5Base): #Simple
load, a portal type class does not use GhostPortalType in its __bases__
anymore.
"""
- def __init__(self, *args, **kw):
- self.__class__.loadClass()
- getattr(self, '__init__')(*args, **kw)
-
- def __getattribute__(self, attr):
- """
- This is only called once to load the class.
- Because __bases__ is changed, the behavior of this object
- will change after the first call.
- """
- # Class must be loaded if '__of__' is requested because otherwise,
- # next call to __getattribute__ would lose any acquisition wrapper.
- if attr in ('__class__',
- '__getnewargs__',
- '__getstate__',
- '__dict__',
- '__module__',
- '__name__',
- '__repr__',
- '__str__') or attr[:3] in ('_p_', '_v_'):
- return super(GhostPortalType, self).__getattribute__(attr)
- #LOG("ERP5Type.Dynamic", BLATHER,
- # "loading attribute %s.%s..." % (name, attr))
- self.__class__.loadClass()
- return getattr(self, attr)
+ def __init__(cls, name, bases, dictionary):
+ super(GhostBaseMetaClass, cls).__init__(name, bases, dictionary)
+
+ def __init__(self, *args, **kw):
+ self.__class__.loadClass()
+ getattr(self, '__init__')(*args, **kw)
+
+ def __getattribute__(self, attr):
+ """
+ This is only called once to load the class.
+ Because __bases__ is changed, the behavior of this object
+ will change after the first call.
+ """
+ # Class must be loaded if '__of__' is requested because otherwise,
+ # next call to __getattribute__ would lose any acquisition wrapper.
+ if attr in ('__class__',
+ '__getnewargs__',
+ '__getstate__',
+ '__dict__',
+ '__module__',
+ '__name__',
+ '__repr__',
+ '__str__') or attr[:3] in ('_p_', '_v_'):
+ return super(cls, self).__getattribute__(attr)
+ #LOG("ERP5Type.Dynamic", BLATHER,
+ # "loading attribute %s.%s..." % (name, attr))
+ self.__class__.loadClass()
+ return getattr(self, attr)
+
+ cls.__getattribute__ = __getattribute__
+ cls.__init__ = __init__
+ cls.__doc__ = GhostBaseMetaClass.ghost_doc
+
+InitGhostBase = GhostBaseMetaClass('InitGhostBase', (ERP5Base,), {})
-class PortalTypeMetaClass(ExtensionClass):
+class PortalTypeMetaClass(GhostBaseMetaClass):
"""
Meta class that is used by portal type classes
@@ -79,7 +94,7 @@ class PortalTypeMetaClass(ExtensionClass
PortalTypeMetaClass.subclass_register.setdefault(parent, []).append(cls)
cls.__ghostbase__ = None
- super(PortalTypeMetaClass, cls).__init__(name, bases, dictionary)
+ super(GhostBaseMetaClass, cls).__init__(name, bases, dictionary)
@classmethod
def getSubclassList(metacls, cls):
@@ -124,7 +139,9 @@ class PortalTypeMetaClass(ExtensionClass
'__ghostbase__',
'portal_type'):
delattr(cls, attr)
- cls.__bases__ = cls.__ghostbase__
+ # generate a ghostbase that derives from all previous bases
+ ghostbase = GhostBaseMetaClass('GhostBase', cls.__bases__, {})
+ cls.__bases__ = (ghostbase,)
cls.__ghostbase__ = None
cls.resetAcquisitionAndSecurity()
@@ -193,4 +210,4 @@ class PortalTypeMetaClass(ExtensionClass
ERP5Base.aq_method_lock.release()
def generateLazyPortalTypeClass(portal_type_name):
- return PortalTypeMetaClass(portal_type_name, (GhostPortalType,), {})
+ return PortalTypeMetaClass(portal_type_name, (InitGhostBase,), {})
Modified: erp5/trunk/products/ERP5Type/tests/testDynamicClassGeneration.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/tests/testDynamicClassGeneration.py?rev=40827&r1=40826&r2=40827&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/tests/testDynamicClassGeneration.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/tests/testDynamicClassGeneration.py [utf8] Mon Nov 29 06:49:08 2010
@@ -240,6 +240,51 @@ class TestPortalTypeClass(ERP5TypeTestCa
implemented_by = list(implementedBy(InterfaceTestType))
self.failIf(IForTest in implemented_by)
+ def testClassHierarchyAfterReset(self):
+ """
+ Check that after a class reset, the class hierarchy is unchanged until
+ un-ghostification happens. This is very important for multithreaded
+ environments:
+ Thread A. reset dynamic classes
+ Thread B. in Folder code for instance: CMFBTreeFolder.method(self)
+
+ If a reset happens before the B) method call, and does not keep the
+ correct hierarchy (for instance Folder superclass is removed from
+ the mro()), a TypeError might be raised:
+ "method expected CMFBTreeFolder instance, got erp5.portal_type.xxx
+ instead"
+
+ This used to be broken because the ghost state was only what is called
+ lazy_class.InitGhostBase: a "simple" subclass of ERP5Type.Base
+ """
+ name = "testClassHierarchyAfterReset Module"
+ types_tool = self.portal.portal_types
+
+ ptype = types_tool.newContent(id=name, type_class="Folder")
+ transaction.commit()
+ module_class = types_tool.getPortalTypeClass(name)
+ module_class.loadClass()
+
+ # first manually reset and check that everything works
+ from Products.ERP5Type.Core.Folder import Folder
+ self.assertTrue(issubclass(module_class, Folder))
+ synchronizeDynamicModules(self.portal, force=True)
+ self.assertTrue(issubclass(module_class, Folder))
+
+ # then change the type value to something not descending from Folder
+ # and check behavior
+ ptype.setTypeClass('Address')
+
+ # while the class has not been reset is should still descend from Folder
+ self.assertTrue(issubclass(module_class, Folder))
+ # finish transaction and trigger workflow/DynamicModule reset
+ transaction.commit()
+ # while the class has not been unghosted it's still a Folder
+ self.assertTrue(issubclass(module_class, Folder))
+ # but it changes as soon as the class is loaded
+ module_class.loadClass()
+ self.assertFalse(issubclass(module_class, Folder))
+
class TestZodbPropertySheet(ERP5TypeTestCase):
"""
XXX: WORK IN PROGRESS
More information about the Erp5-report
mailing list