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

Differences between version dated 2004-02-10 10:19:41 and 2005-02-03 13:16:14 (spanning 18 versions)

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

python interface library, available from
[]. Or use your operating
system's package facility if it has one (for example, on FreeBSD
you could do "cd /usr/ports/database/py-PySQLite; make install".
you could do "cd /usr/ports/database/py-PySQLite; make install" and on Gentoo Linux you can "emerge pysqlite").

`describe` sounds like it might be interesting. Let's try
1> help \describe
help: no such command: \describe
}}} Well, we know there is one. Perhaps that `\` is just
syntactic sugar? {{{
1> help describe
\describe [-d delim] [-m style] [-h] [-f] [-v] [name] -- describe objects in database, or named object
-d delim use specified delimiter

-h suppress header
-f suppress footer
-v verbose; give more information
}}} That's better.
OK, so we can use this to get information about named objects.
}}} OK, so we can use this to get information about named objects.
But name is optional, in which case it describes the objects
in the database. Sounds like what we want. {{{
1> \describe

}}} Ah, good. Looks like "customers" is probably the table
we want. Let's see if we can find out more about it. {{{
1> \describe customers
Feature not implemented yet.
}}} That's not as helpful. Perhaps what we need is that "verbose"
option on the general \describe command. {{{

('Fred', 'ordinary')
}}} Well, that's pretty cool. Here is another
thing you might expect to have work, that does: {{{
>>> for i in c('select * from custgroups'):
>>> for i in c('select * from customers'):
... print "Name: %-10s Group: %-10s" % (i.NAME, i.GRP)
Name: Jeff Group: vip

n2 code asks for it.
The fantastic thing about this arrangement is that someone can come
along later and write an adpater that takes, say, an `IIMAPConnection`
along later and write an adapter that takes, say, an `IIMAPConnection`
and provides an `IN2Interactor` interface. They can declare this
adapter in some module completely apart from either n2 or the `IMAP`
module. And then, supposing there is already a naming scheme for

our new database.
First, we'll need a reference to the database as a class
variable in our DM. {{{
variable in our Data Manager. {{{
customerdb = binding.Obtain(PropertyName('corporate.customerdb'))

Next, we'll need to change our `_load` method to get the right
message, using auxiliary info from the customer data base. Remember,
our database connection object is callable; if we pass it an SQL
command, we'll get back an interable cursor containing the results:
command, we'll get back an iterable cursor containing the results:
    def _load(self, oid, ob):
        row = ~self.customerdb("select GRP from custgroups where NAME='%s'" %
        row = ~self.customerdb("select GRP from customers where NAME='%s'" %
        m =[row.GRP] % oid
        m =[row.GRP]['text'] % oid
        return {'forname': oid, 'text': m}
}}} What's that funny looking `~` doing in front of our database
call? The `ICursor` interface, which is an interface supported by
the type of object returned by a DM, defines the python unary
the type of object returned by a DM (Data Manager, not to be confused with Domain Model),
defines the python unary
negation operator to be the function `oneOf`. `oneOf` will raise
an error if there is anything other than one row accessable from
the cursor. If there is only one row, it returns it.

the error we'd otherwise get when it tried to load the non-existent
`forname`. This is logical, but the quirk that makes the app
still work is that our storage implementation for the message
database will ''udpate'' the message even though we did a
database will ''update'' the message even though we did a
``newItem`` to get the object. This is not something that a
good app design should depend on, so we'll fix it right in a

As long as we're rewriting anyway, we might as well get rid of that
clunky file and move to a real database for our messages. Perhaps
someday they'll move into a table on the corporate database, but
for now we'll stick with our SQLite database, since has proven its
for now we'll stick with our SQLite database, since it has proven its
worth so far.
We'll replace the `messagefile` configuration item in our `hello` file

messagedb = naming.LinkRef('sqlite:messages.db')
We also need to create the database and the `messages` table.
As when we created the `customers` database, `n2` is handy here: {{{
% peak n2 sqlite:messages.db
1> create table messages (name varchar(20), text varchar(80));
1> commit
Our `` file will see the greatest amount of change.
Let's start with the simpler class, the Data Manager for the
Customers. This data is read only, so we can use `QueryDM` here: {{{

    GroupDM = binding.Obtain(storage.DMFor(Group))
    def _load(self, oid, ob):
        row = ~self.customerdb("select GRP from custgroups where NAME='%s'" %
        row = ~self.customerdb("select GRP from customers where NAME='%s'" %
        group = self.GroupDM[row.GRP]
        return {'name': oid, 'group': group}

that until later in the file, we don't have a reference problem,
because the dereference doesn't happen until runtime.
Second, we're returning an instance retreived from the `GroupDM`
Second, we're returning an instance retrieved from the `GroupDM`
as the value of one of our attributes. This kind of arrangement
is why Data Managers return ghosts: when we access a `Customer`
object, even though it contains a reference to a Group object,

        except exceptions.TooFewResults:
            return None
        return {'name': oid, 'text': row.text}
        return {'name': oid, 'greetingtemplate': row.text}
    def _load(self, oid, ob):
        state = self._getstate(oid)

        if self.get(
            raise KeyError, "%s is already in the database" %
        self.messagedb(("insert into messages (name, text) values "
            "('%s', '%s')") % (, ob.text))
            "('%s', '%s')") % (, ob.greetingtemplate))
    def _save(self, ob):
        self.messagedb("update messages set text='%s' where name='%s'" %
    def get(self, oid, default=None):

the database.
We do, however, now actually need different methods for the `_new`
versus `_save` case, because the SQL commands to udpate a record
versus `_save` case, because the SQL commands to update a record
are very different from those used to add a record.
To implement a `get` method similar in efficiency to the one we had

advantage of this to deal with the case where our bosses are
serious about not wanting tailored messages. We'll allow
for messages that don't have a substitution point: {{{
class Customer(model.Element):
    class name(model.Attribute):

        else: return
Now our `print` line in `` becomes:{{{
Now our `print` line in `` becomes:{{{
print >>self.stdout, self.Customers[name].greeting()
}}} Which is more Demeter-proof anyway.
And now everyone is happy:{{{
% ./hello to Jeff
I am so happy to see you, Jeff
% ./hello for vvip:"Greetings, Your Excelency!"
% ./hello for vvip:"Greetings, Your Excellency!"
% ./hello to Jackie
Greetings, Your Excelency!
Greetings, Your Excellency!
Up: IntroToPeak Previous: IntroToPeak/LessonThree Next: IntroToPeak/LessonFive

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