[TransWarp] peak.binding questions

Phillip J. Eby pje at telecommunity.com
Wed Jun 4 14:09:25 EDT 2003


At 06:55 PM 6/4/03 +0200, Ulrich Eck wrote:
>Hi Phillip,
>
>while i have done lots of coding with peak during the last weeks
>the following problem occured regularly and i don't know
>how to "peak"ish solve it:
>
>- I have different Implementations of something (e.g.
>   platform-dependent) i specify them in my package-ini:
>
>[some.property]
>variantA = importString('some.path.to:A')
>variantB = importString('some.path.to:B')
>
>- then i have some Component where i want to use *one*
>   of those variants. i also specify the choice in some
>   user-conf(ini) file:
>
>[main.config]
>wichVariant = 'variantA'
>
>the user-conf file should be as easy as possible e.g. no
>cryptic config.getProperty(propertyMap ........)

The .ini format isn't intended for end-users; if complexity is an issue you 
should be using ZConfig.

ZConfig lets you create a very user-friendly configuration format.  In 
essence, it's a friendly serialization of components.  Here's a possible 
format:

LdapURL       ldap://user:auth@server/base
WhichVariant  variantA

In the schema, you define a "data type" reference to a function that 
converts an object with the above-named instance variables into your "real" 
component.  Each field in a ZConfig file can define its own data type 
converter as well.

While this is a good bit more work to set up than an .ini format, it is far 
more suitable for end users.


>within the component i end up with once methods that do the
>property lookups and create the instance by hand.
>
>is there an easier way to do this?

Again, the answer is ZConfig.  What you'd do in a ZConfig schema for this 
is define an "abstract section type" that essentially defines an interface 
the "variant" components must support.  Then you define "concrete section 
types" that "implement" the interface, and finally you define in a parent 
schema the requirement for a section that implements the section type.

Let's say you have an application with a top-level schema that contains 
"LdapURL" and "WhichVariant" as its required items.  You create an abstract 
section type, "Variant", and two concrete section types, VariantA and 
VariantB.  In the concrete section types, you define the configuration 
fields that each type needs (since A might have different fields than 
B).  Each of the concrete section types will also have a different data 
type: Variant A will specify 'some.path.to.A' and variant B will specify 
'some.path.to.B'.  (If they are binding.Components, you will probably 
specify 'some.path.to.A.fromZConfig', etc.)

In the top level schema, you will define a required section 'whichVariant', 
that is to be of type 'Variant'.  Then, a user will do one of these two things:

# Option 1
LdapURL       ldap://user:auth@server/base

<VariantA>
# Variant-A specific parameters, if any
</VariantA>

OR

# Option 2

LdapURL       ldap://user:auth@server/base

<VariantB>
# Variant-B specific parameters, if any
</VariantB>

When the ZConfig file is processed, and the variant section is processed, 
ZConfig will know what class to instantiate.  The resulting object will be 
placed in the top-level "section" object, which will then be passed to the 
top-level constructor.  If your top-level schema datatype is 
'some.path.to.MyApp.fromZConfig', then it will use a component constructor 
that does a 'suggestParentComponent' to the variant component.

So, what you end up with is an app object with all its configuration 
components nicely specified, and they can be any object you've set up to be 
available via the schema.  And, if you run 'peak zconfig.schema:myAppSchema 
someZConfigFile', PEAK will launch your app for you as well.  If you have a 
sitewide PEAK_CONFIG file, you can also add your schema URL to the 
'peak.running.shortcuts' property namespace, and be able to use a shortcut 
like 'peak myapp configfile'.


>same applies when i wanna configure some e.g. LDAPConnection:
>
>i want to specify it in a config-file:
>
>[main.config]
>ldapurl = 'ldap://user:auth@server/base'
>
>but i cannot say:
>
>class MyDBComponent(binding.Base):
>
>     connection = \
>          binding.bindTo(config.getProperty('main.config.ldapurl'))

If you can make the URL a LinkRef, then:

     binding.bindTo("config:main.config.ldapurl/")

will do what you want.


>if these situations could be solved without writing once-methods
>my could would look much better.

Ah.  Well, that's a different question than the one I've been 
answering.  :)  ZConfig doesn't get rid of all of your indirection needs, 
I'm afraid.  Typically, I use lambdas for simple things like this.  e.g.:

     connection = binding.Once(lambda s,d,a: s.lookupComponent(s.ldapURL))

You have made me realize, however, that a missing piece of functionality in 
PEAK's current ZConfig support is the ability to resolve a naming system 
name as part of assembling an app from a ZConfig file.  The problem is that 
since ZConfig assembly is bottom-up, at the time ZConfig processes a 
"name", the object parent doesn't exist yet.

I'll have to give this one some thought.  One idea that comes to mind is 
having 'lookupComponent()' follow 'naming.LinkRef' objects found as 
attributes.  Then, any URL fields in a ZConfig schema could be defined with 
a datatype of 'naming.LinkRef'.  Another thought is to create a special 
LinkRef-like type that replaces itself with the lookup at assembly 
time.  But that seems ugly.

Of course, right now the framework allows you to override the 'fromZConfig' 
method to do preprocessing like this.  I mostly intend to "wait and see" 
what kind of overrides I end up commonly needing to do, before trying to 
add more ZConfig hooks.  Still, having a way to do this for names would 
probably be good.

All in all, maybe the simplest thing to do is to have a 'bindIndirectlyVia' 
function or something of that sort, e.g.:

def bindIndirectlyVia(fromPath, *args, **kw):
     return binding.Once(
         lambda s,d,a: s.lookupComponent(s.lookupComponent(fromPath)),
         *args, **kw
     )

Now, 'fromPath' could be a PropertyName, an attribute name, or even a URL, 
and the result returned from would be used for the second lookup.  For use 
with ZConfig, I would do something like:

     connectionURL = binding.requireBinding('URL to connect to')
     connection    = binding.bindIndirectlyVia('connectionURL')

And then set up the ZConfig schema to set 'connectionURL' from the URL in 
the configuration file.

Notice that you can use this function yourself right now, without waiting 
for me to add it, however.  :)  Perhaps you can let me know how using it 
works out for you, so I can see if this is the "right thing to do" for 
PEAK.  :)

It probably needs two things to be "right", actually.  1. A clearer 
name.  2. Experience with its use to see if one maybe needs to pass 
additional arguments into either of the lookupComponent() calls.





More information about the PEAK mailing list