##// END OF EJS Templates
move eventful to html.widgets.eventful
Min RK -
Show More
@@ -1,40 +1,40 b''
1 1 from .widget import Widget, DOMWidget, CallbackDispatcher, register, widget_serialization
2 2
3 from .trait_types import Color
3 from .trait_types import Color, EventfulDict, EventfulList
4 4
5 5 from .widget_bool import Checkbox, ToggleButton, Valid
6 6 from .widget_button import Button
7 7 from .widget_box import Box, FlexBox, HBox, VBox
8 8 from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider
9 9 from .widget_image import Image
10 10 from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider
11 11 from .widget_output import Output
12 12 from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select, SelectMultiple
13 13 from .widget_selectioncontainer import Tab, Accordion
14 14 from .widget_string import HTML, Latex, Text, Textarea
15 15 from .interaction import interact, interactive, fixed, interact_manual
16 16 from .widget_link import jslink, jsdlink
17 17
18 18 # Deprecated classes
19 19 from .widget_bool import CheckboxWidget, ToggleButtonWidget
20 20 from .widget_button import ButtonWidget
21 21 from .widget_box import ContainerWidget
22 22 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
23 23 from .widget_image import ImageWidget
24 24 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
25 25 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
26 26 from .widget_selectioncontainer import TabWidget, AccordionWidget
27 27 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
28 28
29 29 # We use warn_explicit so we have very brief messages without file or line numbers.
30 30 # The concern is that file or line numbers will confuse the interactive user.
31 31 # To ignore this warning, do:
32 32 #
33 33 # from warnings import filterwarnings
34 34 # filterwarnings('ignore', module='IPython.html.widgets')
35 35
36 36 from warnings import warn_explicit
37 37 __warningregistry__ = {}
38 38 warn_explicit("IPython widgets are experimental and may change in the future.",
39 39 FutureWarning, '', 0, module = 'IPython.html.widgets',
40 40 registry = __warningregistry__, module_globals = globals)
1 NO CONTENT: file renamed from traitlets/eventful.py to IPython/html/widgets/eventful.py
@@ -1,20 +1,83 b''
1 1 """Test trait types of the widget packages."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from unittest import TestCase
7 7 from IPython.utils.traitlets import HasTraits
8 8 from traitlets.tests.test_traitlets import TraitTestBase
9 from IPython.html.widgets import Color
9 from IPython.html.widgets import Color, EventfulDict, EventfulList
10 10
11 11
12 12 class ColorTrait(HasTraits):
13 13 value = Color("black")
14 14
15 15
16 16 class TestColor(TraitTestBase):
17 17 obj = ColorTrait()
18 18
19 19 _good_values = ["blue", "#AA0", "#FFFFFF"]
20 20 _bad_values = ["vanilla", "blues"]
21
22
23 class TestEventful(TestCase):
24
25 def test_list(self):
26 """Does the EventfulList work?"""
27 event_cache = []
28
29 class A(HasTraits):
30 x = EventfulList([c for c in 'abc'])
31 a = A()
32 a.x.on_events(lambda i, x: event_cache.append('insert'), \
33 lambda i, x: event_cache.append('set'), \
34 lambda i: event_cache.append('del'), \
35 lambda: event_cache.append('reverse'), \
36 lambda *p, **k: event_cache.append('sort'))
37
38 a.x.remove('c')
39 # ab
40 a.x.insert(0, 'z')
41 # zab
42 del a.x[1]
43 # zb
44 a.x.reverse()
45 # bz
46 a.x[1] = 'o'
47 # bo
48 a.x.append('a')
49 # boa
50 a.x.sort()
51 # abo
52
53 # Were the correct events captured?
54 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
55
56 # Is the output correct?
57 self.assertEqual(a.x, [c for c in 'abo'])
58
59 def test_dict(self):
60 """Does the EventfulDict work?"""
61 event_cache = []
62
63 class A(HasTraits):
64 x = EventfulDict({c: c for c in 'abc'})
65 a = A()
66 a.x.on_events(lambda k, v: event_cache.append('add'), \
67 lambda k, v: event_cache.append('set'), \
68 lambda k: event_cache.append('del'))
69
70 del a.x['c']
71 # ab
72 a.x['z'] = 1
73 # abz
74 a.x['z'] = 'z'
75 # abz
76 a.x.pop('a')
77 # bz
78
79 # Were the correct events captured?
80 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
81
82 # Is the output correct?
83 self.assertEqual(a.x, {c: c for c in 'bz'})
@@ -1,28 +1,70 b''
1 1 # encoding: utf-8
2 2 """
3 3 Trait types for html widgets.
4 4 """
5 5
6 6 # Copyright (c) IPython Development Team.
7 7 # Distributed under the terms of the Modified BSD License.
8 8
9 9 import re
10 10 from IPython.utils import traitlets
11 11
12 #-----------------------------------------------------------------------------
13 # Utilities
14 #-----------------------------------------------------------------------------
12 from . import eventful
15 13
16 14 _color_names = ['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred ', 'indigo ', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
17 15 _color_re = re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$')
18 16
19 17
20 18 class Color(traitlets.Unicode):
21 19 """A string holding a valid HTML color such as 'blue', '#060482', '#A80'"""
22 20
23 21 info_text = 'a valid HTML color'
24 22
25 23 def validate(self, obj, value):
26 24 if value.lower() in _color_names or _color_re.match(value):
27 25 return value
28 26 self.error(obj, value)
27
28
29 class EventfulDict(traitlets.Instance):
30 """An instance of an EventfulDict."""
31
32 def __init__(self, default_value={}, **metadata):
33 """Create a EventfulDict trait type from a dict.
34
35 The default value is created by doing
36 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
37 ``default_value``.
38 """
39 if default_value is None:
40 args = None
41 elif isinstance(default_value, dict):
42 args = (default_value,)
43 elif isinstance(default_value, SequenceTypes):
44 args = (default_value,)
45 else:
46 raise TypeError('default value of EventfulDict was %s' % default_value)
47
48 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
49 **metadata)
50
51
52 class EventfulList(traitlets.Instance):
53 """An instance of an EventfulList."""
54
55 def __init__(self, default_value=None, **metadata):
56 """Create a EventfulList trait type from a dict.
57
58 The default value is created by doing
59 ``eventful.EvenfulList(default_value)``, which creates a copy of the
60 ``default_value``.
61 """
62 if default_value is None:
63 args = ((),)
64 else:
65 args = (default_value,)
66
67 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
68 **metadata)
69
70
@@ -1,1634 +1,1573 b''
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.traitlets."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6 #
7 7 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
8 8 # also under the terms of the Modified BSD License.
9 9
10 10 import pickle
11 11 import re
12 12 import sys
13 13 from unittest import TestCase
14 14
15 15 import nose.tools as nt
16 16 from nose import SkipTest
17 17
18 18 from IPython.utils.traitlets import (
19 19 HasTraits, MetaHasTraits, TraitType, Any, Bool, CBytes, Dict, Enum,
20 20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
21 21 Union, Undefined, Type, This, Instance, TCPAddress, List, Tuple,
22 22 ObjectName, DottedObjectName, CRegExp, link, directional_link,
23 EventfulList, EventfulDict, ForwardDeclaredType, ForwardDeclaredInstance,
23 ForwardDeclaredType, ForwardDeclaredInstance,
24 24 )
25 25 from IPython.utils import py3compat
26 26 from IPython.testing.decorators import skipif
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Helper classes for testing
30 30 #-----------------------------------------------------------------------------
31 31
32 32
33 33 class HasTraitsStub(HasTraits):
34 34
35 35 def _notify_trait(self, name, old, new):
36 36 self._notify_name = name
37 37 self._notify_old = old
38 38 self._notify_new = new
39 39
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Test classes
43 43 #-----------------------------------------------------------------------------
44 44
45 45
46 46 class TestTraitType(TestCase):
47 47
48 48 def test_get_undefined(self):
49 49 class A(HasTraits):
50 50 a = TraitType
51 51 a = A()
52 52 self.assertEqual(a.a, Undefined)
53 53
54 54 def test_set(self):
55 55 class A(HasTraitsStub):
56 56 a = TraitType
57 57
58 58 a = A()
59 59 a.a = 10
60 60 self.assertEqual(a.a, 10)
61 61 self.assertEqual(a._notify_name, 'a')
62 62 self.assertEqual(a._notify_old, Undefined)
63 63 self.assertEqual(a._notify_new, 10)
64 64
65 65 def test_validate(self):
66 66 class MyTT(TraitType):
67 67 def validate(self, inst, value):
68 68 return -1
69 69 class A(HasTraitsStub):
70 70 tt = MyTT
71 71
72 72 a = A()
73 73 a.tt = 10
74 74 self.assertEqual(a.tt, -1)
75 75
76 76 def test_default_validate(self):
77 77 class MyIntTT(TraitType):
78 78 def validate(self, obj, value):
79 79 if isinstance(value, int):
80 80 return value
81 81 self.error(obj, value)
82 82 class A(HasTraits):
83 83 tt = MyIntTT(10)
84 84 a = A()
85 85 self.assertEqual(a.tt, 10)
86 86
87 87 # Defaults are validated when the HasTraits is instantiated
88 88 class B(HasTraits):
89 89 tt = MyIntTT('bad default')
90 90 self.assertRaises(TraitError, B)
91 91
92 92 def test_info(self):
93 93 class A(HasTraits):
94 94 tt = TraitType
95 95 a = A()
96 96 self.assertEqual(A.tt.info(), 'any value')
97 97
98 98 def test_error(self):
99 99 class A(HasTraits):
100 100 tt = TraitType
101 101 a = A()
102 102 self.assertRaises(TraitError, A.tt.error, a, 10)
103 103
104 104 def test_dynamic_initializer(self):
105 105 class A(HasTraits):
106 106 x = Int(10)
107 107 def _x_default(self):
108 108 return 11
109 109 class B(A):
110 110 x = Int(20)
111 111 class C(A):
112 112 def _x_default(self):
113 113 return 21
114 114
115 115 a = A()
116 116 self.assertEqual(a._trait_values, {})
117 117 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
118 118 self.assertEqual(a.x, 11)
119 119 self.assertEqual(a._trait_values, {'x': 11})
120 120 b = B()
121 121 self.assertEqual(b._trait_values, {'x': 20})
122 122 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
123 123 self.assertEqual(b.x, 20)
124 124 c = C()
125 125 self.assertEqual(c._trait_values, {})
126 126 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
127 127 self.assertEqual(c.x, 21)
128 128 self.assertEqual(c._trait_values, {'x': 21})
129 129 # Ensure that the base class remains unmolested when the _default
130 130 # initializer gets overridden in a subclass.
131 131 a = A()
132 132 c = C()
133 133 self.assertEqual(a._trait_values, {})
134 134 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
135 135 self.assertEqual(a.x, 11)
136 136 self.assertEqual(a._trait_values, {'x': 11})
137 137
138 138
139 139
140 140 class TestHasTraitsMeta(TestCase):
141 141
142 142 def test_metaclass(self):
143 143 self.assertEqual(type(HasTraits), MetaHasTraits)
144 144
145 145 class A(HasTraits):
146 146 a = Int
147 147
148 148 a = A()
149 149 self.assertEqual(type(a.__class__), MetaHasTraits)
150 150 self.assertEqual(a.a,0)
151 151 a.a = 10
152 152 self.assertEqual(a.a,10)
153 153
154 154 class B(HasTraits):
155 155 b = Int()
156 156
157 157 b = B()
158 158 self.assertEqual(b.b,0)
159 159 b.b = 10
160 160 self.assertEqual(b.b,10)
161 161
162 162 class C(HasTraits):
163 163 c = Int(30)
164 164
165 165 c = C()
166 166 self.assertEqual(c.c,30)
167 167 c.c = 10
168 168 self.assertEqual(c.c,10)
169 169
170 170 def test_this_class(self):
171 171 class A(HasTraits):
172 172 t = This()
173 173 tt = This()
174 174 class B(A):
175 175 tt = This()
176 176 ttt = This()
177 177 self.assertEqual(A.t.this_class, A)
178 178 self.assertEqual(B.t.this_class, A)
179 179 self.assertEqual(B.tt.this_class, B)
180 180 self.assertEqual(B.ttt.this_class, B)
181 181
182 182 class TestHasTraitsNotify(TestCase):
183 183
184 184 def setUp(self):
185 185 self._notify1 = []
186 186 self._notify2 = []
187 187
188 188 def notify1(self, name, old, new):
189 189 self._notify1.append((name, old, new))
190 190
191 191 def notify2(self, name, old, new):
192 192 self._notify2.append((name, old, new))
193 193
194 194 def test_notify_all(self):
195 195
196 196 class A(HasTraits):
197 197 a = Int
198 198 b = Float
199 199
200 200 a = A()
201 201 a.on_trait_change(self.notify1)
202 202 a.a = 0
203 203 self.assertEqual(len(self._notify1),0)
204 204 a.b = 0.0
205 205 self.assertEqual(len(self._notify1),0)
206 206 a.a = 10
207 207 self.assertTrue(('a',0,10) in self._notify1)
208 208 a.b = 10.0
209 209 self.assertTrue(('b',0.0,10.0) in self._notify1)
210 210 self.assertRaises(TraitError,setattr,a,'a','bad string')
211 211 self.assertRaises(TraitError,setattr,a,'b','bad string')
212 212 self._notify1 = []
213 213 a.on_trait_change(self.notify1,remove=True)
214 214 a.a = 20
215 215 a.b = 20.0
216 216 self.assertEqual(len(self._notify1),0)
217 217
218 218 def test_notify_one(self):
219 219
220 220 class A(HasTraits):
221 221 a = Int
222 222 b = Float
223 223
224 224 a = A()
225 225 a.on_trait_change(self.notify1, 'a')
226 226 a.a = 0
227 227 self.assertEqual(len(self._notify1),0)
228 228 a.a = 10
229 229 self.assertTrue(('a',0,10) in self._notify1)
230 230 self.assertRaises(TraitError,setattr,a,'a','bad string')
231 231
232 232 def test_subclass(self):
233 233
234 234 class A(HasTraits):
235 235 a = Int
236 236
237 237 class B(A):
238 238 b = Float
239 239
240 240 b = B()
241 241 self.assertEqual(b.a,0)
242 242 self.assertEqual(b.b,0.0)
243 243 b.a = 100
244 244 b.b = 100.0
245 245 self.assertEqual(b.a,100)
246 246 self.assertEqual(b.b,100.0)
247 247
248 248 def test_notify_subclass(self):
249 249
250 250 class A(HasTraits):
251 251 a = Int
252 252
253 253 class B(A):
254 254 b = Float
255 255
256 256 b = B()
257 257 b.on_trait_change(self.notify1, 'a')
258 258 b.on_trait_change(self.notify2, 'b')
259 259 b.a = 0
260 260 b.b = 0.0
261 261 self.assertEqual(len(self._notify1),0)
262 262 self.assertEqual(len(self._notify2),0)
263 263 b.a = 10
264 264 b.b = 10.0
265 265 self.assertTrue(('a',0,10) in self._notify1)
266 266 self.assertTrue(('b',0.0,10.0) in self._notify2)
267 267
268 268 def test_static_notify(self):
269 269
270 270 class A(HasTraits):
271 271 a = Int
272 272 _notify1 = []
273 273 def _a_changed(self, name, old, new):
274 274 self._notify1.append((name, old, new))
275 275
276 276 a = A()
277 277 a.a = 0
278 278 # This is broken!!!
279 279 self.assertEqual(len(a._notify1),0)
280 280 a.a = 10
281 281 self.assertTrue(('a',0,10) in a._notify1)
282 282
283 283 class B(A):
284 284 b = Float
285 285 _notify2 = []
286 286 def _b_changed(self, name, old, new):
287 287 self._notify2.append((name, old, new))
288 288
289 289 b = B()
290 290 b.a = 10
291 291 b.b = 10.0
292 292 self.assertTrue(('a',0,10) in b._notify1)
293 293 self.assertTrue(('b',0.0,10.0) in b._notify2)
294 294
295 295 def test_notify_args(self):
296 296
297 297 def callback0():
298 298 self.cb = ()
299 299 def callback1(name):
300 300 self.cb = (name,)
301 301 def callback2(name, new):
302 302 self.cb = (name, new)
303 303 def callback3(name, old, new):
304 304 self.cb = (name, old, new)
305 305
306 306 class A(HasTraits):
307 307 a = Int
308 308
309 309 a = A()
310 310 a.on_trait_change(callback0, 'a')
311 311 a.a = 10
312 312 self.assertEqual(self.cb,())
313 313 a.on_trait_change(callback0, 'a', remove=True)
314 314
315 315 a.on_trait_change(callback1, 'a')
316 316 a.a = 100
317 317 self.assertEqual(self.cb,('a',))
318 318 a.on_trait_change(callback1, 'a', remove=True)
319 319
320 320 a.on_trait_change(callback2, 'a')
321 321 a.a = 1000
322 322 self.assertEqual(self.cb,('a',1000))
323 323 a.on_trait_change(callback2, 'a', remove=True)
324 324
325 325 a.on_trait_change(callback3, 'a')
326 326 a.a = 10000
327 327 self.assertEqual(self.cb,('a',1000,10000))
328 328 a.on_trait_change(callback3, 'a', remove=True)
329 329
330 330 self.assertEqual(len(a._trait_notifiers['a']),0)
331 331
332 332 def test_notify_only_once(self):
333 333
334 334 class A(HasTraits):
335 335 listen_to = ['a']
336 336
337 337 a = Int(0)
338 338 b = 0
339 339
340 340 def __init__(self, **kwargs):
341 341 super(A, self).__init__(**kwargs)
342 342 self.on_trait_change(self.listener1, ['a'])
343 343
344 344 def listener1(self, name, old, new):
345 345 self.b += 1
346 346
347 347 class B(A):
348 348
349 349 c = 0
350 350 d = 0
351 351
352 352 def __init__(self, **kwargs):
353 353 super(B, self).__init__(**kwargs)
354 354 self.on_trait_change(self.listener2)
355 355
356 356 def listener2(self, name, old, new):
357 357 self.c += 1
358 358
359 359 def _a_changed(self, name, old, new):
360 360 self.d += 1
361 361
362 362 b = B()
363 363 b.a += 1
364 364 self.assertEqual(b.b, b.c)
365 365 self.assertEqual(b.b, b.d)
366 366 b.a += 1
367 367 self.assertEqual(b.b, b.c)
368 368 self.assertEqual(b.b, b.d)
369 369
370 370
371 371 class TestHasTraits(TestCase):
372 372
373 373 def test_trait_names(self):
374 374 class A(HasTraits):
375 375 i = Int
376 376 f = Float
377 377 a = A()
378 378 self.assertEqual(sorted(a.trait_names()),['f','i'])
379 379 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
380 380
381 381 def test_trait_metadata(self):
382 382 class A(HasTraits):
383 383 i = Int(config_key='MY_VALUE')
384 384 a = A()
385 385 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
386 386
387 387 def test_trait_metadata_default(self):
388 388 class A(HasTraits):
389 389 i = Int()
390 390 a = A()
391 391 self.assertEqual(a.trait_metadata('i', 'config_key'), None)
392 392 self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default')
393 393
394 394 def test_traits(self):
395 395 class A(HasTraits):
396 396 i = Int
397 397 f = Float
398 398 a = A()
399 399 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
400 400 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
401 401
402 402 def test_traits_metadata(self):
403 403 class A(HasTraits):
404 404 i = Int(config_key='VALUE1', other_thing='VALUE2')
405 405 f = Float(config_key='VALUE3', other_thing='VALUE2')
406 406 j = Int(0)
407 407 a = A()
408 408 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
409 409 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
410 410 self.assertEqual(traits, dict(i=A.i))
411 411
412 412 # This passes, but it shouldn't because I am replicating a bug in
413 413 # traits.
414 414 traits = a.traits(config_key=lambda v: True)
415 415 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
416 416
417 417 def test_init(self):
418 418 class A(HasTraits):
419 419 i = Int()
420 420 x = Float()
421 421 a = A(i=1, x=10.0)
422 422 self.assertEqual(a.i, 1)
423 423 self.assertEqual(a.x, 10.0)
424 424
425 425 def test_positional_args(self):
426 426 class A(HasTraits):
427 427 i = Int(0)
428 428 def __init__(self, i):
429 429 super(A, self).__init__()
430 430 self.i = i
431 431
432 432 a = A(5)
433 433 self.assertEqual(a.i, 5)
434 434 # should raise TypeError if no positional arg given
435 435 self.assertRaises(TypeError, A)
436 436
437 437 #-----------------------------------------------------------------------------
438 438 # Tests for specific trait types
439 439 #-----------------------------------------------------------------------------
440 440
441 441
442 442 class TestType(TestCase):
443 443
444 444 def test_default(self):
445 445
446 446 class B(object): pass
447 447 class A(HasTraits):
448 448 klass = Type(allow_none=True)
449 449
450 450 a = A()
451 451 self.assertEqual(a.klass, None)
452 452
453 453 a.klass = B
454 454 self.assertEqual(a.klass, B)
455 455 self.assertRaises(TraitError, setattr, a, 'klass', 10)
456 456
457 457 def test_value(self):
458 458
459 459 class B(object): pass
460 460 class C(object): pass
461 461 class A(HasTraits):
462 462 klass = Type(B)
463 463
464 464 a = A()
465 465 self.assertEqual(a.klass, B)
466 466 self.assertRaises(TraitError, setattr, a, 'klass', C)
467 467 self.assertRaises(TraitError, setattr, a, 'klass', object)
468 468 a.klass = B
469 469
470 470 def test_allow_none(self):
471 471
472 472 class B(object): pass
473 473 class C(B): pass
474 474 class A(HasTraits):
475 475 klass = Type(B)
476 476
477 477 a = A()
478 478 self.assertEqual(a.klass, B)
479 479 self.assertRaises(TraitError, setattr, a, 'klass', None)
480 480 a.klass = C
481 481 self.assertEqual(a.klass, C)
482 482
483 483 def test_validate_klass(self):
484 484
485 485 class A(HasTraits):
486 486 klass = Type('no strings allowed')
487 487
488 488 self.assertRaises(ImportError, A)
489 489
490 490 class A(HasTraits):
491 491 klass = Type('rub.adub.Duck')
492 492
493 493 self.assertRaises(ImportError, A)
494 494
495 495 def test_validate_default(self):
496 496
497 497 class B(object): pass
498 498 class A(HasTraits):
499 499 klass = Type('bad default', B)
500 500
501 501 self.assertRaises(ImportError, A)
502 502
503 503 class C(HasTraits):
504 504 klass = Type(None, B)
505 505
506 506 self.assertRaises(TraitError, C)
507 507
508 508 def test_str_klass(self):
509 509
510 510 class A(HasTraits):
511 511 klass = Type('IPython.utils.ipstruct.Struct')
512 512
513 513 from IPython.utils.ipstruct import Struct
514 514 a = A()
515 515 a.klass = Struct
516 516 self.assertEqual(a.klass, Struct)
517 517
518 518 self.assertRaises(TraitError, setattr, a, 'klass', 10)
519 519
520 520 def test_set_str_klass(self):
521 521
522 522 class A(HasTraits):
523 523 klass = Type()
524 524
525 525 a = A(klass='IPython.utils.ipstruct.Struct')
526 526 from IPython.utils.ipstruct import Struct
527 527 self.assertEqual(a.klass, Struct)
528 528
529 529 class TestInstance(TestCase):
530 530
531 531 def test_basic(self):
532 532 class Foo(object): pass
533 533 class Bar(Foo): pass
534 534 class Bah(object): pass
535 535
536 536 class A(HasTraits):
537 537 inst = Instance(Foo, allow_none=True)
538 538
539 539 a = A()
540 540 self.assertTrue(a.inst is None)
541 541 a.inst = Foo()
542 542 self.assertTrue(isinstance(a.inst, Foo))
543 543 a.inst = Bar()
544 544 self.assertTrue(isinstance(a.inst, Foo))
545 545 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
546 546 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
547 547 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
548 548
549 549 def test_default_klass(self):
550 550 class Foo(object): pass
551 551 class Bar(Foo): pass
552 552 class Bah(object): pass
553 553
554 554 class FooInstance(Instance):
555 555 klass = Foo
556 556
557 557 class A(HasTraits):
558 558 inst = FooInstance(allow_none=True)
559 559
560 560 a = A()
561 561 self.assertTrue(a.inst is None)
562 562 a.inst = Foo()
563 563 self.assertTrue(isinstance(a.inst, Foo))
564 564 a.inst = Bar()
565 565 self.assertTrue(isinstance(a.inst, Foo))
566 566 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
567 567 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
568 568 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
569 569
570 570 def test_unique_default_value(self):
571 571 class Foo(object): pass
572 572 class A(HasTraits):
573 573 inst = Instance(Foo,(),{})
574 574
575 575 a = A()
576 576 b = A()
577 577 self.assertTrue(a.inst is not b.inst)
578 578
579 579 def test_args_kw(self):
580 580 class Foo(object):
581 581 def __init__(self, c): self.c = c
582 582 class Bar(object): pass
583 583 class Bah(object):
584 584 def __init__(self, c, d):
585 585 self.c = c; self.d = d
586 586
587 587 class A(HasTraits):
588 588 inst = Instance(Foo, (10,))
589 589 a = A()
590 590 self.assertEqual(a.inst.c, 10)
591 591
592 592 class B(HasTraits):
593 593 inst = Instance(Bah, args=(10,), kw=dict(d=20))
594 594 b = B()
595 595 self.assertEqual(b.inst.c, 10)
596 596 self.assertEqual(b.inst.d, 20)
597 597
598 598 class C(HasTraits):
599 599 inst = Instance(Foo, allow_none=True)
600 600 c = C()
601 601 self.assertTrue(c.inst is None)
602 602
603 603 def test_bad_default(self):
604 604 class Foo(object): pass
605 605
606 606 class A(HasTraits):
607 607 inst = Instance(Foo)
608 608
609 609 self.assertRaises(TraitError, A)
610 610
611 611 def test_instance(self):
612 612 class Foo(object): pass
613 613
614 614 def inner():
615 615 class A(HasTraits):
616 616 inst = Instance(Foo())
617 617
618 618 self.assertRaises(TraitError, inner)
619 619
620 620
621 621 class TestThis(TestCase):
622 622
623 623 def test_this_class(self):
624 624 class Foo(HasTraits):
625 625 this = This
626 626
627 627 f = Foo()
628 628 self.assertEqual(f.this, None)
629 629 g = Foo()
630 630 f.this = g
631 631 self.assertEqual(f.this, g)
632 632 self.assertRaises(TraitError, setattr, f, 'this', 10)
633 633
634 634 def test_this_inst(self):
635 635 class Foo(HasTraits):
636 636 this = This()
637 637
638 638 f = Foo()
639 639 f.this = Foo()
640 640 self.assertTrue(isinstance(f.this, Foo))
641 641
642 642 def test_subclass(self):
643 643 class Foo(HasTraits):
644 644 t = This()
645 645 class Bar(Foo):
646 646 pass
647 647 f = Foo()
648 648 b = Bar()
649 649 f.t = b
650 650 b.t = f
651 651 self.assertEqual(f.t, b)
652 652 self.assertEqual(b.t, f)
653 653
654 654 def test_subclass_override(self):
655 655 class Foo(HasTraits):
656 656 t = This()
657 657 class Bar(Foo):
658 658 t = This()
659 659 f = Foo()
660 660 b = Bar()
661 661 f.t = b
662 662 self.assertEqual(f.t, b)
663 663 self.assertRaises(TraitError, setattr, b, 't', f)
664 664
665 665 def test_this_in_container(self):
666 666
667 667 class Tree(HasTraits):
668 668 value = Unicode()
669 669 leaves = List(This())
670 670
671 671 tree = Tree(
672 672 value='foo',
673 673 leaves=[Tree('bar'), Tree('buzz')]
674 674 )
675 675
676 676 with self.assertRaises(TraitError):
677 677 tree.leaves = [1, 2]
678 678
679 679 class TraitTestBase(TestCase):
680 680 """A best testing class for basic trait types."""
681 681
682 682 def assign(self, value):
683 683 self.obj.value = value
684 684
685 685 def coerce(self, value):
686 686 return value
687 687
688 688 def test_good_values(self):
689 689 if hasattr(self, '_good_values'):
690 690 for value in self._good_values:
691 691 self.assign(value)
692 692 self.assertEqual(self.obj.value, self.coerce(value))
693 693
694 694 def test_bad_values(self):
695 695 if hasattr(self, '_bad_values'):
696 696 for value in self._bad_values:
697 697 try:
698 698 self.assertRaises(TraitError, self.assign, value)
699 699 except AssertionError:
700 700 assert False, value
701 701
702 702 def test_default_value(self):
703 703 if hasattr(self, '_default_value'):
704 704 self.assertEqual(self._default_value, self.obj.value)
705 705
706 706 def test_allow_none(self):
707 707 if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
708 708 None in self._bad_values):
709 709 trait=self.obj.traits()['value']
710 710 try:
711 711 trait.allow_none = True
712 712 self._bad_values.remove(None)
713 713 #skip coerce. Allow None casts None to None.
714 714 self.assign(None)
715 715 self.assertEqual(self.obj.value,None)
716 716 self.test_good_values()
717 717 self.test_bad_values()
718 718 finally:
719 719 #tear down
720 720 trait.allow_none = False
721 721 self._bad_values.append(None)
722 722
723 723 def tearDown(self):
724 724 # restore default value after tests, if set
725 725 if hasattr(self, '_default_value'):
726 726 self.obj.value = self._default_value
727 727
728 728
729 729 class AnyTrait(HasTraits):
730 730
731 731 value = Any
732 732
733 733 class AnyTraitTest(TraitTestBase):
734 734
735 735 obj = AnyTrait()
736 736
737 737 _default_value = None
738 738 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
739 739 _bad_values = []
740 740
741 741 class UnionTrait(HasTraits):
742 742
743 743 value = Union([Type(), Bool()])
744 744
745 745 class UnionTraitTest(TraitTestBase):
746 746
747 747 obj = UnionTrait(value='IPython.utils.ipstruct.Struct')
748 748 _good_values = [int, float, True]
749 749 _bad_values = [[], (0,), 1j]
750 750
751 751 class OrTrait(HasTraits):
752 752
753 753 value = Bool() | Unicode()
754 754
755 755 class OrTraitTest(TraitTestBase):
756 756
757 757 obj = OrTrait()
758 758 _good_values = [True, False, 'ten']
759 759 _bad_values = [[], (0,), 1j]
760 760
761 761 class IntTrait(HasTraits):
762 762
763 763 value = Int(99)
764 764
765 765 class TestInt(TraitTestBase):
766 766
767 767 obj = IntTrait()
768 768 _default_value = 99
769 769 _good_values = [10, -10]
770 770 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
771 771 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
772 772 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
773 773 if not py3compat.PY3:
774 774 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
775 775
776 776
777 777 class LongTrait(HasTraits):
778 778
779 779 value = Long(99 if py3compat.PY3 else long(99))
780 780
781 781 class TestLong(TraitTestBase):
782 782
783 783 obj = LongTrait()
784 784
785 785 _default_value = 99 if py3compat.PY3 else long(99)
786 786 _good_values = [10, -10]
787 787 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
788 788 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
789 789 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
790 790 u'-10.1']
791 791 if not py3compat.PY3:
792 792 # maxint undefined on py3, because int == long
793 793 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
794 794 _bad_values.extend([[long(10)], (long(10),)])
795 795
796 796 @skipif(py3compat.PY3, "not relevant on py3")
797 797 def test_cast_small(self):
798 798 """Long casts ints to long"""
799 799 self.obj.value = 10
800 800 self.assertEqual(type(self.obj.value), long)
801 801
802 802
803 803 class IntegerTrait(HasTraits):
804 804 value = Integer(1)
805 805
806 806 class TestInteger(TestLong):
807 807 obj = IntegerTrait()
808 808 _default_value = 1
809 809
810 810 def coerce(self, n):
811 811 return int(n)
812 812
813 813 @skipif(py3compat.PY3, "not relevant on py3")
814 814 def test_cast_small(self):
815 815 """Integer casts small longs to int"""
816 816 if py3compat.PY3:
817 817 raise SkipTest("not relevant on py3")
818 818
819 819 self.obj.value = long(100)
820 820 self.assertEqual(type(self.obj.value), int)
821 821
822 822
823 823 class FloatTrait(HasTraits):
824 824
825 825 value = Float(99.0)
826 826
827 827 class TestFloat(TraitTestBase):
828 828
829 829 obj = FloatTrait()
830 830
831 831 _default_value = 99.0
832 832 _good_values = [10, -10, 10.1, -10.1]
833 833 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
834 834 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
835 835 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
836 836 if not py3compat.PY3:
837 837 _bad_values.extend([long(10), long(-10)])
838 838
839 839
840 840 class ComplexTrait(HasTraits):
841 841
842 842 value = Complex(99.0-99.0j)
843 843
844 844 class TestComplex(TraitTestBase):
845 845
846 846 obj = ComplexTrait()
847 847
848 848 _default_value = 99.0-99.0j
849 849 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
850 850 10.1j, 10.1+10.1j, 10.1-10.1j]
851 851 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
852 852 if not py3compat.PY3:
853 853 _bad_values.extend([long(10), long(-10)])
854 854
855 855
856 856 class BytesTrait(HasTraits):
857 857
858 858 value = Bytes(b'string')
859 859
860 860 class TestBytes(TraitTestBase):
861 861
862 862 obj = BytesTrait()
863 863
864 864 _default_value = b'string'
865 865 _good_values = [b'10', b'-10', b'10L',
866 866 b'-10L', b'10.1', b'-10.1', b'string']
867 867 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
868 868 ['ten'],{'ten': 10},(10,), None, u'string']
869 869 if not py3compat.PY3:
870 870 _bad_values.extend([long(10), long(-10)])
871 871
872 872
873 873 class UnicodeTrait(HasTraits):
874 874
875 875 value = Unicode(u'unicode')
876 876
877 877 class TestUnicode(TraitTestBase):
878 878
879 879 obj = UnicodeTrait()
880 880
881 881 _default_value = u'unicode'
882 882 _good_values = ['10', '-10', '10L', '-10L', '10.1',
883 883 '-10.1', '', u'', 'string', u'string', u"€"]
884 884 _bad_values = [10, -10, 10.1, -10.1, 1j,
885 885 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
886 886 if not py3compat.PY3:
887 887 _bad_values.extend([long(10), long(-10)])
888 888
889 889
890 890 class ObjectNameTrait(HasTraits):
891 891 value = ObjectName("abc")
892 892
893 893 class TestObjectName(TraitTestBase):
894 894 obj = ObjectNameTrait()
895 895
896 896 _default_value = "abc"
897 897 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
898 898 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
899 899 None, object(), object]
900 900 if sys.version_info[0] < 3:
901 901 _bad_values.append(u"ΓΎ")
902 902 else:
903 903 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
904 904
905 905
906 906 class DottedObjectNameTrait(HasTraits):
907 907 value = DottedObjectName("a.b")
908 908
909 909 class TestDottedObjectName(TraitTestBase):
910 910 obj = DottedObjectNameTrait()
911 911
912 912 _default_value = "a.b"
913 913 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
914 914 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
915 915 if sys.version_info[0] < 3:
916 916 _bad_values.append(u"t.ΓΎ")
917 917 else:
918 918 _good_values.append(u"t.ΓΎ")
919 919
920 920
921 921 class TCPAddressTrait(HasTraits):
922 922 value = TCPAddress()
923 923
924 924 class TestTCPAddress(TraitTestBase):
925 925
926 926 obj = TCPAddressTrait()
927 927
928 928 _default_value = ('127.0.0.1',0)
929 929 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
930 930 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
931 931
932 932 class ListTrait(HasTraits):
933 933
934 934 value = List(Int)
935 935
936 936 class TestList(TraitTestBase):
937 937
938 938 obj = ListTrait()
939 939
940 940 _default_value = []
941 941 _good_values = [[], [1], list(range(10)), (1,2)]
942 942 _bad_values = [10, [1,'a'], 'a']
943 943
944 944 def coerce(self, value):
945 945 if value is not None:
946 946 value = list(value)
947 947 return value
948 948
949 949 class Foo(object):
950 950 pass
951 951
952 952 class NoneInstanceListTrait(HasTraits):
953 953
954 954 value = List(Instance(Foo))
955 955
956 956 class TestNoneInstanceList(TraitTestBase):
957 957
958 958 obj = NoneInstanceListTrait()
959 959
960 960 _default_value = []
961 961 _good_values = [[Foo(), Foo()], []]
962 962 _bad_values = [[None], [Foo(), None]]
963 963
964 964
965 965 class InstanceListTrait(HasTraits):
966 966
967 967 value = List(Instance(__name__+'.Foo'))
968 968
969 969 class TestInstanceList(TraitTestBase):
970 970
971 971 obj = InstanceListTrait()
972 972
973 973 def test_klass(self):
974 974 """Test that the instance klass is properly assigned."""
975 975 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
976 976
977 977 _default_value = []
978 978 _good_values = [[Foo(), Foo()], []]
979 979 _bad_values = [['1', 2,], '1', [Foo], None]
980 980
981 981 class UnionListTrait(HasTraits):
982 982
983 983 value = List(Int() | Bool())
984 984
985 985 class TestUnionListTrait(HasTraits):
986 986
987 987 obj = UnionListTrait()
988 988
989 989 _default_value = []
990 990 _good_values = [[True, 1], [False, True]]
991 991 _bad_values = [[1, 'True'], False]
992 992
993 993
994 994 class LenListTrait(HasTraits):
995 995
996 996 value = List(Int, [0], minlen=1, maxlen=2)
997 997
998 998 class TestLenList(TraitTestBase):
999 999
1000 1000 obj = LenListTrait()
1001 1001
1002 1002 _default_value = [0]
1003 1003 _good_values = [[1], [1,2], (1,2)]
1004 1004 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
1005 1005
1006 1006 def coerce(self, value):
1007 1007 if value is not None:
1008 1008 value = list(value)
1009 1009 return value
1010 1010
1011 1011 class TupleTrait(HasTraits):
1012 1012
1013 1013 value = Tuple(Int(allow_none=True), default_value=(1,))
1014 1014
1015 1015 class TestTupleTrait(TraitTestBase):
1016 1016
1017 1017 obj = TupleTrait()
1018 1018
1019 1019 _default_value = (1,)
1020 1020 _good_values = [(1,), (0,), [1]]
1021 1021 _bad_values = [10, (1, 2), ('a'), (), None]
1022 1022
1023 1023 def coerce(self, value):
1024 1024 if value is not None:
1025 1025 value = tuple(value)
1026 1026 return value
1027 1027
1028 1028 def test_invalid_args(self):
1029 1029 self.assertRaises(TypeError, Tuple, 5)
1030 1030 self.assertRaises(TypeError, Tuple, default_value='hello')
1031 1031 t = Tuple(Int, CBytes, default_value=(1,5))
1032 1032
1033 1033 class LooseTupleTrait(HasTraits):
1034 1034
1035 1035 value = Tuple((1,2,3))
1036 1036
1037 1037 class TestLooseTupleTrait(TraitTestBase):
1038 1038
1039 1039 obj = LooseTupleTrait()
1040 1040
1041 1041 _default_value = (1,2,3)
1042 1042 _good_values = [(1,), [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
1043 1043 _bad_values = [10, 'hello', {}, None]
1044 1044
1045 1045 def coerce(self, value):
1046 1046 if value is not None:
1047 1047 value = tuple(value)
1048 1048 return value
1049 1049
1050 1050 def test_invalid_args(self):
1051 1051 self.assertRaises(TypeError, Tuple, 5)
1052 1052 self.assertRaises(TypeError, Tuple, default_value='hello')
1053 1053 t = Tuple(Int, CBytes, default_value=(1,5))
1054 1054
1055 1055
1056 1056 class MultiTupleTrait(HasTraits):
1057 1057
1058 1058 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1059 1059
1060 1060 class TestMultiTuple(TraitTestBase):
1061 1061
1062 1062 obj = MultiTupleTrait()
1063 1063
1064 1064 _default_value = (99,b'bottles')
1065 1065 _good_values = [(1,b'a'), (2,b'b')]
1066 1066 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1067 1067
1068 1068 class CRegExpTrait(HasTraits):
1069 1069
1070 1070 value = CRegExp(r'')
1071 1071
1072 1072 class TestCRegExp(TraitTestBase):
1073 1073
1074 1074 def coerce(self, value):
1075 1075 return re.compile(value)
1076 1076
1077 1077 obj = CRegExpTrait()
1078 1078
1079 1079 _default_value = re.compile(r'')
1080 1080 _good_values = [r'\d+', re.compile(r'\d+')]
1081 1081 _bad_values = ['(', None, ()]
1082 1082
1083 1083 class DictTrait(HasTraits):
1084 1084 value = Dict()
1085 1085
1086 1086 def test_dict_assignment():
1087 1087 d = dict()
1088 1088 c = DictTrait()
1089 1089 c.value = d
1090 1090 d['a'] = 5
1091 1091 nt.assert_equal(d, c.value)
1092 1092 nt.assert_true(c.value is d)
1093 1093
1094 1094 class ValidatedDictTrait(HasTraits):
1095 1095
1096 1096 value = Dict(Unicode())
1097 1097
1098 1098 class TestInstanceDict(TraitTestBase):
1099 1099
1100 1100 obj = ValidatedDictTrait()
1101 1101
1102 1102 _default_value = {}
1103 1103 _good_values = [{'0': 'foo'}, {'1': 'bar'}]
1104 1104 _bad_values = [{'0': 0}, {'1': 1}]
1105 1105
1106 1106
1107 1107 def test_dict_default_value():
1108 1108 """Check that the `{}` default value of the Dict traitlet constructor is
1109 1109 actually copied."""
1110 1110
1111 1111 d1, d2 = Dict(), Dict()
1112 1112 nt.assert_false(d1.get_default_value() is d2.get_default_value())
1113 1113
1114 1114
1115 1115 class TestValidationHook(TestCase):
1116 1116
1117 1117 def test_parity_trait(self):
1118 1118 """Verify that the early validation hook is effective"""
1119 1119
1120 1120 class Parity(HasTraits):
1121 1121
1122 1122 value = Int(0)
1123 1123 parity = Enum(['odd', 'even'], default_value='even')
1124 1124
1125 1125 def _value_validate(self, value, trait):
1126 1126 if self.parity == 'even' and value % 2:
1127 1127 raise TraitError('Expected an even number')
1128 1128 if self.parity == 'odd' and (value % 2 == 0):
1129 1129 raise TraitError('Expected an odd number')
1130 1130 return value
1131 1131
1132 1132 u = Parity()
1133 1133 u.parity = 'odd'
1134 1134 u.value = 1 # OK
1135 1135 with self.assertRaises(TraitError):
1136 1136 u.value = 2 # Trait Error
1137 1137
1138 1138 u.parity = 'even'
1139 1139 u.value = 2 # OK
1140 1140
1141 1141
1142 1142 class TestLink(TestCase):
1143 1143
1144 1144 def test_connect_same(self):
1145 1145 """Verify two traitlets of the same type can be linked together using link."""
1146 1146
1147 1147 # Create two simple classes with Int traitlets.
1148 1148 class A(HasTraits):
1149 1149 value = Int()
1150 1150 a = A(value=9)
1151 1151 b = A(value=8)
1152 1152
1153 1153 # Conenct the two classes.
1154 1154 c = link((a, 'value'), (b, 'value'))
1155 1155
1156 1156 # Make sure the values are the same at the point of linking.
1157 1157 self.assertEqual(a.value, b.value)
1158 1158
1159 1159 # Change one of the values to make sure they stay in sync.
1160 1160 a.value = 5
1161 1161 self.assertEqual(a.value, b.value)
1162 1162 b.value = 6
1163 1163 self.assertEqual(a.value, b.value)
1164 1164
1165 1165 def test_link_different(self):
1166 1166 """Verify two traitlets of different types can be linked together using link."""
1167 1167
1168 1168 # Create two simple classes with Int traitlets.
1169 1169 class A(HasTraits):
1170 1170 value = Int()
1171 1171 class B(HasTraits):
1172 1172 count = Int()
1173 1173 a = A(value=9)
1174 1174 b = B(count=8)
1175 1175
1176 1176 # Conenct the two classes.
1177 1177 c = link((a, 'value'), (b, 'count'))
1178 1178
1179 1179 # Make sure the values are the same at the point of linking.
1180 1180 self.assertEqual(a.value, b.count)
1181 1181
1182 1182 # Change one of the values to make sure they stay in sync.
1183 1183 a.value = 5
1184 1184 self.assertEqual(a.value, b.count)
1185 1185 b.count = 4
1186 1186 self.assertEqual(a.value, b.count)
1187 1187
1188 1188 def test_unlink(self):
1189 1189 """Verify two linked traitlets can be unlinked."""
1190 1190
1191 1191 # Create two simple classes with Int traitlets.
1192 1192 class A(HasTraits):
1193 1193 value = Int()
1194 1194 a = A(value=9)
1195 1195 b = A(value=8)
1196 1196
1197 1197 # Connect the two classes.
1198 1198 c = link((a, 'value'), (b, 'value'))
1199 1199 a.value = 4
1200 1200 c.unlink()
1201 1201
1202 1202 # Change one of the values to make sure they don't stay in sync.
1203 1203 a.value = 5
1204 1204 self.assertNotEqual(a.value, b.value)
1205 1205
1206 1206 def test_callbacks(self):
1207 1207 """Verify two linked traitlets have their callbacks called once."""
1208 1208
1209 1209 # Create two simple classes with Int traitlets.
1210 1210 class A(HasTraits):
1211 1211 value = Int()
1212 1212 class B(HasTraits):
1213 1213 count = Int()
1214 1214 a = A(value=9)
1215 1215 b = B(count=8)
1216 1216
1217 1217 # Register callbacks that count.
1218 1218 callback_count = []
1219 1219 def a_callback(name, old, new):
1220 1220 callback_count.append('a')
1221 1221 a.on_trait_change(a_callback, 'value')
1222 1222 def b_callback(name, old, new):
1223 1223 callback_count.append('b')
1224 1224 b.on_trait_change(b_callback, 'count')
1225 1225
1226 1226 # Connect the two classes.
1227 1227 c = link((a, 'value'), (b, 'count'))
1228 1228
1229 1229 # Make sure b's count was set to a's value once.
1230 1230 self.assertEqual(''.join(callback_count), 'b')
1231 1231 del callback_count[:]
1232 1232
1233 1233 # Make sure a's value was set to b's count once.
1234 1234 b.count = 5
1235 1235 self.assertEqual(''.join(callback_count), 'ba')
1236 1236 del callback_count[:]
1237 1237
1238 1238 # Make sure b's count was set to a's value once.
1239 1239 a.value = 4
1240 1240 self.assertEqual(''.join(callback_count), 'ab')
1241 1241 del callback_count[:]
1242 1242
1243 1243 class TestDirectionalLink(TestCase):
1244 1244 def test_connect_same(self):
1245 1245 """Verify two traitlets of the same type can be linked together using directional_link."""
1246 1246
1247 1247 # Create two simple classes with Int traitlets.
1248 1248 class A(HasTraits):
1249 1249 value = Int()
1250 1250 a = A(value=9)
1251 1251 b = A(value=8)
1252 1252
1253 1253 # Conenct the two classes.
1254 1254 c = directional_link((a, 'value'), (b, 'value'))
1255 1255
1256 1256 # Make sure the values are the same at the point of linking.
1257 1257 self.assertEqual(a.value, b.value)
1258 1258
1259 1259 # Change one the value of the source and check that it synchronizes the target.
1260 1260 a.value = 5
1261 1261 self.assertEqual(b.value, 5)
1262 1262 # Change one the value of the target and check that it has no impact on the source
1263 1263 b.value = 6
1264 1264 self.assertEqual(a.value, 5)
1265 1265
1266 1266 def test_link_different(self):
1267 1267 """Verify two traitlets of different types can be linked together using link."""
1268 1268
1269 1269 # Create two simple classes with Int traitlets.
1270 1270 class A(HasTraits):
1271 1271 value = Int()
1272 1272 class B(HasTraits):
1273 1273 count = Int()
1274 1274 a = A(value=9)
1275 1275 b = B(count=8)
1276 1276
1277 1277 # Conenct the two classes.
1278 1278 c = directional_link((a, 'value'), (b, 'count'))
1279 1279
1280 1280 # Make sure the values are the same at the point of linking.
1281 1281 self.assertEqual(a.value, b.count)
1282 1282
1283 1283 # Change one the value of the source and check that it synchronizes the target.
1284 1284 a.value = 5
1285 1285 self.assertEqual(b.count, 5)
1286 1286 # Change one the value of the target and check that it has no impact on the source
1287 1287 b.value = 6
1288 1288 self.assertEqual(a.value, 5)
1289 1289
1290 1290 def test_unlink(self):
1291 1291 """Verify two linked traitlets can be unlinked."""
1292 1292
1293 1293 # Create two simple classes with Int traitlets.
1294 1294 class A(HasTraits):
1295 1295 value = Int()
1296 1296 a = A(value=9)
1297 1297 b = A(value=8)
1298 1298
1299 1299 # Connect the two classes.
1300 1300 c = directional_link((a, 'value'), (b, 'value'))
1301 1301 a.value = 4
1302 1302 c.unlink()
1303 1303
1304 1304 # Change one of the values to make sure they don't stay in sync.
1305 1305 a.value = 5
1306 1306 self.assertNotEqual(a.value, b.value)
1307 1307
1308 1308 class Pickleable(HasTraits):
1309 1309 i = Int()
1310 1310 j = Int()
1311 1311
1312 1312 def _i_default(self):
1313 1313 return 1
1314 1314
1315 1315 def _i_changed(self, name, old, new):
1316 1316 self.j = new
1317 1317
1318 1318 def test_pickle_hastraits():
1319 1319 c = Pickleable()
1320 1320 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1321 1321 p = pickle.dumps(c, protocol)
1322 1322 c2 = pickle.loads(p)
1323 1323 nt.assert_equal(c2.i, c.i)
1324 1324 nt.assert_equal(c2.j, c.j)
1325 1325
1326 1326 c.i = 5
1327 1327 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1328 1328 p = pickle.dumps(c, protocol)
1329 1329 c2 = pickle.loads(p)
1330 1330 nt.assert_equal(c2.i, c.i)
1331 1331 nt.assert_equal(c2.j, c.j)
1332 1332
1333 1333
1334 1334 def test_hold_trait_notifications():
1335 1335 changes = []
1336 1336
1337 1337 class Test(HasTraits):
1338 1338 a = Integer(0)
1339 1339 b = Integer(0)
1340 1340
1341 1341 def _a_changed(self, name, old, new):
1342 1342 changes.append((old, new))
1343 1343
1344 1344 def _b_validate(self, value, trait):
1345 1345 if value != 0:
1346 1346 raise TraitError('Only 0 is a valid value')
1347 1347 return value
1348 1348
1349 1349 # Test context manager and nesting
1350 1350 t = Test()
1351 1351 with t.hold_trait_notifications():
1352 1352 with t.hold_trait_notifications():
1353 1353 t.a = 1
1354 1354 nt.assert_equal(t.a, 1)
1355 1355 nt.assert_equal(changes, [])
1356 1356 t.a = 2
1357 1357 nt.assert_equal(t.a, 2)
1358 1358 with t.hold_trait_notifications():
1359 1359 t.a = 3
1360 1360 nt.assert_equal(t.a, 3)
1361 1361 nt.assert_equal(changes, [])
1362 1362 t.a = 4
1363 1363 nt.assert_equal(t.a, 4)
1364 1364 nt.assert_equal(changes, [])
1365 1365 t.a = 4
1366 1366 nt.assert_equal(t.a, 4)
1367 1367 nt.assert_equal(changes, [])
1368 1368
1369 1369 nt.assert_equal(changes, [(0, 4)])
1370 1370 # Test roll-back
1371 1371 try:
1372 1372 with t.hold_trait_notifications():
1373 1373 t.b = 1 # raises a Trait error
1374 1374 except:
1375 1375 pass
1376 1376 nt.assert_equal(t.b, 0)
1377 1377
1378 1378
1379 1379 class OrderTraits(HasTraits):
1380 1380 notified = Dict()
1381 1381
1382 1382 a = Unicode()
1383 1383 b = Unicode()
1384 1384 c = Unicode()
1385 1385 d = Unicode()
1386 1386 e = Unicode()
1387 1387 f = Unicode()
1388 1388 g = Unicode()
1389 1389 h = Unicode()
1390 1390 i = Unicode()
1391 1391 j = Unicode()
1392 1392 k = Unicode()
1393 1393 l = Unicode()
1394 1394
1395 1395 def _notify(self, name, old, new):
1396 1396 """check the value of all traits when each trait change is triggered
1397 1397
1398 1398 This verifies that the values are not sensitive
1399 1399 to dict ordering when loaded from kwargs
1400 1400 """
1401 1401 # check the value of the other traits
1402 1402 # when a given trait change notification fires
1403 1403 self.notified[name] = {
1404 1404 c: getattr(self, c) for c in 'abcdefghijkl'
1405 1405 }
1406 1406
1407 1407 def __init__(self, **kwargs):
1408 1408 self.on_trait_change(self._notify)
1409 1409 super(OrderTraits, self).__init__(**kwargs)
1410 1410
1411 1411 def test_notification_order():
1412 1412 d = {c:c for c in 'abcdefghijkl'}
1413 1413 obj = OrderTraits()
1414 1414 nt.assert_equal(obj.notified, {})
1415 1415 obj = OrderTraits(**d)
1416 1416 notifications = {
1417 1417 c: d for c in 'abcdefghijkl'
1418 1418 }
1419 1419 nt.assert_equal(obj.notified, notifications)
1420 1420
1421 1421
1422 class TestEventful(TestCase):
1423
1424 def test_list(self):
1425 """Does the EventfulList work?"""
1426 event_cache = []
1427
1428 class A(HasTraits):
1429 x = EventfulList([c for c in 'abc'])
1430 a = A()
1431 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1432 lambda i, x: event_cache.append('set'), \
1433 lambda i: event_cache.append('del'), \
1434 lambda: event_cache.append('reverse'), \
1435 lambda *p, **k: event_cache.append('sort'))
1436
1437 a.x.remove('c')
1438 # ab
1439 a.x.insert(0, 'z')
1440 # zab
1441 del a.x[1]
1442 # zb
1443 a.x.reverse()
1444 # bz
1445 a.x[1] = 'o'
1446 # bo
1447 a.x.append('a')
1448 # boa
1449 a.x.sort()
1450 # abo
1451
1452 # Were the correct events captured?
1453 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1454
1455 # Is the output correct?
1456 self.assertEqual(a.x, [c for c in 'abo'])
1457
1458 def test_dict(self):
1459 """Does the EventfulDict work?"""
1460 event_cache = []
1461
1462 class A(HasTraits):
1463 x = EventfulDict({c: c for c in 'abc'})
1464 a = A()
1465 a.x.on_events(lambda k, v: event_cache.append('add'), \
1466 lambda k, v: event_cache.append('set'), \
1467 lambda k: event_cache.append('del'))
1468
1469 del a.x['c']
1470 # ab
1471 a.x['z'] = 1
1472 # abz
1473 a.x['z'] = 'z'
1474 # abz
1475 a.x.pop('a')
1476 # bz
1477
1478 # Were the correct events captured?
1479 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1480
1481 # Is the output correct?
1482 self.assertEqual(a.x, {c: c for c in 'bz'})
1483 1422
1484 1423 ###
1485 1424 # Traits for Forward Declaration Tests
1486 1425 ###
1487 1426 class ForwardDeclaredInstanceTrait(HasTraits):
1488 1427
1489 1428 value = ForwardDeclaredInstance('ForwardDeclaredBar', allow_none=True)
1490 1429
1491 1430 class ForwardDeclaredTypeTrait(HasTraits):
1492 1431
1493 1432 value = ForwardDeclaredType('ForwardDeclaredBar', allow_none=True)
1494 1433
1495 1434 class ForwardDeclaredInstanceListTrait(HasTraits):
1496 1435
1497 1436 value = List(ForwardDeclaredInstance('ForwardDeclaredBar'))
1498 1437
1499 1438 class ForwardDeclaredTypeListTrait(HasTraits):
1500 1439
1501 1440 value = List(ForwardDeclaredType('ForwardDeclaredBar'))
1502 1441 ###
1503 1442 # End Traits for Forward Declaration Tests
1504 1443 ###
1505 1444
1506 1445 ###
1507 1446 # Classes for Forward Declaration Tests
1508 1447 ###
1509 1448 class ForwardDeclaredBar(object):
1510 1449 pass
1511 1450
1512 1451 class ForwardDeclaredBarSub(ForwardDeclaredBar):
1513 1452 pass
1514 1453 ###
1515 1454 # End Classes for Forward Declaration Tests
1516 1455 ###
1517 1456
1518 1457 ###
1519 1458 # Forward Declaration Tests
1520 1459 ###
1521 1460 class TestForwardDeclaredInstanceTrait(TraitTestBase):
1522 1461
1523 1462 obj = ForwardDeclaredInstanceTrait()
1524 1463 _default_value = None
1525 1464 _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1526 1465 _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub]
1527 1466
1528 1467 class TestForwardDeclaredTypeTrait(TraitTestBase):
1529 1468
1530 1469 obj = ForwardDeclaredTypeTrait()
1531 1470 _default_value = None
1532 1471 _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub]
1533 1472 _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1534 1473
1535 1474 class TestForwardDeclaredInstanceList(TraitTestBase):
1536 1475
1537 1476 obj = ForwardDeclaredInstanceListTrait()
1538 1477
1539 1478 def test_klass(self):
1540 1479 """Test that the instance klass is properly assigned."""
1541 1480 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1542 1481
1543 1482 _default_value = []
1544 1483 _good_values = [
1545 1484 [ForwardDeclaredBar(), ForwardDeclaredBarSub()],
1546 1485 [],
1547 1486 ]
1548 1487 _bad_values = [
1549 1488 ForwardDeclaredBar(),
1550 1489 [ForwardDeclaredBar(), 3, None],
1551 1490 '1',
1552 1491 # Note that this is the type, not an instance.
1553 1492 [ForwardDeclaredBar],
1554 1493 [None],
1555 1494 None,
1556 1495 ]
1557 1496
1558 1497 class TestForwardDeclaredTypeList(TraitTestBase):
1559 1498
1560 1499 obj = ForwardDeclaredTypeListTrait()
1561 1500
1562 1501 def test_klass(self):
1563 1502 """Test that the instance klass is properly assigned."""
1564 1503 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1565 1504
1566 1505 _default_value = []
1567 1506 _good_values = [
1568 1507 [ForwardDeclaredBar, ForwardDeclaredBarSub],
1569 1508 [],
1570 1509 ]
1571 1510 _bad_values = [
1572 1511 ForwardDeclaredBar,
1573 1512 [ForwardDeclaredBar, 3],
1574 1513 '1',
1575 1514 # Note that this is an instance, not the type.
1576 1515 [ForwardDeclaredBar()],
1577 1516 [None],
1578 1517 None,
1579 1518 ]
1580 1519 ###
1581 1520 # End Forward Declaration Tests
1582 1521 ###
1583 1522
1584 1523 class TestDynamicTraits(TestCase):
1585 1524
1586 1525 def setUp(self):
1587 1526 self._notify1 = []
1588 1527
1589 1528 def notify1(self, name, old, new):
1590 1529 self._notify1.append((name, old, new))
1591 1530
1592 1531 def test_notify_all(self):
1593 1532
1594 1533 class A(HasTraits):
1595 1534 pass
1596 1535
1597 1536 a = A()
1598 1537 self.assertTrue(not hasattr(a, 'x'))
1599 1538 self.assertTrue(not hasattr(a, 'y'))
1600 1539
1601 1540 # Dynamically add trait x.
1602 1541 a.add_trait('x', Int())
1603 1542 self.assertTrue(hasattr(a, 'x'))
1604 1543 self.assertTrue(isinstance(a, (A, )))
1605 1544
1606 1545 # Dynamically add trait y.
1607 1546 a.add_trait('y', Float())
1608 1547 self.assertTrue(hasattr(a, 'y'))
1609 1548 self.assertTrue(isinstance(a, (A, )))
1610 1549 self.assertEqual(a.__class__.__name__, A.__name__)
1611 1550
1612 1551 # Create a new instance and verify that x and y
1613 1552 # aren't defined.
1614 1553 b = A()
1615 1554 self.assertTrue(not hasattr(b, 'x'))
1616 1555 self.assertTrue(not hasattr(b, 'y'))
1617 1556
1618 1557 # Verify that notification works like normal.
1619 1558 a.on_trait_change(self.notify1)
1620 1559 a.x = 0
1621 1560 self.assertEqual(len(self._notify1), 0)
1622 1561 a.y = 0.0
1623 1562 self.assertEqual(len(self._notify1), 0)
1624 1563 a.x = 10
1625 1564 self.assertTrue(('x', 0, 10) in self._notify1)
1626 1565 a.y = 10.0
1627 1566 self.assertTrue(('y', 0.0, 10.0) in self._notify1)
1628 1567 self.assertRaises(TraitError, setattr, a, 'x', 'bad string')
1629 1568 self.assertRaises(TraitError, setattr, a, 'y', 'bad string')
1630 1569 self._notify1 = []
1631 1570 a.on_trait_change(self.notify1, remove=True)
1632 1571 a.x = 20
1633 1572 a.y = 20.0
1634 1573 self.assertEqual(len(self._notify1), 0)
@@ -1,1868 +1,1825 b''
1 1 # encoding: utf-8
2 2 """
3 3 A lightweight Traits like module.
4 4
5 5 This is designed to provide a lightweight, simple, pure Python version of
6 6 many of the capabilities of enthought.traits. This includes:
7 7
8 8 * Validation
9 9 * Type specification with defaults
10 10 * Static and dynamic notification
11 11 * Basic predefined types
12 12 * An API that is similar to enthought.traits
13 13
14 14 We don't support:
15 15
16 16 * Delegation
17 17 * Automatic GUI generation
18 18 * A full set of trait types. Most importantly, we don't provide container
19 19 traits (list, dict, tuple) that can trigger notifications if their
20 20 contents change.
21 21 * API compatibility with enthought.traits
22 22
23 23 There are also some important difference in our design:
24 24
25 25 * enthought.traits does not validate default values. We do.
26 26
27 27 We choose to create this module because we need these capabilities, but
28 28 we need them to be pure Python so they work in all Python implementations,
29 29 including Jython and IronPython.
30 30
31 31 Inheritance diagram:
32 32
33 33 .. inheritance-diagram:: IPython.utils.traitlets
34 34 :parts: 3
35 35 """
36 36
37 37 # Copyright (c) IPython Development Team.
38 38 # Distributed under the terms of the Modified BSD License.
39 39 #
40 40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
41 41 # also under the terms of the Modified BSD License.
42 42
43 43 import contextlib
44 44 import inspect
45 45 import re
46 46 import sys
47 47 import types
48 48 from types import FunctionType
49 49 try:
50 50 from types import ClassType, InstanceType
51 51 ClassTypes = (ClassType, type)
52 52 except:
53 53 ClassTypes = (type,)
54 54 from warnings import warn
55 55
56 56 from IPython.utils import py3compat
57 57 from IPython.utils.getargspec import getargspec
58 58 from IPython.utils.importstring import import_item
59 59 from IPython.utils.py3compat import iteritems, string_types
60 60
61 from . import eventful
62 61 from .sentinel import Sentinel
63 62 SequenceTypes = (list, tuple, set, frozenset)
64 63
65 64 #-----------------------------------------------------------------------------
66 65 # Basic classes
67 66 #-----------------------------------------------------------------------------
68 67
69 68
70 69 NoDefaultSpecified = Sentinel('NoDefaultSpecified', __name__,
71 70 '''
72 71 Used in Traitlets to specify that no defaults are set in kwargs
73 72 '''
74 73 )
75 74
76 75
77 76 class Undefined ( object ): pass
78 77 Undefined = Undefined()
79 78
80 79 class TraitError(Exception):
81 80 pass
82 81
83 82 #-----------------------------------------------------------------------------
84 83 # Utilities
85 84 #-----------------------------------------------------------------------------
86 85
87 86
88 87 def class_of ( object ):
89 88 """ Returns a string containing the class name of an object with the
90 89 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
91 90 'a PlotValue').
92 91 """
93 92 if isinstance( object, py3compat.string_types ):
94 93 return add_article( object )
95 94
96 95 return add_article( object.__class__.__name__ )
97 96
98 97
99 98 def add_article ( name ):
100 99 """ Returns a string containing the correct indefinite article ('a' or 'an')
101 100 prefixed to the specified string.
102 101 """
103 102 if name[:1].lower() in 'aeiou':
104 103 return 'an ' + name
105 104
106 105 return 'a ' + name
107 106
108 107
109 108 def repr_type(obj):
110 109 """ Return a string representation of a value and its type for readable
111 110 error messages.
112 111 """
113 112 the_type = type(obj)
114 113 if (not py3compat.PY3) and the_type is InstanceType:
115 114 # Old-style class.
116 115 the_type = obj.__class__
117 116 msg = '%r %r' % (obj, the_type)
118 117 return msg
119 118
120 119
121 120 def is_trait(t):
122 121 """ Returns whether the given value is an instance or subclass of TraitType.
123 122 """
124 123 return (isinstance(t, TraitType) or
125 124 (isinstance(t, type) and issubclass(t, TraitType)))
126 125
127 126
128 127 def parse_notifier_name(name):
129 128 """Convert the name argument to a list of names.
130 129
131 130 Examples
132 131 --------
133 132
134 133 >>> parse_notifier_name('a')
135 134 ['a']
136 135 >>> parse_notifier_name(['a','b'])
137 136 ['a', 'b']
138 137 >>> parse_notifier_name(None)
139 138 ['anytrait']
140 139 """
141 140 if isinstance(name, string_types):
142 141 return [name]
143 142 elif name is None:
144 143 return ['anytrait']
145 144 elif isinstance(name, (list, tuple)):
146 145 for n in name:
147 146 assert isinstance(n, string_types), "names must be strings"
148 147 return name
149 148
150 149
151 150 class _SimpleTest:
152 151 def __init__ ( self, value ): self.value = value
153 152 def __call__ ( self, test ):
154 153 return test == self.value
155 154 def __repr__(self):
156 155 return "<SimpleTest(%r)" % self.value
157 156 def __str__(self):
158 157 return self.__repr__()
159 158
160 159
161 160 def getmembers(object, predicate=None):
162 161 """A safe version of inspect.getmembers that handles missing attributes.
163 162
164 163 This is useful when there are descriptor based attributes that for
165 164 some reason raise AttributeError even though they exist. This happens
166 165 in zope.inteface with the __provides__ attribute.
167 166 """
168 167 results = []
169 168 for key in dir(object):
170 169 try:
171 170 value = getattr(object, key)
172 171 except AttributeError:
173 172 pass
174 173 else:
175 174 if not predicate or predicate(value):
176 175 results.append((key, value))
177 176 results.sort()
178 177 return results
179 178
180 179 def _validate_link(*tuples):
181 180 """Validate arguments for traitlet link functions"""
182 181 for t in tuples:
183 182 if not len(t) == 2:
184 183 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
185 184 obj, trait_name = t
186 185 if not isinstance(obj, HasTraits):
187 186 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
188 187 if not trait_name in obj.traits():
189 188 raise TypeError("%r has no trait %r" % (obj, trait_name))
190 189
191 190 class link(object):
192 191 """Link traits from different objects together so they remain in sync.
193 192
194 193 Parameters
195 194 ----------
196 195 *args : pairs of objects/attributes
197 196
198 197 Examples
199 198 --------
200 199
201 200 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
202 201 >>> obj1.value = 5 # updates other objects as well
203 202 """
204 203 updating = False
205 204 def __init__(self, *args):
206 205 if len(args) < 2:
207 206 raise TypeError('At least two traitlets must be provided.')
208 207 _validate_link(*args)
209 208
210 209 self.objects = {}
211 210
212 211 initial = getattr(args[0][0], args[0][1])
213 212 for obj, attr in args:
214 213 setattr(obj, attr, initial)
215 214
216 215 callback = self._make_closure(obj, attr)
217 216 obj.on_trait_change(callback, attr)
218 217 self.objects[(obj, attr)] = callback
219 218
220 219 @contextlib.contextmanager
221 220 def _busy_updating(self):
222 221 self.updating = True
223 222 try:
224 223 yield
225 224 finally:
226 225 self.updating = False
227 226
228 227 def _make_closure(self, sending_obj, sending_attr):
229 228 def update(name, old, new):
230 229 self._update(sending_obj, sending_attr, new)
231 230 return update
232 231
233 232 def _update(self, sending_obj, sending_attr, new):
234 233 if self.updating:
235 234 return
236 235 with self._busy_updating():
237 236 for obj, attr in self.objects.keys():
238 237 setattr(obj, attr, new)
239 238
240 239 def unlink(self):
241 240 for key, callback in self.objects.items():
242 241 (obj, attr) = key
243 242 obj.on_trait_change(callback, attr, remove=True)
244 243
245 244 class directional_link(object):
246 245 """Link the trait of a source object with traits of target objects.
247 246
248 247 Parameters
249 248 ----------
250 249 source : pair of object, name
251 250 targets : pairs of objects/attributes
252 251
253 252 Examples
254 253 --------
255 254
256 255 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
257 256 >>> src.value = 5 # updates target objects
258 257 >>> tgt1.value = 6 # does not update other objects
259 258 """
260 259 updating = False
261 260
262 261 def __init__(self, source, *targets):
263 262 if len(targets) < 1:
264 263 raise TypeError('At least two traitlets must be provided.')
265 264 _validate_link(source, *targets)
266 265 self.source = source
267 266 self.targets = targets
268 267
269 268 # Update current value
270 269 src_attr_value = getattr(source[0], source[1])
271 270 for obj, attr in targets:
272 271 setattr(obj, attr, src_attr_value)
273 272
274 273 # Wire
275 274 self.source[0].on_trait_change(self._update, self.source[1])
276 275
277 276 @contextlib.contextmanager
278 277 def _busy_updating(self):
279 278 self.updating = True
280 279 try:
281 280 yield
282 281 finally:
283 282 self.updating = False
284 283
285 284 def _update(self, name, old, new):
286 285 if self.updating:
287 286 return
288 287 with self._busy_updating():
289 288 for obj, attr in self.targets:
290 289 setattr(obj, attr, new)
291 290
292 291 def unlink(self):
293 292 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
294 293 self.source = None
295 294 self.targets = []
296 295
297 296 dlink = directional_link
298 297
299 298
300 299 #-----------------------------------------------------------------------------
301 300 # Base TraitType for all traits
302 301 #-----------------------------------------------------------------------------
303 302
304 303
305 304 class TraitType(object):
306 305 """A base class for all trait descriptors.
307 306
308 307 Notes
309 308 -----
310 309 Our implementation of traits is based on Python's descriptor
311 310 prototol. This class is the base class for all such descriptors. The
312 311 only magic we use is a custom metaclass for the main :class:`HasTraits`
313 312 class that does the following:
314 313
315 314 1. Sets the :attr:`name` attribute of every :class:`TraitType`
316 315 instance in the class dict to the name of the attribute.
317 316 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
318 317 instance in the class dict to the *class* that declared the trait.
319 318 This is used by the :class:`This` trait to allow subclasses to
320 319 accept superclasses for :class:`This` values.
321 320 """
322 321
323 322 metadata = {}
324 323 default_value = Undefined
325 324 allow_none = False
326 325 info_text = 'any value'
327 326
328 327 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
329 328 """Create a TraitType.
330 329 """
331 330 if default_value is not NoDefaultSpecified:
332 331 self.default_value = default_value
333 332 if allow_none is not None:
334 333 self.allow_none = allow_none
335 334
336 335 if 'default' in metadata:
337 336 # Warn the user that they probably meant default_value.
338 337 warn(
339 338 "Parameter 'default' passed to TraitType. "
340 339 "Did you mean 'default_value'?"
341 340 )
342 341
343 342 if len(metadata) > 0:
344 343 if len(self.metadata) > 0:
345 344 self._metadata = self.metadata.copy()
346 345 self._metadata.update(metadata)
347 346 else:
348 347 self._metadata = metadata
349 348 else:
350 349 self._metadata = self.metadata
351 350
352 351 self.init()
353 352
354 353 def init(self):
355 354 pass
356 355
357 356 def get_default_value(self):
358 357 """Create a new instance of the default value."""
359 358 return self.default_value
360 359
361 360 def instance_init(self):
362 361 """Part of the initialization which may depends on the underlying
363 362 HasTraits instance.
364 363
365 364 It is typically overloaded for specific trait types.
366 365
367 366 This method is called by :meth:`HasTraits.__new__` and in the
368 367 :meth:`TraitType.instance_init` method of trait types holding
369 368 other trait types.
370 369 """
371 370 pass
372 371
373 372 def init_default_value(self, obj):
374 373 """Instantiate the default value for the trait type.
375 374
376 375 This method is called by :meth:`TraitType.set_default_value` in the
377 376 case a default value is provided at construction time or later when
378 377 accessing the trait value for the first time in
379 378 :meth:`HasTraits.__get__`.
380 379 """
381 380 value = self.get_default_value()
382 381 value = self._validate(obj, value)
383 382 obj._trait_values[self.name] = value
384 383 return value
385 384
386 385 def set_default_value(self, obj):
387 386 """Set the default value on a per instance basis.
388 387
389 388 This method is called by :meth:`HasTraits.__new__` to instantiate and
390 389 validate the default value. The creation and validation of
391 390 default values must be delayed until the parent :class:`HasTraits`
392 391 class has been instantiated.
393 392 Parameters
394 393 ----------
395 394 obj : :class:`HasTraits` instance
396 395 The parent :class:`HasTraits` instance that has just been
397 396 created.
398 397 """
399 398 # Check for a deferred initializer defined in the same class as the
400 399 # trait declaration or above.
401 400 mro = type(obj).mro()
402 401 meth_name = '_%s_default' % self.name
403 402 for cls in mro[:mro.index(self.this_class)+1]:
404 403 if meth_name in cls.__dict__:
405 404 break
406 405 else:
407 406 # We didn't find one. Do static initialization.
408 407 self.init_default_value(obj)
409 408 return
410 409 # Complete the dynamic initialization.
411 410 obj._trait_dyn_inits[self.name] = meth_name
412 411
413 412 def __get__(self, obj, cls=None):
414 413 """Get the value of the trait by self.name for the instance.
415 414
416 415 Default values are instantiated when :meth:`HasTraits.__new__`
417 416 is called. Thus by the time this method gets called either the
418 417 default value or a user defined value (they called :meth:`__set__`)
419 418 is in the :class:`HasTraits` instance.
420 419 """
421 420 if obj is None:
422 421 return self
423 422 else:
424 423 try:
425 424 value = obj._trait_values[self.name]
426 425 except KeyError:
427 426 # Check for a dynamic initializer.
428 427 if self.name in obj._trait_dyn_inits:
429 428 method = getattr(obj, obj._trait_dyn_inits[self.name])
430 429 value = method()
431 430 # FIXME: Do we really validate here?
432 431 value = self._validate(obj, value)
433 432 obj._trait_values[self.name] = value
434 433 return value
435 434 else:
436 435 return self.init_default_value(obj)
437 436 except Exception:
438 437 # HasTraits should call set_default_value to populate
439 438 # this. So this should never be reached.
440 439 raise TraitError('Unexpected error in TraitType: '
441 440 'default value not set properly')
442 441 else:
443 442 return value
444 443
445 444 def __set__(self, obj, value):
446 445 new_value = self._validate(obj, value)
447 446 try:
448 447 old_value = obj._trait_values[self.name]
449 448 except KeyError:
450 449 old_value = Undefined
451 450
452 451 obj._trait_values[self.name] = new_value
453 452 try:
454 453 silent = bool(old_value == new_value)
455 454 except:
456 455 # if there is an error in comparing, default to notify
457 456 silent = False
458 457 if silent is not True:
459 458 # we explicitly compare silent to True just in case the equality
460 459 # comparison above returns something other than True/False
461 460 obj._notify_trait(self.name, old_value, new_value)
462 461
463 462 def _validate(self, obj, value):
464 463 if value is None and self.allow_none:
465 464 return value
466 465 if hasattr(self, 'validate'):
467 466 value = self.validate(obj, value)
468 467 if obj._cross_validation_lock is False:
469 468 value = self._cross_validate(obj, value)
470 469 return value
471 470
472 471 def _cross_validate(self, obj, value):
473 472 if hasattr(obj, '_%s_validate' % self.name):
474 473 cross_validate = getattr(obj, '_%s_validate' % self.name)
475 474 value = cross_validate(value, self)
476 475 return value
477 476
478 477 def __or__(self, other):
479 478 if isinstance(other, Union):
480 479 return Union([self] + other.trait_types)
481 480 else:
482 481 return Union([self, other])
483 482
484 483 def info(self):
485 484 return self.info_text
486 485
487 486 def error(self, obj, value):
488 487 if obj is not None:
489 488 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
490 489 % (self.name, class_of(obj),
491 490 self.info(), repr_type(value))
492 491 else:
493 492 e = "The '%s' trait must be %s, but a value of %r was specified." \
494 493 % (self.name, self.info(), repr_type(value))
495 494 raise TraitError(e)
496 495
497 496 def get_metadata(self, key, default=None):
498 497 return getattr(self, '_metadata', {}).get(key, default)
499 498
500 499 def set_metadata(self, key, value):
501 500 getattr(self, '_metadata', {})[key] = value
502 501
503 502
504 503 #-----------------------------------------------------------------------------
505 504 # The HasTraits implementation
506 505 #-----------------------------------------------------------------------------
507 506
508 507
509 508 class MetaHasTraits(type):
510 509 """A metaclass for HasTraits.
511 510
512 511 This metaclass makes sure that any TraitType class attributes are
513 512 instantiated and sets their name attribute.
514 513 """
515 514
516 515 def __new__(mcls, name, bases, classdict):
517 516 """Create the HasTraits class.
518 517
519 518 This instantiates all TraitTypes in the class dict and sets their
520 519 :attr:`name` attribute.
521 520 """
522 521 # print "MetaHasTraitlets (mcls, name): ", mcls, name
523 522 # print "MetaHasTraitlets (bases): ", bases
524 523 # print "MetaHasTraitlets (classdict): ", classdict
525 524 for k,v in iteritems(classdict):
526 525 if isinstance(v, TraitType):
527 526 v.name = k
528 527 elif inspect.isclass(v):
529 528 if issubclass(v, TraitType):
530 529 vinst = v()
531 530 vinst.name = k
532 531 classdict[k] = vinst
533 532 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
534 533
535 534 def __init__(cls, name, bases, classdict):
536 535 """Finish initializing the HasTraits class.
537 536
538 537 This sets the :attr:`this_class` attribute of each TraitType in the
539 538 class dict to the newly created class ``cls``.
540 539 """
541 540 for k, v in iteritems(classdict):
542 541 if isinstance(v, TraitType):
543 542 v.this_class = cls
544 543 super(MetaHasTraits, cls).__init__(name, bases, classdict)
545 544
546 545
547 546 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
548 547
549 548 def __new__(cls, *args, **kw):
550 549 # This is needed because object.__new__ only accepts
551 550 # the cls argument.
552 551 new_meth = super(HasTraits, cls).__new__
553 552 if new_meth is object.__new__:
554 553 inst = new_meth(cls)
555 554 else:
556 555 inst = new_meth(cls, **kw)
557 556 inst._trait_values = {}
558 557 inst._trait_notifiers = {}
559 558 inst._trait_dyn_inits = {}
560 559 inst._cross_validation_lock = True
561 560 # Here we tell all the TraitType instances to set their default
562 561 # values on the instance.
563 562 for key in dir(cls):
564 563 # Some descriptors raise AttributeError like zope.interface's
565 564 # __provides__ attributes even though they exist. This causes
566 565 # AttributeErrors even though they are listed in dir(cls).
567 566 try:
568 567 value = getattr(cls, key)
569 568 except AttributeError:
570 569 pass
571 570 else:
572 571 if isinstance(value, TraitType):
573 572 value.instance_init()
574 573 if key not in kw:
575 574 value.set_default_value(inst)
576 575 inst._cross_validation_lock = False
577 576 return inst
578 577
579 578 def __init__(self, *args, **kw):
580 579 # Allow trait values to be set using keyword arguments.
581 580 # We need to use setattr for this to trigger validation and
582 581 # notifications.
583 582 with self.hold_trait_notifications():
584 583 for key, value in iteritems(kw):
585 584 setattr(self, key, value)
586 585
587 586 @contextlib.contextmanager
588 587 def hold_trait_notifications(self):
589 588 """Context manager for bundling trait change notifications and cross
590 589 validation.
591 590
592 591 Use this when doing multiple trait assignments (init, config), to avoid
593 592 race conditions in trait notifiers requesting other trait values.
594 593 All trait notifications will fire after all values have been assigned.
595 594 """
596 595 if self._cross_validation_lock is True:
597 596 yield
598 597 return
599 598 else:
600 599 cache = {}
601 600 _notify_trait = self._notify_trait
602 601
603 602 def merge(previous, current):
604 603 """merges notifications of the form (name, old, value)"""
605 604 if previous is None:
606 605 return current
607 606 else:
608 607 return (current[0], previous[1], current[2])
609 608
610 609 def hold(*a):
611 610 cache[a[0]] = merge(cache.get(a[0]), a)
612 611
613 612 try:
614 613 self._notify_trait = hold
615 614 self._cross_validation_lock = True
616 615 yield
617 616 for name in cache:
618 617 if hasattr(self, '_%s_validate' % name):
619 618 cross_validate = getattr(self, '_%s_validate' % name)
620 619 setattr(self, name, cross_validate(getattr(self, name), self))
621 620 except TraitError as e:
622 621 self._notify_trait = lambda *x: None
623 622 for name in cache:
624 623 if cache[name][1] is not Undefined:
625 624 setattr(self, name, cache[name][1])
626 625 else:
627 626 delattr(self, name)
628 627 cache = {}
629 628 raise e
630 629 finally:
631 630 self._notify_trait = _notify_trait
632 631 self._cross_validation_lock = False
633 632 if isinstance(_notify_trait, types.MethodType):
634 633 # FIXME: remove when support is bumped to 3.4.
635 634 # when original method is restored,
636 635 # remove the redundant value from __dict__
637 636 # (only used to preserve pickleability on Python < 3.4)
638 637 self.__dict__.pop('_notify_trait', None)
639 638
640 639 # trigger delayed notifications
641 640 for v in cache.values():
642 641 self._notify_trait(*v)
643 642
644 643 def _notify_trait(self, name, old_value, new_value):
645 644
646 645 # First dynamic ones
647 646 callables = []
648 647 callables.extend(self._trait_notifiers.get(name,[]))
649 648 callables.extend(self._trait_notifiers.get('anytrait',[]))
650 649
651 650 # Now static ones
652 651 try:
653 652 cb = getattr(self, '_%s_changed' % name)
654 653 except:
655 654 pass
656 655 else:
657 656 callables.append(cb)
658 657
659 658 # Call them all now
660 659 for c in callables:
661 660 # Traits catches and logs errors here. I allow them to raise
662 661 if callable(c):
663 662 argspec = getargspec(c)
664 663
665 664 nargs = len(argspec[0])
666 665 # Bound methods have an additional 'self' argument
667 666 # I don't know how to treat unbound methods, but they
668 667 # can't really be used for callbacks.
669 668 if isinstance(c, types.MethodType):
670 669 offset = -1
671 670 else:
672 671 offset = 0
673 672 if nargs + offset == 0:
674 673 c()
675 674 elif nargs + offset == 1:
676 675 c(name)
677 676 elif nargs + offset == 2:
678 677 c(name, new_value)
679 678 elif nargs + offset == 3:
680 679 c(name, old_value, new_value)
681 680 else:
682 681 raise TraitError('a trait changed callback '
683 682 'must have 0-3 arguments.')
684 683 else:
685 684 raise TraitError('a trait changed callback '
686 685 'must be callable.')
687 686
688 687
689 688 def _add_notifiers(self, handler, name):
690 689 if name not in self._trait_notifiers:
691 690 nlist = []
692 691 self._trait_notifiers[name] = nlist
693 692 else:
694 693 nlist = self._trait_notifiers[name]
695 694 if handler not in nlist:
696 695 nlist.append(handler)
697 696
698 697 def _remove_notifiers(self, handler, name):
699 698 if name in self._trait_notifiers:
700 699 nlist = self._trait_notifiers[name]
701 700 try:
702 701 index = nlist.index(handler)
703 702 except ValueError:
704 703 pass
705 704 else:
706 705 del nlist[index]
707 706
708 707 def on_trait_change(self, handler, name=None, remove=False):
709 708 """Setup a handler to be called when a trait changes.
710 709
711 710 This is used to setup dynamic notifications of trait changes.
712 711
713 712 Static handlers can be created by creating methods on a HasTraits
714 713 subclass with the naming convention '_[traitname]_changed'. Thus,
715 714 to create static handler for the trait 'a', create the method
716 715 _a_changed(self, name, old, new) (fewer arguments can be used, see
717 716 below).
718 717
719 718 Parameters
720 719 ----------
721 720 handler : callable
722 721 A callable that is called when a trait changes. Its
723 722 signature can be handler(), handler(name), handler(name, new)
724 723 or handler(name, old, new).
725 724 name : list, str, None
726 725 If None, the handler will apply to all traits. If a list
727 726 of str, handler will apply to all names in the list. If a
728 727 str, the handler will apply just to that name.
729 728 remove : bool
730 729 If False (the default), then install the handler. If True
731 730 then unintall it.
732 731 """
733 732 if remove:
734 733 names = parse_notifier_name(name)
735 734 for n in names:
736 735 self._remove_notifiers(handler, n)
737 736 else:
738 737 names = parse_notifier_name(name)
739 738 for n in names:
740 739 self._add_notifiers(handler, n)
741 740
742 741 @classmethod
743 742 def class_trait_names(cls, **metadata):
744 743 """Get a list of all the names of this class' traits.
745 744
746 745 This method is just like the :meth:`trait_names` method,
747 746 but is unbound.
748 747 """
749 748 return cls.class_traits(**metadata).keys()
750 749
751 750 @classmethod
752 751 def class_traits(cls, **metadata):
753 752 """Get a `dict` of all the traits of this class. The dictionary
754 753 is keyed on the name and the values are the TraitType objects.
755 754
756 755 This method is just like the :meth:`traits` method, but is unbound.
757 756
758 757 The TraitTypes returned don't know anything about the values
759 758 that the various HasTrait's instances are holding.
760 759
761 760 The metadata kwargs allow functions to be passed in which
762 761 filter traits based on metadata values. The functions should
763 762 take a single value as an argument and return a boolean. If
764 763 any function returns False, then the trait is not included in
765 764 the output. This does not allow for any simple way of
766 765 testing that a metadata name exists and has any
767 766 value because get_metadata returns None if a metadata key
768 767 doesn't exist.
769 768 """
770 769 traits = dict([memb for memb in getmembers(cls) if
771 770 isinstance(memb[1], TraitType)])
772 771
773 772 if len(metadata) == 0:
774 773 return traits
775 774
776 775 for meta_name, meta_eval in metadata.items():
777 776 if type(meta_eval) is not FunctionType:
778 777 metadata[meta_name] = _SimpleTest(meta_eval)
779 778
780 779 result = {}
781 780 for name, trait in traits.items():
782 781 for meta_name, meta_eval in metadata.items():
783 782 if not meta_eval(trait.get_metadata(meta_name)):
784 783 break
785 784 else:
786 785 result[name] = trait
787 786
788 787 return result
789 788
790 789 def trait_names(self, **metadata):
791 790 """Get a list of all the names of this class' traits."""
792 791 return self.traits(**metadata).keys()
793 792
794 793 def traits(self, **metadata):
795 794 """Get a `dict` of all the traits of this class. The dictionary
796 795 is keyed on the name and the values are the TraitType objects.
797 796
798 797 The TraitTypes returned don't know anything about the values
799 798 that the various HasTrait's instances are holding.
800 799
801 800 The metadata kwargs allow functions to be passed in which
802 801 filter traits based on metadata values. The functions should
803 802 take a single value as an argument and return a boolean. If
804 803 any function returns False, then the trait is not included in
805 804 the output. This does not allow for any simple way of
806 805 testing that a metadata name exists and has any
807 806 value because get_metadata returns None if a metadata key
808 807 doesn't exist.
809 808 """
810 809 traits = dict([memb for memb in getmembers(self.__class__) if
811 810 isinstance(memb[1], TraitType)])
812 811
813 812 if len(metadata) == 0:
814 813 return traits
815 814
816 815 for meta_name, meta_eval in metadata.items():
817 816 if type(meta_eval) is not FunctionType:
818 817 metadata[meta_name] = _SimpleTest(meta_eval)
819 818
820 819 result = {}
821 820 for name, trait in traits.items():
822 821 for meta_name, meta_eval in metadata.items():
823 822 if not meta_eval(trait.get_metadata(meta_name)):
824 823 break
825 824 else:
826 825 result[name] = trait
827 826
828 827 return result
829 828
830 829 def trait_metadata(self, traitname, key, default=None):
831 830 """Get metadata values for trait by key."""
832 831 try:
833 832 trait = getattr(self.__class__, traitname)
834 833 except AttributeError:
835 834 raise TraitError("Class %s does not have a trait named %s" %
836 835 (self.__class__.__name__, traitname))
837 836 else:
838 837 return trait.get_metadata(key, default)
839 838
840 839 def add_trait(self, traitname, trait):
841 840 """Dynamically add a trait attribute to the HasTraits instance."""
842 841 self.__class__ = type(self.__class__.__name__, (self.__class__,),
843 842 {traitname: trait})
844 843 trait.set_default_value(self)
845 844
846 845 #-----------------------------------------------------------------------------
847 846 # Actual TraitTypes implementations/subclasses
848 847 #-----------------------------------------------------------------------------
849 848
850 849 #-----------------------------------------------------------------------------
851 850 # TraitTypes subclasses for handling classes and instances of classes
852 851 #-----------------------------------------------------------------------------
853 852
854 853
855 854 class ClassBasedTraitType(TraitType):
856 855 """
857 856 A trait with error reporting and string -> type resolution for Type,
858 857 Instance and This.
859 858 """
860 859
861 860 def _resolve_string(self, string):
862 861 """
863 862 Resolve a string supplied for a type into an actual object.
864 863 """
865 864 return import_item(string)
866 865
867 866 def error(self, obj, value):
868 867 kind = type(value)
869 868 if (not py3compat.PY3) and kind is InstanceType:
870 869 msg = 'class %s' % value.__class__.__name__
871 870 else:
872 871 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
873 872
874 873 if obj is not None:
875 874 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
876 875 % (self.name, class_of(obj),
877 876 self.info(), msg)
878 877 else:
879 878 e = "The '%s' trait must be %s, but a value of %r was specified." \
880 879 % (self.name, self.info(), msg)
881 880
882 881 raise TraitError(e)
883 882
884 883
885 884 class Type(ClassBasedTraitType):
886 885 """A trait whose value must be a subclass of a specified class."""
887 886
888 887 def __init__ (self, default_value=None, klass=None, **metadata):
889 888 """Construct a Type trait
890 889
891 890 A Type trait specifies that its values must be subclasses of
892 891 a particular class.
893 892
894 893 If only ``default_value`` is given, it is used for the ``klass`` as
895 894 well.
896 895
897 896 Parameters
898 897 ----------
899 898 default_value : class, str or None
900 899 The default value must be a subclass of klass. If an str,
901 900 the str must be a fully specified class name, like 'foo.bar.Bah'.
902 901 The string is resolved into real class, when the parent
903 902 :class:`HasTraits` class is instantiated.
904 903 klass : class, str, None
905 904 Values of this trait must be a subclass of klass. The klass
906 905 may be specified in a string like: 'foo.bar.MyClass'.
907 906 The string is resolved into real class, when the parent
908 907 :class:`HasTraits` class is instantiated.
909 908 allow_none : bool [ default True ]
910 909 Indicates whether None is allowed as an assignable value. Even if
911 910 ``False``, the default value may be ``None``.
912 911 """
913 912 if default_value is None:
914 913 if klass is None:
915 914 klass = object
916 915 elif klass is None:
917 916 klass = default_value
918 917
919 918 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
920 919 raise TraitError("A Type trait must specify a class.")
921 920
922 921 self.klass = klass
923 922
924 923 super(Type, self).__init__(default_value, **metadata)
925 924
926 925 def validate(self, obj, value):
927 926 """Validates that the value is a valid object instance."""
928 927 if isinstance(value, py3compat.string_types):
929 928 try:
930 929 value = self._resolve_string(value)
931 930 except ImportError:
932 931 raise TraitError("The '%s' trait of %s instance must be a type, but "
933 932 "%r could not be imported" % (self.name, obj, value))
934 933 try:
935 934 if issubclass(value, self.klass):
936 935 return value
937 936 except:
938 937 pass
939 938
940 939 self.error(obj, value)
941 940
942 941 def info(self):
943 942 """ Returns a description of the trait."""
944 943 if isinstance(self.klass, py3compat.string_types):
945 944 klass = self.klass
946 945 else:
947 946 klass = self.klass.__name__
948 947 result = 'a subclass of ' + klass
949 948 if self.allow_none:
950 949 return result + ' or None'
951 950 return result
952 951
953 952 def instance_init(self):
954 953 self._resolve_classes()
955 954 super(Type, self).instance_init()
956 955
957 956 def _resolve_classes(self):
958 957 if isinstance(self.klass, py3compat.string_types):
959 958 self.klass = self._resolve_string(self.klass)
960 959 if isinstance(self.default_value, py3compat.string_types):
961 960 self.default_value = self._resolve_string(self.default_value)
962 961
963 962 def get_default_value(self):
964 963 return self.default_value
965 964
966 965
967 966 class DefaultValueGenerator(object):
968 967 """A class for generating new default value instances."""
969 968
970 969 def __init__(self, *args, **kw):
971 970 self.args = args
972 971 self.kw = kw
973 972
974 973 def generate(self, klass):
975 974 return klass(*self.args, **self.kw)
976 975
977 976
978 977 class Instance(ClassBasedTraitType):
979 978 """A trait whose value must be an instance of a specified class.
980 979
981 980 The value can also be an instance of a subclass of the specified class.
982 981
983 982 Subclasses can declare default classes by overriding the klass attribute
984 983 """
985 984
986 985 klass = None
987 986
988 987 def __init__(self, klass=None, args=None, kw=None, **metadata):
989 988 """Construct an Instance trait.
990 989
991 990 This trait allows values that are instances of a particular
992 991 class or its subclasses. Our implementation is quite different
993 992 from that of enthough.traits as we don't allow instances to be used
994 993 for klass and we handle the ``args`` and ``kw`` arguments differently.
995 994
996 995 Parameters
997 996 ----------
998 997 klass : class, str
999 998 The class that forms the basis for the trait. Class names
1000 999 can also be specified as strings, like 'foo.bar.Bar'.
1001 1000 args : tuple
1002 1001 Positional arguments for generating the default value.
1003 1002 kw : dict
1004 1003 Keyword arguments for generating the default value.
1005 1004 allow_none : bool [default True]
1006 1005 Indicates whether None is allowed as a value.
1007 1006
1008 1007 Notes
1009 1008 -----
1010 1009 If both ``args`` and ``kw`` are None, then the default value is None.
1011 1010 If ``args`` is a tuple and ``kw`` is a dict, then the default is
1012 1011 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
1013 1012 None, the None is replaced by ``()`` or ``{}``, respectively.
1014 1013 """
1015 1014 if klass is None:
1016 1015 klass = self.klass
1017 1016
1018 1017 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
1019 1018 self.klass = klass
1020 1019 else:
1021 1020 raise TraitError('The klass attribute must be a class'
1022 1021 ' not: %r' % klass)
1023 1022
1024 1023 # self.klass is a class, so handle default_value
1025 1024 if args is None and kw is None:
1026 1025 default_value = None
1027 1026 else:
1028 1027 if args is None:
1029 1028 # kw is not None
1030 1029 args = ()
1031 1030 elif kw is None:
1032 1031 # args is not None
1033 1032 kw = {}
1034 1033
1035 1034 if not isinstance(kw, dict):
1036 1035 raise TraitError("The 'kw' argument must be a dict or None.")
1037 1036 if not isinstance(args, tuple):
1038 1037 raise TraitError("The 'args' argument must be a tuple or None.")
1039 1038
1040 1039 default_value = DefaultValueGenerator(*args, **kw)
1041 1040
1042 1041 super(Instance, self).__init__(default_value, **metadata)
1043 1042
1044 1043 def validate(self, obj, value):
1045 1044 if isinstance(value, self.klass):
1046 1045 return value
1047 1046 else:
1048 1047 self.error(obj, value)
1049 1048
1050 1049 def info(self):
1051 1050 if isinstance(self.klass, py3compat.string_types):
1052 1051 klass = self.klass
1053 1052 else:
1054 1053 klass = self.klass.__name__
1055 1054 result = class_of(klass)
1056 1055 if self.allow_none:
1057 1056 return result + ' or None'
1058 1057
1059 1058 return result
1060 1059
1061 1060 def instance_init(self):
1062 1061 self._resolve_classes()
1063 1062 super(Instance, self).instance_init()
1064 1063
1065 1064 def _resolve_classes(self):
1066 1065 if isinstance(self.klass, py3compat.string_types):
1067 1066 self.klass = self._resolve_string(self.klass)
1068 1067
1069 1068 def get_default_value(self):
1070 1069 """Instantiate a default value instance.
1071 1070
1072 1071 This is called when the containing HasTraits classes'
1073 1072 :meth:`__new__` method is called to ensure that a unique instance
1074 1073 is created for each HasTraits instance.
1075 1074 """
1076 1075 dv = self.default_value
1077 1076 if isinstance(dv, DefaultValueGenerator):
1078 1077 return dv.generate(self.klass)
1079 1078 else:
1080 1079 return dv
1081 1080
1082 1081
1083 1082 class ForwardDeclaredMixin(object):
1084 1083 """
1085 1084 Mixin for forward-declared versions of Instance and Type.
1086 1085 """
1087 1086 def _resolve_string(self, string):
1088 1087 """
1089 1088 Find the specified class name by looking for it in the module in which
1090 1089 our this_class attribute was defined.
1091 1090 """
1092 1091 modname = self.this_class.__module__
1093 1092 return import_item('.'.join([modname, string]))
1094 1093
1095 1094
1096 1095 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1097 1096 """
1098 1097 Forward-declared version of Type.
1099 1098 """
1100 1099 pass
1101 1100
1102 1101
1103 1102 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1104 1103 """
1105 1104 Forward-declared version of Instance.
1106 1105 """
1107 1106 pass
1108 1107
1109 1108
1110 1109 class This(ClassBasedTraitType):
1111 1110 """A trait for instances of the class containing this trait.
1112 1111
1113 1112 Because how how and when class bodies are executed, the ``This``
1114 1113 trait can only have a default value of None. This, and because we
1115 1114 always validate default values, ``allow_none`` is *always* true.
1116 1115 """
1117 1116
1118 1117 info_text = 'an instance of the same type as the receiver or None'
1119 1118
1120 1119 def __init__(self, **metadata):
1121 1120 super(This, self).__init__(None, **metadata)
1122 1121
1123 1122 def validate(self, obj, value):
1124 1123 # What if value is a superclass of obj.__class__? This is
1125 1124 # complicated if it was the superclass that defined the This
1126 1125 # trait.
1127 1126 if isinstance(value, self.this_class) or (value is None):
1128 1127 return value
1129 1128 else:
1130 1129 self.error(obj, value)
1131 1130
1132 1131
1133 1132 class Union(TraitType):
1134 1133 """A trait type representing a Union type."""
1135 1134
1136 1135 def __init__(self, trait_types, **metadata):
1137 1136 """Construct a Union trait.
1138 1137
1139 1138 This trait allows values that are allowed by at least one of the
1140 1139 specified trait types. A Union traitlet cannot have metadata on
1141 1140 its own, besides the metadata of the listed types.
1142 1141
1143 1142 Parameters
1144 1143 ----------
1145 1144 trait_types: sequence
1146 1145 The list of trait types of length at least 1.
1147 1146
1148 1147 Notes
1149 1148 -----
1150 1149 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1151 1150 with the validation function of Float, then Bool, and finally Int.
1152 1151 """
1153 1152 self.trait_types = trait_types
1154 1153 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1155 1154 self.default_value = self.trait_types[0].get_default_value()
1156 1155 super(Union, self).__init__(**metadata)
1157 1156
1158 1157 def instance_init(self):
1159 1158 for trait_type in self.trait_types:
1160 1159 trait_type.name = self.name
1161 1160 trait_type.this_class = self.this_class
1162 1161 trait_type.instance_init()
1163 1162 super(Union, self).instance_init()
1164 1163
1165 1164 def validate(self, obj, value):
1166 1165 for trait_type in self.trait_types:
1167 1166 try:
1168 1167 v = trait_type._validate(obj, value)
1169 1168 self._metadata = trait_type._metadata
1170 1169 return v
1171 1170 except TraitError:
1172 1171 continue
1173 1172 self.error(obj, value)
1174 1173
1175 1174 def __or__(self, other):
1176 1175 if isinstance(other, Union):
1177 1176 return Union(self.trait_types + other.trait_types)
1178 1177 else:
1179 1178 return Union(self.trait_types + [other])
1180 1179
1181 1180 #-----------------------------------------------------------------------------
1182 1181 # Basic TraitTypes implementations/subclasses
1183 1182 #-----------------------------------------------------------------------------
1184 1183
1185 1184
1186 1185 class Any(TraitType):
1187 1186 default_value = None
1188 1187 info_text = 'any value'
1189 1188
1190 1189
1191 1190 class Int(TraitType):
1192 1191 """An int trait."""
1193 1192
1194 1193 default_value = 0
1195 1194 info_text = 'an int'
1196 1195
1197 1196 def validate(self, obj, value):
1198 1197 if isinstance(value, int):
1199 1198 return value
1200 1199 self.error(obj, value)
1201 1200
1202 1201 class CInt(Int):
1203 1202 """A casting version of the int trait."""
1204 1203
1205 1204 def validate(self, obj, value):
1206 1205 try:
1207 1206 return int(value)
1208 1207 except:
1209 1208 self.error(obj, value)
1210 1209
1211 1210 if py3compat.PY3:
1212 1211 Long, CLong = Int, CInt
1213 1212 Integer = Int
1214 1213 else:
1215 1214 class Long(TraitType):
1216 1215 """A long integer trait."""
1217 1216
1218 1217 default_value = 0
1219 1218 info_text = 'a long'
1220 1219
1221 1220 def validate(self, obj, value):
1222 1221 if isinstance(value, long):
1223 1222 return value
1224 1223 if isinstance(value, int):
1225 1224 return long(value)
1226 1225 self.error(obj, value)
1227 1226
1228 1227
1229 1228 class CLong(Long):
1230 1229 """A casting version of the long integer trait."""
1231 1230
1232 1231 def validate(self, obj, value):
1233 1232 try:
1234 1233 return long(value)
1235 1234 except:
1236 1235 self.error(obj, value)
1237 1236
1238 1237 class Integer(TraitType):
1239 1238 """An integer trait.
1240 1239
1241 1240 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1242 1241
1243 1242 default_value = 0
1244 1243 info_text = 'an integer'
1245 1244
1246 1245 def validate(self, obj, value):
1247 1246 if isinstance(value, int):
1248 1247 return value
1249 1248 if isinstance(value, long):
1250 1249 # downcast longs that fit in int:
1251 1250 # note that int(n > sys.maxint) returns a long, so
1252 1251 # we don't need a condition on this cast
1253 1252 return int(value)
1254 1253 if sys.platform == "cli":
1255 1254 from System import Int64
1256 1255 if isinstance(value, Int64):
1257 1256 return int(value)
1258 1257 self.error(obj, value)
1259 1258
1260 1259
1261 1260 class Float(TraitType):
1262 1261 """A float trait."""
1263 1262
1264 1263 default_value = 0.0
1265 1264 info_text = 'a float'
1266 1265
1267 1266 def validate(self, obj, value):
1268 1267 if isinstance(value, float):
1269 1268 return value
1270 1269 if isinstance(value, int):
1271 1270 return float(value)
1272 1271 self.error(obj, value)
1273 1272
1274 1273
1275 1274 class CFloat(Float):
1276 1275 """A casting version of the float trait."""
1277 1276
1278 1277 def validate(self, obj, value):
1279 1278 try:
1280 1279 return float(value)
1281 1280 except:
1282 1281 self.error(obj, value)
1283 1282
1284 1283 class Complex(TraitType):
1285 1284 """A trait for complex numbers."""
1286 1285
1287 1286 default_value = 0.0 + 0.0j
1288 1287 info_text = 'a complex number'
1289 1288
1290 1289 def validate(self, obj, value):
1291 1290 if isinstance(value, complex):
1292 1291 return value
1293 1292 if isinstance(value, (float, int)):
1294 1293 return complex(value)
1295 1294 self.error(obj, value)
1296 1295
1297 1296
1298 1297 class CComplex(Complex):
1299 1298 """A casting version of the complex number trait."""
1300 1299
1301 1300 def validate (self, obj, value):
1302 1301 try:
1303 1302 return complex(value)
1304 1303 except:
1305 1304 self.error(obj, value)
1306 1305
1307 1306 # We should always be explicit about whether we're using bytes or unicode, both
1308 1307 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1309 1308 # we don't have a Str type.
1310 1309 class Bytes(TraitType):
1311 1310 """A trait for byte strings."""
1312 1311
1313 1312 default_value = b''
1314 1313 info_text = 'a bytes object'
1315 1314
1316 1315 def validate(self, obj, value):
1317 1316 if isinstance(value, bytes):
1318 1317 return value
1319 1318 self.error(obj, value)
1320 1319
1321 1320
1322 1321 class CBytes(Bytes):
1323 1322 """A casting version of the byte string trait."""
1324 1323
1325 1324 def validate(self, obj, value):
1326 1325 try:
1327 1326 return bytes(value)
1328 1327 except:
1329 1328 self.error(obj, value)
1330 1329
1331 1330
1332 1331 class Unicode(TraitType):
1333 1332 """A trait for unicode strings."""
1334 1333
1335 1334 default_value = u''
1336 1335 info_text = 'a unicode string'
1337 1336
1338 1337 def validate(self, obj, value):
1339 1338 if isinstance(value, py3compat.unicode_type):
1340 1339 return value
1341 1340 if isinstance(value, bytes):
1342 1341 try:
1343 1342 return value.decode('ascii', 'strict')
1344 1343 except UnicodeDecodeError:
1345 1344 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1346 1345 raise TraitError(msg.format(value, self.name, class_of(obj)))
1347 1346 self.error(obj, value)
1348 1347
1349 1348
1350 1349 class CUnicode(Unicode):
1351 1350 """A casting version of the unicode trait."""
1352 1351
1353 1352 def validate(self, obj, value):
1354 1353 try:
1355 1354 return py3compat.unicode_type(value)
1356 1355 except:
1357 1356 self.error(obj, value)
1358 1357
1359 1358
1360 1359 class ObjectName(TraitType):
1361 1360 """A string holding a valid object name in this version of Python.
1362 1361
1363 1362 This does not check that the name exists in any scope."""
1364 1363 info_text = "a valid object identifier in Python"
1365 1364
1366 1365 if py3compat.PY3:
1367 1366 # Python 3:
1368 1367 coerce_str = staticmethod(lambda _,s: s)
1369 1368
1370 1369 else:
1371 1370 # Python 2:
1372 1371 def coerce_str(self, obj, value):
1373 1372 "In Python 2, coerce ascii-only unicode to str"
1374 1373 if isinstance(value, unicode):
1375 1374 try:
1376 1375 return str(value)
1377 1376 except UnicodeEncodeError:
1378 1377 self.error(obj, value)
1379 1378 return value
1380 1379
1381 1380 def validate(self, obj, value):
1382 1381 value = self.coerce_str(obj, value)
1383 1382
1384 1383 if isinstance(value, string_types) and py3compat.isidentifier(value):
1385 1384 return value
1386 1385 self.error(obj, value)
1387 1386
1388 1387 class DottedObjectName(ObjectName):
1389 1388 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1390 1389 def validate(self, obj, value):
1391 1390 value = self.coerce_str(obj, value)
1392 1391
1393 1392 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1394 1393 return value
1395 1394 self.error(obj, value)
1396 1395
1397 1396
1398 1397 class Bool(TraitType):
1399 1398 """A boolean (True, False) trait."""
1400 1399
1401 1400 default_value = False
1402 1401 info_text = 'a boolean'
1403 1402
1404 1403 def validate(self, obj, value):
1405 1404 if isinstance(value, bool):
1406 1405 return value
1407 1406 self.error(obj, value)
1408 1407
1409 1408
1410 1409 class CBool(Bool):
1411 1410 """A casting version of the boolean trait."""
1412 1411
1413 1412 def validate(self, obj, value):
1414 1413 try:
1415 1414 return bool(value)
1416 1415 except:
1417 1416 self.error(obj, value)
1418 1417
1419 1418
1420 1419 class Enum(TraitType):
1421 1420 """An enum that whose value must be in a given sequence."""
1422 1421
1423 1422 def __init__(self, values, default_value=None, **metadata):
1424 1423 self.values = values
1425 1424 super(Enum, self).__init__(default_value, **metadata)
1426 1425
1427 1426 def validate(self, obj, value):
1428 1427 if value in self.values:
1429 1428 return value
1430 1429 self.error(obj, value)
1431 1430
1432 1431 def info(self):
1433 1432 """ Returns a description of the trait."""
1434 1433 result = 'any of ' + repr(self.values)
1435 1434 if self.allow_none:
1436 1435 return result + ' or None'
1437 1436 return result
1438 1437
1439 1438 class CaselessStrEnum(Enum):
1440 1439 """An enum of strings that are caseless in validate."""
1441 1440
1442 1441 def validate(self, obj, value):
1443 1442 if not isinstance(value, py3compat.string_types):
1444 1443 self.error(obj, value)
1445 1444
1446 1445 for v in self.values:
1447 1446 if v.lower() == value.lower():
1448 1447 return v
1449 1448 self.error(obj, value)
1450 1449
1451 1450 class Container(Instance):
1452 1451 """An instance of a container (list, set, etc.)
1453 1452
1454 1453 To be subclassed by overriding klass.
1455 1454 """
1456 1455 klass = None
1457 1456 _cast_types = ()
1458 1457 _valid_defaults = SequenceTypes
1459 1458 _trait = None
1460 1459
1461 1460 def __init__(self, trait=None, default_value=None, **metadata):
1462 1461 """Create a container trait type from a list, set, or tuple.
1463 1462
1464 1463 The default value is created by doing ``List(default_value)``,
1465 1464 which creates a copy of the ``default_value``.
1466 1465
1467 1466 ``trait`` can be specified, which restricts the type of elements
1468 1467 in the container to that TraitType.
1469 1468
1470 1469 If only one arg is given and it is not a Trait, it is taken as
1471 1470 ``default_value``:
1472 1471
1473 1472 ``c = List([1,2,3])``
1474 1473
1475 1474 Parameters
1476 1475 ----------
1477 1476
1478 1477 trait : TraitType [ optional ]
1479 1478 the type for restricting the contents of the Container. If unspecified,
1480 1479 types are not checked.
1481 1480
1482 1481 default_value : SequenceType [ optional ]
1483 1482 The default value for the Trait. Must be list/tuple/set, and
1484 1483 will be cast to the container type.
1485 1484
1486 1485 allow_none : bool [ default False ]
1487 1486 Whether to allow the value to be None
1488 1487
1489 1488 **metadata : any
1490 1489 further keys for extensions to the Trait (e.g. config)
1491 1490
1492 1491 """
1493 1492 # allow List([values]):
1494 1493 if default_value is None and not is_trait(trait):
1495 1494 default_value = trait
1496 1495 trait = None
1497 1496
1498 1497 if default_value is None:
1499 1498 args = ()
1500 1499 elif isinstance(default_value, self._valid_defaults):
1501 1500 args = (default_value,)
1502 1501 else:
1503 1502 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1504 1503
1505 1504 if is_trait(trait):
1506 1505 self._trait = trait() if isinstance(trait, type) else trait
1507 1506 self._trait.name = 'element'
1508 1507 elif trait is not None:
1509 1508 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1510 1509
1511 1510 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1512 1511
1513 1512 def element_error(self, obj, element, validator):
1514 1513 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1515 1514 % (self.name, class_of(obj), validator.info(), repr_type(element))
1516 1515 raise TraitError(e)
1517 1516
1518 1517 def validate(self, obj, value):
1519 1518 if isinstance(value, self._cast_types):
1520 1519 value = self.klass(value)
1521 1520 value = super(Container, self).validate(obj, value)
1522 1521 if value is None:
1523 1522 return value
1524 1523
1525 1524 value = self.validate_elements(obj, value)
1526 1525
1527 1526 return value
1528 1527
1529 1528 def validate_elements(self, obj, value):
1530 1529 validated = []
1531 1530 if self._trait is None or isinstance(self._trait, Any):
1532 1531 return value
1533 1532 for v in value:
1534 1533 try:
1535 1534 v = self._trait._validate(obj, v)
1536 1535 except TraitError:
1537 1536 self.element_error(obj, v, self._trait)
1538 1537 else:
1539 1538 validated.append(v)
1540 1539 return self.klass(validated)
1541 1540
1542 1541 def instance_init(self):
1543 1542 if isinstance(self._trait, TraitType):
1544 1543 self._trait.this_class = self.this_class
1545 1544 self._trait.instance_init()
1546 1545 super(Container, self).instance_init()
1547 1546
1548 1547
1549 1548 class List(Container):
1550 1549 """An instance of a Python list."""
1551 1550 klass = list
1552 1551 _cast_types = (tuple,)
1553 1552
1554 1553 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1555 1554 """Create a List trait type from a list, set, or tuple.
1556 1555
1557 1556 The default value is created by doing ``List(default_value)``,
1558 1557 which creates a copy of the ``default_value``.
1559 1558
1560 1559 ``trait`` can be specified, which restricts the type of elements
1561 1560 in the container to that TraitType.
1562 1561
1563 1562 If only one arg is given and it is not a Trait, it is taken as
1564 1563 ``default_value``:
1565 1564
1566 1565 ``c = List([1,2,3])``
1567 1566
1568 1567 Parameters
1569 1568 ----------
1570 1569
1571 1570 trait : TraitType [ optional ]
1572 1571 the type for restricting the contents of the Container. If unspecified,
1573 1572 types are not checked.
1574 1573
1575 1574 default_value : SequenceType [ optional ]
1576 1575 The default value for the Trait. Must be list/tuple/set, and
1577 1576 will be cast to the container type.
1578 1577
1579 1578 minlen : Int [ default 0 ]
1580 1579 The minimum length of the input list
1581 1580
1582 1581 maxlen : Int [ default sys.maxsize ]
1583 1582 The maximum length of the input list
1584 1583
1585 1584 allow_none : bool [ default False ]
1586 1585 Whether to allow the value to be None
1587 1586
1588 1587 **metadata : any
1589 1588 further keys for extensions to the Trait (e.g. config)
1590 1589
1591 1590 """
1592 1591 self._minlen = minlen
1593 1592 self._maxlen = maxlen
1594 1593 super(List, self).__init__(trait=trait, default_value=default_value,
1595 1594 **metadata)
1596 1595
1597 1596 def length_error(self, obj, value):
1598 1597 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1599 1598 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1600 1599 raise TraitError(e)
1601 1600
1602 1601 def validate_elements(self, obj, value):
1603 1602 length = len(value)
1604 1603 if length < self._minlen or length > self._maxlen:
1605 1604 self.length_error(obj, value)
1606 1605
1607 1606 return super(List, self).validate_elements(obj, value)
1608 1607
1609 1608 def validate(self, obj, value):
1610 1609 value = super(List, self).validate(obj, value)
1611 1610 value = self.validate_elements(obj, value)
1612 1611 return value
1613 1612
1614 1613
1615 1614 class Set(List):
1616 1615 """An instance of a Python set."""
1617 1616 klass = set
1618 1617 _cast_types = (tuple, list)
1619 1618
1620 1619
1621 1620 class Tuple(Container):
1622 1621 """An instance of a Python tuple."""
1623 1622 klass = tuple
1624 1623 _cast_types = (list,)
1625 1624
1626 1625 def __init__(self, *traits, **metadata):
1627 1626 """Tuple(*traits, default_value=None, **medatata)
1628 1627
1629 1628 Create a tuple from a list, set, or tuple.
1630 1629
1631 1630 Create a fixed-type tuple with Traits:
1632 1631
1633 1632 ``t = Tuple(Int, Str, CStr)``
1634 1633
1635 1634 would be length 3, with Int,Str,CStr for each element.
1636 1635
1637 1636 If only one arg is given and it is not a Trait, it is taken as
1638 1637 default_value:
1639 1638
1640 1639 ``t = Tuple((1,2,3))``
1641 1640
1642 1641 Otherwise, ``default_value`` *must* be specified by keyword.
1643 1642
1644 1643 Parameters
1645 1644 ----------
1646 1645
1647 1646 *traits : TraitTypes [ optional ]
1648 1647 the types for restricting the contents of the Tuple. If unspecified,
1649 1648 types are not checked. If specified, then each positional argument
1650 1649 corresponds to an element of the tuple. Tuples defined with traits
1651 1650 are of fixed length.
1652 1651
1653 1652 default_value : SequenceType [ optional ]
1654 1653 The default value for the Tuple. Must be list/tuple/set, and
1655 1654 will be cast to a tuple. If `traits` are specified, the
1656 1655 `default_value` must conform to the shape and type they specify.
1657 1656
1658 1657 allow_none : bool [ default False ]
1659 1658 Whether to allow the value to be None
1660 1659
1661 1660 **metadata : any
1662 1661 further keys for extensions to the Trait (e.g. config)
1663 1662
1664 1663 """
1665 1664 default_value = metadata.pop('default_value', None)
1666 1665
1667 1666 # allow Tuple((values,)):
1668 1667 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1669 1668 default_value = traits[0]
1670 1669 traits = ()
1671 1670
1672 1671 if default_value is None:
1673 1672 args = ()
1674 1673 elif isinstance(default_value, self._valid_defaults):
1675 1674 args = (default_value,)
1676 1675 else:
1677 1676 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1678 1677
1679 1678 self._traits = []
1680 1679 for trait in traits:
1681 1680 t = trait() if isinstance(trait, type) else trait
1682 1681 t.name = 'element'
1683 1682 self._traits.append(t)
1684 1683
1685 1684 if self._traits and default_value is None:
1686 1685 # don't allow default to be an empty container if length is specified
1687 1686 args = None
1688 1687 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1689 1688
1690 1689 def validate_elements(self, obj, value):
1691 1690 if not self._traits:
1692 1691 # nothing to validate
1693 1692 return value
1694 1693 if len(value) != len(self._traits):
1695 1694 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1696 1695 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1697 1696 raise TraitError(e)
1698 1697
1699 1698 validated = []
1700 1699 for t, v in zip(self._traits, value):
1701 1700 try:
1702 1701 v = t._validate(obj, v)
1703 1702 except TraitError:
1704 1703 self.element_error(obj, v, t)
1705 1704 else:
1706 1705 validated.append(v)
1707 1706 return tuple(validated)
1708 1707
1709 1708 def instance_init(self):
1710 1709 for trait in self._traits:
1711 1710 if isinstance(trait, TraitType):
1712 1711 trait.this_class = self.this_class
1713 1712 trait.instance_init()
1714 1713 super(Container, self).instance_init()
1715 1714
1716 1715
1717 1716 class Dict(Instance):
1718 1717 """An instance of a Python dict."""
1719 1718 _trait = None
1720 1719
1721 1720 def __init__(self, trait=None, default_value=NoDefaultSpecified, **metadata):
1722 1721 """Create a dict trait type from a dict.
1723 1722
1724 1723 The default value is created by doing ``dict(default_value)``,
1725 1724 which creates a copy of the ``default_value``.
1726 1725
1727 1726 trait : TraitType [ optional ]
1728 1727 the type for restricting the contents of the Container. If unspecified,
1729 1728 types are not checked.
1730 1729
1731 1730 default_value : SequenceType [ optional ]
1732 1731 The default value for the Dict. Must be dict, tuple, or None, and
1733 1732 will be cast to a dict if not None. If `trait` is specified, the
1734 1733 `default_value` must conform to the constraints it specifies.
1735 1734
1736 1735 allow_none : bool [ default False ]
1737 1736 Whether to allow the value to be None
1738 1737
1739 1738 """
1740 1739 if default_value is NoDefaultSpecified and trait is not None:
1741 1740 if not is_trait(trait):
1742 1741 default_value = trait
1743 1742 trait = None
1744 1743 if default_value is NoDefaultSpecified:
1745 1744 default_value = {}
1746 1745 if default_value is None:
1747 1746 args = None
1748 1747 elif isinstance(default_value, dict):
1749 1748 args = (default_value,)
1750 1749 elif isinstance(default_value, SequenceTypes):
1751 1750 args = (default_value,)
1752 1751 else:
1753 1752 raise TypeError('default value of Dict was %s' % default_value)
1754 1753
1755 1754 if is_trait(trait):
1756 1755 self._trait = trait() if isinstance(trait, type) else trait
1757 1756 self._trait.name = 'element'
1758 1757 elif trait is not None:
1759 1758 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1760 1759
1761 1760 super(Dict,self).__init__(klass=dict, args=args, **metadata)
1762 1761
1763 1762 def element_error(self, obj, element, validator):
1764 1763 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1765 1764 % (self.name, class_of(obj), validator.info(), repr_type(element))
1766 1765 raise TraitError(e)
1767 1766
1768 1767 def validate(self, obj, value):
1769 1768 value = super(Dict, self).validate(obj, value)
1770 1769 if value is None:
1771 1770 return value
1772 1771 value = self.validate_elements(obj, value)
1773 1772 return value
1774 1773
1775 1774 def validate_elements(self, obj, value):
1776 1775 if self._trait is None or isinstance(self._trait, Any):
1777 1776 return value
1778 1777 validated = {}
1779 1778 for key in value:
1780 1779 v = value[key]
1781 1780 try:
1782 1781 v = self._trait._validate(obj, v)
1783 1782 except TraitError:
1784 1783 self.element_error(obj, v, self._trait)
1785 1784 else:
1786 1785 validated[key] = v
1787 1786 return self.klass(validated)
1788 1787
1789 1788 def instance_init(self):
1790 1789 if isinstance(self._trait, TraitType):
1791 1790 self._trait.this_class = self.this_class
1792 1791 self._trait.instance_init()
1793 1792 super(Dict, self).instance_init()
1794 1793
1795 1794
1796 class EventfulDict(Instance):
1797 """An instance of an EventfulDict."""
1798
1799 def __init__(self, default_value={}, **metadata):
1800 """Create a EventfulDict trait type from a dict.
1801
1802 The default value is created by doing
1803 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1804 ``default_value``.
1805 """
1806 if default_value is None:
1807 args = None
1808 elif isinstance(default_value, dict):
1809 args = (default_value,)
1810 elif isinstance(default_value, SequenceTypes):
1811 args = (default_value,)
1812 else:
1813 raise TypeError('default value of EventfulDict was %s' % default_value)
1814
1815 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1816 **metadata)
1817
1818
1819 class EventfulList(Instance):
1820 """An instance of an EventfulList."""
1821
1822 def __init__(self, default_value=None, **metadata):
1823 """Create a EventfulList trait type from a dict.
1824
1825 The default value is created by doing
1826 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1827 ``default_value``.
1828 """
1829 if default_value is None:
1830 args = ((),)
1831 else:
1832 args = (default_value,)
1833
1834 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1835 **metadata)
1836
1837
1838 1795 class TCPAddress(TraitType):
1839 1796 """A trait for an (ip, port) tuple.
1840 1797
1841 1798 This allows for both IPv4 IP addresses as well as hostnames.
1842 1799 """
1843 1800
1844 1801 default_value = ('127.0.0.1', 0)
1845 1802 info_text = 'an (ip, port) tuple'
1846 1803
1847 1804 def validate(self, obj, value):
1848 1805 if isinstance(value, tuple):
1849 1806 if len(value) == 2:
1850 1807 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1851 1808 port = value[1]
1852 1809 if port >= 0 and port <= 65535:
1853 1810 return value
1854 1811 self.error(obj, value)
1855 1812
1856 1813 class CRegExp(TraitType):
1857 1814 """A casting compiled regular expression trait.
1858 1815
1859 1816 Accepts both strings and compiled regular expressions. The resulting
1860 1817 attribute will be a compiled regular expression."""
1861 1818
1862 1819 info_text = 'a regular expression'
1863 1820
1864 1821 def validate(self, obj, value):
1865 1822 try:
1866 1823 return re.compile(value)
1867 1824 except:
1868 1825 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now