==================
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.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. 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
  >>> here = os.path.dirname(__file__)
  >>> filename = os.path.join(here, 'testmodule.py')
  >>> f = open(filename, 'w')
  >>> f.write('# dummy module\n')
  >>> f.close()

The temporary module is not already imported, but will be once
we've called safe_import():

  >>> module_name = 'zope.app.apidoc.testmodule'
  >>> module_name in sys.modules
  False
  >>> safe_import(module_name).__name__ == module_name
  True
  >>> module_name in sys.modules
  True

Now clean up the temporary module, just to play nice:

  >>> os.unlink(filename)
  >>> if os.path.exists(filename + 'c'):
  ...     os.unlink(filename + 'c')
  >>> if os.path.exists(filename + 'o'):
  ...     os.unlink(filename + 'o')
