forked from ipython/ipython
-
Notifications
You must be signed in to change notification settings - Fork 0
/
traitlets.py
322 lines (250 loc) · 6.7 KB
/
traitlets.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
313
314
315
316
317
318
319
320
321
322
"""Traitlets -- a light-weight meta-class free stand-in for Traits.
Traitlet behaviour
==================
- Automatic casting, equivalent to traits.C* classes, e.g. CFloat, CBool etc.
- By default, validation is done by attempting to cast a given value
to the underlying type, e.g. bool for Bool, float for Float etc.
- To set or get a Traitlet value, use the ()-operator. E.g.
>>> b = Bool(False)
>>> b(True)
>>> print b # returns a string representation of the Traitlet
True
>>> print b() # returns the underlying bool object
True
This makes it possible to change values "in-place", unlike an assigment
of the form
>>> c = Bool(False)
>>> c = True
which results in
>>> print type(b), type(c)
<class 'IPython.config.traitlets.Bool'> <type 'bool'>
- Each Traitlet keeps track of its modification state, e.g.
>>> c = Bool(False)
>>> print c.modified
False
>>> c(False)
>>> print c.modified
False
>>> c(True)
>>> print c.modified
True
How to customize Traitlets
==========================
The easiest way to create a new Traitlet is by wrapping an underlying
Python type. This is done by setting the "_type" class attribute. For
example, creating an int-like Traitlet is done as follows:
>>> class MyInt(Traitlet):
... _type = int
>>> i = MyInt(3)
>>> i(4)
>>> print i
4
>>> try:
... i('a')
... except ValidationError:
... pass # this is expected
... else:
... "This should not be reached."
Furthermore, the following methods are provided for finer grained
control of validation and assignment:
- validate(self,value)
Ensure that "value" is valid. If not, raise an exception of any kind
with a suitable error message, which is reported to the user.
- prepare_value(self)
When using the ()-operator to query the underlying Traitlet value,
that value is first passed through prepare_value. For example:
>>> class Eval(Traitlet):
... _type = str
...
... def prepare_value(self):
... return eval(self._value)
>>> x = Eval('1+1')
>>> print x
'1+1'
>>> print x()
2
- __repr__(self)
By default, repr(self._value) is returned. This can be customised
to, for example, first call prepare_value and return the repr of
the resulting object.
"""
import re
import types
class ValidationError(Exception):
pass
class Traitlet(object):
"""Traitlet which knows its modification state.
"""
def __init__(self, value):
"Validate and store the default value. State is 'unmodified'."
self._type = getattr(self,'_type',None)
value = self._parse_validation(value)
self._default_value = value
self.reset()
def reset(self):
self._value = self._default_value
self._changed = False
def validate(self, value):
"Validate the given value."
if self._type is not None:
self._type(value)
def _parse_validation(self, value):
"""Run validation and return a descriptive error if needed.
"""
try:
self.validate(value)
except Exception, e:
err_message = 'Cannot convert "%s" to %s' % \
(value, self.__class__.__name__.lower())
if e.message:
err_message += ': %s' % e.message
raise ValidationError(err_message)
else:
# Cast to appropriate type before storing
if self._type is not None:
value = self._type(value)
return value
def prepare_value(self):
"""Run on value before it is ever returned to the user.
"""
return self._value
def __call__(self,value=None):
"""Query or set value depending on whether `value` is specified.
"""
if value is None:
return self.prepare_value()
self._value = self._parse_validation(value)
self._changed = (self._value != self._default_value)
@property
def modified(self):
"Whether value has changed from original definition."
return self._changed
def __repr__(self):
"""This class is represented by the underlying repr. Used when
dumping value to file.
"""
return repr(self._value)
class Float(Traitlet):
"""
>>> f = Float(0)
>>> print f.modified
False
>>> f(3)
>>> print f()
3.0
>>> print f.modified
True
>>> f(0)
>>> print f()
0.0
>>> print f.modified
False
>>> try:
... f('a')
... except ValidationError:
... pass
"""
_type = float
class Enum(Traitlet):
"""
>>> c = Enum('a','b','c')
>>> print c()
a
>>> try:
... c('unknown')
... except ValidationError:
... pass
>>> print c.modified
False
>>> c('b')
>>> print c()
b
"""
def __init__(self, *options):
self._options = options
super(Enum,self).__init__(options[0])
def validate(self, value):
if not value in self._options:
raise ValueError('must be one of %s' % str(self._options))
class Module(Traitlet):
"""
>>> m = Module('some.unknown.module')
>>> print m
'some.unknown.module'
>>> m = Module('re')
>>> assert type(m()) is types.ModuleType
"""
_type = str
def prepare_value(self):
try:
module = eval(self._value)
except:
module = None
if type(module) is not types.ModuleType:
raise ValueError("Invalid module name: %s" % self._value)
else:
return module
class URI(Traitlet):
"""
>>> u = URI('http://')
>>> try:
... u = URI('something.else')
... except ValidationError:
... pass
>>> u = URI('http://ipython.scipy.org/')
>>> print u
'http://ipython.scipy.org/'
"""
_regexp = re.compile(r'^[a-zA-Z]+:\/\/')
_type = str
def validate(self, uri):
if not self._regexp.match(uri):
raise ValueError()
class Int(Traitlet):
"""
>>> i = Int(3.5)
>>> print i
3
>>> print i()
3
>>> i = Int('4')
>>> print i
4
>>> try:
... i = Int('a')
... except ValidationError:
... pass
... else:
... raise "Should fail"
"""
_type = int
class Bool(Traitlet):
"""
>>> b = Bool(2)
>>> print b
True
>>> print b()
True
>>> b = Bool('True')
>>> print b
True
>>> b(True)
>>> print b.modified
False
>>> print Bool(0)
False
"""
_type = bool
class Unicode(Traitlet):
"""
>>> u = Unicode(123)
>>> print u
u'123'
>>> u = Unicode('123')
>>> print u.modified
False
>>> u('hello world')
>>> print u
u'hello world'
"""
_type = unicode