[TransWarp] simple application skeleton?

Phillip J. Eby pje at telecommunity.com
Tue Mar 4 18:47:58 EST 2003


At 12:25 AM 3/5/03 +0200, Vladimir Bormotov wrote:
>==== PeakApp.py ====
>#!/usr/bin/python
>"""PEAK Application Example
>"""
>
>from peak.api import *
>
>
>__all__ = ['Tick', 'main']
>
>
>class Tick(binding.Component):
>     """Tick is binding.Componenet child"""
>
>     def __init__(self, increment=None):
>         """get given increment from argument or config"""
>         self.tick = binding.New(int)

There are two problems with the above line.  First, bindings *must* be in 
the class, not assigned to the instance, as is illustrated by all existing 
tutorials, docs, examples, and the code of PEAK itself.  Second, 
binding.New will call its first argument without arguments to create a new 
instance.  So 'dict' and 'list' work as arguments to binding.New because 
they create new empty dictionaries or lists, respectively.  But calling 
'int()' will just give you an error.  Also, ints are immutable, so there is 
no reason not to just put one in the class and skip the binding.New altogether!


>         self.increment = increment or \
>                          binding.bindToProperty('PeakApp.tick_increment',
>                                                 default=5)

A similar issue applies here.  May I suggest instead, as the simplest code 
that would satisfy your intentions:

class Tick(binding.Component):

     tick = 0
     increment = binding.bindToProperty('PeakApp.tick_increment', default=5)

     def next_tick(self):
         """make next tick"""
         self.tick += self.increment

You can re-add your debug logging as you wish, and leave the rest 
intact.  Note that this version of the Tick class will still accept an 
'increment' keyword argument, even though it does not have an __init__ 
method.  (It will also accept a 'tick' keyword argument, which will set the 
initial tick value.)

Your __init__ method did not call the super() __init__ method and so left 
some binding requirements unsatisfied.  If you *must* override __init__ for 
some reason (and you'll notice that it's rare within PEAK itself and PEAK 
applications to override __init__), you should be sure to call the 
superclass __init__ unless you really know what you are doing.

Oh, by the way, you don't need to use the export on the command line if 
you're just writing a test script...  you can always do this at the top of 
your script, before using any PEAK functionality:

PEAK_CONFIG = 'PeakApp.ini'



>  some dummy questions, answers on which I think would find in examples
>
>  at first, logging:
>
>   - how set PRI_DEBUG for default logger?

Add this to your PeakApp.ini:

[peak]
log_level = importObject('peak.running.logs:PRI_DEBUG')


>   - where read messages writed to log via LOG_DEBUG?

The default logging method is writing to sys.stderr.


>   - how set log-file-name for default logger?

This will be slow, but work:

[Provide Utilities]
peak.running.logs.ILogSink = naming.lookup('logfile:/path/to/aLogfile')

It's probably better to add a property to an application class, e.g.:

from peak.api import *

class MyApp(binding.Component):
     aLog = binding.bindTo('logfile:/path/to/aLog', provides=logs.ILogSink)

and then ensure that any objects created have a MyApp instance in their 
parent hierarchy, or at least that you use a MyApp instance as the 'parent' 
argument in all your logging calls.  Anyway, this will ensure that 'aLog' 
is only created once, whereas the .ini based way will recreate the logfile 
object on every logging event.

So, for example:

app = MyApp()

for i in range(1000):
     LOG_DEBUG("I'm counting...", app)  # could also be 'parent=app'

This guarantees that the log used will be the 'aLog' attribute of the 'app' 
instance of 'MyApp'.


>   I think, "source mining" not best way to learn "how programm work".
>  Me prefer "full debug log reading"...
>
>  at second, config...
>  But, with logging, I can add at "dark places of PEAK" calls of LOG_*
>  myself, read logfile, and understand "how it work..." ;)

That would be interesting.  There are many places where I'm sure the logs 
would surprise even me.  I specifically wrote PEAK so as to not to need to 
understand *when* things happen, because that is a major source of errors 
in applications (temporal coupling).  So PEAK is "lazy" in not doing things 
until it's needed, because then you don't have to know whether it has been 
done; you can just assume that things will be there when you need them.

 From the issues in your post, I'm guessing that the thing that would be 
most useful for you to understand in order to improve PEAK's usefulness to 
you, is the idea of component hierarchies.  Your 'Tick()' class, for 
instance, is an object without context; a root object in itself.  This 
means that it must define its own utilities, or else fall back to the 
global configuration.  Falling back to a global configuration is usually a 
bad thing, because it makes the component less reusable.  It cannot adapt 
itself to the environment it is being used in.

In PEAK, components can have a context or "parent" component from which 
they can obtain access to more "global" services, without falling back to 
the truly global systemwide configuration.  For example:

class Ticker(Tick):

     def next_tick(self):
         self.tick += self.increment
         LOG_DEBUG("Ticking", self)


app = MyApp()
ticker = Ticker(app)

ticker.next_tick()   # logs to /path/to/aLogfile

app.aLog = naming.lookup('logfile:/another/file')

ticker.next_tick()   # logs to /another/file

The default '__init__' for binding.Base and binding.Component accepts as 
the first positional argument, an object to be the context or "parent" 
component for the object being created.  When the new object needs a 
utility (such as a logfile) or configuration property that it doesn't 
"know" on its own, it will ask its parent, and so on until an object 
without a parent is reached.  It is then that there is fallback to two 
special configuration objects: the LocalConfig and the GlobalConfig.  By 
default there is one LocalConfig, and there is always only one GlobalConfig 
(but you can set the GlobalConfig to an object of your choice as long as it 
hasn't been used yet).  You can also designate LocalConfig objects to be 
used for specific "root" objects.

Anyway, the use of LocalConfig and GlobalConfig manipulation is intended 
only for complex systems such as application servers which need to have 
several applications running, each of which has its own separate (but 
seemingly "global" from that app's perspective) configuration.  Normally, 
you should construct apps so that all of their components exist as a tree 
descending from a top-level application component.  All of PEAK's APIs are 
constructed so that if you give them a context, the objects they return 
will respect that context.  For example, bindings that result in PEAK 
objects like logfiles or database connections, will create those connection 
objects with the owning object as their parent component, so that the 
logfile, connection, or whatever will get all its utilities and 
configuration from the place where it was bound.

This is one of the ideas I wanted to convey in the 'peak.binding' tutorial, 
but as yet it stops just short of this point  (Section 2.4, "Connecting 
Components by Name or Interface" is really where it would begin.)




More information about the PEAK mailing list