-
Notifications
You must be signed in to change notification settings - Fork 4
/
persistent.py
153 lines (128 loc) · 5.72 KB
/
persistent.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""KeyReference for persistent objects.
Provides an IKeyReference adapter for persistent objects.
"""
import zope.interface
from ZODB.ConflictResolution import PersistentReference
from ZODB.interfaces import IConnection
import zope.keyreference.interfaces
@zope.interface.implementer(zope.keyreference.interfaces.IKeyReference)
class KeyReferenceToPersistent:
"""An IKeyReference for persistent objects which is comparable.
These references compare by database name and _p_oids of the objects they
reference.
"""
key_type_id = 'zope.app.keyreference.persistent'
def __init__(self, object):
if not getattr(object, '_p_oid', None):
connection = IConnection(object, None)
if connection is None:
raise zope.keyreference.interfaces.NotYet(object)
connection.add(object)
self.object = object
def __call__(self):
return self.object
def __hash__(self):
if isinstance(self.object, PersistentReference):
# we are doing conflict resolution.
database_name = self.object.database_name
if database_name is None:
# we can't hash
raise ValueError('database name unavailable at this time')
oid = self.object.oid
else:
database_name = self.object._p_jar.db().database_name
oid = self.object._p_oid
return hash((database_name, oid))
def _get_cmp_keys(self, other):
if self.key_type_id == other.key_type_id:
# While it makes subclassing this class inconvenient,
# comparing the object's type is faster than doing an
# isinstance check. The intent of using type instead
# of isinstance is to avoid loading state just to
# determine if we're in conflict resolution.
if isinstance(self.object, PersistentReference):
# We are doing conflict resolution.
assert isinstance(other.object, PersistentReference), (
'other object claims to be '
'zope.app.keyreference.persistent but, during conflict '
'resolution, object is not a PersistentReference')
self_name = self.object.database_name
other_name = other.object.database_name
if (self_name is None) ^ (other_name is None):
# one of the two database_names are None during conflict
# resolution. At this time the database_name is
# inaccessible, not unset (it is the same database as the
# object being resolved). If they were both None, we
# would know they are from the same database, so we can
# compare the oids. If neither were None, we would be
# able to reliably compare. However, in this case,
# one is None and the other is not, so we can't know how
# they would sort outside of conflict resolution. Give
# up.
raise ValueError('cannot sort reliably')
self_oid = self.object.oid
other_oid = other.object.oid
else:
self_name = self.object._p_jar.db().database_name
self_oid = self.object._p_oid
other_name = other.object._p_jar.db().database_name
other_oid = other.object._p_oid
return (self_name, self_oid), (other_name, other_oid)
return self.key_type_id, other.key_type_id
def __eq__(self, other):
a, b = self._get_cmp_keys(other)
return a == b
def __lt__(self, other):
a, b = self._get_cmp_keys(other)
return a < b
def __ne__(self, other):
a, b = self._get_cmp_keys(other)
return a != b
def __gt__(self, other):
a, b = self._get_cmp_keys(other)
return a > b
def __le__(self, other):
a, b = self._get_cmp_keys(other)
return a <= b
def __ge__(self, other):
a, b = self._get_cmp_keys(other)
return a >= b
@zope.interface.implementer(IConnection)
def connectionOfPersistent(ob):
"""An adapter which gets a ZODB connection of a persistent object.
We are assuming the object has a parent if it has been created in
this transaction.
Raises ValueError if it is impossible to get a connection.
"""
cur = ob
while not getattr(cur, '_p_jar', None):
cur = getattr(cur, '__parent__', None)
if cur is None:
return None
return cur._p_jar
# BBB: If zope.app.keyreference is not installed, we still want
# old key references to be available. So fake a module to make
# them unpickleable.
try:
import zope.app.keyreference
except ImportError:
import sys
from types import ModuleType as module
z_a_k = module('zope.app.keyreference')
sys.modules['zope.app.keyreference'] = z_a_k
z_a_k_p = module('zope.app.keyreference.persistent')
z_a_k_p.KeyReferenceToPersistent = KeyReferenceToPersistent
sys.modules['zope.app.keyreference.persistent'] = z_a_k_p