Determining information about simple objects
*********************************************

:Test-Layer: nonunit

Describing objects
==================

Simple objects
--------------

Simple objects are considered the primitive types provided by Python
as simple numbers, strings, classes etc. As in a way every thing in
Python can be seen as a simple object, the ``ObjectInfo`` can handle
every type of object although the delivered data might lack special
aspects of more complicated objects.

To handle more comlex objects, there are extended versions of
``ObjectInfo`` available with zope.introspector that we will discuss
below and you can write your own ones.

The plain base of all object descriptions generated by
zope.introspector is the ``ObjectInfo`` class. ObjectInfos can be
created by passing an object to examine::

   >>> from zope.introspector.objectinfo import ObjectInfo
   >>> info = ObjectInfo(object())
   >>> info
   <zope.introspector...ObjectInfo object at 0x...>

ObjectInfos provide the IObjectInfo interface::

   >>> from zope.introspector.interfaces import IObjectInfo
   >>> IObjectInfo.providedBy(info)
   True

We can retrieve basal information about any object with ObjectInfos::

   >>> info.getType()
   <type 'object'>

Other methods include such which are provided by the Python
``inspect`` package like ``isModule()``, ``isClass()``,
``getMembers()``, etc.::

   >>> info.isModule()
   False

   >>> info.isClass()
   False

Note, that the implementations of this methods in ``zope.introspector``
might vary from those of the ``inspect`` package.

As we are speaking about aspects of objects, we can say that every
Python object is covered by IObjectInfo, as it provides information,
that is available with every object regardless of as complex it may
be.


More complex objects
--------------------

Describing more complex objects in terms of ``zope.introspector``
means to deliver information about certain aspects of those objects,
that are not covered by the basic ``IObjectInfo`` interface.

As an example we require from the introspector to deliver on request a
list of local .txt files if an object turns out to be a Python
package. This will fail with the standard ``ObjectInfo``::

   >>> from zope import introspector
   >>> info = ObjectInfo(introspector)
   >>> info.getPackageFiles(filter='.txt')
   Traceback (most recent call last):
   ...
   AttributeError: 'ObjectInfo' object has no attribute 'getPackageFiles'

Fortunately, there is an ``PackageInfo`` available, which gives us
this information::

   >>> from zope.introspector.objectinfo import PackageInfo
   >>> info = PackageInfo(introspector)
   >>> info.getPackageFiles(filter='.txt')
   ['README.txt', ...]


Getting specialized descriptors
===============================

Overall, the ...Info classes act as descriptors of objects. Doing::

   info = ObjectInfo(obj)

means to generate a descriptor of the object `obj`.


Getting a specialist based on the object type
---------------------------------------------

It would, however, be nice to let the Zope framework look up a
suitable descriptor or 'specialist' for us more automatically. This is
in fact possible, using Zope adapters.

To demonstrate the usage of this, we first have to register the
adapters and utilities of ``zope.introspector``::

   >>> import grokcore.component
   >>> grokcore.component.testing.grok('zope.introspector')

Now we want to get an descriptor (aka Info object) for a module::

   >>> from zope.introspector import objectinfo
   >>> from zope.introspector.interfaces import IObjectInfo
   >>> info = IObjectInfo(objectinfo)
   >>> info
   <zope.introspector.objectinfo.ModuleInfo object at 0x...>

Apparently we got a specialized descriptor here, ``ModuleInfo``
instead of a plain ``ObjectInfo``.

This is, because modules provide a special type and our framework
offers a specialized adapter for this type.

We therefore can define 'specialists' for any type of object, as long
as the Python type is really different.


Getting a specialist based on the descriptor interface
------------------------------------------------------

Things become more complicated when it comes to different kinds of
objects, that do not differ in the implemented interfaces and Python
types. 

A use case would be to require different descriptors for packages and
modules. As packages are modules and both are of the same type for
Python, we cannot define a specialized adapter based on the type::

   >>> from zope.introspector import tests as subpkg
   >>> IObjectInfo(subpkg, name='object')
   <zope.introspector.objectinfo.ModuleInfo object at 0x...>

That's okay in the sense, that packages are in fact modules, but there
is an even better descriptor called `PackageInfo`, that provides us
with data covering the package-specific aspects of `subpkg`. 

