[TransWarp] peak.naming .. first contact

Phillip J. Eby pje at telecommunity.com
Sat Feb 15 12:01:27 EST 2003


At 10:56 PM 2/14/03 +0100, Ulrich Eck wrote:

>in the case of imap-folder i'ld need to access two attributes:
>- ACL: IMAP-ACL's are basically a list of tuples [('user', 'rights',), (...),]
>        that are managed in my current implementation through a customized 
> model.Collection.
>- Flags: Flags is a list of Names e.g. 'Marked','Noselect' that can be 
>read for each folder.
>
>one could use it like this (just a tiny draft):
>
>folder = 
>naming.lookup('imap://user:auth@mail.server.local/INBOX.path.to.folder')
>acl = folder.getAttribute('acl')
>del acl['testuser']
>acl['someotheruser'] = 'lrwpicdsa'
>folder.setAttribute('acl', acl)
>
>the underlying ContextImplementation would need to know how to handle 
>these Attributes.
>In case of an LDAP-Context this could be very usefull as well, cause any 
>object
>can have children and attributes at the same time.

Yes, in JNDI the attribute functions are part of the "directory context" 
interface, which is intended for directory services like LDAP.  So 
attributes are multi-valued lists.  Of course, for LDAP, the values in the 
lists are always strings in some sense.

I don't believe that a getAttribute or setAttribute function make sense; 
they need to be getAttributes() and modifyAttributes(), with the former 
allowing a list of attributes desired, and the latter taking a list of 
modification operations.  I guess I just need to take another look at the 
interface package and see about defining them.  The main open question 
there is how to format the parameters, I suppose.



>of course one should be able to add Folders to the collection and _save() 
>them.
>
>i'm not 100% clear about your description with the relationships.
>what do you mean by "the QueryLink should be on the "parent" pointer side 
>of the relationship" ??

Meaning that if you want the _save() to look at the folder collection, then 
you shouldn't be using a QueryLink there.  If the _save() will work by 
looking at an "owner" or "container" or "parent" reference that says what 
folder this folder belongs to, then the QueryLink goes on the collection side.

Here's a question: can IMAP folders move?  If not, then the simplest thing 
to do is to give folders a "parent" model.Reference that refers to their 
parent folder.  Your _new() method will use this to know where to add the 
folder.  Even if folders *can* move, you can still save them in their moved 
position using that parent reference.

This may be completely irrelevant to what you're doing, if you're using 
folder names to determine their location, in which case it doesn't matter 
where you put the QueryLink objects.

But, I don't recommend using data values that can change (folder path) as 
an oid.  If folders can't move, then you could use the path as an oid.


>how would one basically setup the relations to be able to use 
>model.Collection/Sequence fields
>together with DataManagers ?

The model is independent.  Just define the object model in an "idealized" 
way, how you would want to work with and think about the objects in a 
perfect world.  Then, and only then, define the datamanager's 
mapping.  Otherwise, you will carry imperfections through from your 
underlying mechanism to the object model, which eliminates the point of the 
exercise in the first place.  :)

As for the mapping, well that is dependent on the underlying mechanism.


>in the old TW.Database i did manual querying and updates on the DM from 
>within the features
>get/set methods.
>
>i'ld like to be able to do something like this:
>
># setup stuff to use IMAP_DM
>....
>storage.begin(IMAP_DM)
>
># retrieve folder and use it
>folder = IMAP_DM['INBOX.test']
>print folder.folders
><prints out a list of instances of child-folders>
>print folder.acl
>[('test1', 'lrpwicdsa',), ('test2', 'lrwpicdsa',)]
>
># modify folder
>folder.removeAcl(('test1', 'lrpwicdsa',))
># an acl may get an object as well .. don't know yet.
>
># create a subfolder
># this below is not mandatory if there is a cleaner way to do
>new_folder = IMAPFolder(IMAP_DM, componentName='sub01')

Er, don't call it 'componentName'; for Element objects that sets the 
_p_oid.  You need to make that field part of the model itself, as a field 
for say "name" or "folderName".  And do it like this:

