[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/ Fixed XML-RPC view factory discovery in apidoc. Added a bunch of tests to

Stephan Richter srichter at cosmos.phy.tufts.edu
Wed Oct 26 19:47:51 EDT 2005


Log message for revision 39660:
  Fixed XML-RPC view factory discovery in apidoc. Added a bunch of tests to 
  cover some untested scenarios.
  
  This fixes also the outstanding failing functional test.
  
  

Changed:
  U   Zope3/trunk/src/zope/app/apidoc/component.py
  U   Zope3/trunk/src/zope/app/apidoc/ifacemodule/browser.py
  U   Zope3/trunk/src/zope/app/apidoc/presentation.py
  U   Zope3/trunk/src/zope/app/apidoc/presentation.txt
  U   Zope3/trunk/src/zope/app/apidoc/utilities.py
  U   Zope3/trunk/src/zope/app/publisher/xmlrpc/metaconfigure.py
  U   Zope3/trunk/src/zope/app/xmlrpcintrospection/configure.zcml

-=-
Modified: Zope3/trunk/src/zope/app/apidoc/component.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/component.py	2005-10-26 22:11:34 UTC (rev 39659)
+++ Zope3/trunk/src/zope/app/apidoc/component.py	2005-10-26 23:47:51 UTC (rev 39660)
@@ -89,7 +89,7 @@
                        iface.extends(required_iface):
                     yield reg
                     continue
-            
+
         if level & SPECIFIC_INTERFACE_LEVEL:
             for required_iface in reg.required:
                 if required_iface is iface:
@@ -161,8 +161,8 @@
     if iface is None:
         return None
     return {'module': iface.__module__, 'name': iface.getName()}
-    
 
+
 def getAdapterInfoDictionary(reg):
     """Return a PT-friendly info dictionary for an adapter registration."""
     factory = getRealFactory(reg.value)

Modified: Zope3/trunk/src/zope/app/apidoc/ifacemodule/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/ifacemodule/browser.py	2005-10-26 22:11:34 UTC (rev 39659)
+++ Zope3/trunk/src/zope/app/apidoc/ifacemodule/browser.py	2005-10-26 23:47:51 UTC (rev 39660)
@@ -99,12 +99,12 @@
         # We have to really, really remove all proxies, since self.context (an
         # interface) is usually security proxied and location proxied. To get
         # the types, we need all proxies gone, otherwise the proxies'
-        # interfaces are picked up as well. 
+        # interfaces are picked up as well.
         iface = removeAllProxies(self.context)
         return [{'name': type.getName(),
                  'path': getPythonPath(type)}
                 for type in interface.getInterfaceTypes(iface)]
-    
+
     def getAttributes(self):
         """Return a list of attributes in the order they were specified."""
         # The `Interface` and `Attribute` class have no security declarations,
@@ -178,7 +178,7 @@
             level=component.GENERIC_INTERFACE_LEVEL))
         return [component.getAdapterInfoDictionary(reg)
                 for reg in regs]
-        
+
     def getProvidedAdapters(self):
         """Get adapters where this interface is provided."""
         # Must remove security and location proxies, so that we have access to
@@ -243,7 +243,7 @@
             views[(type in views) and type or None].append(reg)
 
 
-        sort_function = lambda x, y: cmp(x['name'], y['name']) 
+        sort_function = lambda x, y: cmp(x['name'], y['name'])
 
         for type, sel_views in views.items():
             for level, qualifier in level_map.items():

Modified: Zope3/trunk/src/zope/app/apidoc/presentation.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/presentation.py	2005-10-26 22:11:34 UTC (rev 39659)
+++ Zope3/trunk/src/zope/app/apidoc/presentation.py	2005-10-26 23:47:51 UTC (rev 39660)
@@ -36,6 +36,9 @@
 EXTENDED_INTERFACE_LEVEL = 2
 GENERIC_INTERFACE_LEVEL = 4
 