To get this specialized package descriptor, we can construct a
``PackageInfo`` object directly::

   >>> from zope.introspector import PackageInfo
   >>> info = PackageInfo(subpkg)

or lookup its special interface, for which an adapter should be
registered:: 

   >>> from zope.introspector.interfaces import IPackageInfo
   >>> info = IPackageInfo(subpkg)
   >>> info.getPackageFiles(filter='.txt')
   ['descriptorgrokker.txt']


Extending the introspector.core
===============================

Definition of additional descriptors for zope.introspector can be done
using adapters and interfaces.


Writing an adapter for new types
--------------------------------

Descriptors might inherit from zope.introspector.ObjectInfo. We
define a new kind of object, for which we want to provide extra
information::

   >>> class Cave(object):
   ...     space=12 # The space in squarefeet

If we look up this class via adapters, we get a type descriptor::

   >>> from zope.introspector.interfaces import IObjectInfo
   >>> IObjectInfo(Cave)
   <zope.introspector.objectinfo.TypeInfo object at 0x...>

For objects of this class an ordinary object descriptor will be found
with stock zope.introspector::

   >>> IObjectInfo(Cave())
   <zope.introspector.objectinfo.ObjectInfo object at 0x...>

Let's now build a descriptor, that describes ``Cave`` objects,
i.e. instances of ``Cave``::

   >>> class CaveInfo(ObjectInfo):
   ...     grokcore.component.context(Cave)
   ...     grokcore.component.provides(IObjectInfo)
   ...     def getSpace(self):
   ...          return context.space

The g.c.provides() statement is not really needed here, because it
will be inherited from ObjectInfo. But this way we make clear, that
this class defines an adapter, that adapts Caves to IObjectInfo.

Before we can use the new adapter, we must register it::

   >>> grokcore.component.grok_component('CaveInfo', CaveInfo)
   True

   >>> IObjectInfo(Cave())
   <CaveInfo object at 0x...>

Note, that for the ``Cave`` *class* we will still get the same adapter
as before::

   >>> IObjectInfo(Cave)
   <zope.introspector.objectinfo.TypeInfo object at 0x...>


Writing an interface to cover aspects of an adapted type
--------------------------------------------------------

Imagine now, that for introspection purposes we want to distuingish
between plain caves and large caves. Large caves are considered those,
that have space of more than 20 squarefeet.

Apparently, large caves are also caves which is reflected by the fact,
that a ``Cave`` object with a ``space`` value greater than 20 is still
a ``Cave`` object. Large caves and simple caves are of the same type.

This means, that we cannot separate both kinds of cave by looking up
their types or interfaces. We could indeed define a new interface for
large caves, but in real life we will see lots of objects for which we
cannot easily define an interface afterwards. 

Just think of packages and modules. While packages *are* also modules,
they have some interesting extra data to provide like a list of
contained .txt files, subpackages etc., which plain modules do not
have.

In other words: packages are modules that provide an additional
aspect.

While we cannot easily bind those objects with additional aspects to
an interface, we can define at least a different interface for the
expected data. This allows us also to get the additional data by
looking up adapters.

We define a new interface for large caves::

   >>> from zope.interface import Interface
   >>> class ILargeCaveInfo(Interface):
   ...     """Just a marker interface."""

We do not need more than a marker here. This means, that in fact every
object implements ``ILargeCaveInfo`` but we use it only to register
and lookup adapters.

We define and register an adapter, that provides special infos about large
caves:: 

   >>> class LargeCaveInfo(grokcore.component.Adapter):
   ...     grokcore.component.provides(ILargeCaveInfo)
   ...     grokcore.component.context(Cave)

   >>> grokcore.component.grok_component(
   ...    'LargeCaveInfo', LargeCaveInfo)
   True

Now we can lookup this adapter asking for the new interface
``ILargeCaveInfo`` instead of ``IObjectInfo``::

   >>> cave = Cave()
   >>> ILargeCaveInfo(cave)
   <LargeCaveInfo object at 0x...>

We indicate by this call, that we do not want the normal set of
information regarding caves, but the special information that is
provided by large caves only.

