Commit c89c6533 authored by Arnaud Fontaine's avatar Arnaud Fontaine

Add erp5.component.extension as a dynamic module where extensions are lazily loaded.

parent 610b2abe
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
############################################################################## ##############################################################################
# #
# Copyright (c) 2010 Nexedi SA and Contributors. All Rights Reserved. # Copyright (c) 2012 Nexedi SA and Contributors. All Rights Reserved.
# Arnaud Fontaine <arnaud.fontaine@nexedi.com>
# Jean-Paul Smets <jp@nexedi.com>
# #
# WARNING: This program as such is intended to be used by professional # WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential # programmers who take the whole responsibility of assessing all potential
...@@ -26,6 +28,9 @@ ...@@ -26,6 +28,9 @@
# #
############################################################################## ##############################################################################
import imp
import os
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
from Products.ERP5Type.Base import Base from Products.ERP5Type.Base import Base
...@@ -53,15 +58,20 @@ class DocumentComponent(Base): ...@@ -53,15 +58,20 @@ class DocumentComponent(Base):
'Reference', 'Reference',
'TextDocument') 'TextDocument')
def loadComponent(self): def load(self):
""" # XXX-arnau: There should be a load_source() taking a string rather than
""" # creating a temporary file
from Products.ERP5Type.Utils import importLocalDocument
from App.config import getConfiguration from App.config import getConfiguration
instance_home = getConfiguration().instancehome instance_home = getConfiguration().instancehome
path = '%s/component/document' % instance_home path = '%s/Component' % instance_home
component_path = '%s/%s.py' % (path, self.getReference()) # using ID would be better... with some extensions in importLocalDocument if not os.path.isdir(path):
component_file = open(component_path, 'w') os.mkdir(path)
component_file.write(self.getTextContent())
component_file.close() component_path = '%s/%s.py' % (path, self.getId())
importLocalDocument(self.getReference(), path=path) with open(component_path, 'w') as component_file:
component_file.write(self.getTextContent())
try:
return imp.load_source(self.getReference(), component_path)
finally:
os.remove(component_path)
...@@ -27,88 +27,19 @@ ...@@ -27,88 +27,19 @@
# #
############################################################################## ##############################################################################
""" Component Tool module for ERP5 """
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Tool.BaseTool import BaseTool from Products.ERP5Type.Tool.BaseTool import BaseTool
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
class ComponentLoader:
"""
A callable class which delegates component load to ComponentTool
and which contains attributes which can either be called or
implement default component access for different types of components
"""
def __init__(self):
self.component = self
# This could be automated by introspecting
# portal_types and listing all component types
self.document = TypedComponentLoader('Document Component')
self.interface = TypedComponentLoader('Interface Component')
self.mixin = TypedComponentLoader('Mixin Component')
self.accessor = TypedComponentLoader('Accessor Component')
self.test = TypedComponentLoader('Test Component')
self.extension = TypedComponentLoader('Extension Component')
def __call__(self, portal_type, reference, version=None):
site = getSite()
return site.portal_components.loadComponent(portal_type, reference, version=version)
def TypedComponentLoader(ComponentLoader):
"""
A callable class which delegates component load to
ComponentLoader and provides default component access through
attributes.
"""
def __init__(self, portal_type):
self._portal_type = portal_type
def __call__(self, reference, version=None):
return ComponentLoader.__call__(self._portal_type, reference, version=version)
def __getattr__(self, key):
if key.startswith('_'):
return self.__dict__[key]
return self(key)
component = ComponentLoader()
component_revision = None
component_dict = None
class ComponentTool(BaseTool): class ComponentTool(BaseTool):
""" """
This tool provides methods to load the the different types This tool provides methods to load the the different types
of components of the ERP5 framework: Document classes, interfaces, of components of the ERP5 framework: Document classes, interfaces,
mixin classes, fields, accessors, etc. mixin classes, fields, accessors, etc.
""" """
id = "portal_components" id = "portal_components"
meta_type = "ERP5 Component Tool" meta_type = "ERP5 Component Tool"
portal_type = "Component Tool" portal_type = "Component Tool"
security = ClassSecurityInfo() security = ClassSecurityInfo()
manage_options = BaseTool.manage_options security.declareObjectProtected(Permissions.AccessContentsInformation)
def loadComponent(self, portal_type, reference, version=None):
"""
This first version compiles all components once. A lazy
version could be more efficient.
"""
global component_dict
# Reset cache if modified
#if component_revision is None:
# component_dict = None
if component_dict is None:
component_dict = {}
for document in contentValues():
portal_type = document.getPortalType()
version = document.getVersion()
reference = document.getReference()
component = document.loadComponent()
typed_component_dict = component_dict.setdefault(portal_type, {})
component_version_dict = typed_component_dict.setdefault(reference, {})
# How to handle default ?
component_version_dict[version] = component
component_version_dict[None] = component
return component_dict[portal_type][reference][version]
##############################################################################
#
# Copyright (c) 2012 Nexedi SARL and Contributors. All Rights Reserved.
# Arnaud Fontaine <arnaud.fontaine@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
##############################################################################
from zLOG import LOG, INFO
class ComponentProxyClass(object):
"""
XXX-arnau: should maybe use Ghost class?
"""
def __init__(self, component, module):
self._component = component
self._module = module
self.__isghost__ = False
# XXX-arnau: metaclass!
self.__class__.__name__ = component.getReference()
self.__class__.__module__ = component.getId().rsplit('.', 1)[0]
description = component.getDescription()
if description:
self.__doc__ = description
def restoreGhostState(self):
self.__isghost__ = True
def __getattr__(self, name):
if self.__isghost__:
self._module = self._component.load()
self.__isghost__ = False
LOG("ERP5Type.dynamic", INFO, "Reloaded %s" % self._component.getId())
return getattr(self._module, name)
def generateComponentClassWrapper(namespace):
def generateComponentClass(component_name):
from Products.ERP5.ERP5Site import getSite
site = getSite()
component_name = '%s.%s' % (namespace, component_name)
try:
component = getattr(site.portal_components, component_name)
except AttributeError:
LOG("ERP5Type.dynamic", INFO,
"Could not find %s, perhaps it has not been migrated yet?" % component_name)
raise
else:
if component.getValidationState() == 'validated':
klass = ComponentProxyClass(component, component.load())
LOG("ERP5Type.dynamic", INFO, "Loaded successfully %s" % component_name)
return klass
else:
raise AttributeError("Component %s not validated" % component_name)
return generateComponentClass
...@@ -87,6 +87,8 @@ def initializeDynamicModules(): ...@@ -87,6 +87,8 @@ def initializeDynamicModules():
holds accessors holders of Portal Types holds accessors holders of Portal Types
erp5.component: erp5.component:
holds component modules holds component modules
erp5.component.extension:
holds extension classes previously found in bt5 in instancehome/Extensions
""" """
erp5 = ModuleType("erp5") erp5 = ModuleType("erp5")
sys.modules["erp5"] = erp5 sys.modules["erp5"] = erp5
...@@ -122,3 +124,8 @@ def initializeDynamicModules(): ...@@ -122,3 +124,8 @@ def initializeDynamicModules():
# Components # Components
erp5.component = ModuleType("erp5.component") erp5.component = ModuleType("erp5.component")
sys.modules["erp5.component"] = erp5.component sys.modules["erp5.component"] = erp5.component
from component_class import generateComponentClassWrapper
erp5.component.extension = registerDynamicModule(
'erp5.component.extension',
generateComponentClassWrapper('erp5.component.extension'))
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