The PEAK Developers' Center   Diff for "IntroToPeak/LessonThree" UserPreferences
 
HelpContents Search Diffs Info Edit Subscribe XML Print View Up
Ignore changes in the amount of whitespace

Differences between version dated 2003-12-18 23:00:18 and 2005-02-01 11:15:34 (spanning 12 versions)

Deletions are marked like this.
Additions are marked like this.

to record a new greeting. Our `hello` program will now have two
different functions we need it to perform: greeting, and recording
new greetings. (Of course, we could also implement these as
two separate commands, but then we wouldn't have an excuse to talk
two separate commands, but then we wouldn't have an excuse to
talk about `AbstractInterpreter` and demonstrate `Bootstrap`).
 
So in this lesson we'll expand the `hello` command to have

`commands.Bootstrap`: {{{
#!python
from peak.api import *
from hello_model import Message
from helloworld.model import Message
 
 
class HelloWorld(commands.Bootstrap):

    to -- displays a greeting
"""
 
    Messages = bindings.Make(
        'hello_storage.MessageDM', offerAs=[storage.DMFor(Message)]
    Messages = binding.Make(
        'helloworld.storage.MessageDM', offerAs=[storage.DMFor(Message)]
    )
 
 

subclass of `commands.Bootstrap`. `HelloWorld` is still the holder
for the `MessageDM` binding. The `Bootstrap` class will automatically
make the subcommands into children of our `HelloWorld` component, so
the subcommands will be able to `Obtain` the DM from their context.
the subcommands will be able to `Obtain` the DM from their context,
as discussed in the last chapter.
 
The only other thing it's got is a `usage` class variable.
To see how this is used, try typing `./hello` at your
command prompt: {{{
The only other thing it's got is a `usage` class variable. To see
how this is used, try typing `./hello` at your command prompt: {{{
 
% ./hello
 

./hello: missing argument(s)
 
}}} As you can see, PEAK is taking care of a lot of the routine
tasks associated with writing a script!
tasks associated with writing a script.
 
Our original `AbstractCommand` is still there, but now we've named
it `toCmd`. And, since it a different class from `HelloWorld` where

 
}}}
 
Hm. If we look at the `peak.ini` file, we see a section called
`[peak.running.shortcuts]`, containing a bunch of properties called
`runIni`, `help`, and many other commands.
Hmm. So, if something defined in a particular "property namespace"
affects the way the peak command behaves, that must mean that the
peak command has some place to get those properties, which means
it probably has an ini file. Sure enough, a little poking around the
peak directories will reveal a `peak.ini` file. In that file we
can find a section called `[peak.running.shortcuts]`, containing a
bunch of properties called `runIni`, `help`, and many other commands.
 
Does this mean that if we add a similar section to our `hello` file,
we can create subcommands of our own? Let's try adding this new
section to `hello`: {{{
 
[peak.running.shortcuts]
to = importString('helloworld.toCmd')
to = importString('helloworld.commands.toCmd')
 
}}}
 

configuration value, like this: {{{
#!python
    __toCmd = binding.Obtain(
        'import:helloworld.toCmd',
        'import:helloworld.commands.toCmd',
        offerAs=['peak.running.shortcuts.to']
    )
}}}

 
[peak.running.shortcuts]
* = commands.NoSuchSubcommand
to = importString('helloworld.toCmd')
to = importString('helloworld.commands.toCmd')
 
}}}
 

With these changes, our Bootstrap derivative will now do the right
thing. Let's move on to the `for` command now.
 
 
== Storing a New Message: the "for" Subcommand ==
 
Now, we know we're going to have to rewrite our `hello_storage.py` to
Now, we know we're going to have to rewrite our `storage.py` to
allow us to write to the database, but let's start this part of the
task by writing the subcommand first. As you'll quickly see, any
consideration of how we implement the saving of the data is

 
[peak.running.shortcuts]
* = commands.NoSuchSubcommand
to = importString('helloworld.toCmd')
for = importString('helloworld.forCmd')
to = importString('helloworld.commands.toCmd')
for = importString('helloworld.commands.forCmd')
 
}}} and another `AbstractCommand` subclass in `helloworld.py`
}}} and another `AbstractCommand` subclass in `commands.py`
 
{{{
#!python

== Storing a New Message: Modifying the Data Manager ==
 
OK, it's time to do some serious surgery on our Data Manager.
First, we need to exchange our `QueryDM` base class to a base
First, we need to exchange our `QueryDM` base class for a base
class that supports updating the database. That would be
`storage.EntityDM`.
 
`EntityDM` requires two additional methods to be defined by the
concrete class: `_new`, and `_save`. `_new` is called when a new
object is added to the DM, and needs to store the data for that
object is added to the DM and needs to store the data for that
object in the external database. `_save` is called when an object's
state is changed, and a transaction boundry has been reached where
that state needs to be synchronized with the external database.
 
Let's write the new `hello_storage.py`: {{{
Let's write the new `storage.py`: {{{
#!python
from peak.api import *
from hello_model import Message
from helloworld.model import Message
 
class MessageDM(storage.EntityDM):
 

        self._save(ob)
        return ob.forname
 
    def _save(ob):
    def _save(self,ob):
        self.data[ob.forname] = {'forname':ob.forname, 'text':ob.text}
}}}
 
That was easy. The `_new()` method is responsible for both saving state and returning the object ID of the new object. This is because `_new` is responsible for assigning object IDs. In this case, we simply return `ob.forname`, since that's what we're using as an object ID), after calling `self._save(ob)`. Let's run the script, and try it out: {{{
That was easy. The `_new()` method is responsible for both saving state and returning the object ID of the new object. This is because `_new` is responsible for assigning object IDs. In this case, we simply return `ob.forname`, since that's what we're using as an object ID, after calling `self._save(ob)`. Let's run the script, and try it out: {{{
 
% ./hello for Fred: Hi, guy!
% ./hello to Fred

 
Oops. All we did was update our in-memory `data` dictionary. We didn't save it to disk, so the change didn't stay in place for long. How can we fix that?
 
If we look at the `storage.IWritableDM` interface (see `peak help storage.IWritableDM`), we'll see that it includes a `flush()` method. `flush()` is called as part of the transaction commit process, and the default implementation of this method in `EntityDM` is what calls our `_save()` and `_new()` methods for the appropriate objects. If we define our own version of `flush()`, that first calls the standard `flush()`, and then writes our `data` array to disk, we'll be all set: {{{
If we look at the `storage.IWritableDM` interface (see `peak help storage.IWritableDM`), we'll see that it includes a `flush()` method. `flush()` is called as part of the transaction commit process, and the default implementation of this method in `EntityDM` is what calls our `_save()` and `_new()` methods for the appropriate objects. If we define our own version of `flush()` that first calls the standard `flush()` and then writes our `data` array to disk, we'll be all set: {{{
#!python
    def flush(self,ob=None):
        super(MessageDM,self).flush(ob)

especially since we will already be inside a transaction whenever we use
the data manager.
 
Okay, so let's fix up `hello_storage.py` to use `EditableFile`: {{{
Okay, so let's fix up `storage.py` to use `EditableFile`: {{{
#!python
from peak.api import *
from peak.storage.files import EditableFile
from hello_model import Message
from helloworld.model import Message
 
class MessageDM(storage.EntityDM):
 

 
An easy way to do this would be to override the `abortTransaction()` method, similar to what we did for `flush()`, and delete the `data` dictionary if the transaction is aborted: {{{
#!python
    def abortTransaction(self, txnSvc):
        del self.data
    def abortTransaction(self, ob):
        self._delBinding("data")
        super(MessageDM,self).abortTransaction(ob)
}}}
 

        return oid in self.data
}}}
 
Now we can update our `forCmd._run()` method in `helloworld.py`: {{{
Now we can update our `forCmd._run()` method in `commands.py`: {{{
#!python
    def _run(self):
 

 
        return default
}}}
DM's have a `cache` attribute that holds on to currently loaded objects,
so that multiple requests for a given object ID, will always return the
DM's have a `cache` attribute that holds onto currently loaded objects,
so that multiple requests for a given object ID will always return the
same object. So, by checking it here first, we can avoid doing the
lookup in `self.data` if the requested object is already loaded.
 

PythonPowered
ShowText of this page
EditText of this page
FindPage by browsing, title search , text search or an index
Or try one of these actions: AttachFile, DeletePage, LikePages, LocalSiteMap, SpellCheck