[Zope-Checkins] SVN: Products.Five/branches/1.4/ Added preliminary support for being able to register any python package as a zope2 product (so it shows up in Control Panel).

Rocky Burt rocky at serverzen.com
Sun Mar 12 14:42:15 EST 2006


Log message for revision 65927:
  Added preliminary support for being able to register any python package as a zope2 product (so it shows up in Control Panel).
  

Changed:
  _U  Products.Five/branches/1.4/
  U   Products.Five/branches/1.4/__init__.py
  U   Products.Five/branches/1.4/fiveconfigure.py
  A   Products.Five/branches/1.4/pythonproducts.py
  U   Products.Five/branches/1.4/tests/test_registerpackage.py
  A   Products.Five/branches/1.4/tests/testing/pythonproduct2/
  A   Products.Five/branches/1.4/tests/testing/pythonproduct2/__init__.py

-=-

Property changes on: Products.Five/branches/1.4
___________________________________________________________________
Name: svk:merge
   - 98c0701e-2f07-0410-a1a0-c1bc72243522:/local/Products.Five/branches/1.4:8741
   + 98c0701e-2f07-0410-a1a0-c1bc72243522:/local/Products.Five/branches/1.4:8742

Modified: Products.Five/branches/1.4/__init__.py
===================================================================
--- Products.Five/branches/1.4/__init__.py	2006-03-12 19:41:31 UTC (rev 65926)
+++ Products.Five/branches/1.4/__init__.py	2006-03-12 19:42:14 UTC (rev 65927)
@@ -19,6 +19,7 @@
 from Globals import INSTANCE_HOME
 
 import zcml
+import pythonproducts
 
 # public API provided by Five
 # usage: from Products.Five import <something>
@@ -26,4 +27,5 @@
 from skin.standardmacros import StandardMacros
 
 def initialize(context):
+    pythonproducts.setupPythonProducts(context)
     zcml.load_site()

Modified: Products.Five/branches/1.4/fiveconfigure.py
===================================================================
--- Products.Five/branches/1.4/fiveconfigure.py	2006-03-12 19:41:31 UTC (rev 65926)
+++ Products.Five/branches/1.4/fiveconfigure.py	2006-03-12 19:42:14 UTC (rev 65927)
@@ -23,6 +23,8 @@
 import warnings
 
 import App.config
+from App.Product import initializeProduct
+from App.ProductContext import ProductContext
 import Products
 from zLOG import LOG, ERROR
 
@@ -41,6 +43,7 @@
 from traversable import Traversable
 from bridge import fromZ2Interface
 from browser.metaconfigure import page
+import pythonproducts
 
 debug_mode = App.config.getConfiguration().debug_mode
 
@@ -258,13 +261,26 @@
         args = (class_, meta_type, permission, addview, icon, global_)
         )
 
-# clean up code
-
 def _registerPackage(module_, initFunc=None):
     """Registers the given python package as a Zope 2 style product
     """
 
+    if not hasattr(module_, '__path__'):
+        raise ValueError("Must be a package and the " \
+                         "package must be filesystem based")
+    
+    product = initializeProduct(module_, 
+                                module_.__name__, 
+                                module_.__path__[0], 
+                                pythonproducts._zope_app)
 
+    product.package_name = module_.__name__
+
+    if initFunc is not None:
+        newContext = ProductContext(product, pythonproducts._zope_app, module_)
+        initFunc(newContext)
+
+
 def registerPackage(_context, package, initialize=None):
     """ZCML directive function for registering a python package product
     """
@@ -275,6 +291,7 @@
         args = (package,initialize)
         )
 
+# clean up code
 
 def killMonkey(class_, name, fallback, attr=None):
     """Die monkey, die!"""

