[Twisted-Python] adapt from more than one interface

glyph at divmod.com glyph at divmod.com
Sun Apr 12 09:16:04 EDT 2009


On 09:08 am, esteve at sindominio.net wrote:
>On Sunday 12 April 2009 02:31:29 glyph at divmod.com wrote:
>>This is a long and terrible road you're about to start down.  It's 
>>hard
>>for me to explain exactly why it's so painful, but trust me, it is.
>
>No, I won't trust you :-) I hope you don't take what I'm going to say 
>too
>seriously, but you couldn't sound more patronising.

Sorry for that; it certainly wasn't my intention.  I meant just what I 
said: I find it difficult to communicate about.  Perhaps I should have 
said, "trust me, my experience suggests that it is".
>Anyway, if it's so hard to explain, it's probably harder to understand, 
>and
>thus, it's something that you cannot figure out by yourself. So, if you 
>don't
>mind, why do you think it's so bad?

It's hard to explain because it's hard to describe a full use-case.  My 
experiences with adapter registries are diffuse, so I can't put it all 
together into one nice concrete example; just a bunch of places where I 
thought maybe I wanted multi-adaptation, started in that direction, and 
then realized that I actually wanted something else when it made a mess. 
A concrete description of one of these use-cases would probably end up 
talking more about email or templating than about interfaces.

Abstract examples tend to proliferate metasyntactic variables, so it's 
not clear what the relationships are of all the entities.  In fact, it 
may be that I'm not understanding *your* use-case because "IFooFactory" 
isn't a particularly expository name :).  But let me give it a go.

Multi-adapters are problematic because they specify a collection of 
objects, but they don't specify the relationship between those objects. 
When you adapt a model/request pair to a view in Zope, how do you know 
which one's the model and which one's the request?  If multiadapt(IFoo, 
(a, b)) works, should multiadapt(IFoo, (b, a))?  If not, why not?  If 
so, why so?  Is the position of the parameters significant?  Does it 
imply something about the role that each component plays to its adapter? 
What about multiadapt(IFoo, (b, b, a))?  I know that zope.interface has 
answers to all these questions (and indeed, in most cases I think I know 
what those answers are) but they're very hard to figure out from first 
principles; they're kind of arbitrary, whereas other edge-case rules in 
z.i, for example, the resolution order of adapters in inheritance 
relationships make perfect sense to me.

Maybe if you use zope.component it works out.  I don't have much 
experience with it, but I do know that it gives you a lot more options 
in terms of managing what adapters get used when, and by whom.  Adapters 
also have names, which gives you another level of disambiguation.

But in the context of Twisted's adapter registry, you just have one 
global registry.  When you're using this for f(x)->y type relationships, 
it provides a reasonable way for one system to hook into another, 
because system X can receive a bar when it only knows about foos, and 
say IFoo(bar) and that works because of an adapter registered by the 
system that declared bar.  But when you start putting additional 
arguments in there, it's not clear how those different systems will 
handle them.

For example, if you want a system that accepts "configuration", you can 
register an adapter from a foo, username, and password, to produce a 
bar.  i.e. (IFoo, str, str) -> IBar.  Later, what if we want an adapter 
from a foo, hostname and path?  (IFoo, str, str) is already taken.

But, if you do this by explicitly declaring an intermediate interface, 
it's much clearer.  IBarFactory(foo).configureUsernamePassword(username, 
password)->Bar makes the placement of the responsibility very clear; 
later, you could add IBarFactory(bar).configureHostnamePath(hostname, 
path)->Bar.

And this is why the explanation of its wrong-ness is difficult.  I know 
that you could get the same effect by doing (IFoo, IUsernamePassword) -> 
IBar and (IFoo, IHostnamePath).
>So the problem is that the resulting factory is not complete, it 
>doesn't have
>enough information to build protocols. The only workaround is to 
>explicitly
>set those extra parameters after it has been instantiated.

