[Twisted-Python] adapt from more than one interface

Hi all. I have a bunch of services, which implement a set of interfaces, and I use a bunch of adapters to turn them into factories, making those services protocol-independent. The interface/adapter stuff works great and I'm able to expose those services to several protocols, without changing a single line in them. However, some of the protocols need some extra information to be passed, so I have to do this: factory = IFooFactory(myService) factory.bar = "some value" it works, but I'd rather use this: factory = IFooFactory(myService, configurationObject) which would take a second argument, an object implementing another interface (e.g. IConfigurationDescriptor), containing all the necessary information for that adapter. I don't want to put that information in the service, as it's only useful to the protocol and would tie the service to a particular protocol. In order to implement this, registerAdapter would have to be able to take a tuple of interfaces, like this: components.registerAdapter( AdaptToFactory, (IFooService, IConfigurationDescriptor), IFooFactory) would that feature make sense? If so, I'll file an issue right now :-) Cheers.

On 11 Apr, 08:41 pm, esteve@sindominio.net wrote:
factory = IFooFactory(myService) factory.bar = "some value"
it works, but I'd rather use this:
factory = IFooFactory(myService, configurationObject)
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. What you're doing now, passing out-of-band arguments to the IFooFactory adapter, whose concrete type you are *not supposed to know* at that point in the control flow, is bad. But trying to force adaptation to become full-fledged multimethods is worse. If you want multimethods, I believe PEAK provides a package that implements them. If you want to keep doing what you're doing, you should do this: factory = MyServiceFooFactory(myService) factory.configureBar("some value") i.e. don't ask for an adaptation which might give you an object without a "bar" attribute, just create the concrete type that does what you need based on the type of myService and configure it with explicitly documented methods. Although if I misunderstand the spec of "IFooFactory" and it *does* include a bar attribute, nevermind. If you want something more magical and adapter-ish, them maybe you want this: IFooFactory(ConfigurationObject(myService, bar="some value")) i.e. by the time you are getting around to the IFooFactory adaptation, you should have an object that fully provides enough information that the concrete FooFactory adapter type can be fully initialized when it is created.
In order to implement this, registerAdapter would have to be able to take a tuple of interfaces, like this:
components.registerAdapter( AdaptToFactory, (IFooService, IConfigurationDescriptor), IFooFactory)
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.

On Sun, Apr 12, 2009 at 10:31 AM, <glyph@divmod.com> wrote:
On 11 Apr, 08:41 pm, esteve@sindominio.net wrote:
factory = IFooFactory(myService) factory.bar = "some value"
it works, but I'd rather use this:
factory = IFooFactory(myService, configurationObject)
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.
What you're doing now, passing out-of-band arguments to the IFooFactory adapter, whose concrete type you are *not supposed to know* at that point in the control flow, is bad. But trying to force adaptation to become full-fledged multimethods is worse. If you want multimethods, I believe PEAK provides a package that implements them.
Also, Zope has getMultiAdapter(). The canonical example is adapting a model object and a request object to a view object. I don't think it's as bad as glyph says. I probably wouldn't use it in the case you describe -- calling methods on objects is almost always better. jml

Hi On Sunday 12 April 2009 02:31:29 glyph@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. 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?
What you're doing now, passing out-of-band arguments to the IFooFactory adapter, whose concrete type you are *not supposed to know* at that point in the control flow, is bad. But trying to force adaptation to become full-fledged multimethods is worse. If you want multimethods, I believe PEAK provides a package that implements them.
I know it's bad, because the resulting factory is incomplete, but that's why I wanted to adapt from two objects: the service itself and something that would make the factory complete (i.e. some configuration parameters required by protocols). The main problem I see with the current adapter registry, is that adapters aren't configurable. In our case, we have a bunch of services, which are exposed to AMQP (adapted into a factory that builds instances of the AMQP protocol class), but since this is a client protocol that needs some extra arguments that can't be set beforehand in the adapter (username, password, vhost, encoding), they are passed after the service has been adapted into a factory. 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.
If you want to keep doing what you're doing, you should do this:
factory = MyServiceFooFactory(myService) factory.configureBar("some value")
i.e. don't ask for an adaptation which might give you an object without a "bar" attribute, just create the concrete type that does what you need based on the type of myService and configure it with explicitly documented methods. 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. Interface attributes can be documented, so I don't see what makes configureBar better than bar (which can be turned into a property). Anyway, I think both solutions are flaky, so that's why I thought adapting two interfaces would be more elegant.
If you want something more magical and adapter-ish, them maybe you want this:
IFooFactory(ConfigurationObject(myService, bar="some value"))
i.e. by the time you are getting around to the IFooFactory adaptation, you should have an object that fully provides enough information that the concrete FooFactory adapter type can be fully initialized when it is created.
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.
In order to implement this, registerAdapter would have to be able to take a tuple of interfaces, like this:
components.registerAdapter( AdaptToFactory, (IFooService, IConfigurationDescriptor), IFooFactory)
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 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() Cheers.

On 09:08 am, esteve@sindominio.net wrote:
On Sunday 12 April 2009 02:31:29 glyph@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:
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

On Sunday 12 April 2009 15:16:04 glyph@divmod.com wrote:
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".
I'm too thin-skinned sometimes, so just forget what I said :-)
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).
Actually I just discovered some of those flaws in Zope's adapter registry, so you're right that multi-adapters are not that easy to implement.
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".
I guess that would be sufficient, an "adapter factory" in some sense. In fact, I already came up with something similar, but found it too cumbersome (attached). Maybe, instead of supporting multi-adapters, a recipe like yours could be added to the documentation.
So ConfigurationObject is purely for the IFooFactory implementation?
Yep. In our case, AMQP protocol instances require some arguments to be passed (e.g. username and password for connecting to the broker). BTW, I just realized that we should use usage.Options instead of our own config object.
Thanks for that link, by the way, that's a great introduction to zope.component. Not one I'd seen before.
It covers pretty much everything about adapters and interfaces, I found it very detailed and useful.
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 :).
It's very simple and elegant, I like it :-) I think it's a good idea to include it, or maybe add an entry to the FAQ. Thanks!

On Sun, Apr 12, 2009 at 7:08 PM, Esteve Fernandez <esteve@sindominio.net> wrote:
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
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()
IFoo(bar) works just fine with the Zope adapter registry. jml
participants (3)
-
Esteve Fernandez
-
glyph@divmod.com
-
Jonathan Lange