[Twisted-Python] Components

Phillip J. Eby pje at telecommunity.com
Fri Feb 27 07:58:23 MST 2004


At 12:40 AM 2/27/04 -0500, Glyph Lefkowitz wrote:

>Considering that this is a rather uncommon use-case, can it be made into
>the non-default Interface class?  Or an attribute/feature of the default
>one, such as
>
>         class MyAbstractSub(IMyAbstract.ABC):
>                 pass
>
>         class DontUseABC:
>                 __implements__ = MyAbstractSub.Interface

Hmm.  I suppose it's possible I could have a 'protocols.Abstract' that 
could be subclassed in place of Interface, if you wanted to use the ABC 
style.  But then, couldn't you also use an 'adapt()' method, i.e.:

foo = IWhatever.adapt(bar)

?

> > Again, this would be easily solved by a Twisted-specific subclass of
> > protocols.InterfaceClass, and I don't see that doing it is necessarily a
> > bad thing for either Twisted or PyProtocols, although it may be that it
> > should be considered simply a transitional, backward-compatibility thing.
>
>The __call__ hack, for me, is more than just syntactic sugar, or a
>"transitional, backward-compatibility thing".  It's fundamental to my
>personal use of the component system.  One-argument callables are used
>_everywhere_ in Twisted, thanks mostly to Deferreds, but also because
>"thing which takes one argument" is a very convenient interface for
>using for a variety of different kinds of processing.  An idiom I find
>tremendously convenient (even, perhaps especially, when debugging) is
>"return foo.doSomethingDeferred().addCallback(IWhatever)".  IWhatever is
>sometimes a variable, too - and it's quite common to use 'str', 'int',
>or 'list' there instead of an interface.
>
>Of course, I like the syntactic sugar quite a bit, too :).  It's
>self-documenting.  When I have run tutorials for others on the use of
>components, showing them an error on x.what(), and then success on
>IWhatever(x).what() is enlightening.  Previously,  our analogue of
>"adapt", "getAdapter", was difficult to explain.

Certainly, the convenience is tempting.  I worry more about the lack of 
commonality between 'IFoo(x)' and 'adapt(x,IFoo,somedefault)', from a 
presentation standpoint.

I think I'd like to hear some opinions from some existing PyProtocols users 
who aren't Twisted users before I "pronounce" on this.


>(BTW: a common idiom for using interfaces is to scope method names.  An
>optimization I want to add to Twisted at some point is stateless
>interface-method-call syntax which can do this efficiently, something
>like: IWhatever.what(x).  I believe you should be able to avoid a bunch
>of Python object allocation overhead by doing it that way.  Does
>PyProtocols provide any such facility?)

Not at the moment.  What I'd like to do at some point is add 
single-dispatch generic functions, though.  Then

class IWhatever:

     def what(self,...):
         ....

would be the default behavior for IWhatever.what(x), and you would define 
implementations via something like:

def what_foo(self,...):
     ...

IWhatever.what.add(what_foo, forTypes=[Foo])



> > Actually, if I understand correctly, these mostly sound like things 
> outside
> > PyProtocols' scope.  peak.binding and peak.config implement some of this
> > stuff by defining various interfaces they want, and using PyProtocols to
> > adapt things to those interfaces.  But that's entirely independent of
> > PyProtocols itself.
>
> > In other words, PyProtocols isn't tightly coupled to a component
> > architecture, but is instead a convenient base for building component
> > architectures.
>
>Perhaps we should be discussing Twisted using PEAK, then?  I don't want
>to use half a component system and implement the other half myself.

Okay.  The main issue there is going to be that PEAK is definitely still 
alpha, although the core CA stuff is *beginning* to approach PyProtocols' 
stability.  But it's nowhere near PyProtocols in documentedness, of course.


>Maybe you can come up with a counterexample, but it seems to me that the
>benefit of a common protocol system would be lost without the use of a
>common component model.

I don't really see it that way.  Right now, using PyProtocols, one can 
write components that play in both Zope and PEAK's component 
architectures.  (Granted, that's to some extent because Zope changed their 
CA to be more like PEAK, with interfaces for walking to parents and getting 
config data out of them.)

Not that I'm trying to talk you out of using PEAK's CA, mind.  I'm just 
saying that if you have radically different requirements, you might prefer 
to build your own.  If it's built atop PyProtocols, it'll be even easier to 
integrate your CA with other CAs if people have a need or desire to do so.



> > Let's take a specific example: you mentioned locating nearby services by
> > interface.  peak.config does this with two interfaces: IConfigSource and
> > IConfigKey:
>
>Woah there, sparky!  That looks a lot like the earlier documentation I
>was having trouble with.  A brief example, maybe? :)

Okay, let's say I want to get a hold of the event loop I should be a 
participant of...

class SomethingThatNeedsEvents(binding.Component):

     eventLoop = binding.Obtain(events.IEventLoop)

Okay, that's it.  That's the brief example.  :)  Seriously, this class is a 
component that, when used as part of a larger application, will 
automatically find the IEventLoop it's supposed to be using.  It may be 
defined in an .ini file, like this:

[Component Factories]
peak.events.interfaces.IEventLoop = "peak.events.io_events.EventLoop"

This says that when somebody asks for an IEventLoop, we will look at the 
requester's "service area" (a specially designated parent component), and 
if the service area doesn't already have one, we'll create a "singleton" 
instance of the peak.events.io_events.EventLoop class.

I say "singleton" in quotes because PEAK avoids true singletons like the 
plague.  Services are homed in a "service area", so there is usually one 
service instance per service area.  Typically, an app will do all its work 
in a single service area, but sometimes there are reasons to have 
additional service areas.

The other way you can make a service available (besides configuration for a 
service area) is to "offer" them from other components.  For example:

class SomethingWithACustomEventLoop(binding.Component):

     eventLoop = binding.Make(MyEventLoopClass, offerAs=[events.IEventLoop])

Each instance of this component will have its own private event loop 
instance.  In addition, any child component of an instance of this class 
will receive the instance's event loop whenever it requests an IEventLoop 
service.  So, if an instance of the first example class is made a child of 
an instance of this second example class, it will end up iwth a 
MyEventLoopClass instance as its eventLoop attribute.

Is this the sort of thing you're asking about?


> > By the way, though, I don't know what you mean by "default adapter".  Do
> > you mean the adapter for type 'object', perhaps?  I can't imagine why
> > somebody would care about that, though.
>
>I mean the adapter for type "Thing", mostly.  The general idea being
>that you want to override what happens before someone picks up a
>"normal" object when they're under the influence of a particular
>enchantment.  It is difficult to specify rules for which enchantment's
>hook takes precedence, so authors get into fights by specifying
>ever-lower numbers.

Ah.  That sounds like something that calls for a more explicit chain of 
responsibility that's not adapter driven.  That is, a component 
architecture issue.  Adaptation should be something you do to elements in 
the chain of responsibility in order to see if they want to participate in 
the current event.  But maybe I'm misunderstanding something about your use 
case.


>Really, an interface specification / lookup system is a pretty basic
>part of a system which uses it, on par with a function calling
>convention.  I want to know _exactly_ what goes on when I use it; I
>never want to be surprised by a weird component getting looked up when
>that wasn't what I intended.  With so much indirection in place that's
>too easy already.

There's really an easy solution to that.  If you want to be sure there's no 
confusion, define a new interface when you have a new use case.  And if an 
old interface mostly fits, you can make the new one a Variation of it or 
declare it to be implied by the old one.





More information about the Twisted-Python mailing list