~landscape/zope3/ztk-1.1.3

« back to all changes in this revision

Viewing changes to src/zope/keyreference/persistent.txt

  • Committer: Sidnei da Silva
  • Date: 2010-07-05 21:07:01 UTC
  • Revision ID: sidnei.da.silva@canonical.com-20100705210701-zmqhqrbzad1mhzsl
- Reduce deps

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
=====================================
2
 
Key References for Persistent Objects
3
 
=====================================
4
 
 
5
 
`zope.keyreference.persistent.KeyReferenceToPersistent` provides an
6
 
`zope.keyreference.interfaces.IKeyReference` reference for persistent
7
 
objects.
8
 
 
9
 
Let's look at an example. First, we'll create some persistent objects
10
 
in a database:
11
 
 
12
 
    >>> from ZODB.tests.util import DB
13
 
    >>> import transaction
14
 
    >>> from persistent.dict import PersistentDict
15
 
 
16
 
    >>> db = DB()
17
 
    >>> conn = db.open()
18
 
    >>> root = conn.root()
19
 
 
20
 
    >>> root['ob1'] = PersistentDict()
21
 
    >>> root['ob2'] = PersistentDict()
22
 
 
23
 
    >>> transaction.commit()
24
 
 
25
 
Then we'll create some key references:
26
 
 
27
 
    >>> from zope.keyreference.persistent import KeyReferenceToPersistent
28
 
 
29
 
    >>> key1 = KeyReferenceToPersistent(root['ob1'])
30
 
    >>> key2 = KeyReferenceToPersistent(root['ob2'])
31
 
 
32
 
We can call the keys to get the objects:
33
 
 
34
 
    >>> key1() is root['ob1'], key2() is root['ob2']
35
 
    (True, True)
36
 
 
37
 
New keys to the same objects are equal to the old:
38
 
 
39
 
    >>> KeyReferenceToPersistent(root['ob1']) == key1
40
 
    True
41
 
 
42
 
and have the same hashes:
43
 
 
44
 
    >>> hash(KeyReferenceToPersistent(root['ob1'])) == hash(key1)
45
 
    True
46
 
 
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:
50
 
 
51
 
    >>> from zope.interface import implements
52
 
    >>> from zope.keyreference.interfaces import IKeyReference
53
 
 
54
 
    >>> class DummyKeyReference(object):
55
 
    ...     implements(IKeyReference)
56
 
    ...     key_type_id = 'zope.app.keyreference.object'
57
 
    ...     def __init__(self, obj):
58
 
    ...         self.object = 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)
63
 
 
64
 
    >>> dummy_key1 = DummyKeyReference(object())
65
 
    >>> dummy_key2 = DummyKeyReference(object())
66
 
    >>> dummy_key3 = DummyKeyReference(object())
67
 
 
68
 
    >>> keys = [key1, dummy_key1, dummy_key2, key2, dummy_key3]
69
 
    >>> keys.sort()
70
 
    >>> key_type_ids = [key.key_type_id for key in keys]
71
 
    >>> key_type_ids[0:3].count('zope.app.keyreference.object')
72
 
    3
73
 
    >>> key_type_ids[3:].count('zope.app.keyreference.persistent')
74
 
    2
75
 
 
76
 
We'll store the key references in the database:
77
 
 
78
 
    >>> root['key1'] = key1
79
 
    >>> root['key2'] = key2
80
 
 
81
 
and use the keys to store the objects again:
82
 
 
83
 
    >>> root[key1] = root['ob1']
84
 
    >>> root[key2] = root['ob2']
85
 
 
86
 
    >>> transaction.commit()
87
 
 
88
 
Now we'll open another connection:
89
 
 
90
 
    >>> conn2 = db.open()
91
 
 
92
 
And verify that we can use the keys to look up the objects:
93
 
 
94
 
    >>> root2 = conn2.root()
95
 
    >>> key1 = root2['key1']
96
 
    >>> root2[key1] is root2['ob1']
97
 
    True
98
 
    >>> key2 = root2['key2']
99
 
    >>> root2[key2] is root2['ob2']
100
 
    True
101
 
 
102
 
and that we can also call the keys to get the objects:
103
 
 
104
 
    >>> key1() is root2['ob1']
105
 
    True
106
 
    >>> key2() is root2['ob2']
107
 
    True
108
 
 
109
 
We can't get the key reference for an object that hasn't been saved
110
 
yet:
111
 
 
112
 
    >>> KeyReferenceToPersistent(PersistentDict())
