[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