[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