[TransWarp] PEAK binding & naming howto
Phillip J. Eby
pje at telecommunity.com
Thu Mar 6 18:11:49 EST 2003
At 07:49 PM 3/6/03 +0200, alexander smishlajev wrote:
>are there any recommended readings that may cast some light on the PEAK
>binding and naming techniques while the tutorial is incomplete?
Not really. But most of your questions actually seem to relate more to the
peak.config package, so reading its source (or at least the reference docs
and interfaces module), and the contents of 'peak.ini' would probably be
helpful. While you can create bindings for utilities, properties, etc.,
these are actually looked up by the peak.config package.
>1. suppose i have a (global) service Spam implementing interface ISpam:
>
> class ISpam(Interface.Interface):
> """Spam interface"""
> def eggs():
> """provide eggs"""
>
> class Spam(binding.Component):
> __implements__ = ISpam
> def eggs(self):
> return "eggs"
>
>how and when do i instantiate the Spam service object?
Unless it's your top-level application object, or an "element" rather than
a service, you *don't* instantiate it directly. You create a binding for
it, or register it as a utility via a configuration file. So, for example,
if you decide that your 'App' class will always include a Spam instance,
you might do:
class MyApp(binding.Component):
my_spam = binding.New(Spam, provides=ISpam)
And then, when any object that is a child of your app instance requests
binding to an ISpam utility, it will cause my_spam to be initialized (if it
wasn't already) and the binding on the child object will be set from it.
Alternatively, you can do this:
[Provide Utilities]
mypkg.spammod.ISpam =
config.CachingProvider(
lambda foundIn: importString('mypkg.spammod.Spam')(foundIn),
local = True,
)
in an .ini file. The disadvantages to this approach are that it's verbose,
obscure, and inefficient for many situations. This approach is really best
reserved for situations where you need to replace a built-in PEAK default
utility, such as the transaction service.
Hm. I probably should find a way to express the above idiom more clearly
in an .ini file, as it seems to pop up for any simple "global service"-type
components, such as logfiles, transactions, etc.
>how do i register this service so that all components know that eggs may
>be got from that object?
I actually just answered that. :) Any component which is a child of the
component which declared it to provide that service, will see it as a
provider, so long as there is not a "closer" provider. Think Zope-style
acquisition, only without all the gotchas, or the Zope 2 "placeful utility"
concept, but with less overhead in the implementation. PEAK components
acquire placeful things based on a fixed notion of context: their "parent"
component. Zope 3 components have a context that depends on "where you got
them from", which may vary for the same object and thus requires context
wrappers.
Of course, the registration techniques I've shown are all "static", at
least in the sense that they are defined in code and not at
"run-time". Classes that subclass 'binding.Component' (as opposed to
'binding.Base') can also declare providers at runtime, using their
'registerProvider()' method (see the config.IConfigurable interface, which
is subclassed by binding.IComponent).
>2. consider application-wide utility Foo:
>
> class Foo(binding.Component):
> bar = "bar"
> def baz(self):
> return "baz"
>
>is it rational to bind this utility to "/foo"?
>
>other application components may then use the utility like this:
>
> class FooBar(binding.Component):
> foo = naming.lookup("/foo")
>
>.. am i right?
naming.lookup() looks something up right away; binding.bindTo() creates a
descriptor that will do the lookup when the attribute is accessed. Also,
bindTo() can look up interfaces and configuration properties as well as
naming system names, and the simple component path names are actually not
supported by the naming package; if you want to look up a relative
component path "right away", you have to use binding.lookupComponent().
Anyway, what you want to write here would be:
class FooBar(binding.Component):
foo = binding.bindTo("/foo")
However, as I believe I've mentioned before, it is not wise to do this for
components you might want to reuse, since FooBar will now *insist* that its
instances be used in a hierarchy where the root component has a 'foo'
object that implements the Foo behavior. It might be best to use an
interface or a property for this instead.
>what bindinding function should be used in class Foo?
Er, whatever you need. If you're asking me, "how do I make it be found as
'/foo'?", the answer is to set a root component's "foo" attribute to an
instance of it. It will then be '/foo' for any component that is a child
of that root.
Note that any PEAK component without a parent component is a "root
component". So if I create a binding.Component() and then set its "foo"
attribute to a Foo(), and its "bar" attribute to a FooBar(), then as far as
those three Component, Foo, and FooBar instances are concerned, '/' is the
Component(), '/foo' is the Foo(), and '/bar' is the FooBar().
(This means, by the way, that it's useless to lookup a component path
without having some component to use as context, since there can be
arbitrarily many "root" objects in a PEAK application. For example, PEAK
considers any non-PEAK object to be a root. If you ask PEAK what the
component path of the number "42" is, PEAK will tell you it's "/", because
42 has no parent component. The only non-PEAK object type PEAK can guess
parent components for, is ModuleType. PEAK considers every top-level
Python module to be a root component, but modules contained in packages
have the package as their parent component.)
>should foo be a property of application object? say,
>
> class FooBarApp(binding.component):
> foo = binding.New(Foo)
That's definitely one way to do it, and certainly one of the easier
ways. I would suggest you add a 'provides=' keyword to the binding.New(),
giving either an interface or a PropertyName() (or a tuple of either), so
that components can find it by function rather than by location. Because
PEAK bindings are always computed once and then cached, there is almost no
performance difference to look something up by interface or property name
instead of by absolute location.
>if not, then how and when the utility object should be instantiated?
You can also do it "manually" or register a provider rule via
'registerProvider()', but I haven't yet run into a situation where I needed
or wanted to. It's generally easiest to just create bindings, or if you
want it to be externally customizable, use a config file.
More information about the PEAK
mailing list