[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