+BROWSER_DIRECTIVES_MODULE = 'zope.app.publisher.browser.viewmeta'
+XMLRPC_DIRECTIVES_MODULE = 'zope.app.publisher.xmlrpc.metaconfigure'
+
 def getViewFactoryData(factory):
     """Squeeze some useful information out of the view factory"""
     info = {'path': None, 'url': None, 'template': None, 'resource': None,
@@ -49,27 +52,45 @@
         info['path'] = base.__module__ + '.' + base.__name__
         info['template'] = relativizePath(factory.index.filename)
 
+    # Basic Type is a factory
     elif isinstance(factory, (str, unicode, float, int, list, tuple)):
         info['referencable'] = False
 
     elif factory.__module__ is not None and \
-         factory.__module__.startswith('zope.app.publisher.browser.viewmeta'):
+             factory.__module__.startswith(BROWSER_DIRECTIVES_MODULE):
         info['path'] = getPythonPath(factory.__bases__[0])
 
+    # XML-RPC view factory, generated during registration
+    elif factory.__module__ is not None and \
+             factory.__module__.startswith(XMLRPC_DIRECTIVES_MODULE):
+
+        # Those factories are method publisher and security wrapped
+        info['path'] = getPythonPath(factory.__bases__[0].__bases__[0])
+
+    # Special for views registered with the zope:view directive; the proxy
+    # view implements the security wrapping
     elif hasattr(factory, '__class__') and \
              factory.__class__.__name__ == 'ProxyView':
         factory = factory.factory
         info['path'] = factory.__module__ + '.' + factory.__name__
 
+    # A factory that is a class instance; since we cannot reference instances,
+    # reference the class.
     elif not hasattr(factory, '__name__'):
         info['path'] = getPythonPath(factory.__class__)
 
+    # A simple class-based factory
     elif type(factory) in (type, ClassType):
         info['path'] = getPythonPath(factory)
 
+    # Sometimes factories are functions; there are two cases: (1) the factory
+    # itself is a function, and (2) the original factory was wrapped by a
+    # function; in the latter case the function must have a `factory`
+    # attribute that references the original factory
     elif isinstance(factory, FunctionType):
         info['path'] = getPythonPath(getattr(factory, 'factory', factory))
 
+    # We have tried our best; just get the Python path as good as you can.
     else:
         info['path'] = getPythonPath(factory)
 
@@ -86,7 +107,7 @@
     """Get the presentation type from a layer interface."""
     # Note that the order of the requests matters here, since we want to
     # inspect the most specific one first. For example, IBrowserRequest is also
-    # an IHTTPRequest. 
+    # an IHTTPRequest.
     for type in [IBrowserRequest, IXMLRPCRequest, IHTTPRequest, IFTPRequest]:
         if iface.isOrExtends(type):
             return type
@@ -122,7 +143,7 @@
                        iface.extends(required_iface):
                     yield reg
                     continue
-            
+
         if level & SPECIFIC_INTERFACE_LEVEL:
             for required_iface in reg.required[:-1]:
                 if required_iface is iface:
@@ -144,8 +165,8 @@
     layer = None
     if ILayer.providedBy(reg.required[-1]):
         layer = getInterfaceInfoDictionary(reg.required[-1])
-    
 
+
     info = {'name' : reg.name or '<i>no name</i>',
             'type' : getPythonPath(getPresentationType(reg.required[-1])),
             'factory' : getViewFactoryData(reg.value),
@@ -156,8 +177,8 @@
             'doc': doc,
             'zcml': zcml
             }
-    
+
     # Educated guess of the attribute name
     info.update(getPermissionIds('publishTraverse', klass=reg.value))
-        
+
     return info

Modified: Zope3/trunk/src/zope/app/apidoc/presentation.txt
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/presentation.txt	2005-10-26 22:11:34 UTC (rev 39659)
+++ Zope3/trunk/src/zope/app/apidoc/presentation.txt	2005-10-26 23:47:51 UTC (rev 39660)
@@ -12,10 +12,10 @@
 -----------------------------
 
 This function tries really hard to determine the correct information about a
-view factories. For example, when you create a page, a new type is dynamically
-generated upon generation. Let's look at a couple examples.
+view factory. For example, when you create a page, a new type is dynamically
+generated upon registration. Let's look at a couple examples.
 
-First, let's look at a case where a simple browser page was configured without
+First, let's inspect a case where a simple browser page was configured without
 a special view class. In these cases the factory is a `SimpleViewClass`:
 
   >>> from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
@@ -41,7 +41,7 @@
 the URL under which the factory will be found in the class browser. Some
 views, like icons, also use resources to provide their data. In these cases
 the name of the resource will be provided. Of course, not in all cases all
-values will be available. Empty values are marked with `None`. 
+values will be available. Empty values are marked with `None`.
 
 Believe it or not, in some cases the factory is just a simple type. In these
 cases we cannot retrieve any useful information:
@@ -59,7 +59,7 @@
 
   >>> class Factory(object):
   ...     pass
-  
+
   >>> info = presentation.getViewFactoryData(Factory())
   >>> pprint(info)
   {'path': '__builtin__.Factory',
@@ -69,7 +69,7 @@
    'url': '__builtin__/Factory'}
 
 One of the more common cases, however, is that the factory is a class or
-type. In this case we can just retrieve the reference directly: 
+type. In this case we can just retrieve the reference directly:
 
   >>> info = presentation.getViewFactoryData(Factory)
   >>> pprint(info)
@@ -78,13 +78,13 @@
    'resource': None,
    'template': None,
    'url': '__builtin__/Factory'}
