[Erp5-report] r38656 nicolas.dumazet - /erp5/trunk/products/ERP5Type/Dynamic/

nobody at svn.erp5.org nobody at svn.erp5.org
Mon Sep 27 11:15:30 CEST 2010


Author: nicolas.dumazet
Date: Mon Sep 27 11:15:28 2010
New Revision: 38656

URL: http://svn.erp5.org?rev=38656&view=rev
Log:
Share code necessary for portal type classes.

Note that resetDynamicDocuments is disabled, and
that initializeDynamicModules() is never called:
this change should have no effect.

Added:
    erp5/trunk/products/ERP5Type/Dynamic/lazyclass.py
Modified:
    erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py

Added: erp5/trunk/products/ERP5Type/Dynamic/lazyclass.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/Dynamic/lazyclass.py?rev=38656&view=auto
==============================================================================
--- erp5/trunk/products/ERP5Type/Dynamic/lazyclass.py (added)
+++ erp5/trunk/products/ERP5Type/Dynamic/lazyclass.py [utf8] Mon Sep 27 11:15:28 2010
@@ -0,0 +1,70 @@
+from Products.ERP5Type.Base import Base as ERP5Base
+from ExtensionClass import Base as ExtensionBase
+
+from zLOG import LOG, ERROR, BLATHER
+
+def lazyclass(name, portal_type_class_attr_getter):
+    def load(self, attr):
+        klass = None
+        # self might be a subclass of a portal type class
+        # we need to find the right parent class to change
+        for candidate_klass in self.__class__.__mro__:
+          # XXX hardcoded, this doesnt look too good
+          if candidate_klass.__module__ == "erp5.portal_type":
+            klass = candidate_klass
+            break
+        if klass is None:
+          raise AttributeError("Could not find a portal type class in class hierarchy")
+
+        portal_type = klass.__name__
+        try:
+          baseclasses, attributes = portal_type_class_attr_getter(portal_type)
+        except:
+          LOG("ERP5Type.Dynamic", ERROR,
+              "Could not access Portal Type Object for type %s" % name)
+          import traceback; traceback.print_exc()
+          raise AttributeError("Could not access Portal Type Object for type %s" % name)
+
+        # save the old bases to be able to restore a ghost state later
+        klass.__ghostbase__ = klass.__bases__
+        klass.__bases__ = baseclasses
+
+        for key, value in attributes.iteritems():
+          setattr(klass, key, value)
+
+        # beware of the scary meta type
+        type(ExtensionBase).__init__(klass, klass)
+
+        return getattr(self, attr)
+
+    class GhostPortalType(ERP5Base): #SimpleItem
+        """
+        Ghost state for a portal type that is not loaded.
+
+        One instance of this class exists per portal type class on the system.
+        When an object of this portal type is loaded (a new object is created,
+        or an attribute of an existing object is accessed) this class will
+        change the bases of the portal type class so that it points to the
+        correct Document+Mixin+interfaces+AccessorHolder classes.
+        """
+        def __init__(self, *args, **kw):
+            load(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.
+            """
+            if attr in ('__class__',
+                        '__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))
+            return load(self, attr)
+
+    return type(name, (GhostPortalType,), dict())

Modified: erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py
URL: http://svn.erp5.org/erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py?rev=38656&r1=38655&r2=38656&view=diff
==============================================================================
--- erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py [utf8] (original)
+++ erp5/trunk/products/ERP5Type/Dynamic/portaltypeclass.py [utf8] Mon Sep 27 11:15:28 2010
@@ -1,5 +1,173 @@
+
+import dynamicmodule
+
+import lazyclass
+import sys
+import inspect
+from types import ModuleType
+
+from Products.ERP5Type.patches.getSite import getSite
+from Products.ERP5Type.Globals import InitializeClass
+from Products.ERP5Type.Utils import setDefaultClassProperties
+from Products.ERP5Type.Cache import ZODBCookie
+
+from Products.ERP5Type import document_class_registry, mixin_class_registry
+
 from zLOG import LOG, ERROR, BLATHER
 
+def _import_class(classpath):
+  try:
+    module_path, class_name = classpath.rsplit('.', 1)
+    module = __import__(module_path, {}, {}, (module_path,))
+    klass = getattr(module, class_name)
+
+    # XXX is this required? (here?)
+    setDefaultClassProperties(klass)
+    InitializeClass(klass)
+
+    return klass
+  except:
+    import traceback; traceback.print_exc()
+    raise ImportError('Could not import document class %s' % classpath)
+
+def portal_type_factory(portal_type_name):
+  """
+  Given a portal type, look up in Types Tool the corresponding
+  Base Type object holding the definition of this portal type,
+  and computes __bases__ and __dict__ for the class that will
+  be created to represent this portal type
+  """
+  LOG("ERP5Type.Dynamic", 0, "Loading portal type %s..." % portal_type_name)
+
+  type_class = None
+  mixin_list = []
+  interface_list = []
+  # two exceptions that cant be loaded from types tool:
+  if portal_type_name == "Base Type":
+    # avoid chicken and egg issue:
+    # you can access portal_types/Foo if you havent
+    # loaded Base Type class, but you cant load
+    # Base Type class without accessing portal_types/Base Type
+    type_class = "ERP5TypeInformation"
+  elif portal_type_name == "Business Template":
+    # When installing a BT, Business Templates are loaded
+    # before creating any Base Type object
+    type_class = "BusinessTemplate"
+  else:
+    site = getSite()
+
+    type_tool = site.portal_types
+    try:
+      portal_type = getattr(type_tool, portal_type_name)
+    except:
+      import traceback; traceback.print_stack()
+      raise AttributeError('portal type %s not found in Types Tool' \
+                              % portal_type_name)
+
+    # type_class has a compatibility getter that should return
+    # something even if the field is not set (i.e. Base Type object
+    # was not migrated yet)
+    type_class = portal_type.getTypeClass()
+
+    # But no such getter exist for Mixins and Interfaces:
+    # in reality, we can live with such a failure
+    try:
+      mixin_list = portal_type.getTypeMixinList()
+      interface_list = portal_type.getTypeInterfaceList()
+    except:
+      # log loudly the error, but it's not _critical_
+      LOG("ERP5Type.Dynamic", ERROR,
+          "Could not load interfaces or Mixins for portal type %s" \
+              % portal_type)
+
+  if type_class is not None:
+    type_class = document_class_registry.get(type_class)
+  if type_class is None:
+    raise AttributeError('Document class is not defined on Portal Type %s' % portal_type_name)
+
+  mixin_path_list = []
+  if mixin_list:
+    mixin_path_list = map(mixin_class_registry.__getitem__, mixin_list)
+
+  ##
+  # XXX initialize interfaces here too
+  # XXX adding accesor_holder for property sheets should be done here
+  ##
+
+  classpath_list = [type_class] + mixin_path_list
+
+  baseclasses = map(_import_class, classpath_list)
+
+  #LOG("ERP5Type.Dynamic", BLATHER,
+  #    "Portal type %s loaded with bases %s" \
+  #        % (portal_type_name, repr(baseclasses)))
+  return tuple(baseclasses), dict(portal_type=portal_type_name)
+
+def initializeDynamicModules():
+  """
+  Create erp5 module and its submodules
+    erp5.portal_type
+      holds portal type classes
+    erp5.temp_portal_type
+      holds portal type classes for temp objects
+    erp5.document
+      holds document classes that have no physical import path,
+      for example classes created through ClassTool that are in
+      $INSTANCE_HOME/Document
+  """
+
+  def portal_type_loader(portal_type_name):
+    """
+    Returns a lazily-loaded "portal-type as a class"
+    """
+    return lazyclass.lazyclass(portal_type_name, portal_type_factory)
+
+  erp5 = ModuleType("erp5")
+  sys.modules["erp5"] = erp5
+  erp5.document = ModuleType("erp5.document")
+  sys.modules["erp5.document"] = erp5.document
+
+  portal_type_container = dynamicmodule.dynamicmodule('erp5.portal_type',
+                                            portal_type_loader)
+  erp5.portal_type = portal_type_container
+
+  def temp_portal_type_loader(portal_type_name):
+    """
+    Returns a class suitable for a temporary portal type
+
+    This class will in fact be a subclass of erp5.portal_type.xxx, which
+    means that loading an attribute on this temporary portal type loads
+    the lazily-loaded parent class, and that any changes on the parent
+    class will be reflected on the temporary objects.
+    """
+    klass = getattr(portal_type_container, portal_type_name)
+
+    from Products.ERP5Type.Accessor.Constant import PropertyGetter as \
+      PropertyConstantGetter
+
+    class TempDocument(klass):
+      isTempDocument = PropertyConstantGetter('isTempDocument', value=True)
+      __roles__ = None
+    TempDocument.__name__ = "Temp" + portal_type_name
+
+    # Replace some attributes.
+    for name in ('isIndexable', 'reindexObject', 'recursiveReindexObject',
+                 'activate', 'setUid', 'setTitle', 'getTitle', 'getUid'):
+      setattr(TempDocument, name, getattr(klass, '_temp_%s' % name))
+
+    # Make some methods public.
+    for method_id in ('reindexObject', 'recursiveReindexObject',
+                      'activate', 'setUid', 'setTitle', 'getTitle',
+                      'edit', 'setProperty', 'getUid', 'setCriterion',
+                      'setCriterionPropertyList'):
+      setattr(TempDocument, '%s__roles__' % method_id, None)
+    return TempDocument
+
+  erp5.temp_portal_type = dynamicmodule.dynamicmodule('erp5.temp_portal_type',
+                                                   temp_portal_type_loader)
+
+
+from ExtensionClass import Base as ExtensionBase
 def resetDynamicDocuments(context, slave=False):
   """
   Allow resetting all classes to ghost state, most likely done after
@@ -9,5 +177,23 @@ def resetDynamicDocuments(context, slave
   to invalidate them globally should set slave=True.
   """
   LOG("ERP5Type.Dynamic", 0, "Resetting dynamic classes")
-  # stub
-  return
+  return # XXX disabled for now
+  import erp5.portal_type
+  for class_name, klass in inspect.getmembers(erp5.portal_type, inspect.isclass):
+    ghostbase = getattr(klass, '__ghostbase__', None)
+    if ghostbase is not None:
+      for attr in klass.__dict__.keys():
+        if attr != '__module__':
+          delattr(klass, attr)
+      klass.__bases__ = ghostbase
+      type(ExtensionBase).__init__(klass, klass)
+
+
+  if not slave:
+    # hard invalidation to force sync between nodes
+    portal = context.getPortalObject()
+    cookie = getattr(portal, '_dynamic_class_cookie', None)
+    if cookie is not None:
+      cookie.value += 1
+    else:
+      portal._dynamic_class_cookie = ZODBCookie()




More information about the Erp5-report mailing list