[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 ====
>"""PEAK Application Example
>from peak.api import *
>__all__ = ['Tick', 'main']
> """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 \
A similar issue applies here. May I suggest instead, as the simplest code
that would satisfy your intentions:
tick = 0
increment = binding.bindToProperty('PeakApp.tick_increment', default=5)
"""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:
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:
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 *
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:
self.tick += self.increment
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