2.5.1 Modifying Mutable Objects

The ZODB uses various Python hooks to catch attribute accesses, and can trap most of the ways of modifying an object, but not all of them. If you modify a User object by assigning to one of its attributes, as in userobj.first_name = 'Andrew', the ZODB will mark the object as having been changed, and it'll be written out on the following commit().

The most common idiom that isn't caught by the ZODB is mutating a list or dictionary. If User objects have a attribute named friends containing a list, calling userobj.friends.append(otherUser) doesn't mark userobj as modified; from the ZODB's point of view, userobj.friends was only read, and its value, which happened to be an ordinary Python list, was returned. The ZODB isn't aware that the object returned was subsequently modified.

This is one of the few quirks you'll have to remember when using the ZODB; if you modify a mutable attribute of an object in place, you have to manually mark the object as having been modified by setting its dirty bit to true. This is done by setting the _p_changed attribute of the object to true:

userobj.friends.append(otherUser)
userobj._p_changed = 1

An obsolete way of doing this that's still supported is calling the __changed__() method instead, but setting _p_changed is the preferred way.

You can hide the implementation detail of having to mark objects as dirty by designing your class's API to not use direct attribute access; instead, you can use the Java-style approach of accessor methods for everything, and then set the dirty bit within the accessor method. For example, you might forbid accessing the friends attribute directly, and add a get_friend_list() accessor and an add_friend() modifier method to the class. add_friend() would then look like this:

    def add_friend(self, friend):
        self.friends.append(otherUser)
        self._p_changed = 1

Alternatively, you could use a ZODB-aware list or mapping type that handles the dirty bit for you. The ZODB comes with a PersistentMapping class, and I've contributed a PersistentList class that's included in my ZODB distribution, and may make it into a future upstream release of Zope.