# unless IMAPfolder is the default, in which case leave it out
new_folder = IMAP_DM.newItem(IMAPFolder)

new_folder.name = 'sub01'


>new_folder.addAcl(('user', 'rights',))
>folder.addFolders(new_folder)
>
># storing stuff on commit
># an imap-connection is not really transactional
># so it may not make sense - does it ??
>storage.commit()

That should be storage.commit(IMAP_DM)...  it might coincidentally work 
because you only have one transaction, but if IMAP_DM is using a different 
transaction service than the global one, that commit will fail.

I would suggest that you have your DM raise an error when it is asked to 
abort, if it has already committed anything.  Something like:

     def abortTransaction(self, txnService):

         savedCount = len(self.saved)

         # do normal abort cleanup
         super(myIMAP_DM_Class,self).abortTransaction(txnService)

         if savedCount:
             raise SomeKindOfError("Already saved %d objects to IMAP" % 
savedCount)

You see, objects aren't saved to the underlying storage until the 'flush()' 
method is called.  This method is ordinarily called at the pre-voting phase 
of transaction commit, which is part of the 'storage.commit()' call.  So 
yes, it does make sense to use transactions with a data manager.  In fact, 
Entity DMs *must* be in a transaction, they're not built to work any other 
way.  (Query DM's can in principle be run without transaction 
support.)  Anyway, as long as the transaction doesn't abort *during* the 
commit attempt, everything will be fine.  And the transaction machinery can 
be hooked to do something special for partial aborts, like logging it, 
shutting down, etc.

The other time that 'flush()' would be called is if a co-operating QueryDM 
wants to ensure that the external state matches the cache state.  So if you 
have a QueryDM that retrieves a list of folders, for example, then it would 
want to call 'flush()' on the DM that handles adding or modifying folders, 
so that the IMAP server would be up-to-date with respect to what has been 
done in the transaction.

You'll notice that this means that it might be a good idea to use different 
EntityDM's to handle folders as distinct from messages.  Even if you use 
the same DM "behind the scenes", it's probably good to stick in some 
FacadeDM's so that your code only sees "user-friendly" primary keys.

The whole purpose of the peak.model and peak.storage systems is to present 
a friendly face on your "back end" systems.  It's a bad idea to expose the 
inner workings of these things to application-level code.  Your view code 
(if you're using Zope 3) should only have to deal with user-friendly keys 
issued to a DM (that may be a Facade or not, but the UI shouldn't care) and 
with the interfaces exposed by the model instances.



>that commit would do the following:
>
>- store the modified acls for folder
>
>- use folder.path + [new_folder.getComponentName()] as imapFolderPath
>  in this case: 'INBOX.test.sub01'
>  and create the subfolder in the imap-server and set's acls like defined
>
>
>for the ACL set/get it's fairly easy to fetch them at <dm>._load() time
>and store them when _save() is called.
>
>how should this be done for subfolders ??

You lost me here.  Folders are folders, aren't they?  What's the difference?

If you mean, "how can I ensure that a folder's parent is created before it 
is created", then what you do is in your _new() method, you call 
'self.oidFor(ob.parent)' to ensure that the parent gets an oid assigned 
(presumably, by adding *its* parent's oid to its name part).  Something like:

def _new(self,ob)

     ob._p_oid = '%s.%s' % (self.oidFor(ob.parentFolder), ob.name)

     # do whatever's needed to save the new folder
     # possibly including a call to self._save(ob) if its code is reusable...

     return ob._p_oid

Anyway, the first line there will create an oid that's the full folder 
path.  In practice, you may want a folderPath attribute in your object 
model; if so, you can always use that in your _new() method rather than 
recalculating it, but be sure to call the 'oidFor()' check if there's a 
chance that the folder's parent hasn't been saved yet.

Note that the way that an EntityDM determines whether an object exists in 
external storage is by whether it has a non-None _p_oid.  If you assign the 
oid ahead of time, _new() is *never* called for that object.  _new()'s job 
is to assign the oid and ensure the object exists in external storage.




More information about the PEAK mailing list