[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