[TransWarp] Tips on using model, storage, config, and naming (was Re: First attempt to use the storage package in PEAK)

Roché Compaan roche at upfrontsystems.co.za
Sun Dec 29 03:31:45 EST 2002


* Phillip J. Eby <pje at telecommunity.com> [2002-12-28 23:08]:
> 
> I'm actually working on the validation framework for 'peak.model' right 
> now; the main issues I'm running into are in dealing with multi-object 
> constraints and combinatorial validation issues.  For example, where one 
> says that every invoice line item for a product must meet such-and-such 
> criteria.  It seems necessary to support incremental validation for such 
> circumstances, but in order to do that, it's necessary to know what the 
> increments are.

Jeez, you think of everything ;-) But I'm glad you put so much effort
into a proper validation framework - I found that without it you quickly
end up with spaghetti code especially when you have to validate changes
accross fields and accross objects. I wont buy into a framework that
validates fields atomically.

> 
> Ideally, if one could specify constraints in "relatively declarative" form, 
> it would make validating constraints that much easier.  Actually, I kind of 
> wonder if maybe I shouldn't be looking into OCL, because then one could put 
> that into the UML model for an application from the start.

OCL would be great, but expressing constraints in OCL shouldn't be a
requirement. I think one should be able to express constraints in python
as well, not free from, but in a form that can be mapped back to OCL.

> >> >class ContactDM(storage.EntityDM):
> >> >
> >> >    defaultClass = Contact
> >> >
> >> >    attrs = ['Name', 'Surname', 'HomePhone', 'WorkPhone', 'Email']
> >> >
> >> >    DBConn = binding.bindTo(storage.ISQLConnection)
> >>
> >> Replace the line above with:
> >>
> >>      DBConn = binding.bindTo("mysql://roche:mypasswd@localhost/Contacts")
> >>
> >> This suffices to instantiate a MySQLConnection.  You also don't need to
> >> import MySQLConnection.  This is one of the things that the naming system
> >> is for.  :)
> >
> >Now I can appreciate what peak.naming is for. This is great!
> 
> That's only the beginning; ordinarily you wouldn't even use a hardcoded 
> address like that, but instead a name like:
> 
>     DBConn = binding.bindTo("MyContactsDatabase")
> 
> This would be looked up first in the component's parents, and if not found, 
> would then be looked up in the default "initial naming context" for the 
> component.  (Which is set by the 'peak.naming.initialContextFactory' 
> property.)  The idea here is that you'll configure the initial naming 
> context to be based on some kind of configuration file or naming service, 
> which will then resolve "MyContactsDatabase" to its actual address.  Here's 
> another way to do it, without creating a special naming provider, but just 
> using the configuration properties system from peak.config:
> 
>     DBConn = binding.bindTo("config:MyContacts.Database/")
> 

Thanks for all the great tips on the config and naming packages. I love
the fact that configuration variables can be aliased, are in one place
and that components can easily discover them. I still have a hell of a
time maintaining instances of Zope apps where mailhost and database
connections store configuration variables in themselves.

At the moment PEAK addresses almost all my concerns as an application
developer and I can really see that it will make maintaining
applications orders of magnitude easier. There is one big concern that
is not necessarily the responsibility of a framework like PEAK and has
more bearing on the implementation of the problem domain namely, 3rd
party customisation. I mention it in the hope that you can already see a
pattern that will work well with PEAK.  The best solutions  I've seen so
far is CMF Skins and by far the best one is the hyperdb used by Roundup
(a python based issue tracker to which I contributed some code a while
back - http://roudnup.sf.net/). You most probably know CMF Skins so I'll
just give a short example of how customisation works with Roundup.

When you install Roundup the core is installed in
lib/python/site-packages.  Creating an instance of an issue tracker is a
two stage process.  First you add an instance with a command-line script
to anywhere on the file system. This installs a default class schema and
html template in the directory you specified. You can go ahead
immediately and "initialise" the instance and you will have a working
issue tracker or you can modify the schema.

You can for instance modify the schema for an user from:

    user = Class(db, "user", username=String(), organisation=String(),
        password=String(), address=String(), realname=String(), phone=String())

to include new properties or reference new classes you defined in the
schema:

    user = Class(db, "user", username=String(), organisation=Link("organisation"),
        password=String(), address=String(), realname=String(), phone=String(),
        anotherStringProperty=String())

Now I only have to modify the html template for the user, initialise the
instance and start using my customised instance.

Although the hyperdb is quite flexible wrt to custom schemas and user
interfaces the downside is that it has relatively few hooks where custom
methods can be plugged in.

Whereas CMF Skins allows for generous customisation of the user
interface and provides you with a perfect place to add custom logic
through scripts and external methods it only solves part of the problem.
One still needs something that can accommodate 3rd party modifications
to class schemas that won't be heavily disrupted by upgrades of the app.

I am still thinking of ways that will work best with PEAK but haven't
come up with something that satisfies me yet. I think the way you
illustrated how to include the MySQLConnection without modifying code in
the storage package might be the answer but I'll have to play with my
Contact example some more to see how well the patterns works with domain
entities.

I've attached the mysql driver if you want to include it - it was
fairly simple to write since MySQLdb implements the DB API.

-- 
Roché Compaan
Upfront Systems                 http://www.upfrontsystems.co.za
-------------- next part --------------
--- SQL.py	Sat Dec 21 17:08:49 2002
+++ SQL.py	Tue Dec 24 16:24:59 2002
@@ -8,7 +8,7 @@
 
 __all__ = [
     'SQLCursor', 'GenericSQL_URL', 'SQLConnection', 'SybaseConnection',
-    'GadflyURL', 'GadflyConnection',
+    'GadflyURL', 'GadflyConnection', 'MySQLConnection',
 ]
 
 
@@ -408,6 +408,57 @@
 
 
 
+
+
+
+
+
+
+
+class MySQLConnection(SQLConnection):
+
+    API = binding.bindTo("import:MySQLdb")
+
+
+    def _open(self):
+
+        a = self.address
+
+        return self.API.connect(
+            host=a.server, db=a.db, user=a.user, passwd=a.passwd
+        )
+            
+
+    def onJoinTxn(self, txnService):
+        self.connection.begin()
+
+
+
+    def txnTime(self,d,a):
+
+        # First, ensure that we're in a transaction
+        self.joinedTxn
+
+        # Then retrieve the server's idea of the current time
+        r = ~ self('SELECT NOW()')
+        return r[0]
+
+    txnTime = binding.Once(txnTime)
+
+    supportedTypes = (
+        'BINARY','DATE','TIME','TIMESTAMP','NUMBER',
+        'ROWID','STRING',
+    )
+
+
+
+
+
+
+
+
+
+
 class GadflyURL(naming.ParsedURL):
 
     supportedSchemes = ('gadfly',)
@@ -451,7 +502,7 @@
 
 class GenericSQL_URL(naming.ParsedURL):
 
-    supportedSchemes = ('sybase', 'pgsql')
+    supportedSchemes = ('sybase', 'pgsql', 'mysql',)
 
     pattern = """(?x)
     (//)?
@@ -483,4 +534,5 @@
 drivers = {
     'sybase': SybaseConnection,
     'pgsql':  PGSQLConnection,
+    'mysql': MySQLConnection,
 }


More information about the PEAK mailing list