Up:
Contents
At base, a PEAK program is a Python program. PEAK is a toolkit and framework, which means it provides stuff you can use in your Python programs to get things done more easily and more quickly, and usually more elegantly.
So, the simplest PEAK "Hello, world!" program is also, in a trivial sense, the Python "Hello, world!" program:
This, however, is not very interesting.
One of the things PEAK provides is a framework for giving application programs easy access to their associated configuration data. The goal of this first chapter will be to show how to turn the output of the simple "Hello, world!" program into a configured string. I'm going to get us there step by step, keeping the program in a runnable state at each step (except the first!)
Let's start by introducing the peak
command. For convenience in
starting PEAK applications, the peak.running
package provides a
peak
command-line script, that is installed alongside Python when
you install PEAK. We'll use the peak
script to invoke our application.
First, just type:
(The last line will list the actual path to peak.ini
on your system.)
Okay, that looks helpful, although the stuff about command interfaces
and shortcuts is perhaps a little intimidating. For now, we'll stick to
the easy part, where it said we could use a callable! Let's make a
helloworld
module, with a HelloWorld
function, in helloworld.py
:
Can we run it with the peak
script now? Let's try it:
Oops. What's wrong? Oh, wait, since helloworld
is a module, not a script,
it needs to be on the PYTHONPATH
. Let's fix that:
Nice! But we said we were going to make the message configurable.
To do that, we're going to use the peak runIni
command.
runIni
is one of the built-in "shortcut" commands that were alluded to in
the peak
script's usage message. It takes the argument
following runIni
, and loads it as a configuration file. Take a
moment now to type peak runIni
, and look at the usage message:
Okay, there's that business about executable command interfaces again, but we won't let that put us off because we know we can get away with using a function for now.
So, now we know we need an .ini
-format file, with a peak.running.app
property. But it doesn't have to actually be named with .ini
, so let's
just call our configuration file helloworld
:
(Note to experienced Pythonistas: PEAK does not use the Python
ConfigParser
module to parse .ini
files, so don't assume that
you can use ConfigParser
syntax or semantics here. To meet PEAK's
configuration requirements, both the syntax and semantics needed to
be different from those supplied by ConfigParser
.)
How did we know to format the file this way? Well, we looked at the peak.ini
file, of course, and learned by example. Here we've defined a configuration
rule for the property named peak.running.app
. The rule says (in effect),
"when someone asks for the peak.running.app
property, call the
PEAK importString
function on the string "helloworld.HelloWorld"
, and
return the value.
The importString()
function acts like a Python import statement,
such that importString("x.y.z")
is equivalent to
from x.y import z
. Thus, the importString()
line of the config
file shown above is roughly equivalent to:
The reason we say "roughly" is that PEAK configuration files aren't executed the way Python code is. Properties in the configuration file are not Python assignment statements: they're rules that supply a Python expression whose value is computed only when the property is looked up. When the configuration file is loaded, the expressions are read, but not executed. Indeed, if the property is never looked up, the expression is never evaluated at all.
This can be a big advantage over using a Python script for configuration, since you can create a shared .ini file that describes properties used by several applications. Even if some of those properties might involve a time-consuming operation such as opening a database connection, only the applications that actually use the property will pay the cost of computing it.
Indeed, this allows you to use a single site-wide configuration file to provide configuration for all of your PEAK-based applications, using the PEAK_CONFIG environment variable to point to the file. Of course, the applications can't use property names that conflict with other applications' property names. But, there are many configuration properties used by PEAK that you may wish to share across multiple applications, such as logging rules, naming services, and command shortcuts, to name just a few. Property definitions supplied by application-specific configuration files will of course override any definitions in the site-wide configuration defaults.
Anyway, we now have a configuration file for our application, trivial as it is so far. Let's try it out:
Looks like it still works.
By the way, just as a side note, if an application object like our
HelloWorld
function returns a value, that value has significance.
If the value is an integer (or None
), it will be used as the return
code from the peak
script, and of our command line as a whole. If
it is not an integer, it will be echoed to the terminal, and 1
will
be used as the return code from the peak
script. If you want, try
this out now by adding various return
statements to your
helloworld.py
and checking the exit code from the peak
script in
your shell.
The configuration file for a complex application is often central to that application. Under Unix-like command shells, PEAK allows configuration files to be the "executable" file of the application, the file whose name is entered at the console to invoke the program. This is done using the "magic cookie" of the unix shell:
This uses the Unix utilityenv
to find the peak
command
in the current PATH, and calls it. peak
then parses the
file just like it does when called via peak runIni [file]
from the command line. (Note the configuration file must
also be made executable with chmod +x helloworld
if you're
using a Unix-like OS, as opposed to just a Unix-like shell.)
(Unfortunately, the format of #!
lines is extremely non-portable.
The example we gave above will work on BSD-ish Unixes, which pass
all of the arguments on a #!
line separately. For other operating
systems, you may need to use the invoke
program, as described
on the
Python still needs imported modules to be on the
PYTHONPATH
, though, so you have to call this helloworld
file like this:
PYTHONPATH
separately, as we did earlier. In
that case, you can just run ./helloworld
. Try it now.)
In a real application, you could of course install your modules
in a directory on the system PYTHONPATH
(such as in the Python
site-packages
directory), thus enabling your command to
simply be called by name, without setting any pesky environment
variables.
If you can't arrange for the package to be on the system PYTHONPATH
,
or you don't want it to be there, you have a couple of different
options. For example, you might change the "magic cookie" line to
read:
But this doesn't work on operating systems that treat the entire #!
line as a single command argument. For those operating systems, you
might write a short shell script instead. In this case you
might call the config file helloworld.ini
, since the shell script
will be called helloworld
. If helloworld.ini
and helloworld.py
are in the same directory as the helloworld
script, the script might
look like this:
/usr/local/bin
directory or equivalent and the module files
in some special directory, and replace $here
with the
actual path to those module files.
We're going to assume from here on out that you've got your system
configured to run the helloworld
example when you type ./helloworld
.
You can do that by making helloworld
an executable in one of the
ways we've described, or you can just cheat and type
peak runIni helloworld
everywhere we say to type ./helloworld
.
Okay, enough messing around with application deployment issues, and on to configuration. Let's move the "Hello world" message into the configuration file.
The configration file follows .ini
format, which means you have
"sections" in square brackets followed by lines
that set values for various names within the section. PEAK doesn't
place many restrictions on what sections or property names can appear in an
.ini
file, so we can pretty much choose whatever section name and
property names we want, as long as they're valid Python identifiers.
We'll be very unoriginal and call our configuration section
helloworld
. (It's generally a good idea to have an application's
properties grouped under names that begin with the application name,
so they won't conflict with those of other applications.) The text
of our message will be placed in the message
configuration property:
helloworld
again, and nothing will
have changed. PEAK will have loaded the new configuration information
we provided, but our program doesn't yet actually use that information.
To use the configuration information we need to obtain it via the PEAK configuration framework. At this point we are actually starting to use PEAK facilities directly in our Python code, so we'll need to import them from the PEAK package. PEAK provides a simple way to access the vast majority of the PEAK facilities through its api module:
This will load into the local namespace a minimal set of names through which all of the PEAK frameworks can be accessed. It uses a lazy import mechanism, so ultimately your program will only end up loading into memory those PEAK facilities it actually uses. (As you might be noticing, "lazy loading" and "lazy evaluation" are recurring themes in PEAK. We'll be running into them over and over again, in this and subsequent chapters.)In addition, we can no longer use a simple, dumb function object as our application. In order to access the configuration information, the application object must participate in the appropriate PEAK frameworks.
In this case, the framework for a simple command line command such
as our helloworld
is provided by the AbstractCommand
class of the
peak.running.commands
module (lazily imported for us by peak.api
):
In the case of AbstractCommand
, there is only one method we need
to override: the _run
method, which will do the actual work of
our application (and optionally return an exit code, just like our
function could). So, without getting into the configurability yet,
our complete helloworld.py
file would now look like this:
Hmm, what's that output redirection? Doesn't print normally
write to stdout
?
Indeed, it does. And self.stdout
for an AbstractCommand
is
normally the same as Python's sys.stdout
; certainly this is
so for our application here. But an AbstractCommand
can be
run in many environments, not just from a shell script. For example,
in a later lesson we'll run this very same HelloWorld
application
as a service over a TCP/IP socket. If we wrote a simple print
statement, it would be harder to reuse our command object.
So, when writing a command object, we use self.stdin
, self.stdout
,
self.stderr
, self.environ
, and self.argv
to obtain the files
and values that a standalone program would obtain via the sys
and
os
modules. This allows our command objects to be composed into
larger commands within a Python program, almost as though we were
building Unix pipelines within a single process.
Anyway, we can run our program again at this point, and get the same
results as before. But we still haven't used the configuration
information. To do that, we need the binding
package, to "bind"
an attribute of our application to a configuration variable.
Specifically, we will use the Obtain
class from the peak.binding
package. Obtain
, coupled with the PropertyName
class, allows us
to pull values out of the configuration file:
Note that this code does not mean, "set message
to the configuration
property helloworld.message
. Instead it means, "when the message
attribute of an instance of this class is accessed, obtain the
helloworld.message
property, and use that as the attribute's value
from then on." (See? We told you lazy loading was a recurring theme
in PEAK!)
Notice that this means bindings like Obtain
are Python "descriptors",
similar to the Python built-in type property
. So, in order for a
binding to be used, it must be placed in a class, and then used from
an instance of the class.
Obtain
can find things using mechanisms other than PropertyName
,
but PropertyName
is the one we are interested in here. PropertyName
is a string subclass, and its constructor takes as its argument the fully
qualified property name. By "fully qualified", I mean
the name of the section the property is found in, followed by a
dot, followed by the rest of the property name. So the message
property from our helloworld
section becomes the PropertyName
helloworld.message
.
The PropertyName
class ensures that its instances are of
valid syntax for use in a configuration file, but that's not the only
reason we need to use it. If we gave Obtain
an ordinary string, it
would not look it up in our configuration file. Instead, it would try
to use the string as a URL or a component path -- two ideas we don't
need or want to get into right now.
Anyway, when we access our HelloWorld
object's message
attribute
in the _run()
method, the binding.Obtain
descriptor will look up
the configuration property for us. It does this by searching the
context in which the instance is located. The
peak.running.commands
framework will provide us
with with such a context automatically, when it creates an instance
of our HelloWorld
class. (We won't deal with the specifics of
this mechanism here; suffice to say that some code we inherit from
AbstractCommand
will take care of the details for us.)
Let's look at the revised program:
With this version of helloworld.py
we are finally using the
configuration information provided in the helloworld
file. To prove
this, first run the helloworld
command:
helloworld
to hellonewworld
and edit it to look like this: If you now run this new command: you'll be greeted by our new message: Although this is a very simple example, it should already be clear
that PEAK provides an easy to use and powerful configuration
mechanism. Simple use of PropertyName
and values provided in config files
barely even scratch the surface of what PEAK can do, but even if
you used nothing else PEAK would still be useful just for making
it easy to write configurable python shell scripts!
Before moving on to the next lesson, let's recap the concepts we've covered so far:
Running applications
The peak
script can be used to execute applications, by telling it to import either a function, or a subclass of commands.AbstractCommand
(among many other possibilities we're not covering here).
A commands.AbstractCommand
subclass must define a _run()
method to do the actual work of the command.
The return value of an executed function (or _run()
method) will become the exit code for the entire program, when it is run by the peak
script.
_run
implementations should use the variables provided by self
(stdin
, stdout
, stderr
, argv
, and environ
) to communicate with their environment. In this way, commands can be reused by other commands in a manner similar to Unix pipelines, but inside the same Python program.
Configuration files
PEAK's configuration framework supports .ini
-style configuration files, using property names as section headings, and Python expressions for supplied values. (Note: PEAK also supports other configuration formats, but the .ini
format is most useful for configuration done by developers. Other formats (such as ZConfig) may be more suitable for end-users.)
The PEAK_CONFIG
environment variable can name a configuration file that will be used to supply configuration defaults, before application-specific configuration is loaded.
Properties are defined in .ini
files with Python expressions that are "lazily evaluated" when the property is looked up. Simply loading the file does not execute the expressions.
The peak runIni
command can "execute" a configuration file, by looking for its peak.running.app
property. The property can be any "executable" just as could be run directly by the peak
script.
With Unix-like shells, a configuration file can be made executable, by using peak runIni
as part of the file's #!
"magic cookie" line.
An application executed via peak runIni
will be supplied a "configuration context" or "parent component", that offers access to the properties defined by the configuration file.
Attribute bindings and configuration keys
binding.Obtain
is an attribute descriptor that can be used to automatically set attributes to values found in a configuration context.
PropertyName
is a string subclass that can be used with binding.Obtain
to look up configuration properties. A PropertyName
must be syntactically valid, such that it can be used in an .ini
-style configuration file.
Up: