[TransWarp] first experiences with Transwarp

Phillip J. Eby pje at telecommunity.com
Mon Mar 18 20:20:19 EST 2002


At 07:48 PM 3/18/02 +0100, Ulrich Eck wrote:
>Hi there,
>
>after looking into TransWarp 0.2 preview, i find it very interesting.

[jumping up and down and exclaiming with joy]

I'm absolutely amazed...  First, that someone got this far with only the 
reference documentation and perhaps the code.  Second, that so many things 
I have never even tested worked so darn well.  :)  Third, and perhaps most 
surprising of all, that you fixed the few bugs that did exist by using 
module inheritance.

By the way, may I ask:

* How long have you been playing with this?

* What did you read?  Was the documentation adequate for you to get 
started, or was it necessary to read the code?  (I realize you had to read 
the code to debug it, I'm just wondering if you needed to in order to get 
started.)

* Are you working from the release package or from CVS?



>i have played around with modules-inheritance which works pretty well and
>tried the Database-Layer.
>
>There were a few things that i needed to change to get the Database-part with
>LDAP running (running at the moments means: Querying a record of a simple 
>type):
>
>I included my example and will comment the changes here:
>
>Files:
>|- MyConnections.py     Modifications of TW.Database.Connections
>|- MyLDAPModel.py       Modifications of TW.Database.LDAPModel
>|- MyDataLayer.py       My Datalayer with Database and Type definitions
>|- test.py                      how to use it ...
>
>1. Changes to Connections
>  - Error with QueryResult:
>    """May be broken - needs testing since removal of ExtensionClass.Base""""

I've checked in what should be a fix for this, but I didn't test it.  Most 
likely, the API for connections will change in future to use iterators 
instead of QueryResult objects.



>class LDAPConnection:
>
>         ## add some debugging
>         def __call__(self, baseDN=None, scope=None, filter=None, attrs=None,
>             dn_only=0, timeout=-1, resultMaker=None):
>                 print baseDN,scope,filter,attrs
>                 ret = 
> __proceed__(self,baseDN,scope,filter,attrs,dn_only,timeout,resultMaker)
>                 print ret
>                 return ret

Nice use of module inheritance, there.



>2. Changes to the LDAPModel
>MyLDAPModel.py-------------------------------------------------------------
>--------------
>from TW.API import *
>from TW.Database import LDAPModel,DataModel
>
>__bases__ = LDAPModel,
>
>def normalizeDN(dn):
>    from ldap import explode_dn
>    dn = explode_dn(dn.lower())
>
>    ## fixed typo replaced 'key' with 'dn'
>    return ','.join(
>        ['='.join([x.strip() for x in y.split('=',1)]) for y in dn]
>    )

This fix is now in CVS.



>    def _loadDataFor(self, record, typeName=None):
>
>        """Define in subclasses to read data into record"""
>
>[snip]
>
>         ## wrong error-msg if no result
>        assert results is not None, ("No LDAP entry found for %s" % record)

I don't understand why you did this.  Are you getting a 'None' return from 
the LDAP search?  If so, that's a bug in LDAPConnection; please let me 
know.  Also, note that getting no results means that the record doesn't 
exist, not that there's an error.



>class RecordManager:
>
>         ## there is an error in the WeakCache Module:
>         ##  File "/usr/lib/python2.2/site-packages/TW/Database/DataModel.py",
>         ##  line 587, in _getFromCache
>         ##  record = get(key)
>         ##  File "/usr/lib/python2.2/weakref.py", line 68, in get
>         ##  wr = self.data[key]
>         ##  AttributeError: 'WeakCache' object has no attribute 'data'

Interesting.  This turned out to be an issue with combining new-style and 
old-style classes.  If you define a class thusly:

class mixed(newStyle,oldStyle):
     def __init__(self):
         super(self,mixed).__init__()

And 'oldStyle' defines an '__init__' method but 'newStyle' does not, then 
'oldStyle.__init__' is never called.  This is because mixed.__mro__ will 
list 'object' before 'oldStyle', which means that 'super(self,mixed)' is 
'object.__init__', not 'oldStyle.__init__'.

