Customizing Attribute Access

Hooking __getattr__()

The __getattr__ method works pretty much the same for persistent classes as it does for other classes. No special handling is needed. If an object is a ghost, then it will be activated before __getattr__ is called.

In this example, our objects returns a tuple with the attribute name, converted to upper case and the value of _p_changed, for any attribute that isn’t handled by the default machinery.

>>> from persistent.tests.attrhooks import OverridesGetattr
>>> o = OverridesGetattr()
>>> o._p_changed
False
>>> o._p_oid
>>> o._p_jar
>>> o.spam
('SPAM', False)
>>> o.spam = 1
>>> o.spam
1

We’ll save the object, so it can be deactivated:

>>> from persistent.tests.attrhooks import _resettingJar
>>> jar = _resettingJar()
>>> jar.add(o)
>>> o._p_deactivate()
>>> o._p_changed

And now, if we ask for an attribute it doesn’t have,

>>> o.eggs
('EGGS', False)

And we see that the object was activated before calling the __getattr__() method.

Hooking All Access

In this example, we’ll provide an example that shows how to override the __getattribute__(), __setattr__(), and __delattr__() methods. We’ll create a class that stores it’s attributes in a secret dictionary within the instance dictionary.

The class will have the policy that variables with names starting with tmp_ will be volatile.

Our sample class takes initial values as keyword arguments to the constructor:

>>> from persistent.tests.attrhooks import VeryPrivate
>>> o = VeryPrivate(x=1)

Hooking __getattribute__`()

The __getattribute__() method is called for all attribute accesses. It overrides the attribute access support inherited from Persistent.

>>> o._p_changed
False
>>> o._p_oid
>>> o._p_jar
>>> o.x
1
>>> o.y
Traceback (most recent call last):
...
AttributeError: y

Next, we’ll save the object in a database so that we can deactivate it:

>>> from persistent.tests.attrhooks import _rememberingJar
>>> jar = _rememberingJar()
>>> jar.add(o)
>>> o._p_deactivate()
>>> o._p_changed

And we’ll get some data:

>>> o.x
1

which activates the object:

>>> o._p_changed
False

It works for missing attributes too:

>>> o._p_deactivate()
>>> o._p_changed

>>> o.y
Traceback (most recent call last):
...
AttributeError: y

>>> o._p_changed
False

Hooking __setattr__`()

The __setattr__() method is called for all attribute assignments. It overrides the attribute assignment support inherited from Persistent.

Implementors of __setattr__() methods:

  1. Must call Persistent._p_setattr first to allow it to handle some attributes and to make sure that the object is activated if necessary, and

  2. Must set _p_changed to mark objects as changed.

>>> o = VeryPrivate()
>>> o._p_changed
False
>>> o._p_oid
>>> o._p_jar
>>> o.x
Traceback (most recent call last):
...
AttributeError: x

>>> o.x = 1
>>> o.x
1

Because the implementation doesn’t store attributes directly in the instance dictionary, we don’t have a key for the attribute:

>>> 'x' in o.__dict__
False

Next, we’ll give the object a “remembering” jar so we can deactivate it:

>>> jar = _rememberingJar()
>>> jar.add(o)
>>> o._p_deactivate()
>>> o._p_changed

We’ll modify an attribute

>>> o.y = 2
>>> o.y
2

which reactivates it, and marks it as modified, because our implementation marked it as modified:

>>> o._p_changed
True

Now, if fake a commit:

>>> jar.fake_commit()
>>> o._p_changed
False

And deactivate the object:

>>> o._p_deactivate()
>>> o._p_changed

and then set a variable with a name starting with tmp_, The object will be activated, but not marked as modified, because our __setattr__() implementation doesn’t mark the object as changed if the name starts with tmp_:

>>> o.tmp_foo = 3
>>> o._p_changed
False
>>> o.tmp_foo
3

Hooking __delattr__`()

The __delattr__ method is called for all attribute deletions. It overrides the attribute deletion support inherited from Persistent.

Implementors of __delattr__() methods:

  1. Must call Persistent._p_delattr first to allow it to handle some attributes and to make sure that the object is activated if necessary, and

  2. Must set _p_changed to mark objects as changed.

>>> o = VeryPrivate(x=1, y=2, tmp_z=3)
>>> o._p_changed
False
>>> o._p_oid
>>> o._p_jar
>>> o.x
1
>>> del o.x
>>> o.x
Traceback (most recent call last):
...
AttributeError: x

Next, we’ll save the object in a jar so that we can deactivate it:

>>> jar = _rememberingJar()
>>> jar.add(o)
>>> o._p_deactivate()
>>> o._p_changed

If we delete an attribute:

>>> del o.y

The object is activated. It is also marked as changed because our implementation marked it as changed.

>>> o._p_changed
True
>>> o.y
Traceback (most recent call last):
...
AttributeError: y

>>> o.tmp_z
3

Now, if fake a commit:

>>> jar.fake_commit()
>>> o._p_changed
False

And deactivate the object:

>>> o._p_deactivate()
>>> o._p_changed

and then delete a variable with a name starting with tmp_, The object will be activated, but not marked as modified, because our __delattr__() implementation doesn’t mark the object as changed if the name starts with tmp_:

>>> del o.tmp_z
>>> o._p_changed
False
>>> o.tmp_z
Traceback (most recent call last):
...
AttributeError: tmp_z

If we attempt to delete _p_oid, we find that we can’t, and the object is also not activated or changed:

>>> del o._p_oid
Traceback (most recent call last):
...
ValueError: can't delete _p_oid of cached object
>>> o._p_changed
False

We are allowed to delete _p_changed, which sets it to None:

>>> del o._p_changed
>>> o._p_changed is None
True