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