[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