[TransWarp] (also) New to PEAK

Phillip J. Eby pje at telecommunity.com
Thu Jun 19 14:12:18 EDT 2003


At 02:40 PM 6/18/03 -0700, John Landahl wrote:

>So now to some specific questions:
>
>1) I haven't been able to find any example PEAK programs (though I'm 
>beginning
>to try to digest Ulrich Eck's code) -- are there any out there that show how
>to startup a basic PEAK application, define a few components and services,
>use them to perform a simple task, and then exit on a certain condition
>(keyboard interrupt, response from a user, etc.)?

If you intend to make an invocable application, a simple way to do this is 
to subclass one of the 'peak.running.commands' classes.  If subclass 
AbstractCommand or one of its subclasses, you can then run your application 
by using the 'peak' script:

peak import:MyPackage.MyCommandClass

You can follow that with other arguments, which your command subclass' 
instances will see as 'self.argv' (where argv[0] will be 
'import:MyPackage.MyCommandClass', and argv[1:] are the rest of the arguments).

If your application will be event-driven, you could also use the 
"EventDriven.xml" ZConfig schema as a basis for your application's 
schema.  For each of your various event-driven objects, you'd need to 
define configuration section types that implement the 'running.Task' 
abstract section type.  You can then make the config file executable with a 
#! line like:

#!peak zconfig.schema:pkgfile:My.Package/MySchema.xml

This will cause PEAK to load 'MySchema.xml' from the 'My.Package' package 
directory, and then use it to parse the config file that the #! line is 
in.  The top-level datatype of the ZConfig schema will be your application 
class, and its 'run()' method will be invoked, as for any other 
AbstractCommand.


>2) How does one setup a PEAK service, and how do application components ask
>for and make use of those services?

Components are found using "component keys"  (binding.IComponentKey 
interface).  "Configuration keys" (binding.IConfigKey) are one example of 
component keys.  Some concrete key types:

PropertyName() -- a dotted, hierarchical, name string

Interfaces - any PEAK or Zope interface is usable as a configuration key 
(and therefore as a component key)

binding.ComponentName() -- an attribute path string, supporting acquisition 
and relative or absolute paths (this is only a component key; it is not 
usable as a configuration key)

Any other name type -- lookup a component using the naming system 
(component key only, not a configuration key)

You can also create your own configuration key or component key types, but 
that's not well documented at present and some of the internal interfaces 
for doing this are expected to change soon.

A component key is looked up by calling 
'someComponent.lookupComponent(key)'.  If the key is a string, it will be 
interpreted as either a ComponentName or a URL, depending on its 
content.  If the key is a "configuration key", the lookup will proceed 
hierarchically upwards from the starting point until the desired component 
is found.  Each component in the hierarchy will be asked (via the 
'_getConfig()' method) to look up the configuration key.

Components that subclass binding.Component have a _getConfig method that 
tries two ways to obtain the supplied configuration key.  They will first 
check a per-instance registry that's a mapping from configuration keys to 
rules for computing the value of the key.  Second, they will check a 
per-class registry that maps from configuration keys to attribute 
names.  If an entry is found in this registry, _getConfig() will getattr() 
the attribute and return the value.

So, for the simplest possible way to offer a service:

from peak.api import *

class MyService(binding.Component):
     pass # Well, you'd need something here...

class MyApp(binding.Component):

     someAttr = binding.New(
         MyService, offerAs = [PropertyName('my.app.service')]
     )

What the above code does is define a 'someAttr' attribute in the 'MyApp' 
class that will automatically be set to a new 'MyService' instance the 
first time the attribute is accessed.  The 'someAttr' attribute name will 
be registered in the class registry for 'MyApp' under the property name 
(i.e. configuration key) "my.app.service".  So, to access this service in 
any subcomponent of an instance of 'MyApp', I can do:

class SomeComponent(binding.Component):

     theService = binding.bindTo(PropertyName('my.app.service'))

Any instance of 'SomeComponent', if it is a subcomponent of a 'MyApp' 
instance, upon referencing its 'theService' attribute, will automatically 
find and store in that attribute, the value of the 'MyApp' instance's 
'someAttr' attribute.

Whew.  That was a bit longwinded, but barely scratches the surface of ways 
to connect components.  For the sake of brevity, I chose to use a 
PropertyName() as a configuration key here, but I could also have used an 
interface.  An interface is usually a better choice, since it more 
explicitly defines the "contract" for the service.