- 
-When factories are created by the directive, they can also be functions. In
+
+When factories are created by a directive, they can also be functions. In
 those cases we just simply return the function path:
 
   >>> def factory():
   ...     pass
-  
+
   # The testing framework does not set the __module__ correctly
   >>> factory.__module__ = '__builtin__'
 
@@ -108,9 +108,60 @@
    'referencable': True,
    'resource': None,
    'template': None,
-   'url': '__builtin__/Factory'} 
+   'url': '__builtin__/Factory'}
 
+Let's now have a look at some extremly specific cases. If a view is registered
+using the ``zope:view`` directive and a permission is specified, a
+``ProxyView`` class instance is created that references its original factory:
 
+  >>> class ProxyView(object):
+  ...
+  ...     def __init__(self, factory):
+  ...         self.factory = factory
+  >>> proxyView = ProxyView(Factory)
+
+  >>> info = presentation.getViewFactoryData(proxyView)
+  >>> pprint(info)
+  {'path': '__builtin__.Factory',
+   'referencable': True,
+   'resource': None,
+   'template': None,
+   'url': '__builtin__/Factory'}
+
+Another use case is when a new type is created by the ``browser:page`` or
+``browser:view`` directive. In those cases the true/original factory is really
+the first base class. Those cases are detected by inspecting the
+``__module__`` string of the type:
+
+  >>> new_class = type(Factory.__name__, (Factory,), {})
+  >>> new_class.__module__ = 'zope.app.publisher.browser.viewmeta'
+
+  >>> info = presentation.getViewFactoryData(new_class)
+  >>> pprint(info)
+  {'path': '__builtin__.Factory',
+   'referencable': True,
+   'resource': None,
+   'template': None,
+   'url': '__builtin__/Factory'}
+
+The same sort of thing happens for XML-RPC views, except that those are
+wrapped twice:
+
+  >>> new_class = type(Factory.__name__, (Factory,), {})
+  >>> new_class.__module__ = 'zope.app.publisher.xmlrpc.metaconfigure'
+
+  >>> new_class2 = type(Factory.__name__, (new_class,), {})
+  >>> new_class2.__module__ = 'zope.app.publisher.xmlrpc.metaconfigure'
+
+  >>> info = presentation.getViewFactoryData(new_class2)
+  >>> pprint(info)
+  {'path': '__builtin__.Factory',
+   'referencable': True,
+   'resource': None,
+   'template': None,
+   'url': '__builtin__/Factory'}
+
+
 `getPresentationType(iface)`
 ----------------------------
 
