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:
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
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:
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
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