API Documentation and Component Inspection for Zope 3
Project description
This Zope 3 package provides fully dynamic API documentation of Zope 3 and registered add-on components. The package is very extensible and can be easily extended by implementing new modules.
The static version of the full Zope tree is available at:
Zope 3 API Documentation
This Zope 3 package provides fully dynamic API documentation of Zope 3 and registered add-on components. The package is very extensible and can be easily extended by implementing new modules.
Besides being an application, the API doctool also provides several public APIs to extract information from various objects used by Zope 3.
utilities – Miscellaneous classes and functions that aid all documentation modules. They are broadly usable.
interface – This module contains functions to inspect interfaces and schemas.
component – This modules provides utility functions to lookup components given an interface.
presentation – Presentation components are generally more complex than others, so a separate utilities module is provided to inspect views.
classregistry – Here a simple dictionary-based registry for all known classes is provided. It allows us to search in classes.
Using the API Dcoumentation
The APIDocumentation class provides access to all available documentation modules. Documentation modules are utilities providing IDocumentationModule:
>>> from zope.app.testing import ztapi >>> from zope.app.apidoc.interfaces import IDocumentationModule >>> from zope.app.apidoc.ifacemodule.ifacemodule import InterfaceModule >>> from zope.app.apidoc.zcmlmodule import ZCMLModule>>> ztapi.provideUtility(IDocumentationModule, InterfaceModule(), ... 'Interface') >>> ztapi.provideUtility(IDocumentationModule, ZCMLModule(), 'ZCML')
Now we can instantiate the class (which is usually done when traversing ‘++apidoc++’) and get a list of available modules:
>>> from zope.app.apidoc.apidoc import APIDocumentation >>> doc = APIDocumentation(None, '++apidoc++')>>> modules = doc.keys() >>> modules.sort() >>> modules [u'Interface', u'ZCML']>>> doc['ZCML'] #doctest:+ELLIPSIS <zope.app.apidoc.zcmlmodule.ZCMLModule object at ...>
Developing a Module
Implement a class that realizes the IDocumentationModule interface.
Register this class as a utility using something like this:
<utility provides="zope.app.apidoc.interfaces.IDocumentationModule" factory=".examplemodule.ExampleModule" name="Example" />
Take care of security by allowing at least IDocumentationModule:
<class class=".ExampleModule"> <allow interface="zope.app.apidoc.interfaces.IDocumentationModule" /> </class>
Provide a browser view called menu.html.
Provide another view, usually index.html, that can show the details for the various menu items.
Note: There are several modules that come with the product. Just look in them for some guidance.
New Static APIDOC-Version
An alternative APIDOC-Version is available through ++apidoc++/static.html Find and Tree is implemented in Javascript. So it should be possible to do a “wget” - Offline-Version of APIDOC
Component Inspection Utilities
Once you have an interface, you really want to discover on how this interface interacts with other components in Zope 3. The functions in
>>> from zope.app.apidoc import component
provide you with utilities to make those discoveries. The functions are explained in detail in this document. Before we start though, we have to have some interfaces to work with:
>>> from zope.interface import Interface >>> class IFoo(Interface): ... pass>>> class IBar(Interface): ... pass>>> class IFooBar(IFoo, IBar): ... pass>>> class IResult(Interface): ... pass>>> class ISpecialResult(IResult): ... pass
getRequiredAdapters(iface, withViews=False)
This function returns adapter registrations for adapters that require the specified interface. So let’s create some adapter registrations:
>>> from zope.publisher.interfaces import IRequest >>> from zope.app.testing import ztapi >>> ztapi.provideAdapter((IFoo,), IResult, None) >>> ztapi.provideAdapter((IFoo, IBar), ISpecialResult, None) >>> ztapi.provideAdapter((IFoo, IRequest), ISpecialResult, None) >>> ztapi.subscribe((IFoo,), None, 'stubFactory')>>> regs = list(component.getRequiredAdapters(IFoo)) >>> regs.sort() >>> regs [AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IBar], ISpecialResult, '', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFoo], IResult, '', None, u''), HandlerRegistration(<BaseGlobalComponents base>, [IFoo], u'', 'stubFactory', u'')]
Note how the adapter requiring an IRequest at the end of the required interfaces is neglected. This is because it is recognized as a view and views are not returned by default. But you can simply turn this flag on:
>>> regs = list(component.getRequiredAdapters(IFoo, withViews=True)) >>> regs.sort() >>> regs [AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IBar], ISpecialResult, '', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IRequest], ISpecialResult, '', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFoo], IResult, '', None, u''), HandlerRegistration(<BaseGlobalComponents base>, [IFoo], u'', 'stubFactory', u'')]
The function will also pick up registrations that have required interfaces the specified interface extends:
>>> regs = list(component.getRequiredAdapters(IFoo)) >>> regs.sort() >>> regs [AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IBar], ISpecialResult, '', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFoo], IResult, '', None, u''), HandlerRegistration(<BaseGlobalComponents base>, [IFoo], u'', 'stubFactory', u'')]
And all of the required interfaces are considered, of course:
>>> regs = list(component.getRequiredAdapters(IBar)) >>> regs.sort() >>> regs [AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IBar], ISpecialResult, '', None, u'')]
getProvidedAdapters(iface, withViews=False)
Of course, we are also interested in the adapters that provide a certain interface. This function returns those adapter registrations, again ignoring views by default.
>>> regs = list(component.getProvidedAdapters(ISpecialResult)) >>> regs.sort() >>> regs [AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IBar], ISpecialResult, '', None, u'')]
And by specifying the withView flag, we get views as well:
>>> regs = list(component.getProvidedAdapters(ISpecialResult, withViews=True)) >>> regs.sort() >>> regs [AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IBar], ISpecialResult, '', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IRequest], ISpecialResult, '', None, u'')]
We can of course also ask for adapters specifying IResult:
>>> regs = list(component.getProvidedAdapters(IResult, withViews=True)) >>> regs.sort() >>> regs [AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IBar], ISpecialResult, '', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IRequest], ISpecialResult, '', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFoo], IResult, '', None, u'')]
getClasses(iface)
This package comes with a little tool called the class registry (see classregistry.txt). It provides a dictionary of all classes in the visible packages. This function utilizes the registry to retrieve all classes that implement the specified interface.
Let’s start by creating and registering some classes:
>>> from zope.interface import implements >>> from zope.app.apidoc.classregistry import classRegistry>>> class MyFoo(object): ... implements(IFoo) >>> classRegistry['MyFoo'] = MyFoo>>> class MyBar(object): ... implements(IBar) >>> classRegistry['MyBar'] = MyBar>>> class MyFooBar(object): ... implements(IFooBar) >>> classRegistry['MyFooBar'] = MyFooBar
Let’s now see whether what results we get:
>>> classes = component.getClasses(IFooBar) >>> classes.sort() >>> classes [('MyFooBar', <class 'zope.app.apidoc.doctest.MyFooBar'>)]>>> classes = component.getClasses(IFoo) >>> classes.sort() >>> classes [('MyFoo', <class 'zope.app.apidoc.doctest.MyFoo'>), ('MyFooBar', <class 'zope.app.apidoc.doctest.MyFooBar'>)]
getFactories(ifaces)
Return the factory registrations of the factories that will return objects providing this interface.
Again, the first step is to create some factories:
>>> from zope.component.factory import Factory >>> from zope.component.interfaces import IFactory >>> ztapi.provideUtility(IFactory, Factory(MyFoo), 'MyFoo') >>> ztapi.provideUtility(IFactory, Factory(MyBar), 'MyBar') >>> ztapi.provideUtility(IFactory, ... Factory(MyFooBar, 'MyFooBar', 'My Foo Bar'), 'MyFooBar')
Let’s see whether we will be able to get them:
>>> regs = list(component.getFactories(IFooBar)) >>> regs.sort() >>> regs [UtilityRegistration(<BaseGlobalComponents base>, IFactory, 'MyFooBar', <Factory for <class 'zope.app.apidoc.doctest.MyFooBar'>>, None, u'')]>>> regs = list(component.getFactories(IFoo)) >>> regs.sort() >>> regs [UtilityRegistration(<BaseGlobalComponents base>, IFactory, 'MyFoo', <Factory for <class 'zope.app.apidoc.doctest.MyFoo'>>, None, u''), UtilityRegistration(<BaseGlobalComponents base>, IFactory, 'MyFooBar', <Factory for <class 'zope.app.apidoc.doctest.MyFooBar'>>, None, u'')]
getUtilities(iface)
Return all utility registrations for utilities that provide the specified interface.
As usual, we have to register some utilities first:
>>> ztapi.provideUtility(IFoo, MyFoo()) >>> ztapi.provideUtility(IBar, MyBar()) >>> ztapi.provideUtility(IFooBar, MyFooBar())
Now let’s have a look what we have:
>>> regs = list(component.getUtilities(IFooBar)) >>> regs.sort() >>> regs #doctest:+ELLIPSIS [UtilityRegistration(<BaseGlobalComponents base>, IFooBar, '', <zope.app.apidoc.doctest.MyFooBar object at ...>, None, u'')]>>> regs = list(component.getUtilities(IFoo)) >>> regs.sort() >>> regs #doctest:+ELLIPSIS [UtilityRegistration(<BaseGlobalComponents base>, IFoo, '', <zope.app.apidoc.doctest.MyFoo object at ...>, None, u''), UtilityRegistration(<BaseGlobalComponents base>, IFooBar, '', <zope.app.apidoc.doctest.MyFooBar object at ...>, None, u'')]
getRealFactory(factory)
During registration, factories are commonly masked by wrapper functions. Also, factories are sometimes also IFactory instances, which are not referencable, so that we would like to return the class. If the wrapper objects/functions play nice, then they provide a factory attribute that points to the next wrapper or the original factory.
The task of this function is to remove all the factory wrappers and make sure that the returned factory is referencable.
>>> class Factory(object): ... pass>>> def wrapper1(*args): ... return Factory(*args) >>> wrapper1.factory = Factory>>> def wrapper2(*args): ... return wrapper1(*args) >>> wrapper2.factory = wrapper1
So whether we pass in Factory,
>>> component.getRealFactory(Factory) <class 'zope.app.apidoc.doctest.Factory'>
wrapper1,
>>> component.getRealFactory(wrapper1) <class 'zope.app.apidoc.doctest.Factory'>
or wrapper2,
>>> component.getRealFactory(wrapper2) <class 'zope.app.apidoc.doctest.Factory'>
the answer should always be the Factory class. Next we are going to pass in an instance, and again we should get our class aas a result:
>>> factory = Factory() >>> component.getRealFactory(factory) <class 'zope.app.apidoc.doctest.Factory'>
Even, if the factory instance is wrapped, we should get the factory class:
>>> def wrapper3(*args): ... return factory(*args) >>> wrapper3.factory = factory>>> component.getRealFactory(wrapper3) <class 'zope.app.apidoc.doctest.Factory'>
getInterfaceInfoDictionary(iface)
This function returns a small info dictionary for an interface. It only reports the module and the name. This is useful for cases when we only want to list interfaces in the context of other components, like adapters and utilities.
>>> pprint(component.getInterfaceInfoDictionary(IFoo)) {'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'}
The functions using this function use it with little care and can also sometimes pass in None. In these cases we want to return None:
>>> component.getInterfaceInfoDictionary(None) is None True
It’s also possible for this function to be passed a zope.interface.declarations.Implements instance. For instance, this function is sometimes used to analyze the required elements of an adapter registration: if an adapter or subscriber is registered against a class, then the required element will be an Implements instance. In this case, we currently believe that we want to return the module and name of the object that the Implements object references. This may change.
>>> from zope.interface import implementedBy >>> pprint(component.getInterfaceInfoDictionary(implementedBy(MyFoo))) {'module': 'zope.app.apidoc.doctest', 'name': 'MyFoo'}
getTypeInfoDictionary(type)
This function returns the info dictionary of a type.
>>> pprint(component.getTypeInfoDictionary(tuple)) {'module': '__builtin__', 'name': 'tuple', 'url': '__builtin__/tuple'}
getSpecificationInfoDictionary(spec)
Thsi function returns an info dictionary for the given specification. A specification can either be an interface or class. If it is an interface, it simply returns the interface dictionary:
>>> pprint(component.getSpecificationInfoDictionary(IFoo)) {'isInterface': True, 'isType': False, 'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'}
In addition to the usual interface infos, there are two flags indicating whether the specification was an interface or type. In our case it is an interface.
Let’s now look at the behavior when passing a type:
>>> import zope.interface >>> tupleSpec = zope.interface.implementedBy(tuple)>>> pprint(component.getSpecificationInfoDictionary(tupleSpec)) {'isInterface': False, 'isType': True, 'module': '__builtin__', 'name': 'tuple', 'url': '__builtin__/tuple'}
For the type, we simply reuse the type info dictionary function.
getAdapterInfoDictionary(reg)
This function returns a page-template-friendly dictionary representing the data of an adapter registration in an output-friendly format.
Let’s first create an adapter registration:
>>> class MyResult(object): ... implements(IResult)>>> from zope.component.registry import AdapterRegistration >>> reg = AdapterRegistration(None, (IFoo, IBar), IResult, 'FooToResult', ... MyResult, 'doc info')
And now get the info dictionary:
>>> pprint(component.getAdapterInfoDictionary(reg)) {'doc': 'doc info', 'factory': 'zope.app.apidoc.doctest.MyResult', 'factory_url': 'zope/app/apidoc/doctest/MyResult', 'name': u'FooToResult', 'provided': {'module': 'zope.app.apidoc.doctest', 'name': 'IResult'}, 'required': [{'isInterface': True, 'isType': False, 'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'}, {'isInterface': True, 'isType': False, 'module': 'zope.app.apidoc.doctest', 'name': 'IBar'}], 'zcml': None}
If the factory’s path cannot be referenced, for example if a type has been created using the type() builtin function, then the URL of the factory will be None:
>>> MyResultType = type('MyResult2', (object,), {}) >>> from zope.interface import classImplements >>> classImplements(MyResultType, IResult)>>> reg = AdapterRegistration(None, (IFoo, IBar), IResult, 'FooToResult', ... MyResultType, 'doc info') >>> pprint(component.getAdapterInfoDictionary(reg)) {'doc': 'doc info', 'factory': 'zope.app.apidoc.doctest.MyResult2', 'factory_url': None, 'name': u'FooToResult', 'provided': {'module': 'zope.app.apidoc.doctest', 'name': 'IResult'}, 'required': [{'isInterface': True, 'isType': False, 'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'}, {'isInterface': True, 'isType': False, 'module': 'zope.app.apidoc.doctest', 'name': 'IBar'}], 'zcml': None}
This function can also handle subscription registrations, which are pretty much like adapter registrations, except that they do not have a name. So let’s see how the function handles subscriptions:
>>> from zope.component.registry import HandlerRegistration >>> reg = HandlerRegistration(None, (IFoo, IBar), '', MyResult, 'doc info')>>> pprint(component.getAdapterInfoDictionary(reg)) {'doc': 'doc info', 'factory': 'zope.app.apidoc.doctest.MyResult', 'factory_url': 'zope/app/apidoc/doctest/MyResult', 'name': u'', 'provided': None, 'required': [{'isInterface': True, 'isType': False, 'module': 'zope.app.apidoc.doctest', 'name': 'IFoo'}, {'isInterface': True, 'isType': False, 'module': 'zope.app.apidoc.doctest', 'name': 'IBar'}], 'zcml': None}
getFactoryInfoDictionary(reg)
This function returns a page-template-friendly dictionary representing the data of a factory (utility) registration in an output-friendly format.
Luckily we have already registered some factories, so we just reuse their registrations:
>>> pprint(component.getFactoryInfoDictionary( ... component.getFactories(IFooBar).next())) {'description': u'<p>My Foo Bar</p>\n', 'name': u'MyFooBar', 'title': 'MyFooBar', 'url': 'zope/app/apidoc/doctest/MyFooBar'}
If the factory’s path cannot be referenced, for example if a type has been created using the type() builtin function, then the URL of the factory will be None:
>>> class IMine(Interface): ... pass>>> class FactoryBase(object): ... def getInterfaces(self): return [IMine]>>> MyFactoryType = type('MyFactory', (FactoryBase,), {}) >>> from zope.interface import classImplements >>> classImplements(MyFactoryType, IFactory) >>> ztapi.provideUtility(IFactory, MyFactoryType(), 'MyFactory')>>> pprint(component.getFactoryInfoDictionary( ... component.getFactories(IMine).next())) {'description': u'', 'name': u'MyFactory', 'title': u'', 'url': None}
getUtilityInfoDictionary(name, factory)
This function returns a page-template-friendly dictionary representing the data of a utility registration in an output-friendly format.
Luckily we have already registered some utilities, so we just reuse their registrations:
>>> pprint(component.getUtilityInfoDictionary( ... component.getUtilities(IFooBar).next())) {'iface_id': 'zope.app.apidoc.doctest.IFooBar', 'name': u'<i>no name</i>', 'path': 'zope.app.apidoc.doctest.MyFooBar', 'url': 'Code/zope/app/apidoc/doctest/MyFooBar', 'url_name': 'X19ub25hbWVfXw=='}
Interface Inspection Utilities
This document is a presentation of the utility functions provided by
>>> from zope.app.apidoc import interface
For the following demonstrations, we need a nice interface that we can inspect:
>>> from zope.interface import Interface, Attribute >>> from zope.schema import Field, TextLine>>> class IFoo(Interface): ... foo = Field(title=u"Foo") ... ... bar = TextLine(title=u"Bar", ... description=u"The Bar", ... required=True, ... default=u"My Bar") ... ... baz = Attribute('baz', ... 'This is the baz attribute') ... ... def blah(one, two, three=None, *args, **kwargs): ... """This is the `blah` method."""
getElements(iface, type=IElement)
Return a dictionary containing all elements in an interface. The type specifies whether we are looking for attributes, fields or methods. So let’s look at an example.
First, let’s get the methods of an interface:
>>> from zope.interface.interfaces import IMethod >>> interface.getElements(IFoo, type=IMethod).keys() ['blah']
and now the fields:
>>> from zope.schema.interfaces import IField >>> names = interface.getElements(IFoo, type=IField).keys() >>> names.sort() >>> names ['bar', 'foo']
We can also get all attributes of course.
>>> from zope.interface.interfaces import IAttribute >>> names = interface.getElements(IFoo, type=IAttribute).keys() >>> names.sort() >>> names ['bar', 'baz', 'blah', 'foo']
You might be surprised by the above result, since the fields and methods are again included. However, fields and methods are just attributes and thus extend the simple attribute implementation. If you want to get a list of attributes that does not include fields and methods, see the getAttributes(iface) function.
The default type is IElement which will simply return all elements of the interface:
>>> names = interface.getElements(IFoo).keys() >>> names.sort() >>> names ['bar', 'baz', 'blah', 'foo']
Note: The interface you pass to this function cannot be proxied! Presentation code often like to wrap interfaces in security proxies and apidoc even uses location proxies for interface.
getFieldsInOrder(iface, _itemsorter=…)
For presentation purposes we often want fields to have the a certain order, most comonly the order they have in the interface. This function returns a list of (name, field) tuples in a specified order.
The _itemsorter argument provides the function that is used to order the fields. The default function, which sorts by the fields’ order attribute, should be the correct one for 99% of your needs.
Reusing the interface created above, we check the output:
>>> [n for n, a in interface.getFieldsInOrder(IFoo)] ['foo', 'bar']
By changing the sort method to sort by names, we get:
>>> [n for n, a in interface.getFieldsInOrder( ... IFoo, _itemsorter=lambda x, y: cmp(x[0], y[0]))] ['bar', 'foo']
getAttributes(iface)
This function returns a (name, attr) tuple for every attribute in the interface. Note that this function will only return pure attributes; it ignores methods and fields.
>>> attrs = interface.getAttributes(IFoo) >>> attrs.sort() >>> attrs #doctest: +ELLIPSIS [('baz', <zope.interface.interface.Attribute object at ...>)]
getMethods(iface)
This function returns a (name, method) tuple for every declared method in the interface.
>>> methods = interface.getMethods(IFoo) >>> methods.sort() >>> methods #doctest: +ELLIPSIS [('blah', <zope.interface.interface.Method object at ...>)]
getFields(iface)
This function returns a (name, field) tuple for every declared field in the interface.
>>> interface.getFields(IFoo) #doctest: +ELLIPSIS [('foo', <zope.schema._bootstrapfields.Field object at ...>), ('bar', <zope.schema._bootstrapfields.TextLine object at ...>)]
Note that this returns the same result as getFieldsInOrder() with the fields sorted by their order attribute, except that you cannot specify the sort function here. This function was mainly provided for symmetry with the other functions.
getInterfaceTypes(iface)
Interfaces can be categorized/grouped by using interface types. Interface types simply extend zope.interface.interfaces.IInterface, which are basically meta-interfaces. The interface types are then provided by particular interfaces.
The getInterfaceTypes() function returns a list of interface types that are provided for the specified interface. Note that you commonly expect only one type per interface, though.
Before we assign any type to our IFoo interface, there are no types declared.
>>> interface.getInterfaceTypes(IFoo) []
Now we define a new type called IContentType
>>> from zope.interface.interfaces import IInterface >>> class IContentType(IInterface): ... pass
and have our interface provide it:
>>> from zope.interface import directlyProvides >>> directlyProvides(IFoo, IContentType)
Note that ZCML has some more convenient methods of doing this. Now let’s get the interface types again:
>>> interface.getInterfaceTypes(IFoo) [<InterfaceClass zope.app.apidoc.doctest.IContentType>]
Again note that the interface passed to this function cannot be proxied, otherwise this method will pick up the proxy’s interfaces as well.
getFieldInterface(field)
This function tries pretty hard to determine the best-matching interface that represents the field. Commonly the field class has the same name as the field interface (minus an “I”). So this is our first choice:
>>> from zope.schema import Text, Int >>> interface.getFieldInterface(Text()) <InterfaceClass zope.schema.interfaces.IText>>>> interface.getFieldInterface(Int()) <InterfaceClass zope.schema.interfaces.IInt>
If the name matching method fails, it picks the first interface that extends IField:
>>> from zope.schema.interfaces import IField >>> class ISpecialField(IField): ... pass >>> class ISomething(Interface): ... pass>>> from zope.interface import implements >>> class MyField: ... implements(ISomething, ISpecialField)>>> interface.getFieldInterface(MyField()) <InterfaceClass zope.app.apidoc.doctest.ISpecialField>
getAttributeInfoDictionary(attr, format=’restructuredtext’)
This function returns a page-template-friendly dictionary for a simple attribute:
>>> pprint(interface.getAttributeInfoDictionary(IFoo['baz'])) {'doc': u'<p>This is the baz attribute</p>\n', 'name': 'baz'}
getMethodInfoDictionary(method, format=’restructuredtext’)
This function returns a page-template-friendly dictionary for a method:
>>> pprint(interface.getMethodInfoDictionary(IFoo['blah'])) #doc {'doc': u'<p>This is the <cite>blah</cite> method.</p>\n', 'name': 'blah', 'signature': '(one, two, three=None, *args, **kwargs)'}
getFieldInfoDictionary(field, format=’restructuredtext’)
This function returns a page-template-friendly dictionary for a field:
>>> pprint(interface.getFieldInfoDictionary(IFoo['bar'])) {'class': {'name': 'TextLine', 'path': 'zope/schema/_bootstrapfields/TextLine'}, 'default': "u'My Bar'", 'description': u'<p>The Bar</p>\n', 'iface': {'id': 'zope.schema.interfaces.ITextLine', 'name': 'ITextLine'}, 'name': 'bar', 'required': True, 'required_string': u'required', 'title': u'Bar'}
Presentation Inspection Utilities
The presentation module provides some nice utilities to inspect presentation registrations.
>>> from zope.app.apidoc import presentation
getViewFactoryData(factory)
This function tries really hard to determine the correct information about a 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 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 >>> view = SimpleViewClass('browser/index.pt') >>> info = presentation.getViewFactoryData(view)
Before we can check the result, we have to make sure that all Windows paths are converted to Unix-like paths. We also clip off instance-specific parts of the template path:
>>> info['template'] = info['template'].replace('\\', '/')[-32:] >>> pprint(info) {'path': 'zope.app.pagetemplate.simpleviewclass.simple', 'referencable': True, 'resource': None, 'template': 'zope/app/apidoc/browser/index.pt', 'template_obj': <BoundPageTemplateFile of None>, 'url': 'zope/app/pagetemplate/simpleviewclass/simple'}
So in the result above we see what the function returns. It is a dictionary (converted to a list for test purposes) that contains the Python path of the view class, a flag that specifies whether the factory can be referenced and thus be viewed by the class browser, the (page) template used for the view and 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.
Believe it or not, in some cases the factory is just a simple type. In these cases we cannot retrieve any useful information:
>>> info = presentation.getViewFactoryData(3) >>> pprint(info) {'path': None, 'referencable': False, 'resource': None, 'template': None, 'url': None}
In some cases factories are callable class instances, where we cannot directly have a referencable name, so we lookup the class and use its name:
>>> class Factory(object): ... pass>>> info = presentation.getViewFactoryData(Factory()) >>> pprint(info) {'path': '__builtin__.Factory', 'referencable': True, 'resource': None, 'template': None, '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:
>>> info = presentation.getViewFactoryData(Factory) >>> pprint(info) {'path': '__builtin__.Factory', 'referencable': True, 'resource': None, 'template': None, 'url': '__builtin__/Factory'}
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__’
>>> info = presentation.getViewFactoryData(factory) >>> pprint(info) {'path': '__builtin__.factory', 'referencable': True, 'resource': None, 'template': None, 'url': '__builtin__/factory'}
However, the function is rather unhelpful, since it will be the same for all views that use that code path. For this reason the function keeps track of the original factory component in a function attribute called factory:
>>> factory.factory = Factory>>> info = presentation.getViewFactoryData(factory) >>> pprint(info) {'path': '__builtin__.Factory', 'referencable': True, 'resource': None, 'template': None, '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'}
Finally, it sometimes happens that a factory is wrapped and the wrapper is wrapped in return:
>>> def wrapper1(*args): ... return Factory(*args)>>> def wrapper2(*args): ... return wrapper1(*args)
Initially, the documentation is not very helpful:
>>> info = presentation.getViewFactoryData(wrapper2) >>> pprint(info) {'path': 'None.wrapper2', 'referencable': True, 'resource': None, 'template': None, 'url': 'None/wrapper2'}
However, if those wrappers play nicely, they provide a factory attribute each step of the way …
>>> wrapper1.factory = Factory >>> wrapper2.factory = wrapper1
and the result is finally our original factory:
>>> info = presentation.getViewFactoryData(wrapper2) >>> pprint(info) {'path': '__builtin__.Factory', 'referencable': True, 'resource': None, 'template': None, 'url': '__builtin__/Factory'}
getPresentationType(iface)
In Zope 3, presentation types (i.e. browser, ftp, …) are defined through their special request interface, such as IBrowserRequest or IFTPRequest. To complicate matters further, layer interfaces are used in browser presentations to allow skinning. Layers extend any request type, but most commonly IBrowserRequest. This function inspects the request interface of any presentation multi-adapter and determines its type, which is returned in form of an interface.
>>> from zope.app.apidoc.presentation import getPresentationType >>> from zope.publisher.interfaces.http import IHTTPRequest >>> from zope.publisher.interfaces.browser import IBrowserRequest>>> class ILayer1(IBrowserRequest): ... pass>>> presentation.getPresentationType(ILayer1) <InterfaceClass zope.publisher.interfaces.browser.IBrowserRequest>>>> class ILayer2(IHTTPRequest): ... pass>>> presentation.getPresentationType(ILayer2) <InterfaceClass zope.publisher.interfaces.http.IHTTPRequest>
If the function cannot determine the presentation type, the interface itself is returned:
>>> from zope.interface import Interface >>> class ILayer3(Interface): ... pass>>> presentation.getPresentationType(ILayer3) <InterfaceClass __builtin__.ILayer3>
Note that more specific presentation types are considered first. For example, IBrowserRequest extends IHTTPRequest, but it will always determine the presentation type to be an IBrowserRequest.
getViews(iface, type=IRequest)
This function retrieves all available view registrations for a given interface and presentation type. The default argument for the presentation type is IRequest, which will effectively return all views for the specified interface.
To see how this works, we first have to register some views:
>>> class IFoo(Interface): ... pass>>> from zope.app.testing import ztapi >>> ztapi.provideAdapter((IFoo, IHTTPRequest), Interface, None, name='foo') >>> ztapi.provideAdapter((Interface, IHTTPRequest), Interface, None, ... name='bar') >>> 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.sort() >>> regs #doctest:+ELLIPSIS [AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IBrowserRequest], Interface, 'blah', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFoo, IHTTPRequest], Interface, 'foo', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [Interface, IHTTPRequest], Interface, 'bar', None, u'')]>>> regs = list(presentation.getViews(Interface, IHTTPRequest)) >>> regs.sort() >>> regs #doctest:+ELLIPSIS [AdapterRegistration(<BaseGlobalComponents base>, [Interface, IHTTPRequest], Interface, 'bar', None, u'')]
filterViewRegistrations(regs, iface, level=SPECIFC_INTERFACE_LEVEL)
Oftentimes the amount of views that are being returned for a particular interface are too much to show at once. It is then good to split the view into categories. The filterViewRegistrations() function allows you to filter the views on how specific they are to the interface. Here are the three levels you can select from:
- SPECIFC_INTERFACE_LEVEL – Only return registrations that require the
specified interface directly.
- EXTENDED_INTERFACE_LEVEL – Only return registrations that require an
interface that the specified interface extends.
- 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 and register some views:
>>> class IContent(Interface): ... pass >>> class IFile(IContent): ... passClear out the registries first, so we know what we have. >>> from zope.testing.cleanup import cleanUp >>> cleanUp()
>>> ztapi.provideAdapter((IContent, IHTTPRequest), Interface, ... None, name='view.html') >>> ztapi.provideAdapter((IContent, IHTTPRequest), Interface, ... None, name='edit.html') >>> ztapi.provideAdapter((IFile, IHTTPRequest), Interface, ... None, name='view.html') >>> 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(<BaseGlobalComponents base>, [IFile, IHTTPRequest], Interface, 'view.html', None, u'')]>>> result = list(presentation.filterViewRegistrations( ... regs, IFile, level=presentation.EXTENDED_INTERFACE_LEVEL)) >>> result.sort() >>> result [AdapterRegistration(<BaseGlobalComponents base>, [IContent, IHTTPRequest], Interface, 'edit.html', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IContent, IHTTPRequest], Interface, 'view.html', None, u'')]>>> result = list(presentation.filterViewRegistrations( ... regs, IFile, level=presentation.GENERIC_INTERFACE_LEVEL)) >>> result.sort() >>> result [AdapterRegistration(<BaseGlobalComponents base>, [Interface, IHTTPRequest], Interface, 'view.html', None, u'')]
You can also specify multiple levels at once using the Boolean OR operator, since all three levels are mutually exclusive.
>>> result = list(presentation.filterViewRegistrations( ... regs, IFile, level=presentation.SPECIFIC_INTERFACE_LEVEL | ... presentation.EXTENDED_INTERFACE_LEVEL)) >>> result.sort() >>> result [AdapterRegistration(<BaseGlobalComponents base>, [IContent, IHTTPRequest], Interface, 'edit.html', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IContent, IHTTPRequest], Interface, 'view.html', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [IFile, IHTTPRequest], Interface, 'view.html', None, u'')]>>> result = list(presentation.filterViewRegistrations( ... regs, IFile, level=presentation.SPECIFIC_INTERFACE_LEVEL | ... presentation.GENERIC_INTERFACE_LEVEL)) >>> result.sort() >>> result [AdapterRegistration(<BaseGlobalComponents base>, [IFile, IHTTPRequest], Interface, 'view.html', None, u''), AdapterRegistration(<BaseGlobalComponents base>, [Interface, IHTTPRequest], Interface, 'view.html', None, u'')]
getViewInfoDictionary(reg)
Now that we have all these utilities to select the registrations, we need to prepare the them for output. For page templates the best data structures are dictionaries and tuples/lists. This utility will generate an informational dictionary for the specified registration.
Let’s first create a registration:
>>> from zope.component.registry import AdapterRegistration >>> reg = AdapterRegistration(None, (IFile, Interface, IHTTPRequest), ... Interface, 'view.html', Factory, 'reg info')>>> pprint(presentation.getViewInfoDictionary(reg)) {'doc': 'reg info', 'factory': {'path': '__builtin__.Factory', 'referencable': True, 'resource': None, 'template': None, 'url': '__builtin__/Factory'}, 'name': u'view.html', 'provided': {'module': 'zope.interface', 'name': 'Interface'}, 'read_perm': None, 'required': [{'module': '__builtin__', 'name': 'IFile'}, {'module': 'zope.interface', 'name': 'Interface'}, {'module': 'zope.publisher.interfaces.http', 'name': 'IHTTPRequest'}], 'type': 'zope.publisher.interfaces.http.IHTTPRequest', 'write_perm': None, 'zcml': None}
Miscellaneous Utilities
The utilities module provides some useful helper functions and classes that make the work of the API doctool and inspection code easier.
>>> from zope.app.apidoc import utilities
relativizePath(path)
When dealing with files, such as page templates and text files, and not with Python paths, it is necessary to keep track of the the absolute path of the file. However, for presentation purposes, the absolute path is inappropriate and we are commonly interested in the path starting at the Zope 3 root directory. This function attempts to remove the absolute path to the root directory and replaces it with “Zope3”.
>>> import os >>> path = os.path.join(utilities.BASEDIR, 'src', 'zope', 'README.txt')>>> utilities.BASEDIR in path True>>> path = utilities.relativizePath(path)>>> utilities.BASEDIR in path False# Be kind to Windows users >>> path.replace(’', ‘/’) ‘Zope3/src/zope/README.txt’
If the base path is not found in a particular path, the original path is returned:
>>> otherpath = 'foo/bar/blah.txt' >>> utilities.relativizePath(otherpath) 'foo/bar/blah.txt'
truncateSysPath(path)
In some cases it is useful to just know the path after the sys path of a module. For example, you have a path of a file in a module. To look up the module, the simplest to do is to retrieve the module path and look into the system’s modules list.
>>> import sys >>> sysBase = sys.path[0]>>> utilities.truncateSysPath(sysBase + '/some/module/path') 'some/module/path'
If there is no matching system path, then the whole path is returned:
>>> utilities.truncateSysPath('some/other/path') 'some/other/path'
ReadContainerBase (Class)
This class serves as a base class for IReadContainer objects that minimizes the implementation of an IReadContainer to two methods, get() and items(), since the other methods can be implemented using these two.
Note that this implementation might be very expensive for certain container, especially if collecting the items is of high order. However, there are many scenarios when one has a complete mapping already and simply want to persent it as an IReadContainer.
Let’s start by making a simple IReadContainer implementation using the class:
>>> class Container(utilities.ReadContainerBase): ... def get(self, key, default=None): ... return {'a': 1, 'b': 2}.get(key, default) ... def items(self): ... return [('a', 1), ('b', 2)]>>> container = Container()
Now we can use the methods. First get()
>>> container.get('a') 1 >>> container.get('c') is None True >>> container['b'] 2
and then items()
>>> container.items() [('a', 1), ('b', 2)] >>> container.keys() ['a', 'b'] >>> container.values() [1, 2]
Then naturally, all the other methods work as well:
__getitem__(key)
>>> container['a'] 1 >>> container['c'] Traceback (most recent call last): ... KeyError: 'c'__contains__(key)
>>> 'a' in container True >>> 'c' in container Falsekeys()
>>> container.keys() ['a', 'b']__iter__()
>>> iterator = iter(container) >>> iterator.next() 1 >>> iterator.next() 2 >>> iterator.next() Traceback (most recent call last): ... StopIterationvalues()
>>> container.values() [1, 2]__len__()
>>> len(container) 2
getPythonPath(obj)
Return the path of the object in standard Python dot-notation.
This function makes only sense for objects that provide a name, since we cannot determine the path otherwise. Instances, for example, do not have a __name__ attribute, so we would expect them to fail.
For interfaces we simply get
>>> from zope.interface import Interface >>> class ISample(Interface): ... pass>>> utilities.getPythonPath(ISample) 'zope.app.apidoc.doctest.ISample'
and for classes
>>> class Sample(object): ... def sample(self): ... pass>>> utilities.getPythonPath(Sample.sample) 'zope.app.apidoc.doctest.Sample'
One can also pass functions
>>> def sample(): ... pass>>> utilities.getPythonPath(sample) 'zope.app.apidoc.doctest.sample'
and even methods. If a method is passed in, its class path is returned.
>>> utilities.getPythonPath(Sample.sample) 'zope.app.apidoc.doctest.Sample'
Modules are another kind of objects that can return a python path:
>>> utilities.getPythonPath(utilities) 'zope.app.apidoc.utilities'
Passing in None returns None:
>>> utilities.getPythonPath(None)
Clearly, instance lookups should fail:
>>> utilities.getPythonPath(Sample()) Traceback (most recent call last): ... AttributeError: 'Sample' object has no attribute '__name__'
isReferencable(path)
Determine whether a path can be referenced in the API doc, usually by the code browser module. Initially you might think that all objects that have paths can be referenced somehow. But that’s not true, partially by design of apidoc, but also due to limitations of the Python language itself.
First, here are some cases that work:
>>> utilities.isReferencable('zope') True >>> utilities.isReferencable('zope.app') True >>> utilities.isReferencable('zope.app.apidoc.apidoc.APIDocumentation') True >>> utilities.isReferencable('zope.app.apidoc.apidoc.handleNamespace') True
The first case is None. When you ask for the python path of None, you get None, so that result should not be referencable:
>>> utilities.isReferencable(None) False
By design we also do not document any private classes and functions:
>>> utilities.isReferencable('some.path.to._Private') False >>> utilities.isReferencable('some.path.to.__Protected') False >>> utilities.isReferencable('zope.app.apidoc.__doc__') True
Some objects might fake their module name, so that it does not exist:
>>> utilities.isReferencable('foo.bar') False
On the other hand, you might have a valid module, but non-existent attribute:
>>> utilities.isReferencable('zope.app.apidoc.MyClass') False
Note that this case is also used for types that are generated using the type() function:
>>> mytype = type('MyType', (object,), {}) >>> path = utilities.getPythonPath(mytype) >>> path 'zope.app.apidoc.doctest.MyType'>>> utilities.isReferencable(path) False
Next, since API doc does not allow the documentation of instances yet, it is not possible to document singletons, so they are not referencable:
>>> class Singelton(object): ... pass>>> utilities.isReferencable('zope.app.apidoc.doctest.Singelton') True>>> Singelton = Singelton()>>> utilities.isReferencable('zope.app.apidoc.doctest.Singelton') False
Finally, the global IGNORE_MODULES list from the class registry is also used to give a negative answer. If a module is listed in IGNORE_MODULES, then False is returned.
>>> import classregistry >>> classregistry.IGNORE_MODULES.append('zope.app.apidoc')>>> utilities.isReferencable('zope.app') True >>> utilities.isReferencable('zope.app.apidoc') False >>> utilities.isReferencable('zope.app.apidoc.apidoc.APIDocumentation') False>>> classregistry.IGNORE_MODULES.pop() 'zope.app.apidoc' >>> utilities.isReferencable('zope.app.apidoc') True
getPermissionIds(name, checker=_marker, klass=_marker)
Get the permissions of a class attribute. The attribute is specified by name.
Either the klass or the checker argument must be specified. If the class is specified, then the checker for it is looked up. Furthermore, this function only works with INameBasedChecker checkers. If another checker is found, None is returned for the permissions.
We start out by defining the class and then the checker for it:
>>> from zope.security.checker import Checker, defineChecker >>> from zope.security.checker import CheckerPublic>>> class Sample(object): ... attr = 'value' ... attr3 = 'value3'>>> class Sample2(object): ... pass>>> checker = Checker({'attr': 'zope.Read', 'attr3': CheckerPublic}, ... {'attr': 'zope.Write', 'attr3': CheckerPublic}) >>> defineChecker(Sample, checker)
Now let’s see how this function works:
>>> entries = utilities.getPermissionIds('attr', klass=Sample) >>> entries['read_perm'] 'zope.Read' >>> entries['write_perm'] 'zope.Write'>>> from zope.security.checker import getCheckerForInstancesOf >>> entries = utilities.getPermissionIds('attr', ... getCheckerForInstancesOf(Sample)) >>> entries['read_perm'] 'zope.Read' >>> entries['write_perm'] 'zope.Write'
The Sample class does not know about the attr2 attribute:
>>> entries = utilities.getPermissionIds('attr2', klass=Sample) >>> print entries['read_perm'] n/a >>> print entries['write_perm'] n/a
The Sample2 class does not have a checker:
>>> entries = utilities.getPermissionIds('attr', klass=Sample2) >>> entries['read_perm'] is None True >>> print entries['write_perm'] is None True
Finally, the Sample class’ attr3 attribute is public:
>>> entries = utilities.getPermissionIds('attr3', klass=Sample) >>> print entries['read_perm'] zope.Public >>> print entries['write_perm'] zope.Public
getFunctionSignature(func)
Return the signature of a function or method. The func argument must be a generic function or a method of a class.
First, we get the signature of a function that has a specific positional and keyword argument:
>>> def func(attr, attr2=None): ... pass >>> utilities.getFunctionSignature(func) '(attr, attr2=None)'
Here is a function that has an unspecified amount of keyword arguments:
>>> def func(attr, **kw): ... pass >>> utilities.getFunctionSignature(func) '(attr, **kw)'
And here we mix specified and unspecified keyword arguments:
>>> def func(attr, attr2=None, **kw): ... pass >>> utilities.getFunctionSignature(func) '(attr, attr2=None, **kw)'
In the next example we have unspecified positional and keyword arguments:
>>> def func(*args, **kw): ... pass >>> utilities.getFunctionSignature(func) '(*args, **kw)'
And finally an example, where we have on unspecified keyword arguments without any positional arguments:
>>> def func(**kw): ... pass >>> utilities.getFunctionSignature(func) '(**kw)'
Next we test whether the signature is correctly determined for class methods. Note that the self argument is removed from the signature, since it is not essential for documentation.
We start out with a simple positional argument:
>>> class Klass(object): ... def func(self, attr): ... pass >>> utilities.getFunctionSignature(Klass.func) '(attr)'
Next we have specific and unspecified positional arguments as well as unspecified keyword arguments:
>>> class Klass(object): ... def func(self, attr, *args, **kw): ... pass >>> utilities.getFunctionSignature(Klass.func) '(attr, *args, **kw)'
If you do not pass a function or method to the function, it will fail:
>>> utilities.getFunctionSignature('func') Traceback (most recent call last): ... TypeError: func must be a function or method
A very uncommon, but perfectly valid, case is that tuple arguments are unpacked inside the argument list of the function. Here is an example:
>>> def func((arg1, arg2)): ... pass >>> utilities.getFunctionSignature(func) '((arg1, arg2))'
Even default assignment is allowed:
>>> def func((arg1, arg2)=(1, 2)): ... pass >>> utilities.getFunctionSignature(func) '((arg1, arg2)=(1, 2))'
However, lists of this type are not allowed inside the argument list:
>>> def func([arg1, arg2]): ... pass Traceback (most recent call last): ... SyntaxError: invalid syntax
Internal assignment is also not legal:
>>> def func((arg1, arg2=1)): ... pass Traceback (most recent call last): ... SyntaxError: invalid syntax
getPublicAttributes(obj)
Return a list of public attribute names for a given object.
This excludes any attribute starting with ‘_’, which includes attributes of the form __attr__, which are commonly considered public, but they are so special that they are excluded. The obj argument can be either a classic class, type or instance of the previous two. Note that the term “attributes” here includes methods and properties.
First we need to create a class with some attributes, properties and methods:
>>> class Nonattr(object): ... def __get__(*a): ... raise AttributeError('nonattr')>>> class Sample(object): ... attr = None ... def __str__(self): ... return '' ... def func(self): ... pass ... def _getAttr(self): ... return self.attr ... attr2 = property(_getAttr) ... ... nonattr = Nonattr() # Should not show up in public attrs
We can simply pass in the class and get the public attributes:
>>> attrs = utilities.getPublicAttributes(Sample) >>> attrs.sort() >>> attrs ['attr', 'attr2', 'func']
Note that we exclude attributes that would raise attribute errors, like our silly Nonattr.
But an instance of that class will work as well.
>>> attrs = utilities.getPublicAttributes(Sample()) >>> attrs.sort() >>> attrs ['attr', 'attr2', 'func']
The function will also take inheritance into account and return all inherited attributes as well:
>>> class Sample2(Sample): ... attr3 = None>>> attrs = utilities.getPublicAttributes(Sample2) >>> attrs.sort() >>> attrs ['attr', 'attr2', 'attr3', 'func']
getInterfaceForAttribute(name, interfaces=_marker, klass=_marker, asPath=True)
Determine the interface in which an attribute is defined. This function is nice, if you have an attribute name which you retrieved from a class and want to know which interface requires it to be there.
Either the interfaces or klass argument must be specified. If interfaces is not specified, the klass is used to retrieve a list of interfaces. interfaces must be iterable.
asPath specifies whether the dotted name of the interface or the interface object is returned.
First, we need to create some interfaces and a class that implements them:
>>> from zope.interface import Interface, Attribute, implements >>> class I1(Interface): ... attr = Attribute('attr')>>> class I2(I1): ... def getAttr(): ... '''get attr'''>>> class Sample(object): ... implements(I2)
First we check whether an aatribute can be found in a list of interfaces:
>>> utilities.getInterfaceForAttribute('attr', (I1, I2), asPath=False) <InterfaceClass zope.app.apidoc.doctest.I1> >>> utilities.getInterfaceForAttribute('getAttr', (I1, I2), asPath=False) <InterfaceClass zope.app.apidoc.doctest.I2>
Now we are repeating the same lookup, but using the class, instead of a list of interfaces:
>>> utilities.getInterfaceForAttribute('attr', klass=Sample, asPath=False) <InterfaceClass zope.app.apidoc.doctest.I1> >>> utilities.getInterfaceForAttribute('getAttr', klass=Sample, asPath=False) <InterfaceClass zope.app.apidoc.doctest.I2>
By default, asPath is True, which means the path of the interface is returned:
>>> utilities.getInterfaceForAttribute('attr', (I1, I2)) 'zope.app.apidoc.doctest.I1'
If no match is found, None is returned.
>>> utilities.getInterfaceForAttribute('attr2', (I1, I2)) is None True >>> utilities.getInterfaceForAttribute('attr2', klass=Sample) is None True
If both, the interfaces and klass argument are missing, raise an error:
>>> utilities.getInterfaceForAttribute('getAttr') Traceback (most recent call last): ... ValueError: need to specify interfaces or klass
Similarly, it does not make sense if both are specified:
>>> utilities.getInterfaceForAttribute('getAttr', interfaces=(I1,I2), ... klass=Sample) Traceback (most recent call last): ... ValueError: must specify only one of interfaces and klass
columnize(entries, columns=3)
This function places a list of entries into columns.
Here are some examples:
>>> utilities.columnize([1], 3) [[1]]>>> utilities.columnize([1, 2], 3) [[1], [2]]>>> utilities.columnize([1, 2, 3], 3) [[1], [2], [3]]>>> utilities.columnize([1, 2, 3, 4], 3) [[1, 2], [3], [4]]>>> utilities.columnize([1], 2) [[1]]>>> utilities.columnize([1, 2], 2) [[1], [2]]>>> utilities.columnize([1, 2, 3], 2) [[1, 2], [3]]>>> utilities.columnize([1, 2, 3, 4], 2) [[1, 2], [3, 4]]
getDocFormat(module)
This function inspects a module to determine the supported documentation format. The function returns a valid renderer source factory id.
If the __docformat__ module attribute is specified, its value will be used to look up the factory id:
>>> from zope.app.apidoc import apidoc >>> utilities.getDocFormat(apidoc) 'zope.source.rest'
By default structured text is returned:
>>> from zope.app.apidoc import tests >>> utilities.getDocFormat(tests) 'zope.source.stx'
This is a sensible default, since we only decided later in development to endorse restructured text, so that many files are still in the structured text format. All converted and new modules will have the __docformat__ attribute.
The __docformat__ attribute can also optionally specify a language field. We simply ignore it:
>>> class Module(object): ... pass >>> module = Module() >>> module.__docformat__ = 'restructuredtext en' >>> utilities.getDocFormat(module) 'zope.source.rest'
dedentString(text)
Before doc strings can be processed using STX or ReST they must be dendented, since otherwise the output will be incorrect. Let’s have a look at some docstrings and see how they are correctly dedented.
Let’s start with a simple one liner. Nothing should happen:
>>> def func(): ... '''One line documentation string'''>>> utilities.dedentString(func.__doc__) 'One line documentation string'
Now what about one line docstrings that start on the second line? While this format is discouraged, it is frequently used:
>>> def func(): ... ''' ... One line documentation string ... '''>>> utilities.dedentString(func.__doc__) '\nOne line documentation string\n'
We can see that the leading whitespace on the string is removed, but not the newline character. Let’s now try a simple multi-line docstring:
>>> def func(): ... '''Short description ... ... Lengthy description, giving some more background information and ... discuss some edge cases. ... '''>>> print utilities.dedentString(func.__doc__) Short description <BLANKLINE> Lengthy description, giving some more background information and discuss some edge cases. <BLANKLINE>
Again, the whitespace was removed only after the first line. Also note that the function determines the indentation level correctly. So what happens if there are multiple indentation levels? The smallest amount of indentation is chosen:
>>> def func(): ... '''Short description ... ... Root Level ... ... Second Level ... '''>>> print utilities.dedentString(func.__doc__) Short description <BLANKLINE> Root Level <BLANKLINE> Second Level <BLANKLINE>>>> def func(): ... '''Short description ... ... $$$ print 'example' ... example ... ... And now the description. ... '''>>> print utilities.dedentString(func.__doc__) Short description <BLANKLINE> $$$ print 'example' example <BLANKLINE> And now the description. <BLANKLINE>
renderText(text, module=None, format=None)
A function that quickly renders the given text using the specified format.
If the module argument is specified, the function will try to determine the format using the module. If the format argument is given, it is simply used. Clearly, you cannot specify both, the module and format argument.
You specify the format as follows:
>>> utilities.renderText('Hello!\n', format='zope.source.rest') u'<p>Hello!</p>\n'
Note that the format string must be a valid source factory id; if the factory id is not given, ‘zope.source.stx’ is used. Thus, specifying the module is often safer (if available):
>>> utilities.renderText('Hello!\n', module=apidoc) u'<p>Hello!</p>\n'
The Class Registry
This little registry allows us to quickly query a complete list of classes that are defined and used by Zope 3. The prime feature of the class is the getClassesThatImplement(iface) method that returns all classes that implement the passed interface. Another method, getSubclassesOf(klass) returns all registered subclassess of the given class.
The class registry, subclassing the dictionary type, can be instantiated like any other dictionary:
>>> from zope.app.apidoc.classregistry import ClassRegistry >>> reg = ClassRegistry()
Let’s now add a couple of classes to registry. The classes should implement some interfaces, so that we can test all methods on the class registry:
>>> from zope.interface import Interface, implements>>> class IA(Interface): ... pass >>> class IB(IA): ... pass >>> class IC(Interface): ... pass >>> class ID(Interface): ... pass>>> class A(object): ... implements(IA) >>> reg['A'] = A>>> class B: ... implements(IB) >>> reg['B'] = B>>> class B2(object): ... implements(IB) >>> reg['B2'] = B2>>> class C(object): ... implements(IC) >>> reg['C'] = C >>> class A2(A): ... pass >>> reg['A2'] = A2
Since the registry is just a dictionary, we can ask for all its keys, which are the names of the classes:
>>> names = reg.keys() >>> names.sort() >>> names ['A', 'A2', 'B', 'B2', 'C']>>> reg['A'] is A True
There are two API methods specific to the class registry:
getClassesThatImplement(iface)
This method returns all classes that implement the specified interface:
>>> pprint(reg.getClassesThatImplement(IA)) #doctest:+ELLIPSIS [('A', <class 'A'>), ('B', <class __builtin__.B at ...>), ('A2', <class 'A2'>), ('B2', <class 'B2'>)]>>> pprint(reg.getClassesThatImplement(IB)) #doctest:+ELLIPSIS [('B', <class __builtin__.B at ...>), ('B2', <class 'B2'>)]>>> pprint(reg.getClassesThatImplement(IC)) [('C', <class 'C'>)]>>> pprint(reg.getClassesThatImplement(ID)) []
getSubclassesOf(klass)
This method will find all classes that inherit the specified class:
>>> pprint(reg.getSubclassesOf(A)) [('A2', <class 'A2'>)]>>> pprint(reg.getSubclassesOf(B)) []
Safe Imports
Using the safe_import() we can quickly look up modules by minimizing import calls.
>>> from zope.app.apidoc import classregistry >>> from zope.app.apidoc.classregistry import safe_import
First we try to find the path in sys.modules, since this lookup is much more efficient than importing it. If it was not found, we go back and try to import the path. For security reasons, importing new modules is disabled by default, unless the global __import_unknown_modules__ variable is set to true. If that also fails, we return the default value.
Here are some examples:
>>> import sys >>> 'zope.app' in sys.modules True >>> safe_import('zope.app') is sys.modules['zope.app'] True >>> safe_import('weirdname') is None True
For this example, we’ll create a dummy module:
>>> import os >>> import tempfile >>> dir = tempfile.mkdtemp() >>> filename = os.path.join(dir, 'testmodule.py') >>> sys.path.insert(0, dir) >>> f = open(filename, 'w') >>> f.write('# dummy module\n') >>> f.close()
The temporary module is not already imported:
>>> module_name = 'testmodule' >>> module_name in sys.modules False
When we try safe_import() now, we will still get the default value, because importing new modules is disabled by default:
>>> safe_import(module_name) is None True
But once we activate the __import_unknown_modules__ hook, the module should be imported:
>>> classregistry.__import_unknown_modules__ = True>>> safe_import(module_name).__name__ == module_name True >>> module_name in sys.modules True
Now clean up the temporary module, just to play nice:
>>> del sys.modules[module_name]
Importing some code we cannot control, such as twisted, might raise errors when imported without having a certain environment. In those cases, the safe import should prevent the error from penetrating:
>>> open(os.path.join(dir, 'alwaysfail.py'), 'w').write('raise ValueError\n') >>> sys.path.insert(0, dir)>>> safe_import('alwaysfail') is None True
Let’s clean up the python path and temporary files:
>>> del sys.path[0] >>> import shutil >>> shutil.rmtree(dir)
Another method to explicitely turning off the import of certain modules is to declare that they should be ignored. For example, if we tell the class registry to ignore zope.app,
>>> classregistry.IGNORE_MODULES.append('zope.app')
then we cannot import it anymore, even though we know it is available:
>>> safe_import('zope.app') is None True
Note that all sub-packages are also unavailable:
>>> safe_import('zope.app.apidoc') is None True
We also need to play nice concerning variables and have to reset the module globals:
>>> classregistry.IGNORE_MODULES.pop() 'zope.app' >>> classregistry.__import_unknown_modules__ = False
CHANGES
3.6.7 (2009-09-29)
Updated the tests after moving ITraverser back to zope.traversing.
3.6.6 (2009-09-15)
Made the tests work again with the most recent Zope Toolkit KGS.
3.6.5 (2009-07-24)
Update documentation file in zope.site from README.txt to site.txt.
3.6.4 (2009-07-23)
The IContained interface moved to zope.location.interfaces. Make a test pass.
3.6.3 (2009-05-16)
Explicitly defined default views.
Replace relative url links with absolute ones.
Added z3c packages to the code browser.
Made bin/static-apidoc principially working (publisher and webserver mode). There are still some files which are not correctly fetched.
3.6.2 (2009-03-17)
Adapt principal registry book chapter to a new place, as it was moved from zope.app.security to zope.principalregistry.
Remove zcml slugs and old zpkg-related files.
3.6.1 (2009-02-04)
When a module provides an interface or has an __all__ attribute, use one of those for the module documentation. Fixes LP #323375.
Undid broken link to savepoint.txt caused in 3.6.0. The latest version of the transaction package puts savepoint.txt in the tests subpackage.
Expanded the presentation of module documentation.
Class documentation now includes constructor information.
3.6.0 (2009-01-31)
Use zope.container instead of zope.app.container.
Use zope.site instead of zope.app.component and zope.app.folder (in at least a few places).
savepoint.txt moved from ZODB’s test directory a level up – we follow.
Make compatible with new zope.traversing and zope.location.
3.5.0 (2009-01-17)
Adapted transaction book chapters for new transaction egg. The README.txt was removed and savepoint.txt was moved. Also add chapter about dooming transactions (doom.txt).
Changed mailing list address to zope-dev at zope.org, because zope3-dev is retired now.
Cleaned up dependencies.
3.4.3 (2007-11-10)
Fix https://bugs.launchpad.net/zope3/+bug/161737: Misleading text in the interface viewer.
Fix https://bugs.launchpad.net/zope3/+bug/161190: The zope3-dev mailinglist has been retired, point to zope-dev.
3.4.2 (2007-10-30)
Avoid deprecation warnings for ZopeMessageFactory.
3.4.1 (2007-10-23)
Avoid deprecation warnings.
3.4.0 (2007-10-10)
Improved package meta-data.
Fixed the code to at least gracefully ignore unzipped eggs. Eventually we want to handle eggs well.
3.4.0a1 (2007-04-22)
Initial release independent of the main Zope tree.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file zope.app.apidoc-3.6.7.tar.gz
.
File metadata
- Download URL: zope.app.apidoc-3.6.7.tar.gz
- Upload date:
- Size: 151.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | d409d3f5a64324ff697cfbbae7cadb2b9301a237fde80431118180e6024adda2 |
|
MD5 | 960a851b517b0f30133a473da46d088e |
|
BLAKE2b-256 | ce14c372f27cfcbd1579dfc6bc1163dd0600b65a8eabc0bc8b994920db467e39 |