[TransWarp] New interfaces

Phillip J. Eby pje at telecommunity.com
Wed May 14 13:12:46 EDT 2003


At 06:33 PM 5/14/03 +0400, Oleg Broytmann wrote:
>Hello.
>
>    I have upgraded to the latest PEAK (from CVS) and started to redebug my
>programs. I changed __implements__ to implements(), exchanged parameters
>for findUtility and so on. One thing that I cannot discover is how to get
>back information that was in __implements__. In previous version I did
>this:
>
>      if _factory.__dict__.has_key("__implements__"):
>          _provides = _factory.__implements__
>
>    How can I do this now?

You don't.  We're specifically deprecating interface introspection.  Use 
adaptation instead.  In other words, every place in PEAK where we used to 
do this:

if IFoo.isImplementedBy(ob):
     spamFoo(ob)
elif IBar.isImplementedBy(ob):
     spamBar(ob)
...

We now do this:

adapt(ob,ISpam).spam()

and separately declare adapters from IFoo to ISpam and IBar to ISpam.  The 
other thing we do, (but it's bad style and I intend to fix the remaining 
uses), is to check the result of an adapt(), like this:

if adapt(ob,ISpam,None) is ob:


This is equivalent to the old 'ISpam.isImplementedBy(ob)'.  But it's bad 
usage of interfaces, because interfaces document *behavior*, not 
metadata.  If you need metadata, you should define  an interface for 
accessing the metadata, adapt to that interface, and then query the 
metadata.  I plan to make that change in those few areas of PEAK that still 
introspect interfaces in this way.

Why is introspection of interfaces bad?  Because it inherently closes code 
to extensibility.  Code that introspects is limited to working with 
interfaces it already knows about.  Only the code's author can extend the 
code to handle new kinds of objects.  With adaptation, a third party can 
define an adapter between a component supplied by one framework, and an 
interface required by another framework.  Better yet, a fourth party can 
use the third party's adapter without having to know about it, as long as 
the adapter has been installed.

If you have code that accesses __implements__ in order to examine an 
object's interfaces as metadata (e.g. for printing out help or 
documentation, to generate other code, etc.), you should define an 
interface for accessing such metadata, and then use adaptation to get at 
that interface.  The protocols package includes interfaces called 
'IOpenImplementor' and 'IOpenProvider' that you can have your classes 
implement, in order to have them receive notification when certain 
interface adaptation information is registered about them.  You can 
subclass the existing PEAK metaclasses that implement that interface (e.g. 
binding.Activator and binding.ActiveClass).

But, I suggest you consider carefully whether you really need to introspect 
interfaces, or whether you just need to adapt to them.  In general, 
'adapt()' makes it trivially easy to direct behavior, without needing to 
introspect.  So far in every case where I've looked at such introspective 
code in PEAK, it has become simpler, cleaner, and much more extensible when 
I refactored it to use adaptation.  For example, it's now possible for you 
to define your own kinds of keys that can be looked up by the 
'lookupComponent()' method of the binding.IComponent interface.  That 
wasn't possible before, because it did isinstance() and isImplementedBy() 
checks before to decide what to do.

By the way, there is going to be one more interface API change; sometime in 
the next day or two we will be refactoring the protocol declaration API to 
have four functions that you'll normally use to do all protocol and adapter 
declaration.  'implements()' and certain others will still be available 
from 'protocols.zope' if you prefer to use the Zope X3 declaration 
syntax.  But the four primary API calls will be:

protocols.advise() - declare protocol information about the class or module
protocols.adviseObject() - declare protocol information about the object 
specified
protocols.declareImplementation() - declare information about a specified 
class or type
protocols.declareAdapter() - declare information about an adapter class or 
function

These calls will all accept keyword arguments, e.g.:

class Foo(Component):

     protocols.advise(
         instancesProvide = [IFoo, IBar],
         classProvides = [IFooFactory]
     )

and:

protocols.declareAdapter(
     FooBarsAsSpam,
     provides = [ISpam],
     asAdapterForProtocols = [IFoo,IBar]
)

(Note that the only "magic" API will be 'protocols.advise'; the other 
functions will all take an explicit first argument in conjunction with the 
keywords.)

'adapt()' and 'protocols' will be available from peak.api, the way 
'binding' and such are available now, so if you are using 'from peak.api 
import *' it will not necessary to change your code to get access to 
them.  'protocols.Interface' and 'protocols.Attribute' will thus be 
available, as an alternative to importing them from 'peak.interface'.

To help minimize transition impact, 'peak.interface' will remain in place 
through 0.5a2, but will be removed when we start work on alpha 3.




More information about the PEAK mailing list