=========================================================
Test core compatibility to twisted component architecture
=========================================================

See http://twistedmatrix.com/projects/core/documentation/howto/components.html. 

But this time we use Interface and implements declaration from seishub.core.

    >>> from seishub.core.core import Interface, implements
    >>> from twisted.python import components
    
Let's imagine an example. We have an electric appliance, say a hair dryer. 
The hair dryer is american voltage. We have two electric sockets, one of them 
an american 110 Volt socket, and one of them a foreign 220 Volt socket. 
If we plug the hair dryer into the 220 Volt socket, it is going to expect 
110 Volt current and errors will result. Going back and changing the hair 
dryer to support both plug110Volt and plug220Volt methods would be tedious, 
and what if we decided we needed to plug the hair dryer into yet another type 
of socket? For example:

    >>> class HairDryer:
    ...     def plug(self, socket):
    ...         if socket.voltage() == 110:
    ...             print "I was plugged in properly and am operating."
    ...         else:
    ...             print "I was plugged in improperly and "
    ...             print "now you have no hair dryer any more."

    >>> class AmericanSocket:
    ...     def voltage(self):
    ...         return 110

    >>> class ForeignSocket:
    ...     def voltage(self):
    ...         return 220

Given these classes, the following operations can be performed:

    >>> hd = HairDryer()
    >>> am = AmericanSocket()
    >>> hd.plug(am)
    I was plugged in properly and am operating.

    >>> fs = ForeignSocket()
    >>> hd.plug(fs)
    I was plugged in improperly and 
    now you have no hair dryer any more.

We are going to attempt to solve this problem by writing an Adapter for the 
ForeignSocket which converts the voltage for use with an American hair dryer. 
An Adapter is a class which is constructed with one and only one argument, 
the "adaptee" or "original" object. In this example, we will show all code 
involved for clarity:

    >>> class AdaptToAmericanSocket:
    ...     def __init__(self, original):
    ...         self.original = original
    ...     
    ...     def voltage(self):
    ...         return self.original.voltage() / 2

Now, we can use it as so:

    >>> hd = HairDryer()
    >>> fs = ForeignSocket()
    >>> adapted = AdaptToAmericanSocket(fs)
    >>> hd.plug(adapted)
    I was plugged in properly and am operating.

So, as you can see, an adapter can 'override' the original implementation. 
It can also 'extend' the interface of the original object by providing methods 
the original object did not have. Note that an Adapter must explicitly 
delegate any method calls it does not wish to modify to the original, 
otherwise the Adapter cannot be used in places where the original is expected. 
Usually this is not a problem, as an Adapter is created to conform an object 
to a particular interface and then discarded.



Interfaces and Components in Twisted code
=========================================

Adapters are a useful way of using multiple classes to factor code into 
discrete chunks. However, they are not very interesting without some more 
infrastructure. If each piece of code which wished to use an adapted object 
had to explicitly construct the adapter itself, the coupling between 
components would be too tight. We would like to achieve "loose coupling", and 
this is where twisted.python.components comes in.

First, we need to discuss Interfaces in more detail. As we mentioned earlier, 
an Interface is nothing more than a class which is used as a marker. 
Interfaces should be subclasses of zope.interface.Interface, and have a very 
odd look to python programmers not used to them:

    >>> class IAmericanSocket(Interface):
    ...     def voltage():
    ...         """Return the voltage produced by this socket object, as an 
    ...         integer."""

Notice how it looks just like a regular class definition, other than 
inheriting from Interface? However, the method definitions inside the class 
block do not have any method body! Since Python does not have any native 
language-level support for Interfaces like Java does, this is what 
distinguishes an Interface definition from a Class.

Now that we have a defined Interface, we can talk about objects using terms 
like this: "The AmericanSocket class implements the IAmericanSocket interface" 
and "Please give me an object which adapts ForeignSocket to the 
IAmericanSocket interface". We can make declarations about what interfaces a 
certain class implements, and we can request adapters which implement a 
certain interface for a specific class.

Let's look at how we declare that a class implements an interface:

    >>> class AmericanSocket:
    ...     
    ...     implements(IAmericanSocket)
    ...     
    ...     def voltage(self):
    ...         return 110