113
 
    ... # doctest: +ELLIPSIS
114
 
    Traceback (most recent call last):
115
 
    ...
116
 
    NotYet: ...
117
 
 
118
 
Note that we get a NotYet error. This indicates that we might be able
119
 
to get a key reference later.
120
 
 
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:
126
 
 
127
 
    >>> import persistent
128
 
    >>> from ZODB.interfaces import IConnection
129
 
    >>> class C(persistent.Persistent):
130
 
    ...     def __conform__(self, iface):
131
 
    ...         if iface is IConnection:
132
 
    ...             return conn2
133
 
 
134
 
    >>> ob3 = C()
135
 
    >>> key3 = KeyReferenceToPersistent(ob3)
136
 
    >>> transaction.abort()
137
 
 
138
 
Conflict Resolution
139
 
-------------------
140
 
 
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.
147
 
 
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.
155
 
 
156
 
__hash__ will work relatively rarely during conflict resolution: only for
157
 
multidatabase references.  Here are a couple of examples.
158
 
 
159
 
    >>> from ZODB.ConflictResolution import PersistentReference
160
 
 
161
 
    >>> def factory(ref):
162
 
    ...     res = KeyReferenceToPersistent.__new__(
163
 
    ...         KeyReferenceToPersistent, ref)
164
 
    ...     res.object = ref
165
 
    ...     return res
166
 
    ...
167
 
 
168
 
    >>> hash(factory(PersistentReference(
169
 
    ...     ('an oid', 'class metadata')))) # a typical reference
170
 
    Traceback (most recent call last):
171
 
    ...
172
 
    ValueError: database name unavailable at this time
173
 
 
174
 
    >>> bool(hash(factory(PersistentReference(
175
 
    ...     ['m', ('a database', 'an oid', 'class metadata')])))) # multidatabase
176
 
    True
177
 
 
178
 
This means that KeyReferenceToPersistent will often hinder conflict resolution
179
 
for classes such as PersistentDict.
180
 
 
181
 
__cmp__ works unless one object is a multidatabase reference and the other is
182
 
not.  Here are a few examples.
183
 
 
184
 
    >>> cmp(factory(PersistentReference(
185
 
    ...         ('an oid', 'class metadata'))),
186
 
    ...     factory(PersistentReference(
187
 
    ...         ('an oid', 'class metadata'))))
188
 
    0
189
 
 
190
 
    >>> cmp(factory(PersistentReference(
191
 
    ...         ('an oid', 'class metadata'))),
192
 
    ...     factory(PersistentReference(
193
 
    ...         ('another oid', 'class metadata'))))
194
 
    -1
195
 
 
196
 
    >>> cmp(factory(PersistentReference('an oid')),
197
 
    ...     factory(PersistentReference(
198
 
    ...         ('an oid', 'class metadata'))))
199
 
    0
200
 
 
201
 
    >>> cmp(factory(PersistentReference('an oid')),
202
 
    ...     factory(PersistentReference(
203
 
    ...         ('an oid', 'class metadata'))))
204
 
    0
205
 
 
206
 
    >>> cmp(factory(PersistentReference(
207
 
    ...         ['m', ('a database', 'an oid', 'class metadata')])),
208
 
    ...     factory(PersistentReference(
209
 
    ...         ['m', ('a database', 'an oid', 'class metadata')])))
210
 
    0
211
 
 
212
 
    >>> cmp(factory(PersistentReference(
213
 
    ...         ['m', ('a database', 'an oid', 'class metadata')])),
214
 
    ...     factory(PersistentReference(
215
 
    ...         ['n', ('a database', 'an oid')])))
216
 
    0
217
 
 
218
 
    >>> cmp(factory(PersistentReference(
219
 
    ...         ['m', ('a database', 'an oid', 'class metadata')])),
220
 
    ...     factory(PersistentReference(
221
 
    ...         ['m', ('another database', 'an oid', 'class metadata')])))
222
 
    -1
223
 
 
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):
229
 
    ...
230
 
    ValueError: cannot sort reliably
231
 
 
232
 
Location-based connection adapter
233
 
---------------------------------
234
 
 
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:
238
 
 
239
 
    >>> from zope.keyreference.persistent import connectionOfPersistent
240
 
    >>> ob3 = PersistentDict()
241
 
    >>> print connectionOfPersistent(ob3)
242
 
    None
243
 
 
244
 
    >>> ob3.__parent__ = root2['ob1']
245
 
    >>> connectionOfPersistent(ob3) is conn2
246
 
    True