The lesson?  Be wary of combining new-style and old-style classes!  In this 
case, the problem was easily fixed by changing the base class 
order.  WeakCache should work correctly now.  [crossing fingers for luck]


>    def _getKeysFor(self, mapping):
>
>[snip]
>                     ## fixed typo replaced: values.append(key)
>                values.append(v)

Fixed in CVS.

>and now the final test.py:
>test.py--------------------------------------------------------------------
>--------------
>import MyDataLayer
>db = MyDataLayer.MyDatabase()
>i = db.Accounts.getItem({'uid':'jack'})
>if i.exists():
>         print "Record exists."
>         print i.items()
>~test.py-------------------------------------------------------------------
>--------------




>I have a few questions on this:
>  - Did I use the Database-package as you think that it is the right way ?

Pretty much, yes.  The main change I would have made, would be that test.py 
would look more like this:

from TW.API import *

class MyApp(SEF.App):

     # apps which use transactions will need a Transaction Manager!
     from TW.Database.Transactions import TransactionManager

     # by making this an attribute, it is created automatically when
     # we access it:
     from MyDataLayer import MyDatabase

     def __init__(self):
         accounts = self.MyDatabase.Accounts  # note that I don't call anything
         record = accounts.getItem([('uid','jack')])  # current API uses items
         if record.exists():
             ...etc.

def __init__():
     if __name__=='__main__':
         MyApp()   # create and run the application object

setupModule()


Notice that the above module is extensible using module inheritance...  :)


>  - How would this application look like if it would make use of the SEF 
> Model ?

You'd have a module with your domain layer, which would consist primarily 
of Specialists and Elements, e.g.:

class User(SEF.Element):

      class userid(SEF.Value):
          ...

      ... other features of User element

class Users(SEF.Specialist):

      ...domain-specific methods

And then a module with your dispatch (implementation) layer, with the db 
interaction code:

import MyDomainLayer
__bases__ = MyDomainLayer,

class User:

     class userid:
         # implementation to read/write data from records/fields
         def _setData()...
         def _getData()...

     etc...


class Users:
     elementClass = User
     recordManager = SEF.bindTo('MyDatabase.Accounts')
     requiredType  = 'PosixAccount'
     keyField = 'uid'

     ...plus db-specific implementations of any needed methods


And finally, your main app class would now look more like this:

class MyApp(SEF.App):

     from TW.Database.Transactions import TransactionManager

     from MyDataLayer import MyDatabase

     from MyDispatchLayer import Users

     def __init__(self):
         user = self.Users.getItem('jack')  # or self.Users['jack']...
         if user is not None:
             ...etc.

Maybe that makes a bit more sense now.  As you can see, this will be quite 
a bit easier to develop with than ZPatterns...

I would advise holding off for a bit on digging too deep into the use of 
Elements and Features, however, as I'm about to redo their API 
entirely.  After that's done, it should be possible to make some simple 
"dispatch wiring" objects to connect the domain and data layers.  These 
objects will be at the field-mapping level, for the most part.  E.g., to 
convert record fields to object fields and vice versa.  There will also at 
some point be mappers that use a field value as a key to look up in another 
Specialist, and mappers that call a query on a Specialist to retrieve a 
collection of related objects.  In general, these mappers will be used in 
the dispatch layer to link Features to their underlying implementation in 
the data layer.  But you can always write custom dispatch methods directly 
in Python; the mappers will just be convenient shortcuts for specification.


>I also build a small logger based on TW.API to learn AOP Basics, which i can
>provide as example as well.

That might be interesting.  Are you using metaclasses to do it?  My theory 
regarding AOP is that AOP = SOP + metaclasses.  TransWarp does SOP using 
module inheritance, so by throwing in  Python metaclasses, one should be 
able to do just about anything...


>I hope this helps others to find into TransWarp as well
>.. as long as what i've done is right ..

Very much so, as you will have seen by now.  Thank you very much for the 
early feedback and testing.





More information about the PEAK mailing list