1
=====================================
2
Key References for Persistent Objects
3
=====================================
5
`zope.keyreference.persistent.KeyReferenceToPersistent` provides an
6
`zope.keyreference.interfaces.IKeyReference` reference for persistent
9
Let's look at an example. First, we'll create some persistent objects
12
>>> from ZODB.tests.util import DB
13
>>> import transaction
14
>>> from persistent.dict import PersistentDict
18
>>> root = conn.root()
20
>>> root['ob1'] = PersistentDict()
21
>>> root['ob2'] = PersistentDict()
23
>>> transaction.commit()
25
Then we'll create some key references:
27
>>> from zope.keyreference.persistent import KeyReferenceToPersistent
29
>>> key1 = KeyReferenceToPersistent(root['ob1'])
30
>>> key2 = KeyReferenceToPersistent(root['ob2'])
32
We can call the keys to get the objects:
34
>>> key1() is root['ob1'], key2() is root['ob2']
37
New keys to the same objects are equal to the old:
39
>>> KeyReferenceToPersistent(root['ob1']) == key1
42
and have the same hashes:
44
>>> hash(KeyReferenceToPersistent(root['ob1'])) == hash(key1)
47
Other key reference implementations are differed by their key type id.
48
Key references should sort first on their key type and second on any
49
type-specific information:
51
>>> from zope.interface import implements
52
>>> from zope.keyreference.interfaces import IKeyReference
54
>>> class DummyKeyReference(object):
55
... implements(IKeyReference)
56
... key_type_id = 'zope.app.keyreference.object'
57
... def __init__(self, obj):
59
... def __cmp__(self, other):
60
... if self.key_type_id == other.key_type_id:
61
... return cmp(self.object, other.object)
62
... return cmp(self.key_type_id, other.key_type_id)
64
>>> dummy_key1 = DummyKeyReference(object())
65
>>> dummy_key2 = DummyKeyReference(object())
66
>>> dummy_key3 = DummyKeyReference(object())
68
>>> keys = [key1, dummy_key1, dummy_key2, key2, dummy_key3]
70
>>> key_type_ids = [key.key_type_id for key in keys]
71
>>> key_type_ids[0:3].count('zope.app.keyreference.object')
73
>>> key_type_ids[3:].count('zope.app.keyreference.persistent')
76
We'll store the key references in the database:
78
>>> root['key1'] = key1
79
>>> root['key2'] = key2
81
and use the keys to store the objects again:
83
>>> root[key1] = root['ob1']
84
>>> root[key2] = root['ob2']
86
>>> transaction.commit()
88
Now we'll open another connection:
92
And verify that we can use the keys to look up the objects:
94
>>> root2 = conn2.root()
95
>>> key1 = root2['key1']
96
>>> root2[key1] is root2['ob1']
98
>>> key2 = root2['key2']
99
>>> root2[key2] is root2['ob2']
102
and that we can also call the keys to get the objects:
104
>>> key1() is root2['ob1']
106
>>> key2() is root2['ob2']
109
We can't get the key reference for an object that hasn't been saved
112
>>> KeyReferenceToPersistent(PersistentDict())
113
... # doctest: +ELLIPSIS
114
Traceback (most recent call last):
118
Note that we get a NotYet error. This indicates that we might be able
119
to get a key reference later.
121
We can get references to unsaved objects if they have an adapter to
122
`ZODB.interfaces.IConnection`. The `add` method on the connection
123
will be used to give the object an object id, which is enough
124
information to compute the reference. To see this, we'll create an
125
object that conforms to `IConnection` in a silly way:
127
>>> import persistent
128
>>> from ZODB.interfaces import IConnection
129
>>> class C(persistent.Persistent):
130
... def __conform__(self, iface):
131
... if iface is IConnection:
135
>>> key3 = KeyReferenceToPersistent(ob3)
136
>>> transaction.abort()
141
During conflict resolution, as discussed in ZODB/ConflictResolution.txt,
142
references to persistent objects are actually instances of
143
ZODB.ConflictResolution.PersistentReference. This is pertinent in two ways
144
for KeyReferenceToPersistent. First, it explains a subtlety of the class: it
145
does not inherit from persistent.Persistent. If it did, it would not be
146
available for conflict resolution, just its PersistentReference stand-in.
148
Second, it explains some of the code in the __hash__ and __cmp__
149
methods. These methods not only handle persistent.Persistent objects,
150
but PersistentReference objects. Without this behavior, objects, such
151
as the classic ZODB BTrees, that use KeyReferenceToPersistent as keys or
152
set members will be unable to resolve conflicts. Even with the special
153
code, in some cases the KeyReferenceToPersistent will refuse to compare
154
and hash during conflict resolution because it cannot reliably do so.
156
__hash__ will work relatively rarely during conflict resolution: only for
157
multidatabase references. Here are a couple of examples.
159
>>> from ZODB.ConflictResolution import PersistentReference
161
>>> def factory(ref):
162
... res = KeyReferenceToPersistent.__new__(
163
... KeyReferenceToPersistent, ref)
168
>>> hash(factory(PersistentReference(
169
... ('an oid', 'class metadata')))) # a typical reference
170
Traceback (most recent call last):
172
ValueError: database name unavailable at this time
174
>>> bool(hash(factory(PersistentReference(
175
... ['m', ('a database', 'an oid', 'class metadata')])))) # multidatabase
178
This means that KeyReferenceToPersistent will often hinder conflict resolution
179
for classes such as PersistentDict.
181
__cmp__ works unless one object is a multidatabase reference and the other is
182
not. Here are a few examples.
184
>>> cmp(factory(PersistentReference(
185
... ('an oid', 'class metadata'))),
186
... factory(PersistentReference(
187
... ('an oid', 'class metadata'))))
190
>>> cmp(factory(PersistentReference(
191
... ('an oid', 'class metadata'))),
192
... factory(PersistentReference(
193
... ('another oid', 'class metadata'))))
196
>>> cmp(factory(PersistentReference('an oid')),
197
... factory(PersistentReference(
198
... ('an oid', 'class metadata'))))
201
>>> cmp(factory(PersistentReference('an oid')),
202
... factory(PersistentReference(
203
... ('an oid', 'class metadata'))))
206
>>> cmp(factory(PersistentReference(
207
... ['m', ('a database', 'an oid', 'class metadata')])),
208
... factory(PersistentReference(
209
... ['m', ('a database', 'an oid', 'class metadata')])))
212
>>> cmp(factory(PersistentReference(
213
... ['m', ('a database', 'an oid', 'class metadata')])),
214
... factory(PersistentReference(
215
... ['n', ('a database', 'an oid')])))
218
>>> cmp(factory(PersistentReference(
219
... ['m', ('a database', 'an oid', 'class metadata')])),
220
... factory(PersistentReference(
221
... ['m', ('another database', 'an oid', 'class metadata')])))
224
>>> cmp(factory(PersistentReference(
225
... ['m', ('a database', 'an oid', 'class metadata')])),
226
... factory(PersistentReference(
227
... ('an oid', 'class metadata'))))
228
Traceback (most recent call last):
230
ValueError: cannot sort reliably
232
Location-based connection adapter
233
---------------------------------
235
The function `zope.keyreference.connectionOfPersistent` adapts
236
objects to connections using a simple location-based heuristic. It
237
checked to see if the object has a `__parent__` that has a connection:
239
>>> from zope.keyreference.persistent import connectionOfPersistent
240
>>> ob3 = PersistentDict()
241
>>> print connectionOfPersistent(ob3)
244
>>> ob3.__parent__ = root2['ob1']
245
>>> connectionOfPersistent(ob3) is conn2