So, to declare that a class implements an interface, we simply call 
seishub.core.implements at the class level.

Now, let's say we want to rewrite the AdaptToAmericanSocket class as a real 
adapter. In this case we also specify it as implementing IAmericanSocket:

    >>> class AdaptToAmericanSocket:
    ...     
    ...     implements(IAmericanSocket)
    ...     
    ...     def __init__(self, original):
    ...         """Pass the original ForeignSocket object as original."""
    ...         self.original = original
    ...     
    ...     def voltage(self):
    ...         return self.original.voltage() / 2

Notice how we placed the implements declaration on this adapter class. 
So far, we have not achieved anything by using components other than 
requiring us to type more. In order for components to be useful, we must use 
the component registry. Since AdaptToAmericanSocket implements IAmericanSocket 
and regulates the voltage of a ForeignSocket object, we can register 
AdaptToAmericanSocket as an IAmericanSocket adapter for the ForeignSocket 
class. It is easier to see how this is done in code than to describe it:

    >>> class IAmericanSocket(Interface):
    ...     def voltage():
    ...       """Return the voltage produced by this socket object, as an integer.
    ...       """

    >>> class AmericanSocket:
    ...     implements(IAmericanSocket)
    ... 
    ...     def voltage(self):
    ...         return 110
 
    >>> class ForeignSocket:
    ...     def voltage(self):
    ...         return 220
 
    >>> class AdaptToAmericanSocket:
    ... 
    ...     implements(IAmericanSocket)
    ... 
    ...     def __init__(self, original):
    ...         self.original = original
    ... 
    ...     def voltage(self):
    ...         return self.original.voltage() / 2

    >>> components.registerAdapter(
    ...     AdaptToAmericanSocket, 
    ...     ForeignSocket, 
    ...     IAmericanSocket)

Now, if we run this script in the interactive interpreter, we can discover a 
little more about how to use components. The first thing we can do is discover 
whether an object implements an interface or not:

    >>> IAmericanSocket.implementedBy(AmericanSocket)
    True
    >>> IAmericanSocket.implementedBy(ForeignSocket)
    False
    >>> ams = AmericanSocket() 
    >>> fos = ForeignSocket()
    >>> IAmericanSocket.providedBy(ams)
    True
    >>> IAmericanSocket.providedBy(fos)
    False

As you can see, the AmericanSocket instance claims to implement 
IAmericanSocket, but the ForeignSocket does not. If we wanted to use the 
HairDryer with the AmericanSocket, we could know that it would be safe to do 
so by checking whether it implements IAmericanSocket. However, if we decide 
we want to use HairDryer with a ForeignSocket instance, we must adapt it to 
IAmericanSocket before doing so. We use the interface object to do this:

    >>> IAmericanSocket(fos)    # doctest: +ELLIPSIS
    <....AdaptToAmericanSocket instance at 0x...>

When calling an interface with an object as an argument, the interface looks 
in the adapter registry for an adapter which implements the interface for the 
given instance's class. If it finds one, it constructs an instance of the 
Adapter class, passing the constructor the original instance, and returns it. 
Now the HairDryer can safely be used with the adapted ForeignSocket. But what 
happens if we attempt to adapt an object which already implements 
IAmericanSocket? We simply get back the original instance:

    >>> IAmericanSocket(ams)    # doctest: +ELLIPSIS
    <....AmericanSocket instance at 0x...>

So, we could write a new "smart"HairDryer which automatically looked up an 
adapter for the socket you tried to plug it into:

    >>> class HairDryer:
    ...     def plug(self, socket):
    ...         adapted = IAmericanSocket(socket)
    ...         assert adapted.voltage() == 110, "BOOM"
    ...         print "I was plugged in properly and am operating"

Now, if we create an instance of our new "smart"HairDryer and attempt to plug 
it in to various sockets, the HairDryer will adapt itself automatically 
depending on the type of socket it is plugged in to:

    >>> ams = AmericanSocket()
    >>> fos = ForeignSocket()
    >>> hd = HairDryer()
    >>> hd.plug(ams)
    I was plugged in properly and am operating
    >>> hd.plug(fos)
    I was plugged in properly and am operating

Voila; the magic of components.

