zope.introspector.util
**********************

Helper functions for the zope.introspector

:Test-Layer: unit

is_namespace_package
====================

Tell, whether a dotted name denotes a namespace package::

  >>> from zope.introspector.util import is_namespace_package
  >>> is_namespace_package('zope')
  True

  >>> is_namespace_package('zope.app')
  True

  >>> is_namespace_package('zope.app.file')
  False

  >>> is_namespace_package('zope.introspector')
  False


get_package_items
=================

A function to gather names of items that are contained in a
package. It looks for modules, subpackages and text files of type
.txt, .rst and .zcml::

  >>> from zope.introspector.util import get_package_items
  >>> sorted(get_package_items('zope.introspector'))
  ['README.txt', 'adapters', ..., 'meta.zcml', ..., 'tests', 
  ... ..., 'viewinfo.txt']

It can also handle namespace packages::

  >>> sorted(get_package_items('zope'))
  ['annotation', 'app', ..., 'traversing']

Often the `zope` package is installed as a zipped egg, but as we can
see here, that makes no difference.

The function ignores directories that are not Python packages and
hidden files and directories, i.e. such starting with a dot in their
name.


getFunctionSignature(func)
==========================

This helper function was taken from ``zope.app.apidoc``.

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:

  >>> from zope.introspector.util import get_function_signature
  >>> def func(attr, attr2=None):
  ...     pass
  >>> get_function_signature(func)
  '(attr, attr2=None)'

Here is a function that has an unspecified amount of keyword arguments:

  >>> def func(attr, **kw):
  ...     pass
  >>> get_function_signature(func)
  '(attr, **kw)'

And here we mix specified and unspecified keyword arguments:

  >>> def func(attr, attr2=None, **kw):
  ...     pass
  >>> get_function_signature(func)
  '(attr, attr2=None, **kw)'

In the next example we have unspecified positional and keyword arguments:

  >>> def func(*args, **kw):
  ...     pass
  >>> get_function_signature(func)
  '(*args, **kw)'

And finally an example, where we have on unspecified keyword arguments without
any positional arguments:

  >>> def func(**kw):
  ...     pass
  >>> get_function_signature(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
  >>> get_function_signature(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
  >>> get_function_signature(Klass.func)
  '(attr, *args, **kw)'

If you do not pass a function or method to the function, it will fail:

  >>> get_function_signature('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
  >>> get_function_signature(func)
  '((arg1, arg2))'

Even default assignment is allowed:

  >>> def func((arg1, arg2)=(1, 2)):
  ...     pass
  >>> get_function_signature(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


get_attributes(obj, public_only=True)
======================================

Return a list of attribute names.

If `public_only` is set to `False` also attribute names that start
with an underscore ('_') are returned::

  >>> from zope.introspector.util import get_attributes
  >>> from zope.introspector.code import Package
  >>> get_attributes(Package)
  ['getModuleInfo', 'getPath']

  >>> get_attributes(Package, public_only=False)
  ['__class__', '__delattr__', ..., 'getPath']


get_python_path(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.introspector.util import get_python_path
  >>> from zope.introspector.interfaces import IInfo
  >>> get_python_path(IInfo)
  'zope.introspector.interfaces.IInfo'

and for classes::

  >>> from zope.introspector.code import PackageInfo
  >>> get_python_path(PackageInfo)
  'zope.introspector.code.PackageInfo'

One can also pass functions::

  >>> get_python_path(get_python_path)
  'zope.introspector.util.get_python_path'

and even methods. If a method is passed in, its class path is returned::

  >>> get_python_path(PackageInfo.getPath)
  'zope.introspector.code.PackageInfo'

Modules are another kind of objects that can return a python path::

  >>> from zope.introspector import util
  >>> get_python_path(util)
  'zope.introspector.util'

Passing in `None` returns `None`::

  >>> util.get_python_path(None)

Clearly, instance lookups should fail::

  >>> get_python_path(PackageInfo(None))
  Traceback (most recent call last):
  ...
  AttributeError: 'PackageInfo' object has no attribute '__name__'


get_interface_for_attribute(name, interfaces=_marker, klass=_marker, as_path=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.

`as_path` 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::

  >>> from zope.introspector.util import get_interface_for_attribute
  >>> get_interface_for_attribute('attr', (I1, I2), as_path=False)
  <InterfaceClass __builtin__.I1>
  >>> get_interface_for_attribute('getAttr', (I1, I2), as_path=False)
  <InterfaceClass __builtin__.I2>

Now we are repeating the same lookup, but using the class, instead of
a list of interfaces::

  >>> get_interface_for_attribute('attr', klass=Sample, as_path=False)
  <InterfaceClass __builtin__.I1>
  >>> get_interface_for_attribute('getAttr', klass=Sample, as_path=False)
  <InterfaceClass __builtin__.I2>

By default, `as_path` is `True`, which means the path of the interface
is returned::

  >>> get_interface_for_attribute('attr', (I1, I2))
  '__builtin__.I1'

If no match is found, ``None`` is returned::

  >>> get_interface_for_attribute('attr2', (I1, I2)) is None
  True
  >>> get_interface_for_attribute('attr2', klass=Sample) is None
  True

If both, the `interfaces` and `klass` argument are missing, raise an
error::

  >>> get_interface_for_attribute('getAttr')
  Traceback (most recent call last):
  ...
  ValueError: need to specify interfaces or klass

Similarly, it does not make sense if both are specified::

  >>> get_interface_for_attribute('getAttr', interfaces=(I1,I2),
  ...                                    klass=Sample)
  Traceback (most recent call last):
  ...
  ValueError: must specify only one of interfaces and klass
