-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
registry.py
312 lines (244 loc) · 9.41 KB
/
registry.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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# -*- coding: utf-8 -*-
from BTrees.OOBTree import OOBTree
from persistent import Persistent
from plone.registry.events import RecordAddedEvent
from plone.registry.events import RecordRemovedEvent
from plone.registry.fieldref import FieldRef
from plone.registry.interfaces import IFieldRef
from plone.registry.interfaces import InvalidRegistryKey
from plone.registry.interfaces import IPersistentField
from plone.registry.interfaces import IRecord
from plone.registry.interfaces import IRegistry
from plone.registry.record import Record
from plone.registry.recordsproxy import RecordsProxy
from plone.registry.recordsproxy import RecordsProxyCollection
from zope.component import queryAdapter
from zope.event import notify
from zope.interface import implementer
from zope.schema import getFieldNames
from zope.schema import getFieldsInOrder
import re
import warnings
import sys
if sys.version_info >= (3,):
basestring = str
@implementer(IRegistry)
class Registry(Persistent):
"""The persistent registry
"""
def __init__(self):
self._records = _Records(self)
# Basic value access API
def __getitem__(self, name):
# Fetch straight from records._values to avoid loading the field
# as a separate persistent object
return self.records._values[name]
def get(self, name, default=None):
# Fetch straight from records._values to avoid loading the field
# as a separate persistent object
return self.records._values.get(name, default)
def __setitem__(self, name, value):
# make sure we get the Record class' validation
self.records[name].value = value
def __contains__(self, name):
return name in self.records._values
# Records - make this a property so that it's readonly
@property
def records(self):
# XXX: On-the-fly migration
if isinstance(self._records, Records):
self._migrateRecords()
return self._records
# Schema interface API
def forInterface(self, interface, check=True, omit=(), prefix=None,
factory=None):
if prefix is None:
prefix = interface.__identifier__
if not prefix.endswith("."):
prefix += '.'
if check:
for name in getFieldNames(interface):
if name not in omit and prefix + name not in self:
raise KeyError(
"Interface `{0}` defines a field `{1}`, for which "
"there is no record.".format(
interface.__identifier__,
name
)
)
if factory is None:
factory = RecordsProxy
return factory(self, interface, omitted=omit, prefix=prefix)
def registerInterface(self, interface, omit=(), prefix=None):
if prefix is None:
prefix = interface.__identifier__
if not prefix.endswith("."):
prefix += '.'
for name, field in getFieldsInOrder(interface):
if name in omit or field.readonly:
continue
record_name = prefix + name
persistent_field = queryAdapter(field, IPersistentField)
if persistent_field is None:
raise TypeError(
"There is no persistent field equivalent for the field "
"`{0}` of type `{1}`.".format(
name,
field.__class__.__name__
)
)
persistent_field.interfaceName = interface.__identifier__
persistent_field.fieldName = name
value = persistent_field.default
# Attempt to retain the exisiting value
if record_name in self.records:
existing_record = self.records[record_name]
value = existing_record.value
bound_field = persistent_field.bind(existing_record)
try:
bound_field.validate(value)
except:
value = persistent_field.default
self.records[record_name] = Record(
persistent_field,
value,
_validate=False
)
def collectionOfInterface(self, interface, check=True, omit=(),
prefix=None, factory=None):
return RecordsProxyCollection(
self,
interface,
check,
omit,
prefix,
factory
)
# BBB
def _migrateRecords(self):
"""BBB: Migrate from the old Records structure to the new. This is
done the first time the "old" structure is accessed.
"""
records = _Records(self)
oldData = getattr(self._records, 'data', None)
if oldData is not None:
for name, oldRecord in oldData.iteritems():
oldRecord._p_activate()
if (
'field' in oldRecord.__dict__
and 'value' in oldRecord.__dict__
):
records._fields[name] = oldRecord.__dict__['field']
records._values[name] = oldRecord.__dict__['value']
self._records = records
class _Records(object):
"""The records stored in the registry. This implements dict-like access
to records, where as the Registry object implements dict-like read-only
access to values.
"""
__parent__ = None
# Similar to zope.schema._field._isdotted, but allows up to one '/'
_validkey = re.compile(
r"([a-zA-Z][a-zA-Z0-9_-]*)"
r"([.][a-zA-Z][a-zA-Z0-9_-]*)*"
r"([/][a-zA-Z][a-zA-Z0-9_-]*)?"
r"([.][a-zA-Z][a-zA-Z0-9_-]*)*"
# use the whole line
r"$").match
def __init__(self, parent):
self.__parent__ = parent
self._fields = OOBTree()
self._values = OOBTree()
def __setitem__(self, name, record):
if not self._validkey(name):
raise InvalidRegistryKey(record)
if not IRecord.providedBy(record):
raise ValueError("Value must be a record")
self._setField(name, record.field)
self._values[name] = record.value
record.__name__ = name
record.__parent__ = self.__parent__
notify(RecordAddedEvent(record))
def __delitem__(self, name):
record = self[name]
# unbind the record so that it won't attempt to look up values from
# the registry anymore
record.__parent__ = None
del self._fields[name]
del self._values[name]
notify(RecordRemovedEvent(record))
def __getitem__(self, name):
field = self._getField(name)
value = self._values[name]
record = Record(field, value, _validate=False)
record.__name__ = name
record.__parent__ = self.__parent__
return record
def get(self, name, default=None):
try:
return self[name]
except KeyError:
return default
def __nonzero__(self):
return self._values.__nonzero__()
def __len__(self):
return self._values.__len__()
def __iter__(self):
return self._values.__iter__()
def has_key(self, name):
return self._values.__contains__(name)
def __contains__(self, name):
return self._values.__contains__(name)
def keys(self, min=None, max=None):
return self._values.keys(min, max)
def maxKey(self, key=None):
return self._values.maxKey(key)
def minKey(self, key=None):
return self._values.minKey(key)
def values(self, min=None, max=None):
return [self[name] for name in self.keys(min, max)]
def items(self, min=None, max=None):
return [(name, self[name],) for name in self.keys(min, max)]
def setdefault(self, key, value):
if key not in self:
self[key] = value
return self[key]
def clear(self):
self._fields.clear()
self._values.clear()
# Helper methods
def _getField(self, name):
field = self._fields[name]
# Handle field reference pointers
if isinstance(field, basestring):
recordName = field
while isinstance(field, basestring):
recordName = field
field = self._fields[recordName]
field = FieldRef(recordName, field)
return field
def _setField(self, name, field):
if not IPersistentField.providedBy(field):
raise ValueError("The record's field must be an IPersistentField.")
if IFieldRef.providedBy(field):
if field.recordName not in self._fields:
raise ValueError(
"Field reference points to non-existent record"
)
self._fields[name] = field.recordName # a pointer, of sorts
else:
field.__name__ = 'value'
self._fields[name] = field
class Records(_Records, Persistent):
"""BBB: This used to be the class for the _records attribute of the
registry. Having this be a Persistent object was always a bad idea. We
keep it here so that we can migrate to the new structure, but it should
no longer be used.
"""
def __init__(self, parent):
warnings.warn(
"The Records persistent class is deprecated and should not be "
"used.",
DeprecationWarning
)
super(Records, self).__init__(parent)