Would the specifics of your application allow you to introduce an 
intermediary IProtocolFactoryConfigurer interface, with .config* methods 
that would return the actual ProtocolFactory, as with my IBarFactory 
above?  "IBar" here would be IProtocolFactory; sorry for the confusion 
with the word "factory".
>>Although if I misunderstand the spec of
>>"IFooFactory" and it *does* include a bar attribute, nevermind.
>
>The IFooFactory interface declares that attribute, but it's not 
>immediately
>available when the service is adapted into a factory.
>
>I fail to see what's the difference between bar= and configureBar() in 
>this
>particular case.

Yeah, when I said "[if] it *does* include a bar attribute, nevermind", 
that's what I meant; I misunderstood and thought that the attribute was 
part of a different interface or concrete type.  Again, If 
.configureBar() *returns* a protocol factory, then maybe it's better.
>>If you want something more magical and adapter-ish, them maybe you 
>>want
>>this:
>>
>>IFooFactory(ConfigurationObject(myService, bar="some value"))

>I think that solution is even worse. That particular instance of
>ConfigurationObject is tied to myService: it can't be (de)serialized, 
>reused
>with other adapters, ConfigurationObject and MyService are completely
>unrelated and I don't think the former should depend on the latter.

So ConfigurationObject is purely for the IFooFactory implementation?
>> >In order to implement this, registerAdapter would have to be able to
>> >take a
>> >tuple of interfaces,
(...)
>> >would that feature make sense? If so, I'll file an issue right now 
>>:-)
>>
>>Unfortunately, much as I've been encouraging people to file tickets
>>lately, no :).  I don't think it makes sense.
>
>I still think it does, but nobody wants to make the ticket count grow
>higher :-) Anyway, Zope already has this nifty thing called multi- 
>adapters,
>which implement exactly what I described:
>
>http://www.muthukadan.net/docs/zca.html#multi-adapter

Thanks for that link, by the way, that's a great introduction to 
zope.component.  Not one I'd seen before.
>but I always liked Twisted's adapter registry better and our 
>application
>already uses it in quite a few places. I like being able to call 
>IFoo(bar)
>and let the registry lookup an adapter automatically, instead of having 
>to
>call getAdapter()/queryAdapter()

You'd still have to call something like getAdapter or queryAdapter in 
order to use multi-adapters with Twisted's registry.  IFoo((a, b, c)) 
will adapt from "tuple" to IFoo, and it would be ambiguous to change it 
to work otherwise - a number of existing systems do register adapters 
for tuples.  IFoo(a, b, c) can't be made to work because 
InterfaceClass.__call__'s signature can't be modified to support it, 
since IFoo(a, b) means "use b as default".

Actually... I got curious about the implementation work required, and I 
realized that no modification to Twisted is really necessary, if what 
you want is this syntactic convenience.  The registry used by Twisted 
already supports multi-adapters (since it's just a zope.interface 
registry), and is already explicitly exposed publicly so you can do 
zope-interface-y stuff to it directly.

I'm still not a big fan of multi-adapters, but the code's so short, and 
so non-invasive (in particular I don't believe it'll affect the 
performance of "normal", single adaptation), that I probably wouldn't 
argue too hard against it.  Feel free to file that ticket, although I 
won't promise that somebody *else* won't come along and object.  In any 
case, you can start using it right away if it suits you :).

Anyway, I hope this is useful:

# multireg.py

from zope.interface.declarations import implementedBy

from twisted.python.components import getRegistry

def registerMulti(adapter, fromInterfaces, *toInterfaces):
    registry = getRegistry()
    for interface in toInterfaces:
        registry.register(fromInterfaces, interface, '', adapter)
        registry.register([implementedBy(multi)], interface, '',
                          translateMulti(interface, registry))

def translateMulti(toInterface, registry):
    def translator(multiple):
        return registry.queryMultiAdapter(
            multiple.conformers, toInterface)
    return translator

class multi(object):
    def __init__(self, *conformers):
        self.conformers = conformers

# and here's an example:

from zope.interface import Interface, implements

class IA(Interface): ""
class IB(Interface): ""
class IC(Interface): ""

class A(object): implements(IA)
class B(object): implements(IB)

class ABC(object):
    implements(IC)
    def __init__(self, a, b):
        self.a = a
        self.b = b

registerMulti(ABC, [IA, IB], IC)

print IC(multi(A(), B()))
print IC(multi(1, 2), 3)

# -glyph




More information about the Twisted-Python mailing list