======================= 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 False * `keys()` >>> container.keys() ['a', 'b'] * `__iter__()` >>> iterator = iter(container) >>> iterator.next() 1 >>> iterator.next() 2 >>> iterator.next() Traceback (most recent call last): ... StopIteration * `values()` >>> 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) >>> utilities.getInterfaceForAttribute('getAttr', (I1, I2), asPath=False) Now we are repeating the same lookup, but using the class, instead of a list of interfaces: >>> utilities.getInterfaceForAttribute('attr', klass=Sample, asPath=False) >>> utilities.getInterfaceForAttribute('getAttr', klass=Sample, asPath=False) 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' `dendentString(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 Lengthy description, giving some more background information and discuss some edge cases. 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 Root Level Second Level >>> def func(): ... '''Short description ... ... $$$ print 'example' ... example ... ... And now the description. ... ''' >>> print utilities.dedentString(func.__doc__) Short description $$$ print 'example' example And now the description. `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'

Hello!

\n' Note that the format string must be a valid source factory id; if the factory id is not a match, 'zope.source.stx' is used. Thus, specifying the module is often safer (if available): >>> utilities.renderText('Hello!\n', module=apidoc) u'

Hello!

\n'