[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