In addition to using class-level declarations, one could also use 
instance-level ones, or declare utilities or property values in an .ini 
file, similar to the 'peak.ini' file.  There are APIs for loading a 
configuration file into a given component's per-instance configuration 
registry.

To work with the configuration system, an application needs a "root 
component" that sets up PEAK system configuration 
defaults.  'config.makeRoot()' creates such a component, and can be told to 
load additional '.ini' files besides the PEAK defaults.  The 'peak' script 
only loads the defaults, so if you want additional .ini-style 
configuration, you'll probably want to just load them directly into your 
application object, either upon an "assembly event", or when your 'run()' 
method is called.


>3) A lot of the data used by the system will be stored centrally either in an
>RDBMS or ZODB (I might want to make this configurable so the end user can
>pick whatever they prefer).  It seems that PEAK's Data Manager framework can
>handle this for me, but I don't yet know how this works.  Are there any
>example programs that show how to use persistence and data managers?

The sample app link I posted earlier includes DM's that map various IMAP 
and LDAP constructs to objects.  You can also look at the 
peak.storage.tests package, which contains some simple DMs that map from/to 
tabular 'MiniTable' objects that are SQL-like.

PEAK's DM framework doesn't directly interface with ZODB at present, 
although you could certainly make a DM that does.  Really, you can define a 
DM for anything that you can write '_save()' and '_load()' methods for.  If 
you can do it in Python, you can load or store it in a DM.


>4) It seems that PEAK's scheduling capabilities are currently limited to
>calling a method periodically (every x seconds).

Not so.  Check out the 'running.IBasicReactor' interface.  All of that is 
available with PEAK standalone.  And if you use PEAK with Twisted, you can 
use all of Twisted's fancy event loop support for Tk, wxPython, etc.


>   Will there be more advanced
>scheduling in future versions?

Define "more advanced".  I think that the current capabilities are adequate 
as a basis for building app-specific scheduling tools.  You can schedule 
any callable with any arguments for a targeted time.  If you need a 
long-term, persistent schedule, yes, you'll need to build up from there, 
but I have yet to think of any such application where I didn't need it to 
be built specifically for the app's requirements.


>5) How can various application components watch for events, either from 
>the OS
>(signals, keyboard interrupt, etc.) or from other components?

For simple things, use 'running.IBasicReactor'.  If you have more complex 
needs, you might need to use Twisted's reactor.  To distribute the events 
to multiple listeners, create a distribution service that components 
automatically subscribe themselves to.  An existing example of this is 
'peak.running.daemons': AdaptiveTasks have a binding that finds a task 
queue and adds the task to the queue when the task joins a component hierarchy:

     __addToQueue = binding.whenAssembled(
         lambda self,d,a:
             self.lookupComponent(ITaskQueue).addTask(self),
     )

This piece of code finds the nearest offered 'ITaskQueue' and adds the 
instance to it, as soon as the component knows all its parent components 
(up to the configuration root).

You could create a similar binding that causes a listener to subscribe to 
some needed event distributor, as soon as it's activated.


>6) I'm still working on getting ZConfig to do what I want it to do... is 
>there
>a more basic way to tell PEAK which components to load and run?

Absolutely.  Write Python code, and use bindings that specify what 
components to load for what attributes.  Python is in fact the easiest and 
most natural way to do this.  You only need to go the ZConfig route when 
you need end users to do configuration.  For developers, Python classes 
coupled with perhaps a strategic '.ini' file is the way to go.  Note that 
.ini files let you define configuration values in terms of Python code 
snippets, too, so really it's all code here.  ZConfig is a less-expressive 
format intended to be easier for end users.  It's important to have 
accessible end-user configuration, but you rarely need to *start* with that.


>I'm going to make my first attempt today of applying PEAK to what I've 
>already
>done so far and see how far I can get with it, after which I will almost
>certainly come up with a few more questions.  :)  Thanks for the great work,
>and if I can help at any point with documentation and such (after I know a
>bit more about what I'm doing, of course), I'd be quite happy to contribute
>to the project.

How-tos and examples would be especially welcome.  I'm much better at 
writing "what to", "when to", and "why (not) to" docs.  :)




More information about the PEAK mailing list