[Zope-dev] ANNOUNCE: __getattr__ for persistent objects!

Phillip J. Eby pje@telecommunity.com
Thu, 30 Dec 1999 13:39:36 -0500


Announcing the PersistWithGetattr.DynPersist class! (the name really needs
some work...) Source code at:

http://telecommunity.com/pkg/PersistWithGetattr.c

This module provides you with a mixin class that can be used to implement a
__getattr__-alternative in Persistent objects.  Example code:

from ZODB.PersistWithGetattr import DynPersist
from Persistence import Persistent
from Acquisition import Implicit

class foo(DynPersist,Persistent,Implicit):
	def __get_attr__(self,name):
		if name[:4]=='foo_' and name[-9:]!='__roles__':
			return name[4:]			
		raise AttributeError,name


Note that your __getattr__ must actually be called __get_attr__, and that
DynPersist must appear *before* Persistent in your list of base classes.
If you are subclassing from another Zope framework class that is already
Persistent, just put DynPersist ahead of it in your base class list.

The class is implemented in about 100 lines of C code; basically all it
does is insert a hook between the normal persistence machinery and the
normal ExtensionClass getattr machinery which will look for a __get_attr__
routine.  This hooking only takes place for objects which subclass
DynPersist, so there is no performance impact on "standard" persistent
objects.  You should only subclass from DynPersist if your objects have a
__get_attr__ method, since DynPersist will only add useless overhead if you
do not have one.

Build/install instructions:  Just add the following line to the
Zope2/lib/python/ZODB/Setup file:

PersistWithGetattr PersistWithGetattr.c   -I../../Components/ExtensionClass

And then rebuild Zope.

I wrote this code last night, and have done a good bit of testing since,
including Ty and I doing an experimental modification to TinyTables to give
them a __get_attr__ which lets you access rows as though they were
attributes.  (This was just for testing and there are currently no plans to
release this as a TinyTable feature.)  We were able to make changes to
TinyTable instances, load data, modify data, and so on, and the changes
persisted in the database, and the __get_attr__ hook worked.  Changes, and
the instances themselves survived multiple server restarts.

Our testing did show that defining a useful "__get_attr__" routine for use
in the Zope framework can be tough, however, as Zope itself and the
acquisition machinery can ask objects for things that seem surprising at
first.  Unlike a "normal" Python object, where attribute requests are
almost always going to be "reasonable" (i.e., things an object would
expect/intend to have available), Acquisition will pipe all sorts of
attribute requests into your object if you're not careful.  And ZPublisher
will ask for things like index_html, __doc__, __roles__, name__doc__,
name__roles__, and so on and so forth.

Our first few attempts at a working __get_attr__ led to some object
breakage because we didn't write a __getattr__ that would handle things
properly.  Here are a couple of tips (which are illustrated by the example
above):

* Always validate the attribute name against a known list or naming
pattern!  If the nature of your need is that it must make up things with
'any' name, then at least check whether the name ends in __doc__ or
__roles__ (or is 'index_html', '__bobo_traverse__' or another
ZPublisher-special name) to help you decide what to do with it.  Note that
if you are validating against a known list of names (such as using a
database or dictionary of some kind), you do not need to check for special
names, since they will (presumably) not be in your list.

* Always "raise AttributeError,name" if you do not want Zope to think the
requested attribute exists.  This is important.  Once we forgot to do this,
and it broke the TinyTable instance we created, because it wound up with an
ID of "None". (__get_attr__ apparently returned None when the code fell
"off the end" of the method).