@@ -126,13 +177,13 @@
   >>> from zope.publisher.interfaces.http import IHTTPRequest
   >>> from zope.publisher.interfaces.browser import IBrowserRequest
 
-  >>> class ILayer1(IBrowserRequest): 
+  >>> class ILayer1(IBrowserRequest):
   ...     pass
 
   >>> presentation.getPresentationType(ILayer1)
   <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>
-  
-  >>> class ILayer2(IHTTPRequest): 
+
+  >>> class ILayer2(IHTTPRequest):
   ...     pass
 
   >>> presentation.getPresentationType(ILayer2)
@@ -140,9 +191,9 @@
 
 If the function cannot determine the presentation type, the interface itself
 is returned:
-  
+
   >>> from zope.interface import Interface
-  >>> class ILayer3(Interface): 
+  >>> class ILayer3(Interface):
   ...     pass
 
   >>> presentation.getPresentationType(ILayer3)
@@ -168,28 +219,28 @@
 
   >>> from zope.app.testing import ztapi
   >>> ztapi.provideAdapter((IFoo, IHTTPRequest), Interface, None, name='foo')
-  >>> ztapi.provideAdapter((Interface, IHTTPRequest), Interface, None, 
+  >>> ztapi.provideAdapter((Interface, IHTTPRequest), Interface, None,
   ...                      name='bar')
-  >>> ztapi.provideAdapter((IFoo, IBrowserRequest), Interface, None, 
+  >>> ztapi.provideAdapter((IFoo, IBrowserRequest), Interface, None,
   ...                      name='blah')
 
 Now let's see what we've got. If we do not specify a type, all registrations
 should be returned:
 
-  >>> regs = list(presentation.getViews(IFoo)) 
+  >>> regs = list(presentation.getViews(IFoo))
   >>> regs.sort()
   >>> regs
-  [AdapterRegistration(('IFoo', 'IBrowserRequest'), 'Interface', 
-                       'blah', None, ''), 
-   AdapterRegistration(('IFoo', 'IHTTPRequest'), 'Interface', 
-                       'foo', None, ''), 
-   AdapterRegistration(('Interface', 'IHTTPRequest'), 'Interface', 
+  [AdapterRegistration(('IFoo', 'IBrowserRequest'), 'Interface',
+                       'blah', None, ''),
+   AdapterRegistration(('IFoo', 'IHTTPRequest'), 'Interface',
+                       'foo', None, ''),
+   AdapterRegistration(('Interface', 'IHTTPRequest'), 'Interface',
                        'bar', None, '')]
 
   >>> regs = list(presentation.getViews(Interface, IHTTPRequest))
   >>> regs.sort()
   >>> regs
-  [AdapterRegistration(('Interface', 'IHTTPRequest'), 'Interface', 
+  [AdapterRegistration(('Interface', 'IHTTPRequest'), 'Interface',
    'bar', None, '')]
 
 
@@ -208,7 +259,7 @@
   * EXTENDED_INTERFACE_LEVEL -- Only return registrations that require an
                                 interface that the specified interface extends.
 
-  * GENERIC_INTERFACE_LEVEL -- Only return registrations that explicitely 
+  * GENERIC_INTERFACE_LEVEL -- Only return registrations that explicitely
                                require the `Interface` interface.
 
 So, let's see how this is done. We first need to create a couple of interfaces
@@ -223,42 +274,42 @@
   >>> from zope.testing.cleanup import cleanUp
   >>> cleanUp()
 
-  >>> ztapi.provideAdapter((IContent, IHTTPRequest), Interface, 
+  >>> ztapi.provideAdapter((IContent, IHTTPRequest), Interface,
   ...                      None, name='view.html')
-  >>> ztapi.provideAdapter((IContent, IHTTPRequest), Interface, 
+  >>> ztapi.provideAdapter((IContent, IHTTPRequest), Interface,
   ...                      None, name='edit.html')
-  >>> ztapi.provideAdapter((IFile, IHTTPRequest), Interface, 
+  >>> ztapi.provideAdapter((IFile, IHTTPRequest), Interface,
   ...                      None, name='view.html')
-  >>> ztapi.provideAdapter((Interface, IHTTPRequest), Interface, 
+  >>> ztapi.provideAdapter((Interface, IHTTPRequest), Interface,
   ...                      None, name='view.html')
 
 Now we get all the registrations:
 
   >>> regs = list(presentation.getViews(IFile, IHTTPRequest))
-  
+
 Let's now filter those registrations:
 
   >>> result = list(presentation.filterViewRegistrations(
   ...     regs, IFile, level=presentation.SPECIFIC_INTERFACE_LEVEL))
   >>> result.sort()
   >>> result
-  [AdapterRegistration(('IFile', 'IHTTPRequest'), 'Interface', 
+  [AdapterRegistration(('IFile', 'IHTTPRequest'), 'Interface',
                        'view.html', None, '')]
 
   >>> result = list(presentation.filterViewRegistrations(
   ...     regs, IFile, level=presentation.EXTENDED_INTERFACE_LEVEL))
   >>> result.sort()
   >>> result
-  [AdapterRegistration(('IContent', 'IHTTPRequest'), 'Interface', 'edit.html', 
-                       None, ''), 
-   AdapterRegistration(('IContent', 'IHTTPRequest'), 'Interface', 'view.html', 
+  [AdapterRegistration(('IContent', 'IHTTPRequest'), 'Interface', 'edit.html',
+                       None, ''),
+   AdapterRegistration(('IContent', 'IHTTPRequest'), 'Interface', 'view.html',
                        None, '')]
 
   >>> result = list(presentation.filterViewRegistrations(
   ...     regs, IFile, level=presentation.GENERIC_INTERFACE_LEVEL))
   >>> result.sort()
   >>> result
-  [AdapterRegistration(('Interface', 'IHTTPRequest'), 'Interface', 'view.html', 
+  [AdapterRegistration(('Interface', 'IHTTPRequest'), 'Interface', 'view.html',
                        None, '')]
 
 You can also specify multiple levels at once using the Boolean OR operator,
@@ -269,11 +320,11 @@
   ...                        presentation.EXTENDED_INTERFACE_LEVEL))
   >>> result.sort()
   >>> result
-  [AdapterRegistration(('IContent', 'IHTTPRequest'), 'Interface', 
-                       'edit.html', None, ''), 
-   AdapterRegistration(('IContent', 'IHTTPRequest'), 'Interface', 
-                       'view.html', None, ''), 
-   AdapterRegistration(('IFile', 'IHTTPRequest'), 'Interface', 
+  [AdapterRegistration(('IContent', 'IHTTPRequest'), 'Interface',
+                       'edit.html', None, ''),
+   AdapterRegistration(('IContent', 'IHTTPRequest'), 'Interface',
+                       'view.html', None, ''),
+   AdapterRegistration(('IFile', 'IHTTPRequest'), 'Interface',
                        'view.html', None, '')]
 
   >>> result = list(presentation.filterViewRegistrations(
@@ -281,9 +332,9 @@
   ...                        presentation.GENERIC_INTERFACE_LEVEL))
   >>> result.sort()
   >>> result
-  [AdapterRegistration(('IFile', 'IHTTPRequest'), 'Interface', 
-                       'view.html', None, ''), 
-   AdapterRegistration(('Interface', 'IHTTPRequest'), 'Interface', 
+  [AdapterRegistration(('IFile', 'IHTTPRequest'), 'Interface',
+                       'view.html', None, ''),
+   AdapterRegistration(('Interface', 'IHTTPRequest'), 'Interface',
                        'view.html', None, '')]
 
 
@@ -298,7 +349,7 @@
 Let's first create a registration:
 
   >>> from zope.component.site import AdapterRegistration
-  >>> reg = AdapterRegistration((IFile, Interface, IHTTPRequest), 
+  >>> reg = AdapterRegistration((IFile, Interface, IHTTPRequest),
   ...                           Interface, 'view.html', Factory, 'reg info')
 
   >>> pprint(presentation.getViewInfoDictionary(reg))

Modified: Zope3/trunk/src/zope/app/apidoc/utilities.py
===================================================================
--- Zope3/trunk/src/zope/app/apidoc/utilities.py	2005-10-26 22:11:34 UTC (rev 39659)
+++ Zope3/trunk/src/zope/app/apidoc/utilities.py	2005-10-26 23:47:51 UTC (rev 39660)
@@ -126,7 +126,7 @@
         entry['write_perm'] = _evalId(checker.setattr_permission_id(name)) \
                               or _('n/a')
     else:
-        entry['read_perm'] = entry['write_perm'] = None 
+        entry['read_perm'] = entry['write_perm'] = None
 
     return entry
 
@@ -215,7 +215,7 @@
     if len(entries)%columns == 0:
         per_col = len(entries)/columns
         last_full_col = columns
-    else: 
+    else:
         per_col = len(entries)/columns + 1
         last_full_col = len(entries)%columns
     columns = []

Modified: Zope3/trunk/src/zope/app/publisher/xmlrpc/metaconfigure.py
===================================================================
--- Zope3/trunk/src/zope/app/publisher/xmlrpc/metaconfigure.py	2005-10-26 22:11:34 UTC (rev 39659)
+++ Zope3/trunk/src/zope/app/publisher/xmlrpc/metaconfigure.py	2005-10-26 23:47:51 UTC (rev 39660)
@@ -28,7 +28,7 @@
 
 def view(_context, for_=None, interface=None, methods=None,
          class_=None,  permission=None, name=None):
-    
+
     interface = interface or []
     methods = methods or []
 
@@ -53,24 +53,26 @@
     # Make sure that the class inherits MethodPublisher, so that the views
     # have a location
     if class_ is None:
-        class_ = MethodPublisher
+        class_ = original_class = MethodPublisher
     else:
+        original_class = class_
         class_ = type(class_.__name__, (class_, MethodPublisher), {})
 
     if name:
         # Register a single view
-        
+
         if permission:
             checker = Checker(require)
 
-            def proxyView(context, request, class_=class_, checker=checker):
+            def proxyView(context, request):
                 view = class_(context, request)
                 # We need this in case the resource gets unwrapped and
                 # needs to be rewrapped
                 view.__Security_checker__ = checker
                 return view
 
-            class_ =  proxyView
+            class_ = proxyView
+            class_.factory = original_class
 
         # Register the new view.
         _context.action(

Modified: Zope3/trunk/src/zope/app/xmlrpcintrospection/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/xmlrpcintrospection/configure.zcml	2005-10-26 22:11:34 UTC (rev 39659)
+++ Zope3/trunk/src/zope/app/xmlrpcintrospection/configure.zcml	2005-10-26 23:47:51 UTC (rev 39660)
@@ -6,7 +6,7 @@
   <xmlrpc:view
       for="zope.interface.Interface"
       methods="listAllMethods methodHelp methodSignature"
-      class="zope.app.xmlrpcintrospection.xmlrpcintrospection.XMLRPCIntrospection"
+      class=".xmlrpcintrospection.XMLRPCIntrospection"
       permission="zope.Public"
       />
 



More information about the Zope3-Checkins mailing list