Added: Products.Five/branches/1.4/pythonproducts.py
===================================================================
--- Products.Five/branches/1.4/pythonproducts.py	2006-03-12 19:41:31 UTC (rev 65926)
+++ Products.Five/branches/1.4/pythonproducts.py	2006-03-12 19:42:14 UTC (rev 65927)
@@ -0,0 +1,147 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Setup necessary monkey patches and other related logic for using 
+regular python packages for zope2 products
+
+$Id$
+"""
+__author__ = "Rocky Burt"
+
+import os
+import types
+
+import Products
+from App.Product import initializeProduct
+from App.ProductContext import ProductContext
+
+_zope_app = None
+
+def setupPythonProducts(appOrContext):
+    """Initialize the python-packages-as-products logic
+    """
+    
+    from OFS.Application import Application
+    
+    if isinstance(appOrContext, Application):
+        _zope_app = appOrContext
+    else:
+        _zope_app = appOrContext._ProductContext__app
+    
+    global _zope_app
+    #applyPatches(_zope_app)
+
+
+def applyPatches(app):
+    """Apply necessary monkey patches to force Zope 2 to be capable of
+    handling "products" that are not necessarily located under the Products
+    package.  Ultimately all functionality provided by these patches should
+    be folded into Zope 2 core.
+    """
+    
+    patch_ProductDispatcher__bobo_traverse__(app)
+    patch_externalmethod(app)
+
+
+# BEGIN MONKEY PATCHES
+# Most of these monkey patches were repurposed from the code I 
+# wrote for Basket - Rocky
+
+def product_packages(app):
+    """Returns all product packages including the regularly defined
+    zope2 packages and those without the Products namespace package.
+    """
+    
+    old_product_packages = {}
+    for x in dir(Products):
+        m = getattr(Products, x)
+        if isinstance(m, types.ModuleType):
+            old_product_packages[x] = m
+    
+    packages = {}
+    products = app.Control_Panel.Products
+    for product_id in products.objectIds():
+        product = products[product_id]
+        if hasattr(product, 'package_name'):
+            packages[product_id] = __import__(product.package_name)
+        elif old_product_packages.has_key(product_id):
+            packages[product_id] = old_product_packages[product_id]
+    
+    return packages
+    
+def patch_ProductDispatcher__bobo_traverse__(app):
+    """Currently, z2's App.FactoryDispatcher.ProductDispatcher only checks
+    the Products module for products to look up existing factory dispatchers
+    on.  This needs to be fixed to look in all enabled product packages
+    as well.
+    """
+    
+    from App.FactoryDispatcher import FactoryDispatcher, ProductDispatcher
+    _original__bobo_traverse__ = ProductDispatcher.__bobo_traverse__
+    global _original__bobo_traverse__
+    
+    def __bobo_traverse__(self, REQUEST, name):
+        product=self.aq_acquire('_getProducts')()._product(name)
+
+        # Try to get a custom dispatcher from a Python product
+        productPkgs = product_packages(app)
+        dispatcher_class=getattr(
+            productPkgs.get(name, None),
+            '__FactoryDispatcher__',
+            FactoryDispatcher)
+
+        dispatcher=dispatcher_class(product, self.aq_parent, REQUEST)
+        return dispatcher.__of__(self)
+    
+    ProductDispatcher.__bobo_traverse__ = __bobo_traverse__
+    
+
+def patch_externalmethod(app):
+    """In an effort to make External Methods work with regular python
+    packages, this function replaces App.Extensions.getPath with a custom 
+    getPath function.  See the getPath doc string for extra details.
+    """
+    
+    from App import Extensions, FactoryDispatcher
+    from Products.ExternalMethod import ExternalMethod
+    
+    _originalGetPath = Extensions.getPath
+    global _originalGetPath
+
+    def getPath(prefix, name, checkProduct=1, suffixes=('',)):
+        """Make sure to check paths of all registered product packages.
+        """
+
+        result = _originalGetPath(prefix, name, checkProduct, suffixes)
+        if result is not None:
+            return result
+
+        try:
+            l = name.find('.')
+            if l > 0:
+                realName = name[l + 1:]
+                toplevel = name[:l]
+                
+                m = __import__(toplevel)
+        
+                d = os.path.join(m.__path__[0], prefix, realName)
+                for s in suffixes:
+                    if s: s="%s.%s" % (d, s)
+                    else: s=d
+                    if os.path.exists(s): 
+                        return s
+        except:
+            pass
+    
+    Extensions.getPath = getPath
+    ExternalMethod.getPath = getPath

Modified: Products.Five/branches/1.4/tests/test_registerpackage.py
===================================================================
--- Products.Five/branches/1.4/tests/test_registerpackage.py	2006-03-12 19:41:31 UTC (rev 65926)
+++ Products.Five/branches/1.4/tests/test_registerpackage.py	2006-03-12 19:42:14 UTC (rev 65927)
@@ -28,9 +28,13 @@
       >>> import Products
       >>> import Products.Five
       >>> from Products.Five import zcml
+      >>> from Products.Five import pythonproducts
+      >>> zcml.load_config('meta.zcml', Products.Five)
+      >>> pythonproducts.setupPythonProducts(app)
 
-    Use the five:registerPackage directive::
-
+    Make sure a python package with no initialize (even though one
+    is specified) will fail::
+    
       >>> configure_zcml = '''
       ... <configure
       ...     xmlns="http://namespaces.zope.org/zope"
@@ -38,11 +42,38 @@
       ...     i18n_domain="foo">
       ...   <five:registerPackage
       ...       package="Products.Five.tests.testing.pythonproduct1"
+      ...       initialize="Products.Five.tests.testing.pythonproduct1.initialize"
       ...       />
       ... </configure>'''
-      >>> zcml.load_config('meta.zcml', Products.Five)
       >>> zcml.load_string(configure_zcml)
+      Traceback (most recent call last):
+          ...
+      ZopeXMLConfigurationError: ...
+      ConfigurationError: ('...pythonproduct1 has no global initialize')    
 
+    Make sure a python package with a valid initialize gets its
+    initialize function called::
+    
+      >>> configure_zcml = '''
+      ... <configure
+      ...     xmlns="http://namespaces.zope.org/zope"
+      ...     xmlns:five="http://namespaces.zope.org/five"
+      ...     i18n_domain="foo">
+      ...   <five:registerPackage
+      ...       package="Products.Five.tests.testing.pythonproduct2"
+      ...       initialize="Products.Five.tests.testing.pythonproduct2.initialize"
+      ...       />
+      ... </configure>'''
+      >>> zcml.load_string(configure_zcml)
+      pythonproduct2 initialized
+      
+    Test to see if the pythonproduct2 python package actually gets setup
+    as a zope2 product in the Control Panel.
+
+      >>> productListing = app.Control_Panel.Products.objectIds()
+      >>> 'Products.Five.tests.testing.pythonproduct2' in productListing
+      True
+
     Clean up:
 
       >>> tearDown()

Added: Products.Five/branches/1.4/tests/testing/pythonproduct2/__init__.py
===================================================================
--- Products.Five/branches/1.4/tests/testing/pythonproduct2/__init__.py	2006-03-12 19:41:31 UTC (rev 65926)
+++ Products.Five/branches/1.4/tests/testing/pythonproduct2/__init__.py	2006-03-12 19:42:14 UTC (rev 65927)
@@ -0,0 +1,4 @@
+
+
+def initialize(context):
+    print "pythonproduct2 initialized"



More information about the Zope-Checkins mailing list