/
field.py
296 lines (201 loc) · 8.15 KB
/
field.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
"""This module defines persistent versions of various fields.
The idea is that when a record is created, we copy relevant field properties
from the transient schema field from zope.schema, into the corresponding
persistent field. Not all field types are supported, but the common types
are.
"""
from persistent import Persistent
from plone.registry.interfaces import IPersistentField
from zope.interface import implementer
from zope.schema.interfaces import IContextSourceBinder
from zope.schema.vocabulary import SimpleVocabulary
import zope.schema
import zope.schema._field
try:
import plone.schema
HASPLONESCHEMA = True
except ImportError:
HASPLONESCHEMA = False
_primitives = (int, bool, str, bytes, tuple, list, set, frozenset, dict, float)
_missing_value_marker = object()
def is_primitive(value):
return value is None or isinstance(value, _primitives)
class DisallowedProperty:
"""A property that may not be set on an instance. It may still be set
defined in a base class.
"""
uses = []
def __init__(self, name):
self._name = name
DisallowedProperty.uses.append(name)
def __get__(self, inst, type_=None):
# look for the object in bases
if type_ is not None:
for c in type_.__mro__:
if self._name in c.__dict__ and not isinstance(
c.__dict__[self._name], DisallowedProperty
):
function = c.__dict__[self._name]
return function.__get__(inst, type_)
raise AttributeError(self._name)
def __set__(self, inst, value):
raise ValueError(
"Persistent fields does not support setting the `{}` "
"property".format(self._name)
)
class StubbornProperty:
"""A property that stays stubbornly at a single, pre-defined value."""
uses = []
def __init__(self, name, value):
StubbornProperty.uses.append(name)
self._name = name
self._value = value
def __set__(self, inst, value):
pass
def __get__(self, inst, type_=None):
return self._value
class InterfaceConstrainedProperty:
"""A property that may only contain values providing a certain interface."""
uses = []
def __init__(self, name, interface):
InterfaceConstrainedProperty.uses.append((name, interface))
self._name = name
self._interface = interface
def __set__(self, inst, value):
if value != inst.missing_value and not self._interface.providedBy(value):
raise ValueError(
"The property `{}` may only contain objects "
"providing `{}`.".format(
self._name,
self._interface.__identifier__,
)
)
inst.__dict__[self._name] = value
@implementer(IPersistentField)
class PersistentField(Persistent):
"""Base class for persistent field definitions."""
# Persistent fields do not have an order
order = StubbornProperty("order", -1)
# We don't allow setting a custom constraint, as this would introduce a
# dependency on a symbol such as a function that may go away
constraint = DisallowedProperty("constraint")
# Details about which interface/field name we originally came form, if any
interfaceName = None
fieldName = None
class PersistentCollectionField(PersistentField, zope.schema._field.AbstractCollection):
"""Ensure that value_type is a persistent field"""
value_type = InterfaceConstrainedProperty("value_type", IPersistentField)
class Bytes(PersistentField, zope.schema.Bytes):
pass
class BytesLine(PersistentField, zope.schema.BytesLine):
pass
class ASCII(PersistentField, zope.schema.ASCII):
pass
class ASCIILine(PersistentField, zope.schema.ASCIILine):
pass
class Text(PersistentField, zope.schema.Text):
pass
class TextLine(PersistentField, zope.schema.TextLine):
pass
class Bool(PersistentField, zope.schema.Bool):
pass
class Int(PersistentField, zope.schema.Int):
pass
class Float(PersistentField, zope.schema.Float):
pass
class Decimal(PersistentField, zope.schema.Decimal):
pass
class Tuple(PersistentCollectionField, zope.schema.Tuple):
pass
class List(PersistentCollectionField, zope.schema.List):
pass
class Set(PersistentCollectionField, zope.schema.Set):
pass
class FrozenSet(PersistentCollectionField, zope.schema.FrozenSet):
pass
class Password(PersistentField, zope.schema.Password):
pass
class Dict(PersistentField, zope.schema.Dict):
key_type = InterfaceConstrainedProperty("key_type", IPersistentField)
value_type = InterfaceConstrainedProperty("value_type", IPersistentField)
class Datetime(PersistentField, zope.schema.Datetime):
pass
class Date(PersistentField, zope.schema.Date):
pass
class Timedelta(PersistentField, zope.schema.Timedelta):
pass
class SourceText(PersistentField, zope.schema.SourceText):
pass
class URI(PersistentField, zope.schema.URI):
pass
class Id(PersistentField, zope.schema.Id):
pass
class DottedName(PersistentField, zope.schema.DottedName):
pass
class Choice(PersistentField, zope.schema.Choice):
# We can only support string name or primitive=list vocabularies
_values = None
_vocabulary = None
def __init__(self, values=None, vocabulary=None, source=None, **kw):
if vocabulary is not None and not isinstance(vocabulary, str):
values = self._normalized_values(vocabulary)
if values is None:
raise ValueError(
"Persistent fields only support named vocabularies or "
"vocabularies based on simple value sets."
)
vocabulary = None
elif source is not None:
raise ValueError(
"Persistent fields do not support sources, only named "
"vocabularies or vocabularies based on simple value sets."
)
assert not (
values is None and vocabulary is None
), "You must specify either values or vocabulary."
assert (
values is None or vocabulary is None
), "You cannot specify both values and vocabulary."
self.vocabularyName = None
if values is not None:
for value in values:
if not is_primitive(value):
raise ValueError(
"Vocabulary values may only contain primitive values."
)
self._values = values
else:
self.vocabularyName = vocabulary
self._init_field = bool(self.vocabularyName)
super(zope.schema.Choice, self).__init__(**kw)
self._init_field = False
def _normalized_values(self, vocabulary):
if getattr(vocabulary, "__iter__", None):
if all([isinstance(term.value, str) for term in vocabulary]):
return [term.value for term in vocabulary]
return None
@property
def vocabulary(self):
# may be set by bind()
if self._vocabulary is not None:
return self._vocabulary
if self._values is not None:
return SimpleVocabulary.fromValues(self._values)
DisallowedProperty.uses.append("vocabulary")
# override bind to allow us to keep constraints on the 'vocabulary'
# property
def bind(self, object):
clone = zope.schema.Field.bind(self, object)
# get registered vocabulary if needed:
if IContextSourceBinder.providedBy(self.vocabulary):
clone._vocabulary = self.vocabulary(object)
assert zope.schema.interfaces.ISource.providedBy(clone.vocabulary)
elif clone.vocabulary is None and self.vocabularyName is not None:
vr = zope.schema.vocabulary.getVocabularyRegistry()
clone._vocabulary = vr.get(object, self.vocabularyName)
assert zope.schema.interfaces.ISource.providedBy(clone.vocabulary)
return clone
if HASPLONESCHEMA:
class JSONField(PersistentField, plone.schema.JSONField):
key_type = InterfaceConstrainedProperty("key_type", IPersistentField)
value_type = InterfaceConstrainedProperty("value_type", IPersistentField)