- A Simple Example
- Detecting Changes
- A Complete Example
- Conclusion
- ZODB Resources
Detecting Changes
One thing that makes ZODB so easy to use is that it doesn't require you to keep track of your changes. All you have to do is make changes to persistent objects and then commit a transaction. Anything that has changed will be stored in the database.
There is one exception to this rule when it comes to simple mutable Python types, such as lists and dictionaries. If you change a list or dictionary that is already stored in the database, then the change will not take effect. Consider this example:
>>> root['employees'].append('Bill') >>> get_transaction().commit()
You would expect this to work, but it doesn't. The reason for this is that ZODB cannot detect that the employees list changed. The employees list is a mutable object that does not notify ZODB when it changes.
Reassigning the Changed Object
There are some very simple ways around this problem. The simplest is to re-assign the changed object:
>>> employees = root['employees'] >>> employees.append('Bill') >>> root['employees'] = employees >>> get_transaction().commit()
Here, you move the employees list to a local variable, change the list, and then re-assign the list back into the database and commit the transaction. This reassignment notifies the database that the list changed and needs to be saved to the database.
In the next section, we'll show you another technique for notifying the ZODB that your objects have changed. Also, in another article, we'll show you how to use simple, ZODB-aware list and dictionary classes that come pre-packaged with ZODB for your convenience.
Persistent Classes
The easiest way to create mutable objects that notify the ZODB of changes is to create a persistent class. Persistent classes let you store your own kinds of objects in the database. For example, consider a class that represents an employee:
import ZODB from Persistence import Persistent class Employee(Persistent): def setName(self, name): self.name = name
To create a persistent class, simply subclass from Persistent.Persistent. Because of some special magic that ZODB does, you must first import ZODB before you can import Persistent. The Persistent module is actually created when you import ZODB.
Now, you can put employee objects in your database:
>>> employees=[] >>> for name in ['Mary', 'Joe', 'Bob']: ... employee = Employee() ... employee.setName(name) ... employees.append(employee) >>> root['employees']=employees >>> get_transaction().commit()
Don't forget to call commit() so that the changes you have made so far are committed to the database, and a new transaction is begun.
Now you can change your employees and they will be saved in the database. For example, you can change Bob's name to Robert:
>>> bob=root['employees'][2] >>> bob.setName('Robert') >>> get_transaction().commit()
You can even change attributes of persistent instances without calling methods:
>>> bob=root['employees'][2] >>> bob._coffee_prefs=('Cream', 'Sugar') >>> get_transaction().commit()
It doesn't matter whether you change an attribute directly, or whether it's changed by a method. As you can tell, all of the normal Python language rules still work as you'd expect.
Mutable Attributes
Earlier you saw how ZODB can't detect changes to normal mutable objects, such as Python lists. This issue still affects you when using persistent instances. This is because persistent instances can have attributes that are normal mutable objects. For example, consider this class:
class Employee(Persistent): def __init__(self): self.tasks = [] def setName(self, name): self.name = name def addTask(self, task): self.task.append(task)
When you call addTask, the ZODB will not know that the mutable attribute self.tasks has changed. As you saw earlier, you can re-assign self.tasks after you change it to get around this problem. However, when you're using persistent instances, you have another choice. You can signal the ZODB that your instance has changed with the _p_changed attribute:
class Employee(Persistent): ... def addTask(self, task): self.task.append(task) self._p_changed = 1
To signal that this object has changed, set the _p_changed attribute to 1. You only need to signal ZODB once, even if you change many mutable attributes.
The _p_changed flag leads us to one of the few rules of you must follow when creating persistent classes: your instances cannot have attributes that begin with _p_; those names are reserved for use by the ZODB.