[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