[TransWarp] PyProtocols: adapting classes to instance

Phillip J. Eby pje at telecommunity.com
Mon Sep 29 11:11:44 EDT 2003


At 02:54 PM 9/28/03 -0500, Ian Bicking wrote:
>I wasn't sure if this is the right place to post questions about 
>PyProtocols...

Go for it; I haven't seen a need yet to set up a separate mailing list.  In 
fact, you're the first person to *ask* anything about PyProtocols that 
wasn't already using PEAK.  :)


>Anyway, I want to adapt classes to instances of those classes.  So you 
>could do something like:
>
>class MyValidator(Validator):
>     protocols.advise(instancesProvide=[IValidator])
>     ....
>
>adapt(MyValidator, IValidator)
># --> MyValidator()
>
>
>This way classes can be used in place of instances in most cases (and 
>obviously the classes are written with this in mind).  But I haven't been 
>sure how to do this.

I don't understand why you want to do this.  It seems to me it would be 
simpler to use a singleton instance, instead of creating new ones all over 
the place.  Perhaps you could explain your use case.

Getting the behavior you want isn't difficult, though, so I'll explain it 
below.  I would still like to know why you want it, though.


>I tried using a metaclass like:
>
>class ValidatorMeta(type):
>     def __new__(meta, className, bases, d):
>         cls = type.__new__(meta, className, bases, d)
>         protocols.declareAdapter(_simpleClassFactory, cls.__provides__,
>                                  forObjects=[cls])
>         return cls
>
>def _simpleClassFactory(v, protocol): return v()

Looks good so far.  (Of course, if the class constructor accepted two 
arguments, you could declare 'cls' to be the adapter without needing the 
separate function.)


>(I set cls.__provides__ manually because I wasn't sure how to find what 
>protocols an object supports from PyProtocols, but that's an aside)

You could get this info by having your metaclass provide 
IOpenImplementor.  The metaclass' 'declareClassImplements()' method would 
then be called with each declared protocol, e.g.:


class ValidatorMeta(type):

     protocols.advise(instancesProvide=protocols.IOpenImplementor)

     def declareClassImplements(cls,protocol,adapter,depth):
         if adapter is protocols.NO_ADAPTER_NEEDED:
             protocols.declareAdapter(lambda o,p: o(), protocol, 
forObjects=[cls])

I believe this should do what you want, without needing a __new__ method, 
and without needing a __provides__ member.



>Unfortunately that didn't work.  I got an error 'Incompatible __conform__ 
>on adapted object'.  I assume that's because Validator defines a 
>__conform__ method, which applies to instances (for conforming validators 
>to a different interface).

Here's the issue: when you declare an adapter for a specific instance (e.g. 
'forObjects=[cls]'), that object needs to support IOpenProvider.  The error 
message you're getting is from the default adapters that provide 
IOpenProvider for most kinds of "singleton" objects (i.e. functions, 
modules, and classes).  There is a more detailed explanation in the 
reference manual; see:

http://peak.telecommunity.com/protocol_ref/protocols-instances.html

That section describes your problem (what happens when you can't use the 
default adapters) and what you need to do instead (mix in 'ProviderMixin').



>So, my basic question (if you understand what I'm trying to do) -- how 
>should I do it?  I might be all backwards, so I'm open to any ideas.
>Add an __adapt__ method to IValidator?  Add a __conform__ method to 
>ValidatorMeta?  I've tried different combinations, but to no avail.

Another relevant excerpt from the manual:

"""class ProviderMixin

     If you have a class with a __conform__ method for its instances, but 
you also want the instances to support IOpenprovider (so that adviseObject 
can be used on them), you may want to include this class as one of your 
class' bases. The default adapters for IOpenprovider can only adapt objects 
that do not already have a __conform__ method of their own.

     So, to support IOpenprovider with a custom __conform__ method, 
subclass ProviderMixin, and have your __conform__ method invoke the base 
__conform__ method as a default, using supermeta(). (E.g. return 
supermeta(MyClass,self).__conform__(protocol).) See below for more on the 
supermeta() function. """


In practice, your situation isn't quite what's described here.  In your 
case, you want to mix ProviderMixin into the metaclass, and you shouldn't 
need to do anything special to the class-level __conform__.  The metaclass 
__conform__ should then be called whenever you adapt the class itself, and 
the class __conform__ should be called whenever you adapt an instance.

So, probably your finished metaclasss will look like this:

class ValidatorMeta(ProviderMixin, type):

     protocols.advise(instancesProvide=protocols.IOpenImplementor)

     def declareClassImplements(cls,protocol,adapter,depth):
         if adapter is protocols.NO_ADAPTER_NEEDED:
             protocols.declareAdapter(lambda o,p: o(), protocol, 
forObjects=[cls])





More information about the PEAK mailing list