##// END OF EJS Templates
Add ObjectName and DottedObjectName trait types for referring to Python identifiers.
Thomas Kluyver -
Show More
@@ -1,593 +1,593 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Display formatters.
3 3
4 4
5 5 Authors:
6 6
7 7 * Robert Kern
8 8 * Brian Granger
9 9 """
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (c) 2010, IPython Development Team.
12 12 #
13 13 # Distributed under the terms of the Modified BSD License.
14 14 #
15 15 # The full license is in the file COPYING.txt, distributed with this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # Stdlib imports
23 23 import abc
24 24 import sys
25 25 # We must use StringIO, as cStringIO doesn't handle unicode properly.
26 26 from StringIO import StringIO
27 27
28 28 # Our own imports
29 29 from IPython.config.configurable import Configurable
30 30 from IPython.lib import pretty
31 from IPython.utils.traitlets import Bool, Dict, Int, Unicode, CUnicode
31 from IPython.utils.traitlets import Bool, Dict, Int, Unicode, CUnicode, ObjectName
32 32
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # The main DisplayFormatter class
36 36 #-----------------------------------------------------------------------------
37 37
38 38
39 39 class DisplayFormatter(Configurable):
40 40
41 41 # When set to true only the default plain text formatter will be used.
42 42 plain_text_only = Bool(False, config=True)
43 43
44 44 # A dict of formatter whose keys are format types (MIME types) and whose
45 45 # values are subclasses of BaseFormatter.
46 46 formatters = Dict(config=True)
47 47 def _formatters_default(self):
48 48 """Activate the default formatters."""
49 49 formatter_classes = [
50 50 PlainTextFormatter,
51 51 HTMLFormatter,
52 52 SVGFormatter,
53 53 PNGFormatter,
54 54 LatexFormatter,
55 55 JSONFormatter,
56 56 JavascriptFormatter
57 57 ]
58 58 d = {}
59 59 for cls in formatter_classes:
60 60 f = cls(config=self.config)
61 61 d[f.format_type] = f
62 62 return d
63 63
64 64 def format(self, obj, include=None, exclude=None):
65 65 """Return a format data dict for an object.
66 66
67 67 By default all format types will be computed.
68 68
69 69 The following MIME types are currently implemented:
70 70
71 71 * text/plain
72 72 * text/html
73 73 * text/latex
74 74 * application/json
75 75 * image/png
76 76 * immage/svg+xml
77 77
78 78 Parameters
79 79 ----------
80 80 obj : object
81 81 The Python object whose format data will be computed.
82 82 include : list or tuple, optional
83 83 A list of format type strings (MIME types) to include in the
84 84 format data dict. If this is set *only* the format types included
85 85 in this list will be computed.
86 86 exclude : list or tuple, optional
87 87 A list of format type string (MIME types) to exclue in the format
88 88 data dict. If this is set all format types will be computed,
89 89 except for those included in this argument.
90 90
91 91 Returns
92 92 -------
93 93 format_dict : dict
94 94 A dictionary of key/value pairs, one or each format that was
95 95 generated for the object. The keys are the format types, which
96 96 will usually be MIME type strings and the values and JSON'able
97 97 data structure containing the raw data for the representation in
98 98 that format.
99 99 """
100 100 format_dict = {}
101 101
102 102 # If plain text only is active
103 103 if self.plain_text_only:
104 104 formatter = self.formatters['text/plain']
105 105 try:
106 106 data = formatter(obj)
107 107 except:
108 108 # FIXME: log the exception
109 109 raise
110 110 if data is not None:
111 111 format_dict['text/plain'] = data
112 112 return format_dict
113 113
114 114 for format_type, formatter in self.formatters.items():
115 115 if include is not None:
116 116 if format_type not in include:
117 117 continue
118 118 if exclude is not None:
119 119 if format_type in exclude:
120 120 continue
121 121 try:
122 122 data = formatter(obj)
123 123 except:
124 124 # FIXME: log the exception
125 125 raise
126 126 if data is not None:
127 127 format_dict[format_type] = data
128 128 return format_dict
129 129
130 130 @property
131 131 def format_types(self):
132 132 """Return the format types (MIME types) of the active formatters."""
133 133 return self.formatters.keys()
134 134
135 135
136 136 #-----------------------------------------------------------------------------
137 137 # Formatters for specific format types (text, html, svg, etc.)
138 138 #-----------------------------------------------------------------------------
139 139
140 140
141 141 class FormatterABC(object):
142 142 """ Abstract base class for Formatters.
143 143
144 144 A formatter is a callable class that is responsible for computing the
145 145 raw format data for a particular format type (MIME type). For example,
146 146 an HTML formatter would have a format type of `text/html` and would return
147 147 the HTML representation of the object when called.
148 148 """
149 149 __metaclass__ = abc.ABCMeta
150 150
151 151 # The format type of the data returned, usually a MIME type.
152 152 format_type = 'text/plain'
153 153
154 154 # Is the formatter enabled...
155 155 enabled = True
156 156
157 157 @abc.abstractmethod
158 158 def __call__(self, obj):
159 159 """Return a JSON'able representation of the object.
160 160
161 161 If the object cannot be formatted by this formatter, then return None
162 162 """
163 163 try:
164 164 return repr(obj)
165 165 except TypeError:
166 166 return None
167 167
168 168
169 169 class BaseFormatter(Configurable):
170 170 """A base formatter class that is configurable.
171 171
172 172 This formatter should usually be used as the base class of all formatters.
173 173 It is a traited :class:`Configurable` class and includes an extensible
174 174 API for users to determine how their objects are formatted. The following
175 175 logic is used to find a function to format an given object.
176 176
177 177 1. The object is introspected to see if it has a method with the name
178 178 :attr:`print_method`. If is does, that object is passed to that method
179 179 for formatting.
180 180 2. If no print method is found, three internal dictionaries are consulted
181 181 to find print method: :attr:`singleton_printers`, :attr:`type_printers`
182 182 and :attr:`deferred_printers`.
183 183
184 184 Users should use these dictionaries to register functions that will be
185 185 used to compute the format data for their objects (if those objects don't
186 186 have the special print methods). The easiest way of using these
187 187 dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
188 188 methods.
189 189
190 190 If no function/callable is found to compute the format data, ``None`` is
191 191 returned and this format type is not used.
192 192 """
193 193
194 194 format_type = Unicode('text/plain')
195 195
196 196 enabled = Bool(True, config=True)
197 197
198 print_method = Unicode('__repr__')
198 print_method = ObjectName('__repr__')
199 199
200 200 # The singleton printers.
201 201 # Maps the IDs of the builtin singleton objects to the format functions.
202 202 singleton_printers = Dict(config=True)
203 203 def _singleton_printers_default(self):
204 204 return {}
205 205
206 206 # The type-specific printers.
207 207 # Map type objects to the format functions.
208 208 type_printers = Dict(config=True)
209 209 def _type_printers_default(self):
210 210 return {}
211 211
212 212 # The deferred-import type-specific printers.
213 213 # Map (modulename, classname) pairs to the format functions.
214 214 deferred_printers = Dict(config=True)
215 215 def _deferred_printers_default(self):
216 216 return {}
217 217
218 218 def __call__(self, obj):
219 219 """Compute the format for an object."""
220 220 if self.enabled:
221 221 obj_id = id(obj)
222 222 try:
223 223 obj_class = getattr(obj, '__class__', None) or type(obj)
224 224 # First try to find registered singleton printers for the type.
225 225 try:
226 226 printer = self.singleton_printers[obj_id]
227 227 except (TypeError, KeyError):
228 228 pass
229 229 else:
230 230 return printer(obj)
231 231 # Next look for type_printers.
232 232 for cls in pretty._get_mro(obj_class):
233 233 if cls in self.type_printers:
234 234 return self.type_printers[cls](obj)
235 235 else:
236 236 printer = self._in_deferred_types(cls)
237 237 if printer is not None:
238 238 return printer(obj)
239 239 # Finally look for special method names.
240 240 if hasattr(obj_class, self.print_method):
241 241 printer = getattr(obj_class, self.print_method)
242 242 return printer(obj)
243 243 return None
244 244 except Exception:
245 245 pass
246 246 else:
247 247 return None
248 248
249 249 def for_type(self, typ, func):
250 250 """Add a format function for a given type.
251 251
252 252 Parameters
253 253 -----------
254 254 typ : class
255 255 The class of the object that will be formatted using `func`.
256 256 func : callable
257 257 The callable that will be called to compute the format data. The
258 258 call signature of this function is simple, it must take the
259 259 object to be formatted and return the raw data for the given
260 260 format. Subclasses may use a different call signature for the
261 261 `func` argument.
262 262 """
263 263 oldfunc = self.type_printers.get(typ, None)
264 264 if func is not None:
265 265 # To support easy restoration of old printers, we need to ignore
266 266 # Nones.
267 267 self.type_printers[typ] = func
268 268 return oldfunc
269 269
270 270 def for_type_by_name(self, type_module, type_name, func):
271 271 """Add a format function for a type specified by the full dotted
272 272 module and name of the type, rather than the type of the object.
273 273
274 274 Parameters
275 275 ----------
276 276 type_module : str
277 277 The full dotted name of the module the type is defined in, like
278 278 ``numpy``.
279 279 type_name : str
280 280 The name of the type (the class name), like ``dtype``
281 281 func : callable
282 282 The callable that will be called to compute the format data. The
283 283 call signature of this function is simple, it must take the
284 284 object to be formatted and return the raw data for the given
285 285 format. Subclasses may use a different call signature for the
286 286 `func` argument.
287 287 """
288 288 key = (type_module, type_name)
289 289 oldfunc = self.deferred_printers.get(key, None)
290 290 if func is not None:
291 291 # To support easy restoration of old printers, we need to ignore
292 292 # Nones.
293 293 self.deferred_printers[key] = func
294 294 return oldfunc
295 295
296 296 def _in_deferred_types(self, cls):
297 297 """
298 298 Check if the given class is specified in the deferred type registry.
299 299
300 300 Returns the printer from the registry if it exists, and None if the
301 301 class is not in the registry. Successful matches will be moved to the
302 302 regular type registry for future use.
303 303 """
304 304 mod = getattr(cls, '__module__', None)
305 305 name = getattr(cls, '__name__', None)
306 306 key = (mod, name)
307 307 printer = None
308 308 if key in self.deferred_printers:
309 309 # Move the printer over to the regular registry.
310 310 printer = self.deferred_printers.pop(key)
311 311 self.type_printers[cls] = printer
312 312 return printer
313 313
314 314
315 315 class PlainTextFormatter(BaseFormatter):
316 316 """The default pretty-printer.
317 317
318 318 This uses :mod:`IPython.external.pretty` to compute the format data of
319 319 the object. If the object cannot be pretty printed, :func:`repr` is used.
320 320 See the documentation of :mod:`IPython.external.pretty` for details on
321 321 how to write pretty printers. Here is a simple example::
322 322
323 323 def dtype_pprinter(obj, p, cycle):
324 324 if cycle:
325 325 return p.text('dtype(...)')
326 326 if hasattr(obj, 'fields'):
327 327 if obj.fields is None:
328 328 p.text(repr(obj))
329 329 else:
330 330 p.begin_group(7, 'dtype([')
331 331 for i, field in enumerate(obj.descr):
332 332 if i > 0:
333 333 p.text(',')
334 334 p.breakable()
335 335 p.pretty(field)
336 336 p.end_group(7, '])')
337 337 """
338 338
339 339 # The format type of data returned.
340 340 format_type = Unicode('text/plain')
341 341
342 342 # This subclass ignores this attribute as it always need to return
343 343 # something.
344 344 enabled = Bool(True, config=False)
345 345
346 346 # Look for a _repr_pretty_ methods to use for pretty printing.
347 print_method = Unicode('_repr_pretty_')
347 print_method = ObjectName('_repr_pretty_')
348 348
349 349 # Whether to pretty-print or not.
350 350 pprint = Bool(True, config=True)
351 351
352 352 # Whether to be verbose or not.
353 353 verbose = Bool(False, config=True)
354 354
355 355 # The maximum width.
356 356 max_width = Int(79, config=True)
357 357
358 358 # The newline character.
359 359 newline = Unicode('\n', config=True)
360 360
361 361 # format-string for pprinting floats
362 362 float_format = Unicode('%r')
363 363 # setter for float precision, either int or direct format-string
364 364 float_precision = CUnicode('', config=True)
365 365
366 366 def _float_precision_changed(self, name, old, new):
367 367 """float_precision changed, set float_format accordingly.
368 368
369 369 float_precision can be set by int or str.
370 370 This will set float_format, after interpreting input.
371 371 If numpy has been imported, numpy print precision will also be set.
372 372
373 373 integer `n` sets format to '%.nf', otherwise, format set directly.
374 374
375 375 An empty string returns to defaults (repr for float, 8 for numpy).
376 376
377 377 This parameter can be set via the '%precision' magic.
378 378 """
379 379
380 380 if '%' in new:
381 381 # got explicit format string
382 382 fmt = new
383 383 try:
384 384 fmt%3.14159
385 385 except Exception:
386 386 raise ValueError("Precision must be int or format string, not %r"%new)
387 387 elif new:
388 388 # otherwise, should be an int
389 389 try:
390 390 i = int(new)
391 391 assert i >= 0
392 392 except ValueError:
393 393 raise ValueError("Precision must be int or format string, not %r"%new)
394 394 except AssertionError:
395 395 raise ValueError("int precision must be non-negative, not %r"%i)
396 396
397 397 fmt = '%%.%if'%i
398 398 if 'numpy' in sys.modules:
399 399 # set numpy precision if it has been imported
400 400 import numpy
401 401 numpy.set_printoptions(precision=i)
402 402 else:
403 403 # default back to repr
404 404 fmt = '%r'
405 405 if 'numpy' in sys.modules:
406 406 import numpy
407 407 # numpy default is 8
408 408 numpy.set_printoptions(precision=8)
409 409 self.float_format = fmt
410 410
411 411 # Use the default pretty printers from IPython.external.pretty.
412 412 def _singleton_printers_default(self):
413 413 return pretty._singleton_pprinters.copy()
414 414
415 415 def _type_printers_default(self):
416 416 d = pretty._type_pprinters.copy()
417 417 d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
418 418 return d
419 419
420 420 def _deferred_printers_default(self):
421 421 return pretty._deferred_type_pprinters.copy()
422 422
423 423 #### FormatterABC interface ####
424 424
425 425 def __call__(self, obj):
426 426 """Compute the pretty representation of the object."""
427 427 if not self.pprint:
428 428 try:
429 429 return repr(obj)
430 430 except TypeError:
431 431 return ''
432 432 else:
433 433 # This uses use StringIO, as cStringIO doesn't handle unicode.
434 434 stream = StringIO()
435 435 printer = pretty.RepresentationPrinter(stream, self.verbose,
436 436 self.max_width, self.newline,
437 437 singleton_pprinters=self.singleton_printers,
438 438 type_pprinters=self.type_printers,
439 439 deferred_pprinters=self.deferred_printers)
440 440 printer.pretty(obj)
441 441 printer.flush()
442 442 return stream.getvalue()
443 443
444 444
445 445 class HTMLFormatter(BaseFormatter):
446 446 """An HTML formatter.
447 447
448 448 To define the callables that compute the HTML representation of your
449 449 objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
450 450 or :meth:`for_type_by_name` methods to register functions that handle
451 451 this.
452 452
453 453 The return value of this formatter should be a valid HTML snippet that
454 454 could be injected into an existing DOM. It should *not* include the
455 455 ```<html>`` or ```<body>`` tags.
456 456 """
457 457 format_type = Unicode('text/html')
458 458
459 print_method = Unicode('_repr_html_')
459 print_method = ObjectName('_repr_html_')
460 460
461 461
462 462 class SVGFormatter(BaseFormatter):
463 463 """An SVG formatter.
464 464
465 465 To define the callables that compute the SVG representation of your
466 466 objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
467 467 or :meth:`for_type_by_name` methods to register functions that handle
468 468 this.
469 469
470 470 The return value of this formatter should be valid SVG enclosed in
471 471 ```<svg>``` tags, that could be injected into an existing DOM. It should
472 472 *not* include the ```<html>`` or ```<body>`` tags.
473 473 """
474 474 format_type = Unicode('image/svg+xml')
475 475
476 print_method = Unicode('_repr_svg_')
476 print_method = ObjectName('_repr_svg_')
477 477
478 478
479 479 class PNGFormatter(BaseFormatter):
480 480 """A PNG formatter.
481 481
482 482 To define the callables that compute the PNG representation of your
483 483 objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
484 484 or :meth:`for_type_by_name` methods to register functions that handle
485 485 this.
486 486
487 487 The return value of this formatter should be raw PNG data, *not*
488 488 base64 encoded.
489 489 """
490 490 format_type = Unicode('image/png')
491 491
492 print_method = Unicode('_repr_png_')
492 print_method = ObjectName('_repr_png_')
493 493
494 494
495 495 class LatexFormatter(BaseFormatter):
496 496 """A LaTeX formatter.
497 497
498 498 To define the callables that compute the LaTeX representation of your
499 499 objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
500 500 or :meth:`for_type_by_name` methods to register functions that handle
501 501 this.
502 502
503 503 The return value of this formatter should be a valid LaTeX equation,
504 504 enclosed in either ```$``` or ```$$```.
505 505 """
506 506 format_type = Unicode('text/latex')
507 507
508 print_method = Unicode('_repr_latex_')
508 print_method = ObjectName('_repr_latex_')
509 509
510 510
511 511 class JSONFormatter(BaseFormatter):
512 512 """A JSON string formatter.
513 513
514 514 To define the callables that compute the JSON string representation of
515 515 your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
516 516 or :meth:`for_type_by_name` methods to register functions that handle
517 517 this.
518 518
519 519 The return value of this formatter should be a valid JSON string.
520 520 """
521 521 format_type = Unicode('application/json')
522 522
523 print_method = Unicode('_repr_json_')
523 print_method = ObjectName('_repr_json_')
524 524
525 525
526 526 class JavascriptFormatter(BaseFormatter):
527 527 """A Javascript formatter.
528 528
529 529 To define the callables that compute the Javascript representation of
530 530 your objects, define a :meth:`_repr_javascript_` method or use the
531 531 :meth:`for_type` or :meth:`for_type_by_name` methods to register functions
532 532 that handle this.
533 533
534 534 The return value of this formatter should be valid Javascript code and
535 535 should *not* be enclosed in ```<script>``` tags.
536 536 """
537 537 format_type = Unicode('application/javascript')
538 538
539 print_method = Unicode('_repr_javascript_')
539 print_method = ObjectName('_repr_javascript_')
540 540
541 541 FormatterABC.register(BaseFormatter)
542 542 FormatterABC.register(PlainTextFormatter)
543 543 FormatterABC.register(HTMLFormatter)
544 544 FormatterABC.register(SVGFormatter)
545 545 FormatterABC.register(PNGFormatter)
546 546 FormatterABC.register(LatexFormatter)
547 547 FormatterABC.register(JSONFormatter)
548 548 FormatterABC.register(JavascriptFormatter)
549 549
550 550
551 551 def format_display_data(obj, include=None, exclude=None):
552 552 """Return a format data dict for an object.
553 553
554 554 By default all format types will be computed.
555 555
556 556 The following MIME types are currently implemented:
557 557
558 558 * text/plain
559 559 * text/html
560 560 * text/latex
561 561 * application/json
562 562 * image/png
563 563 * immage/svg+xml
564 564
565 565 Parameters
566 566 ----------
567 567 obj : object
568 568 The Python object whose format data will be computed.
569 569
570 570 Returns
571 571 -------
572 572 format_dict : dict
573 573 A dictionary of key/value pairs, one or each format that was
574 574 generated for the object. The keys are the format types, which
575 575 will usually be MIME type strings and the values and JSON'able
576 576 data structure containing the raw data for the representation in
577 577 that format.
578 578 include : list or tuple, optional
579 579 A list of format type strings (MIME types) to include in the
580 580 format data dict. If this is set *only* the format types included
581 581 in this list will be computed.
582 582 exclude : list or tuple, optional
583 583 A list of format type string (MIME types) to exclue in the format
584 584 data dict. If this is set all format types will be computed,
585 585 except for those included in this argument.
586 586 """
587 587 from IPython.core.interactiveshell import InteractiveShell
588 588
589 589 InteractiveShell.instance().display_formatter.format(
590 590 obj,
591 591 include,
592 592 exclude
593 593 )
@@ -1,814 +1,847 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.utils.traitlets.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Enthought, Inc. Some of the code in this file comes from enthought.traits
10 10 and is licensed under the BSD license. Also, many of the ideas also come
11 11 from enthought.traits even though our implementation is very different.
12 12 """
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Copyright (C) 2008-2009 The IPython Development Team
16 16 #
17 17 # Distributed under the terms of the BSD License. The full license is in
18 18 # the file COPYING, distributed as part of this software.
19 19 #-----------------------------------------------------------------------------
20 20
21 21 #-----------------------------------------------------------------------------
22 22 # Imports
23 23 #-----------------------------------------------------------------------------
24 24
25 import sys
25 26 from unittest import TestCase
26 27
27 28 from IPython.utils.traitlets import (
28 29 HasTraits, MetaHasTraits, TraitType, Any, CBytes,
29 30 Int, Long, Float, Complex, Bytes, Unicode, TraitError,
30 Undefined, Type, This, Instance, TCPAddress, List, Tuple
31 Undefined, Type, This, Instance, TCPAddress, List, Tuple,
32 ObjectName, DottedObjectName
31 33 )
32 34
33 35
34 36 #-----------------------------------------------------------------------------
35 37 # Helper classes for testing
36 38 #-----------------------------------------------------------------------------
37 39
38 40
39 41 class HasTraitsStub(HasTraits):
40 42
41 43 def _notify_trait(self, name, old, new):
42 44 self._notify_name = name
43 45 self._notify_old = old
44 46 self._notify_new = new
45 47
46 48
47 49 #-----------------------------------------------------------------------------
48 50 # Test classes
49 51 #-----------------------------------------------------------------------------
50 52
51 53
52 54 class TestTraitType(TestCase):
53 55
54 56 def test_get_undefined(self):
55 57 class A(HasTraits):
56 58 a = TraitType
57 59 a = A()
58 60 self.assertEquals(a.a, Undefined)
59 61
60 62 def test_set(self):
61 63 class A(HasTraitsStub):
62 64 a = TraitType
63 65
64 66 a = A()
65 67 a.a = 10
66 68 self.assertEquals(a.a, 10)
67 69 self.assertEquals(a._notify_name, 'a')
68 70 self.assertEquals(a._notify_old, Undefined)
69 71 self.assertEquals(a._notify_new, 10)
70 72
71 73 def test_validate(self):
72 74 class MyTT(TraitType):
73 75 def validate(self, inst, value):
74 76 return -1
75 77 class A(HasTraitsStub):
76 78 tt = MyTT
77 79
78 80 a = A()
79 81 a.tt = 10
80 82 self.assertEquals(a.tt, -1)
81 83
82 84 def test_default_validate(self):
83 85 class MyIntTT(TraitType):
84 86 def validate(self, obj, value):
85 87 if isinstance(value, int):
86 88 return value
87 89 self.error(obj, value)
88 90 class A(HasTraits):
89 91 tt = MyIntTT(10)
90 92 a = A()
91 93 self.assertEquals(a.tt, 10)
92 94
93 95 # Defaults are validated when the HasTraits is instantiated
94 96 class B(HasTraits):
95 97 tt = MyIntTT('bad default')
96 98 self.assertRaises(TraitError, B)
97 99
98 100 def test_is_valid_for(self):
99 101 class MyTT(TraitType):
100 102 def is_valid_for(self, value):
101 103 return True
102 104 class A(HasTraits):
103 105 tt = MyTT
104 106
105 107 a = A()
106 108 a.tt = 10
107 109 self.assertEquals(a.tt, 10)
108 110
109 111 def test_value_for(self):
110 112 class MyTT(TraitType):
111 113 def value_for(self, value):
112 114 return 20
113 115 class A(HasTraits):
114 116 tt = MyTT
115 117
116 118 a = A()
117 119 a.tt = 10
118 120 self.assertEquals(a.tt, 20)
119 121
120 122 def test_info(self):
121 123 class A(HasTraits):
122 124 tt = TraitType
123 125 a = A()
124 126 self.assertEquals(A.tt.info(), 'any value')
125 127
126 128 def test_error(self):
127 129 class A(HasTraits):
128 130 tt = TraitType
129 131 a = A()
130 132 self.assertRaises(TraitError, A.tt.error, a, 10)
131 133
132 134 def test_dynamic_initializer(self):
133 135 class A(HasTraits):
134 136 x = Int(10)
135 137 def _x_default(self):
136 138 return 11
137 139 class B(A):
138 140 x = Int(20)
139 141 class C(A):
140 142 def _x_default(self):
141 143 return 21
142 144
143 145 a = A()
144 146 self.assertEquals(a._trait_values, {})
145 147 self.assertEquals(a._trait_dyn_inits.keys(), ['x'])
146 148 self.assertEquals(a.x, 11)
147 149 self.assertEquals(a._trait_values, {'x': 11})
148 150 b = B()
149 151 self.assertEquals(b._trait_values, {'x': 20})
150 152 self.assertEquals(a._trait_dyn_inits.keys(), ['x'])
151 153 self.assertEquals(b.x, 20)
152 154 c = C()
153 155 self.assertEquals(c._trait_values, {})
154 156 self.assertEquals(a._trait_dyn_inits.keys(), ['x'])
155 157 self.assertEquals(c.x, 21)
156 158 self.assertEquals(c._trait_values, {'x': 21})
157 159 # Ensure that the base class remains unmolested when the _default
158 160 # initializer gets overridden in a subclass.
159 161 a = A()
160 162 c = C()
161 163 self.assertEquals(a._trait_values, {})
162 164 self.assertEquals(a._trait_dyn_inits.keys(), ['x'])
163 165 self.assertEquals(a.x, 11)
164 166 self.assertEquals(a._trait_values, {'x': 11})
165 167
166 168
167 169
168 170 class TestHasTraitsMeta(TestCase):
169 171
170 172 def test_metaclass(self):
171 173 self.assertEquals(type(HasTraits), MetaHasTraits)
172 174
173 175 class A(HasTraits):
174 176 a = Int
175 177
176 178 a = A()
177 179 self.assertEquals(type(a.__class__), MetaHasTraits)
178 180 self.assertEquals(a.a,0)
179 181 a.a = 10
180 182 self.assertEquals(a.a,10)
181 183
182 184 class B(HasTraits):
183 185 b = Int()
184 186
185 187 b = B()
186 188 self.assertEquals(b.b,0)
187 189 b.b = 10
188 190 self.assertEquals(b.b,10)
189 191
190 192 class C(HasTraits):
191 193 c = Int(30)
192 194
193 195 c = C()
194 196 self.assertEquals(c.c,30)
195 197 c.c = 10
196 198 self.assertEquals(c.c,10)
197 199
198 200 def test_this_class(self):
199 201 class A(HasTraits):
200 202 t = This()
201 203 tt = This()
202 204 class B(A):
203 205 tt = This()
204 206 ttt = This()
205 207 self.assertEquals(A.t.this_class, A)
206 208 self.assertEquals(B.t.this_class, A)
207 209 self.assertEquals(B.tt.this_class, B)
208 210 self.assertEquals(B.ttt.this_class, B)
209 211
210 212 class TestHasTraitsNotify(TestCase):
211 213
212 214 def setUp(self):
213 215 self._notify1 = []
214 216 self._notify2 = []
215 217
216 218 def notify1(self, name, old, new):
217 219 self._notify1.append((name, old, new))
218 220
219 221 def notify2(self, name, old, new):
220 222 self._notify2.append((name, old, new))
221 223
222 224 def test_notify_all(self):
223 225
224 226 class A(HasTraits):
225 227 a = Int
226 228 b = Float
227 229
228 230 a = A()
229 231 a.on_trait_change(self.notify1)
230 232 a.a = 0
231 233 self.assertEquals(len(self._notify1),0)
232 234 a.b = 0.0
233 235 self.assertEquals(len(self._notify1),0)
234 236 a.a = 10
235 237 self.assert_(('a',0,10) in self._notify1)
236 238 a.b = 10.0
237 239 self.assert_(('b',0.0,10.0) in self._notify1)
238 240 self.assertRaises(TraitError,setattr,a,'a','bad string')
239 241 self.assertRaises(TraitError,setattr,a,'b','bad string')
240 242 self._notify1 = []
241 243 a.on_trait_change(self.notify1,remove=True)
242 244 a.a = 20
243 245 a.b = 20.0
244 246 self.assertEquals(len(self._notify1),0)
245 247
246 248 def test_notify_one(self):
247 249
248 250 class A(HasTraits):
249 251 a = Int
250 252 b = Float
251 253
252 254 a = A()
253 255 a.on_trait_change(self.notify1, 'a')
254 256 a.a = 0
255 257 self.assertEquals(len(self._notify1),0)
256 258 a.a = 10
257 259 self.assert_(('a',0,10) in self._notify1)
258 260 self.assertRaises(TraitError,setattr,a,'a','bad string')
259 261
260 262 def test_subclass(self):
261 263
262 264 class A(HasTraits):
263 265 a = Int
264 266
265 267 class B(A):
266 268 b = Float
267 269
268 270 b = B()
269 271 self.assertEquals(b.a,0)
270 272 self.assertEquals(b.b,0.0)
271 273 b.a = 100
272 274 b.b = 100.0
273 275 self.assertEquals(b.a,100)
274 276 self.assertEquals(b.b,100.0)
275 277
276 278 def test_notify_subclass(self):
277 279
278 280 class A(HasTraits):
279 281 a = Int
280 282
281 283 class B(A):
282 284 b = Float
283 285
284 286 b = B()
285 287 b.on_trait_change(self.notify1, 'a')
286 288 b.on_trait_change(self.notify2, 'b')
287 289 b.a = 0
288 290 b.b = 0.0
289 291 self.assertEquals(len(self._notify1),0)
290 292 self.assertEquals(len(self._notify2),0)
291 293 b.a = 10
292 294 b.b = 10.0
293 295 self.assert_(('a',0,10) in self._notify1)
294 296 self.assert_(('b',0.0,10.0) in self._notify2)
295 297
296 298 def test_static_notify(self):
297 299
298 300 class A(HasTraits):
299 301 a = Int
300 302 _notify1 = []
301 303 def _a_changed(self, name, old, new):
302 304 self._notify1.append((name, old, new))
303 305
304 306 a = A()
305 307 a.a = 0
306 308 # This is broken!!!
307 309 self.assertEquals(len(a._notify1),0)
308 310 a.a = 10
309 311 self.assert_(('a',0,10) in a._notify1)
310 312
311 313 class B(A):
312 314 b = Float
313 315 _notify2 = []
314 316 def _b_changed(self, name, old, new):
315 317 self._notify2.append((name, old, new))
316 318
317 319 b = B()
318 320 b.a = 10
319 321 b.b = 10.0
320 322 self.assert_(('a',0,10) in b._notify1)
321 323 self.assert_(('b',0.0,10.0) in b._notify2)
322 324
323 325 def test_notify_args(self):
324 326
325 327 def callback0():
326 328 self.cb = ()
327 329 def callback1(name):
328 330 self.cb = (name,)
329 331 def callback2(name, new):
330 332 self.cb = (name, new)
331 333 def callback3(name, old, new):
332 334 self.cb = (name, old, new)
333 335
334 336 class A(HasTraits):
335 337 a = Int
336 338
337 339 a = A()
338 340 a.on_trait_change(callback0, 'a')
339 341 a.a = 10
340 342 self.assertEquals(self.cb,())
341 343 a.on_trait_change(callback0, 'a', remove=True)
342 344
343 345 a.on_trait_change(callback1, 'a')
344 346 a.a = 100
345 347 self.assertEquals(self.cb,('a',))
346 348 a.on_trait_change(callback1, 'a', remove=True)
347 349
348 350 a.on_trait_change(callback2, 'a')
349 351 a.a = 1000
350 352 self.assertEquals(self.cb,('a',1000))
351 353 a.on_trait_change(callback2, 'a', remove=True)
352 354
353 355 a.on_trait_change(callback3, 'a')
354 356 a.a = 10000
355 357 self.assertEquals(self.cb,('a',1000,10000))
356 358 a.on_trait_change(callback3, 'a', remove=True)
357 359
358 360 self.assertEquals(len(a._trait_notifiers['a']),0)
359 361
360 362
361 363 class TestHasTraits(TestCase):
362 364
363 365 def test_trait_names(self):
364 366 class A(HasTraits):
365 367 i = Int
366 368 f = Float
367 369 a = A()
368 370 self.assertEquals(a.trait_names(),['i','f'])
369 371 self.assertEquals(A.class_trait_names(),['i','f'])
370 372
371 373 def test_trait_metadata(self):
372 374 class A(HasTraits):
373 375 i = Int(config_key='MY_VALUE')
374 376 a = A()
375 377 self.assertEquals(a.trait_metadata('i','config_key'), 'MY_VALUE')
376 378
377 379 def test_traits(self):
378 380 class A(HasTraits):
379 381 i = Int
380 382 f = Float
381 383 a = A()
382 384 self.assertEquals(a.traits(), dict(i=A.i, f=A.f))
383 385 self.assertEquals(A.class_traits(), dict(i=A.i, f=A.f))
384 386
385 387 def test_traits_metadata(self):
386 388 class A(HasTraits):
387 389 i = Int(config_key='VALUE1', other_thing='VALUE2')
388 390 f = Float(config_key='VALUE3', other_thing='VALUE2')
389 391 j = Int(0)
390 392 a = A()
391 393 self.assertEquals(a.traits(), dict(i=A.i, f=A.f, j=A.j))
392 394 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
393 395 self.assertEquals(traits, dict(i=A.i))
394 396
395 397 # This passes, but it shouldn't because I am replicating a bug in
396 398 # traits.
397 399 traits = a.traits(config_key=lambda v: True)
398 400 self.assertEquals(traits, dict(i=A.i, f=A.f, j=A.j))
399 401
400 402 def test_init(self):
401 403 class A(HasTraits):
402 404 i = Int()
403 405 x = Float()
404 406 a = A(i=1, x=10.0)
405 407 self.assertEquals(a.i, 1)
406 408 self.assertEquals(a.x, 10.0)
407 409
408 410 #-----------------------------------------------------------------------------
409 411 # Tests for specific trait types
410 412 #-----------------------------------------------------------------------------
411 413
412 414
413 415 class TestType(TestCase):
414 416
415 417 def test_default(self):
416 418
417 419 class B(object): pass
418 420 class A(HasTraits):
419 421 klass = Type
420 422
421 423 a = A()
422 424 self.assertEquals(a.klass, None)
423 425
424 426 a.klass = B
425 427 self.assertEquals(a.klass, B)
426 428 self.assertRaises(TraitError, setattr, a, 'klass', 10)
427 429
428 430 def test_value(self):
429 431
430 432 class B(object): pass
431 433 class C(object): pass
432 434 class A(HasTraits):
433 435 klass = Type(B)
434 436
435 437 a = A()
436 438 self.assertEquals(a.klass, B)
437 439 self.assertRaises(TraitError, setattr, a, 'klass', C)
438 440 self.assertRaises(TraitError, setattr, a, 'klass', object)
439 441 a.klass = B
440 442
441 443 def test_allow_none(self):
442 444
443 445 class B(object): pass
444 446 class C(B): pass
445 447 class A(HasTraits):
446 448 klass = Type(B, allow_none=False)
447 449
448 450 a = A()
449 451 self.assertEquals(a.klass, B)
450 452 self.assertRaises(TraitError, setattr, a, 'klass', None)
451 453 a.klass = C
452 454 self.assertEquals(a.klass, C)
453 455
454 456 def test_validate_klass(self):
455 457
456 458 class A(HasTraits):
457 459 klass = Type('no strings allowed')
458 460
459 461 self.assertRaises(ImportError, A)
460 462
461 463 class A(HasTraits):
462 464 klass = Type('rub.adub.Duck')
463 465
464 466 self.assertRaises(ImportError, A)
465 467
466 468 def test_validate_default(self):
467 469
468 470 class B(object): pass
469 471 class A(HasTraits):
470 472 klass = Type('bad default', B)
471 473
472 474 self.assertRaises(ImportError, A)
473 475
474 476 class C(HasTraits):
475 477 klass = Type(None, B, allow_none=False)
476 478
477 479 self.assertRaises(TraitError, C)
478 480
479 481 def test_str_klass(self):
480 482
481 483 class A(HasTraits):
482 484 klass = Type('IPython.utils.ipstruct.Struct')
483 485
484 486 from IPython.utils.ipstruct import Struct
485 487 a = A()
486 488 a.klass = Struct
487 489 self.assertEquals(a.klass, Struct)
488 490
489 491 self.assertRaises(TraitError, setattr, a, 'klass', 10)
490 492
491 493 class TestInstance(TestCase):
492 494
493 495 def test_basic(self):
494 496 class Foo(object): pass
495 497 class Bar(Foo): pass
496 498 class Bah(object): pass
497 499
498 500 class A(HasTraits):
499 501 inst = Instance(Foo)
500 502
501 503 a = A()
502 504 self.assert_(a.inst is None)
503 505 a.inst = Foo()
504 506 self.assert_(isinstance(a.inst, Foo))
505 507 a.inst = Bar()
506 508 self.assert_(isinstance(a.inst, Foo))
507 509 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
508 510 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
509 511 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
510 512
511 513 def test_unique_default_value(self):
512 514 class Foo(object): pass
513 515 class A(HasTraits):
514 516 inst = Instance(Foo,(),{})
515 517
516 518 a = A()
517 519 b = A()
518 520 self.assert_(a.inst is not b.inst)
519 521
520 522 def test_args_kw(self):
521 523 class Foo(object):
522 524 def __init__(self, c): self.c = c
523 525 class Bar(object): pass
524 526 class Bah(object):
525 527 def __init__(self, c, d):
526 528 self.c = c; self.d = d
527 529
528 530 class A(HasTraits):
529 531 inst = Instance(Foo, (10,))
530 532 a = A()
531 533 self.assertEquals(a.inst.c, 10)
532 534
533 535 class B(HasTraits):
534 536 inst = Instance(Bah, args=(10,), kw=dict(d=20))
535 537 b = B()
536 538 self.assertEquals(b.inst.c, 10)
537 539 self.assertEquals(b.inst.d, 20)
538 540
539 541 class C(HasTraits):
540 542 inst = Instance(Foo)
541 543 c = C()
542 544 self.assert_(c.inst is None)
543 545
544 546 def test_bad_default(self):
545 547 class Foo(object): pass
546 548
547 549 class A(HasTraits):
548 550 inst = Instance(Foo, allow_none=False)
549 551
550 552 self.assertRaises(TraitError, A)
551 553
552 554 def test_instance(self):
553 555 class Foo(object): pass
554 556
555 557 def inner():
556 558 class A(HasTraits):
557 559 inst = Instance(Foo())
558 560
559 561 self.assertRaises(TraitError, inner)
560 562
561 563
562 564 class TestThis(TestCase):
563 565
564 566 def test_this_class(self):
565 567 class Foo(HasTraits):
566 568 this = This
567 569
568 570 f = Foo()
569 571 self.assertEquals(f.this, None)
570 572 g = Foo()
571 573 f.this = g
572 574 self.assertEquals(f.this, g)
573 575 self.assertRaises(TraitError, setattr, f, 'this', 10)
574 576
575 577 def test_this_inst(self):
576 578 class Foo(HasTraits):
577 579 this = This()
578 580
579 581 f = Foo()
580 582 f.this = Foo()
581 583 self.assert_(isinstance(f.this, Foo))
582 584
583 585 def test_subclass(self):
584 586 class Foo(HasTraits):
585 587 t = This()
586 588 class Bar(Foo):
587 589 pass
588 590 f = Foo()
589 591 b = Bar()
590 592 f.t = b
591 593 b.t = f
592 594 self.assertEquals(f.t, b)
593 595 self.assertEquals(b.t, f)
594 596
595 597 def test_subclass_override(self):
596 598 class Foo(HasTraits):
597 599 t = This()
598 600 class Bar(Foo):
599 601 t = This()
600 602 f = Foo()
601 603 b = Bar()
602 604 f.t = b
603 605 self.assertEquals(f.t, b)
604 606 self.assertRaises(TraitError, setattr, b, 't', f)
605 607
606 608 class TraitTestBase(TestCase):
607 609 """A best testing class for basic trait types."""
608 610
609 611 def assign(self, value):
610 612 self.obj.value = value
611 613
612 614 def coerce(self, value):
613 615 return value
614 616
615 617 def test_good_values(self):
616 618 if hasattr(self, '_good_values'):
617 619 for value in self._good_values:
618 620 self.assign(value)
619 621 self.assertEquals(self.obj.value, self.coerce(value))
620 622
621 623 def test_bad_values(self):
622 624 if hasattr(self, '_bad_values'):
623 625 for value in self._bad_values:
624 626 self.assertRaises(TraitError, self.assign, value)
625 627
626 628 def test_default_value(self):
627 629 if hasattr(self, '_default_value'):
628 630 self.assertEquals(self._default_value, self.obj.value)
629 631
630 632
631 633 class AnyTrait(HasTraits):
632 634
633 635 value = Any
634 636
635 637 class AnyTraitTest(TraitTestBase):
636 638
637 639 obj = AnyTrait()
638 640
639 641 _default_value = None
640 642 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
641 643 _bad_values = []
642 644
643 645
644 646 class IntTrait(HasTraits):
645 647
646 648 value = Int(99)
647 649
648 650 class TestInt(TraitTestBase):
649 651
650 652 obj = IntTrait()
651 653 _default_value = 99
652 654 _good_values = [10, -10]
653 655 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
654 656 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
655 657 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
656 658
657 659
658 660 class LongTrait(HasTraits):
659 661
660 662 value = Long(99L)
661 663
662 664 class TestLong(TraitTestBase):
663 665
664 666 obj = LongTrait()
665 667
666 668 _default_value = 99L
667 669 _good_values = [10, -10, 10L, -10L]
668 670 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
669 671 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
670 672 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
671 673 u'-10.1']
672 674
673 675
674 676 class FloatTrait(HasTraits):
675 677
676 678 value = Float(99.0)
677 679
678 680 class TestFloat(TraitTestBase):
679 681
680 682 obj = FloatTrait()
681 683
682 684 _default_value = 99.0
683 685 _good_values = [10, -10, 10.1, -10.1]
684 686 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
685 687 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
686 688 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
687 689
688 690
689 691 class ComplexTrait(HasTraits):
690 692
691 693 value = Complex(99.0-99.0j)
692 694
693 695 class TestComplex(TraitTestBase):
694 696
695 697 obj = ComplexTrait()
696 698
697 699 _default_value = 99.0-99.0j
698 700 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
699 701 10.1j, 10.1+10.1j, 10.1-10.1j]
700 702 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
701 703
702 704
703 705 class BytesTrait(HasTraits):
704 706
705 707 value = Bytes('string')
706 708
707 709 class TestBytes(TraitTestBase):
708 710
709 711 obj = BytesTrait()
710 712
711 713 _default_value = 'string'
712 714 _good_values = ['10', '-10', '10L',
713 715 '-10L', '10.1', '-10.1', 'string']
714 716 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
715 717 ['ten'],{'ten': 10},(10,), None, u'string']
716 718
717 719
718 720 class UnicodeTrait(HasTraits):
719 721
720 722 value = Unicode(u'unicode')
721 723
722 724 class TestUnicode(TraitTestBase):
723 725
724 726 obj = UnicodeTrait()
725 727
726 728 _default_value = u'unicode'
727 729 _good_values = ['10', '-10', '10L', '-10L', '10.1',
728 '-10.1', '', u'', 'string', u'string', ]
730 '-10.1', '', u'', 'string', u'string', u"€"]
729 731 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
730 732 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
731 733
732 734
735 class ObjectNameTrait(HasTraits):
736 value = ObjectName("abc")
737
738 class TestObjectName(TraitTestBase):
739 obj = ObjectNameTrait()
740
741 _default_value = "abc"
742 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
743 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
744 object(), object]
745 if sys.version_info[0] < 3:
746 _bad_values.append(u"ΓΎ")
747 else:
748 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
749
750
751 class DottedObjectNameTrait(HasTraits):
752 value = DottedObjectName("a.b")
753
754 class TestDottedObjectName(TraitTestBase):
755 obj = DottedObjectNameTrait()
756
757 _default_value = "a.b"
758 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
759 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc."]
760 if sys.version_info[0] < 3:
761 _bad_values.append(u"t.ΓΎ")
762 else:
763 _good_values.append(u"t.ΓΎ")
764
765
733 766 class TCPAddressTrait(HasTraits):
734 767
735 768 value = TCPAddress()
736 769
737 770 class TestTCPAddress(TraitTestBase):
738 771
739 772 obj = TCPAddressTrait()
740 773
741 774 _default_value = ('127.0.0.1',0)
742 775 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
743 776 _bad_values = [(0,0),('localhost',10.0),('localhost',-1)]
744 777
745 778 class ListTrait(HasTraits):
746 779
747 780 value = List(Int)
748 781
749 782 class TestList(TraitTestBase):
750 783
751 784 obj = ListTrait()
752 785
753 786 _default_value = []
754 787 _good_values = [[], [1], range(10)]
755 788 _bad_values = [10, [1,'a'], 'a', (1,2)]
756 789
757 790 class LenListTrait(HasTraits):
758 791
759 792 value = List(Int, [0], minlen=1, maxlen=2)
760 793
761 794 class TestLenList(TraitTestBase):
762 795
763 796 obj = LenListTrait()
764 797
765 798 _default_value = [0]
766 799 _good_values = [[1], range(2)]
767 800 _bad_values = [10, [1,'a'], 'a', (1,2), [], range(3)]
768 801
769 802 class TupleTrait(HasTraits):
770 803
771 804 value = Tuple(Int)
772 805
773 806 class TestTupleTrait(TraitTestBase):
774 807
775 808 obj = TupleTrait()
776 809
777 810 _default_value = None
778 811 _good_values = [(1,), None,(0,)]
779 812 _bad_values = [10, (1,2), [1],('a'), ()]
780 813
781 814 def test_invalid_args(self):
782 815 self.assertRaises(TypeError, Tuple, 5)
783 816 self.assertRaises(TypeError, Tuple, default_value='hello')
784 817 t = Tuple(Int, CBytes, default_value=(1,5))
785 818
786 819 class LooseTupleTrait(HasTraits):
787 820
788 821 value = Tuple((1,2,3))
789 822
790 823 class TestLooseTupleTrait(TraitTestBase):
791 824
792 825 obj = LooseTupleTrait()
793 826
794 827 _default_value = (1,2,3)
795 828 _good_values = [(1,), None, (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
796 829 _bad_values = [10, 'hello', [1], []]
797 830
798 831 def test_invalid_args(self):
799 832 self.assertRaises(TypeError, Tuple, 5)
800 833 self.assertRaises(TypeError, Tuple, default_value='hello')
801 834 t = Tuple(Int, CBytes, default_value=(1,5))
802 835
803 836
804 837 class MultiTupleTrait(HasTraits):
805 838
806 839 value = Tuple(Int, Bytes, default_value=[99,'bottles'])
807 840
808 841 class TestMultiTuple(TraitTestBase):
809 842
810 843 obj = MultiTupleTrait()
811 844
812 845 _default_value = (99,'bottles')
813 846 _good_values = [(1,'a'), (2,'b')]
814 847 _bad_values = ((),10, 'a', (1,'a',3), ('a',1))
@@ -1,1352 +1,1397 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A lightweight Traits like module.
5 5
6 6 This is designed to provide a lightweight, simple, pure Python version of
7 7 many of the capabilities of enthought.traits. This includes:
8 8
9 9 * Validation
10 10 * Type specification with defaults
11 11 * Static and dynamic notification
12 12 * Basic predefined types
13 13 * An API that is similar to enthought.traits
14 14
15 15 We don't support:
16 16
17 17 * Delegation
18 18 * Automatic GUI generation
19 19 * A full set of trait types. Most importantly, we don't provide container
20 20 traits (list, dict, tuple) that can trigger notifications if their
21 21 contents change.
22 22 * API compatibility with enthought.traits
23 23
24 24 There are also some important difference in our design:
25 25
26 26 * enthought.traits does not validate default values. We do.
27 27
28 28 We choose to create this module because we need these capabilities, but
29 29 we need them to be pure Python so they work in all Python implementations,
30 30 including Jython and IronPython.
31 31
32 32 Authors:
33 33
34 34 * Brian Granger
35 35 * Enthought, Inc. Some of the code in this file comes from enthought.traits
36 36 and is licensed under the BSD license. Also, many of the ideas also come
37 37 from enthought.traits even though our implementation is very different.
38 38 """
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Copyright (C) 2008-2009 The IPython Development Team
42 42 #
43 43 # Distributed under the terms of the BSD License. The full license is in
44 44 # the file COPYING, distributed as part of this software.
45 45 #-----------------------------------------------------------------------------
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Imports
49 49 #-----------------------------------------------------------------------------
50 50
51 51
52 52 import inspect
53 import re
53 54 import sys
54 55 import types
55 56 from types import (
56 57 InstanceType, ClassType, FunctionType,
57 58 ListType, TupleType
58 59 )
59 60 from .importstring import import_item
60 61
61 62 ClassTypes = (ClassType, type)
62 63
63 64 SequenceTypes = (ListType, TupleType, set, frozenset)
64 65
65 66 #-----------------------------------------------------------------------------
66 67 # Basic classes
67 68 #-----------------------------------------------------------------------------
68 69
69 70
70 71 class NoDefaultSpecified ( object ): pass
71 72 NoDefaultSpecified = NoDefaultSpecified()
72 73
73 74
74 75 class Undefined ( object ): pass
75 76 Undefined = Undefined()
76 77
77 78 class TraitError(Exception):
78 79 pass
79 80
80 81 #-----------------------------------------------------------------------------
81 82 # Utilities
82 83 #-----------------------------------------------------------------------------
83 84
84 85
85 86 def class_of ( object ):
86 87 """ Returns a string containing the class name of an object with the
87 88 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
88 89 'a PlotValue').
89 90 """
90 91 if isinstance( object, basestring ):
91 92 return add_article( object )
92 93
93 94 return add_article( object.__class__.__name__ )
94 95
95 96
96 97 def add_article ( name ):
97 98 """ Returns a string containing the correct indefinite article ('a' or 'an')
98 99 prefixed to the specified string.
99 100 """
100 101 if name[:1].lower() in 'aeiou':
101 102 return 'an ' + name
102 103
103 104 return 'a ' + name
104 105
105 106
106 107 def repr_type(obj):
107 108 """ Return a string representation of a value and its type for readable
108 109 error messages.
109 110 """
110 111 the_type = type(obj)
111 112 if the_type is InstanceType:
112 113 # Old-style class.
113 114 the_type = obj.__class__
114 115 msg = '%r %r' % (obj, the_type)
115 116 return msg
116 117
117 118
118 119 def parse_notifier_name(name):
119 120 """Convert the name argument to a list of names.
120 121
121 122 Examples
122 123 --------
123 124
124 125 >>> parse_notifier_name('a')
125 126 ['a']
126 127 >>> parse_notifier_name(['a','b'])
127 128 ['a', 'b']
128 129 >>> parse_notifier_name(None)
129 130 ['anytrait']
130 131 """
131 132 if isinstance(name, str):
132 133 return [name]
133 134 elif name is None:
134 135 return ['anytrait']
135 136 elif isinstance(name, (list, tuple)):
136 137 for n in name:
137 138 assert isinstance(n, str), "names must be strings"
138 139 return name
139 140
140 141
141 142 class _SimpleTest:
142 143 def __init__ ( self, value ): self.value = value
143 144 def __call__ ( self, test ):
144 145 return test == self.value
145 146 def __repr__(self):
146 147 return "<SimpleTest(%r)" % self.value
147 148 def __str__(self):
148 149 return self.__repr__()
149 150
150 151
151 152 def getmembers(object, predicate=None):
152 153 """A safe version of inspect.getmembers that handles missing attributes.
153 154
154 155 This is useful when there are descriptor based attributes that for
155 156 some reason raise AttributeError even though they exist. This happens
156 157 in zope.inteface with the __provides__ attribute.
157 158 """
158 159 results = []
159 160 for key in dir(object):
160 161 try:
161 162 value = getattr(object, key)
162 163 except AttributeError:
163 164 pass
164 165 else:
165 166 if not predicate or predicate(value):
166 167 results.append((key, value))
167 168 results.sort()
168 169 return results
169 170
170 171
171 172 #-----------------------------------------------------------------------------
172 173 # Base TraitType for all traits
173 174 #-----------------------------------------------------------------------------
174 175
175 176
176 177 class TraitType(object):
177 178 """A base class for all trait descriptors.
178 179
179 180 Notes
180 181 -----
181 182 Our implementation of traits is based on Python's descriptor
182 183 prototol. This class is the base class for all such descriptors. The
183 184 only magic we use is a custom metaclass for the main :class:`HasTraits`
184 185 class that does the following:
185 186
186 187 1. Sets the :attr:`name` attribute of every :class:`TraitType`
187 188 instance in the class dict to the name of the attribute.
188 189 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
189 190 instance in the class dict to the *class* that declared the trait.
190 191 This is used by the :class:`This` trait to allow subclasses to
191 192 accept superclasses for :class:`This` values.
192 193 """
193 194
194 195
195 196 metadata = {}
196 197 default_value = Undefined
197 198 info_text = 'any value'
198 199
199 200 def __init__(self, default_value=NoDefaultSpecified, **metadata):
200 201 """Create a TraitType.
201 202 """
202 203 if default_value is not NoDefaultSpecified:
203 204 self.default_value = default_value
204 205
205 206 if len(metadata) > 0:
206 207 if len(self.metadata) > 0:
207 208 self._metadata = self.metadata.copy()
208 209 self._metadata.update(metadata)
209 210 else:
210 211 self._metadata = metadata
211 212 else:
212 213 self._metadata = self.metadata
213 214
214 215 self.init()
215 216
216 217 def init(self):
217 218 pass
218 219
219 220 def get_default_value(self):
220 221 """Create a new instance of the default value."""
221 222 return self.default_value
222 223
223 224 def instance_init(self, obj):
224 225 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
225 226
226 227 Some stages of initialization must be delayed until the parent
227 228 :class:`HasTraits` instance has been created. This method is
228 229 called in :meth:`HasTraits.__new__` after the instance has been
229 230 created.
230 231
231 232 This method trigger the creation and validation of default values
232 233 and also things like the resolution of str given class names in
233 234 :class:`Type` and :class`Instance`.
234 235
235 236 Parameters
236 237 ----------
237 238 obj : :class:`HasTraits` instance
238 239 The parent :class:`HasTraits` instance that has just been
239 240 created.
240 241 """
241 242 self.set_default_value(obj)
242 243
243 244 def set_default_value(self, obj):
244 245 """Set the default value on a per instance basis.
245 246
246 247 This method is called by :meth:`instance_init` to create and
247 248 validate the default value. The creation and validation of
248 249 default values must be delayed until the parent :class:`HasTraits`
249 250 class has been instantiated.
250 251 """
251 252 # Check for a deferred initializer defined in the same class as the
252 253 # trait declaration or above.
253 254 mro = type(obj).mro()
254 255 meth_name = '_%s_default' % self.name
255 256 for cls in mro[:mro.index(self.this_class)+1]:
256 257 if meth_name in cls.__dict__:
257 258 break
258 259 else:
259 260 # We didn't find one. Do static initialization.
260 261 dv = self.get_default_value()
261 262 newdv = self._validate(obj, dv)
262 263 obj._trait_values[self.name] = newdv
263 264 return
264 265 # Complete the dynamic initialization.
265 266 obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name]
266 267
267 268 def __get__(self, obj, cls=None):
268 269 """Get the value of the trait by self.name for the instance.
269 270
270 271 Default values are instantiated when :meth:`HasTraits.__new__`
271 272 is called. Thus by the time this method gets called either the
272 273 default value or a user defined value (they called :meth:`__set__`)
273 274 is in the :class:`HasTraits` instance.
274 275 """
275 276 if obj is None:
276 277 return self
277 278 else:
278 279 try:
279 280 value = obj._trait_values[self.name]
280 281 except KeyError:
281 282 # Check for a dynamic initializer.
282 283 if self.name in obj._trait_dyn_inits:
283 284 value = obj._trait_dyn_inits[self.name](obj)
284 285 # FIXME: Do we really validate here?
285 286 value = self._validate(obj, value)
286 287 obj._trait_values[self.name] = value
287 288 return value
288 289 else:
289 290 raise TraitError('Unexpected error in TraitType: '
290 291 'both default value and dynamic initializer are '
291 292 'absent.')
292 293 except Exception:
293 294 # HasTraits should call set_default_value to populate
294 295 # this. So this should never be reached.
295 296 raise TraitError('Unexpected error in TraitType: '
296 297 'default value not set properly')
297 298 else:
298 299 return value
299 300
300 301 def __set__(self, obj, value):
301 302 new_value = self._validate(obj, value)
302 303 old_value = self.__get__(obj)
303 304 if old_value != new_value:
304 305 obj._trait_values[self.name] = new_value
305 306 obj._notify_trait(self.name, old_value, new_value)
306 307
307 308 def _validate(self, obj, value):
308 309 if hasattr(self, 'validate'):
309 310 return self.validate(obj, value)
310 311 elif hasattr(self, 'is_valid_for'):
311 312 valid = self.is_valid_for(value)
312 313 if valid:
313 314 return value
314 315 else:
315 316 raise TraitError('invalid value for type: %r' % value)
316 317 elif hasattr(self, 'value_for'):
317 318 return self.value_for(value)
318 319 else:
319 320 return value
320 321
321 322 def info(self):
322 323 return self.info_text
323 324
324 325 def error(self, obj, value):
325 326 if obj is not None:
326 327 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
327 328 % (self.name, class_of(obj),
328 329 self.info(), repr_type(value))
329 330 else:
330 331 e = "The '%s' trait must be %s, but a value of %r was specified." \
331 332 % (self.name, self.info(), repr_type(value))
332 333 raise TraitError(e)
333 334
334 335 def get_metadata(self, key):
335 336 return getattr(self, '_metadata', {}).get(key, None)
336 337
337 338 def set_metadata(self, key, value):
338 339 getattr(self, '_metadata', {})[key] = value
339 340
340 341
341 342 #-----------------------------------------------------------------------------
342 343 # The HasTraits implementation
343 344 #-----------------------------------------------------------------------------
344 345
345 346
346 347 class MetaHasTraits(type):
347 348 """A metaclass for HasTraits.
348 349
349 350 This metaclass makes sure that any TraitType class attributes are
350 351 instantiated and sets their name attribute.
351 352 """
352 353
353 354 def __new__(mcls, name, bases, classdict):
354 355 """Create the HasTraits class.
355 356
356 357 This instantiates all TraitTypes in the class dict and sets their
357 358 :attr:`name` attribute.
358 359 """
359 360 # print "MetaHasTraitlets (mcls, name): ", mcls, name
360 361 # print "MetaHasTraitlets (bases): ", bases
361 362 # print "MetaHasTraitlets (classdict): ", classdict
362 363 for k,v in classdict.iteritems():
363 364 if isinstance(v, TraitType):
364 365 v.name = k
365 366 elif inspect.isclass(v):
366 367 if issubclass(v, TraitType):
367 368 vinst = v()
368 369 vinst.name = k
369 370 classdict[k] = vinst
370 371 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
371 372
372 373 def __init__(cls, name, bases, classdict):
373 374 """Finish initializing the HasTraits class.
374 375
375 376 This sets the :attr:`this_class` attribute of each TraitType in the
376 377 class dict to the newly created class ``cls``.
377 378 """
378 379 for k, v in classdict.iteritems():
379 380 if isinstance(v, TraitType):
380 381 v.this_class = cls
381 382 super(MetaHasTraits, cls).__init__(name, bases, classdict)
382 383
383 384 class HasTraits(object):
384 385
385 386 __metaclass__ = MetaHasTraits
386 387
387 388 def __new__(cls, **kw):
388 389 # This is needed because in Python 2.6 object.__new__ only accepts
389 390 # the cls argument.
390 391 new_meth = super(HasTraits, cls).__new__
391 392 if new_meth is object.__new__:
392 393 inst = new_meth(cls)
393 394 else:
394 395 inst = new_meth(cls, **kw)
395 396 inst._trait_values = {}
396 397 inst._trait_notifiers = {}
397 398 inst._trait_dyn_inits = {}
398 399 # Here we tell all the TraitType instances to set their default
399 400 # values on the instance.
400 401 for key in dir(cls):
401 402 # Some descriptors raise AttributeError like zope.interface's
402 403 # __provides__ attributes even though they exist. This causes
403 404 # AttributeErrors even though they are listed in dir(cls).
404 405 try:
405 406 value = getattr(cls, key)
406 407 except AttributeError:
407 408 pass
408 409 else:
409 410 if isinstance(value, TraitType):
410 411 value.instance_init(inst)
411 412
412 413 return inst
413 414
414 415 def __init__(self, **kw):
415 416 # Allow trait values to be set using keyword arguments.
416 417 # We need to use setattr for this to trigger validation and
417 418 # notifications.
418 419 for key, value in kw.iteritems():
419 420 setattr(self, key, value)
420 421
421 422 def _notify_trait(self, name, old_value, new_value):
422 423
423 424 # First dynamic ones
424 425 callables = self._trait_notifiers.get(name,[])
425 426 more_callables = self._trait_notifiers.get('anytrait',[])
426 427 callables.extend(more_callables)
427 428
428 429 # Now static ones
429 430 try:
430 431 cb = getattr(self, '_%s_changed' % name)
431 432 except:
432 433 pass
433 434 else:
434 435 callables.append(cb)
435 436
436 437 # Call them all now
437 438 for c in callables:
438 439 # Traits catches and logs errors here. I allow them to raise
439 440 if callable(c):
440 441 argspec = inspect.getargspec(c)
441 442 nargs = len(argspec[0])
442 443 # Bound methods have an additional 'self' argument
443 444 # I don't know how to treat unbound methods, but they
444 445 # can't really be used for callbacks.
445 446 if isinstance(c, types.MethodType):
446 447 offset = -1
447 448 else:
448 449 offset = 0
449 450 if nargs + offset == 0:
450 451 c()
451 452 elif nargs + offset == 1:
452 453 c(name)
453 454 elif nargs + offset == 2:
454 455 c(name, new_value)
455 456 elif nargs + offset == 3:
456 457 c(name, old_value, new_value)
457 458 else:
458 459 raise TraitError('a trait changed callback '
459 460 'must have 0-3 arguments.')
460 461 else:
461 462 raise TraitError('a trait changed callback '
462 463 'must be callable.')
463 464
464 465
465 466 def _add_notifiers(self, handler, name):
466 467 if not self._trait_notifiers.has_key(name):
467 468 nlist = []
468 469 self._trait_notifiers[name] = nlist
469 470 else:
470 471 nlist = self._trait_notifiers[name]
471 472 if handler not in nlist:
472 473 nlist.append(handler)
473 474
474 475 def _remove_notifiers(self, handler, name):
475 476 if self._trait_notifiers.has_key(name):
476 477 nlist = self._trait_notifiers[name]
477 478 try:
478 479 index = nlist.index(handler)
479 480 except ValueError:
480 481 pass
481 482 else:
482 483 del nlist[index]
483 484
484 485 def on_trait_change(self, handler, name=None, remove=False):
485 486 """Setup a handler to be called when a trait changes.
486 487
487 488 This is used to setup dynamic notifications of trait changes.
488 489
489 490 Static handlers can be created by creating methods on a HasTraits
490 491 subclass with the naming convention '_[traitname]_changed'. Thus,
491 492 to create static handler for the trait 'a', create the method
492 493 _a_changed(self, name, old, new) (fewer arguments can be used, see
493 494 below).
494 495
495 496 Parameters
496 497 ----------
497 498 handler : callable
498 499 A callable that is called when a trait changes. Its
499 500 signature can be handler(), handler(name), handler(name, new)
500 501 or handler(name, old, new).
501 502 name : list, str, None
502 503 If None, the handler will apply to all traits. If a list
503 504 of str, handler will apply to all names in the list. If a
504 505 str, the handler will apply just to that name.
505 506 remove : bool
506 507 If False (the default), then install the handler. If True
507 508 then unintall it.
508 509 """
509 510 if remove:
510 511 names = parse_notifier_name(name)
511 512 for n in names:
512 513 self._remove_notifiers(handler, n)
513 514 else:
514 515 names = parse_notifier_name(name)
515 516 for n in names:
516 517 self._add_notifiers(handler, n)
517 518
518 519 @classmethod
519 520 def class_trait_names(cls, **metadata):
520 521 """Get a list of all the names of this classes traits.
521 522
522 523 This method is just like the :meth:`trait_names` method, but is unbound.
523 524 """
524 525 return cls.class_traits(**metadata).keys()
525 526
526 527 @classmethod
527 528 def class_traits(cls, **metadata):
528 529 """Get a list of all the traits of this class.
529 530
530 531 This method is just like the :meth:`traits` method, but is unbound.
531 532
532 533 The TraitTypes returned don't know anything about the values
533 534 that the various HasTrait's instances are holding.
534 535
535 536 This follows the same algorithm as traits does and does not allow
536 537 for any simple way of specifying merely that a metadata name
537 538 exists, but has any value. This is because get_metadata returns
538 539 None if a metadata key doesn't exist.
539 540 """
540 541 traits = dict([memb for memb in getmembers(cls) if \
541 542 isinstance(memb[1], TraitType)])
542 543
543 544 if len(metadata) == 0:
544 545 return traits
545 546
546 547 for meta_name, meta_eval in metadata.items():
547 548 if type(meta_eval) is not FunctionType:
548 549 metadata[meta_name] = _SimpleTest(meta_eval)
549 550
550 551 result = {}
551 552 for name, trait in traits.items():
552 553 for meta_name, meta_eval in metadata.items():
553 554 if not meta_eval(trait.get_metadata(meta_name)):
554 555 break
555 556 else:
556 557 result[name] = trait
557 558
558 559 return result
559 560
560 561 def trait_names(self, **metadata):
561 562 """Get a list of all the names of this classes traits."""
562 563 return self.traits(**metadata).keys()
563 564
564 565 def traits(self, **metadata):
565 566 """Get a list of all the traits of this class.
566 567
567 568 The TraitTypes returned don't know anything about the values
568 569 that the various HasTrait's instances are holding.
569 570
570 571 This follows the same algorithm as traits does and does not allow
571 572 for any simple way of specifying merely that a metadata name
572 573 exists, but has any value. This is because get_metadata returns
573 574 None if a metadata key doesn't exist.
574 575 """
575 576 traits = dict([memb for memb in getmembers(self.__class__) if \
576 577 isinstance(memb[1], TraitType)])
577 578
578 579 if len(metadata) == 0:
579 580 return traits
580 581
581 582 for meta_name, meta_eval in metadata.items():
582 583 if type(meta_eval) is not FunctionType:
583 584 metadata[meta_name] = _SimpleTest(meta_eval)
584 585
585 586 result = {}
586 587 for name, trait in traits.items():
587 588 for meta_name, meta_eval in metadata.items():
588 589 if not meta_eval(trait.get_metadata(meta_name)):
589 590 break
590 591 else:
591 592 result[name] = trait
592 593
593 594 return result
594 595
595 596 def trait_metadata(self, traitname, key):
596 597 """Get metadata values for trait by key."""
597 598 try:
598 599 trait = getattr(self.__class__, traitname)
599 600 except AttributeError:
600 601 raise TraitError("Class %s does not have a trait named %s" %
601 602 (self.__class__.__name__, traitname))
602 603 else:
603 604 return trait.get_metadata(key)
604 605
605 606 #-----------------------------------------------------------------------------
606 607 # Actual TraitTypes implementations/subclasses
607 608 #-----------------------------------------------------------------------------
608 609
609 610 #-----------------------------------------------------------------------------
610 611 # TraitTypes subclasses for handling classes and instances of classes
611 612 #-----------------------------------------------------------------------------
612 613
613 614
614 615 class ClassBasedTraitType(TraitType):
615 616 """A trait with error reporting for Type, Instance and This."""
616 617
617 618 def error(self, obj, value):
618 619 kind = type(value)
619 620 if kind is InstanceType:
620 621 msg = 'class %s' % value.__class__.__name__
621 622 else:
622 623 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
623 624
624 625 if obj is not None:
625 626 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
626 627 % (self.name, class_of(obj),
627 628 self.info(), msg)
628 629 else:
629 630 e = "The '%s' trait must be %s, but a value of %r was specified." \
630 631 % (self.name, self.info(), msg)
631 632
632 633 raise TraitError(e)
633 634
634 635
635 636 class Type(ClassBasedTraitType):
636 637 """A trait whose value must be a subclass of a specified class."""
637 638
638 639 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
639 640 """Construct a Type trait
640 641
641 642 A Type trait specifies that its values must be subclasses of
642 643 a particular class.
643 644
644 645 If only ``default_value`` is given, it is used for the ``klass`` as
645 646 well.
646 647
647 648 Parameters
648 649 ----------
649 650 default_value : class, str or None
650 651 The default value must be a subclass of klass. If an str,
651 652 the str must be a fully specified class name, like 'foo.bar.Bah'.
652 653 The string is resolved into real class, when the parent
653 654 :class:`HasTraits` class is instantiated.
654 655 klass : class, str, None
655 656 Values of this trait must be a subclass of klass. The klass
656 657 may be specified in a string like: 'foo.bar.MyClass'.
657 658 The string is resolved into real class, when the parent
658 659 :class:`HasTraits` class is instantiated.
659 660 allow_none : boolean
660 661 Indicates whether None is allowed as an assignable value. Even if
661 662 ``False``, the default value may be ``None``.
662 663 """
663 664 if default_value is None:
664 665 if klass is None:
665 666 klass = object
666 667 elif klass is None:
667 668 klass = default_value
668 669
669 670 if not (inspect.isclass(klass) or isinstance(klass, basestring)):
670 671 raise TraitError("A Type trait must specify a class.")
671 672
672 673 self.klass = klass
673 674 self._allow_none = allow_none
674 675
675 676 super(Type, self).__init__(default_value, **metadata)
676 677
677 678 def validate(self, obj, value):
678 679 """Validates that the value is a valid object instance."""
679 680 try:
680 681 if issubclass(value, self.klass):
681 682 return value
682 683 except:
683 684 if (value is None) and (self._allow_none):
684 685 return value
685 686
686 687 self.error(obj, value)
687 688
688 689 def info(self):
689 690 """ Returns a description of the trait."""
690 691 if isinstance(self.klass, basestring):
691 692 klass = self.klass
692 693 else:
693 694 klass = self.klass.__name__
694 695 result = 'a subclass of ' + klass
695 696 if self._allow_none:
696 697 return result + ' or None'
697 698 return result
698 699
699 700 def instance_init(self, obj):
700 701 self._resolve_classes()
701 702 super(Type, self).instance_init(obj)
702 703
703 704 def _resolve_classes(self):
704 705 if isinstance(self.klass, basestring):
705 706 self.klass = import_item(self.klass)
706 707 if isinstance(self.default_value, basestring):
707 708 self.default_value = import_item(self.default_value)
708 709
709 710 def get_default_value(self):
710 711 return self.default_value
711 712
712 713
713 714 class DefaultValueGenerator(object):
714 715 """A class for generating new default value instances."""
715 716
716 717 def __init__(self, *args, **kw):
717 718 self.args = args
718 719 self.kw = kw
719 720
720 721 def generate(self, klass):
721 722 return klass(*self.args, **self.kw)
722 723
723 724
724 725 class Instance(ClassBasedTraitType):
725 726 """A trait whose value must be an instance of a specified class.
726 727
727 728 The value can also be an instance of a subclass of the specified class.
728 729 """
729 730
730 731 def __init__(self, klass=None, args=None, kw=None,
731 732 allow_none=True, **metadata ):
732 733 """Construct an Instance trait.
733 734
734 735 This trait allows values that are instances of a particular
735 736 class or its sublclasses. Our implementation is quite different
736 737 from that of enthough.traits as we don't allow instances to be used
737 738 for klass and we handle the ``args`` and ``kw`` arguments differently.
738 739
739 740 Parameters
740 741 ----------
741 742 klass : class, str
742 743 The class that forms the basis for the trait. Class names
743 744 can also be specified as strings, like 'foo.bar.Bar'.
744 745 args : tuple
745 746 Positional arguments for generating the default value.
746 747 kw : dict
747 748 Keyword arguments for generating the default value.
748 749 allow_none : bool
749 750 Indicates whether None is allowed as a value.
750 751
751 752 Default Value
752 753 -------------
753 754 If both ``args`` and ``kw`` are None, then the default value is None.
754 755 If ``args`` is a tuple and ``kw`` is a dict, then the default is
755 756 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
756 757 not (but not both), None is replace by ``()`` or ``{}``.
757 758 """
758 759
759 760 self._allow_none = allow_none
760 761
761 762 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))):
762 763 raise TraitError('The klass argument must be a class'
763 764 ' you gave: %r' % klass)
764 765 self.klass = klass
765 766
766 767 # self.klass is a class, so handle default_value
767 768 if args is None and kw is None:
768 769 default_value = None
769 770 else:
770 771 if args is None:
771 772 # kw is not None
772 773 args = ()
773 774 elif kw is None:
774 775 # args is not None
775 776 kw = {}
776 777
777 778 if not isinstance(kw, dict):
778 779 raise TraitError("The 'kw' argument must be a dict or None.")
779 780 if not isinstance(args, tuple):
780 781 raise TraitError("The 'args' argument must be a tuple or None.")
781 782
782 783 default_value = DefaultValueGenerator(*args, **kw)
783 784
784 785 super(Instance, self).__init__(default_value, **metadata)
785 786
786 787 def validate(self, obj, value):
787 788 if value is None:
788 789 if self._allow_none:
789 790 return value
790 791 self.error(obj, value)
791 792
792 793 if isinstance(value, self.klass):
793 794 return value
794 795 else:
795 796 self.error(obj, value)
796 797
797 798 def info(self):
798 799 if isinstance(self.klass, basestring):
799 800 klass = self.klass
800 801 else:
801 802 klass = self.klass.__name__
802 803 result = class_of(klass)
803 804 if self._allow_none:
804 805 return result + ' or None'
805 806
806 807 return result
807 808
808 809 def instance_init(self, obj):
809 810 self._resolve_classes()
810 811 super(Instance, self).instance_init(obj)
811 812
812 813 def _resolve_classes(self):
813 814 if isinstance(self.klass, basestring):
814 815 self.klass = import_item(self.klass)
815 816
816 817 def get_default_value(self):
817 818 """Instantiate a default value instance.
818 819
819 820 This is called when the containing HasTraits classes'
820 821 :meth:`__new__` method is called to ensure that a unique instance
821 822 is created for each HasTraits instance.
822 823 """
823 824 dv = self.default_value
824 825 if isinstance(dv, DefaultValueGenerator):
825 826 return dv.generate(self.klass)
826 827 else:
827 828 return dv
828 829
829 830
830 831 class This(ClassBasedTraitType):
831 832 """A trait for instances of the class containing this trait.
832 833
833 834 Because how how and when class bodies are executed, the ``This``
834 835 trait can only have a default value of None. This, and because we
835 836 always validate default values, ``allow_none`` is *always* true.
836 837 """
837 838
838 839 info_text = 'an instance of the same type as the receiver or None'
839 840
840 841 def __init__(self, **metadata):
841 842 super(This, self).__init__(None, **metadata)
842 843
843 844 def validate(self, obj, value):
844 845 # What if value is a superclass of obj.__class__? This is
845 846 # complicated if it was the superclass that defined the This
846 847 # trait.
847 848 if isinstance(value, self.this_class) or (value is None):
848 849 return value
849 850 else:
850 851 self.error(obj, value)
851 852
852 853
853 854 #-----------------------------------------------------------------------------
854 855 # Basic TraitTypes implementations/subclasses
855 856 #-----------------------------------------------------------------------------
856 857
857 858
858 859 class Any(TraitType):
859 860 default_value = None
860 861 info_text = 'any value'
861 862
862 863
863 864 class Int(TraitType):
864 865 """A integer trait."""
865 866
866 867 default_value = 0
867 868 info_text = 'an integer'
868 869
869 870 def validate(self, obj, value):
870 871 if isinstance(value, int):
871 872 return value
872 873 self.error(obj, value)
873 874
874 875 class CInt(Int):
875 876 """A casting version of the int trait."""
876 877
877 878 def validate(self, obj, value):
878 879 try:
879 880 return int(value)
880 881 except:
881 882 self.error(obj, value)
882 883
883 884
884 885 class Long(TraitType):
885 886 """A long integer trait."""
886 887
887 888 default_value = 0L
888 889 info_text = 'a long'
889 890
890 891 def validate(self, obj, value):
891 892 if isinstance(value, long):
892 893 return value
893 894 if isinstance(value, int):
894 895 return long(value)
895 896 self.error(obj, value)
896 897
897 898
898 899 class CLong(Long):
899 900 """A casting version of the long integer trait."""
900 901
901 902 def validate(self, obj, value):
902 903 try:
903 904 return long(value)
904 905 except:
905 906 self.error(obj, value)
906 907
907 908
908 909 class Float(TraitType):
909 910 """A float trait."""
910 911
911 912 default_value = 0.0
912 913 info_text = 'a float'
913 914
914 915 def validate(self, obj, value):
915 916 if isinstance(value, float):
916 917 return value
917 918 if isinstance(value, int):
918 919 return float(value)
919 920 self.error(obj, value)
920 921
921 922
922 923 class CFloat(Float):
923 924 """A casting version of the float trait."""
924 925
925 926 def validate(self, obj, value):
926 927 try:
927 928 return float(value)
928 929 except:
929 930 self.error(obj, value)
930 931
931 932 class Complex(TraitType):
932 933 """A trait for complex numbers."""
933 934
934 935 default_value = 0.0 + 0.0j
935 936 info_text = 'a complex number'
936 937
937 938 def validate(self, obj, value):
938 939 if isinstance(value, complex):
939 940 return value
940 941 if isinstance(value, (float, int)):
941 942 return complex(value)
942 943 self.error(obj, value)
943 944
944 945
945 946 class CComplex(Complex):
946 947 """A casting version of the complex number trait."""
947 948
948 949 def validate (self, obj, value):
949 950 try:
950 951 return complex(value)
951 952 except:
952 953 self.error(obj, value)
953 954
954 955 # We should always be explicit about whether we're using bytes or unicode, both
955 956 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
956 957 # we don't have a Str type.
957 958 class Bytes(TraitType):
958 959 """A trait for strings."""
959 960
960 961 default_value = ''
961 962 info_text = 'a string'
962 963
963 964 def validate(self, obj, value):
964 965 if isinstance(value, bytes):
965 966 return value
966 967 self.error(obj, value)
967 968
968 969
969 970 class CBytes(Bytes):
970 971 """A casting version of the string trait."""
971 972
972 973 def validate(self, obj, value):
973 974 try:
974 975 return bytes(value)
975 976 except:
976 977 self.error(obj, value)
977 978
978 979
979 980 class Unicode(TraitType):
980 981 """A trait for unicode strings."""
981 982
982 983 default_value = u''
983 984 info_text = 'a unicode string'
984 985
985 986 def validate(self, obj, value):
986 987 if isinstance(value, unicode):
987 988 return value
988 989 if isinstance(value, bytes):
989 990 return unicode(value)
990 991 self.error(obj, value)
991 992
992 993
993 994 class CUnicode(Unicode):
994 995 """A casting version of the unicode trait."""
995 996
996 997 def validate(self, obj, value):
997 998 try:
998 999 return unicode(value)
999 1000 except:
1000 1001 self.error(obj, value)
1002
1003
1004 class ObjectName(TraitType):
1005 """A string holding a valid object name in this version of Python.
1006
1007 This does not check that the name exists in any scope."""
1008 info_text = "a valid object identifier in Python"
1009
1010 if sys.version_info[0] < 3:
1011 # Python 2:
1012 _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")
1013 def isidentifier(self, s):
1014 return bool(self._name_re.match(s))
1015
1016 def coerce_str(self, obj, value):
1017 "In Python 2, coerce ascii-only unicode to str"
1018 if isinstance(value, unicode):
1019 try:
1020 return str(value)
1021 except UnicodeEncodeError:
1022 self.error(obj, value)
1023 return value
1024
1025 else:
1026 # Python 3:
1027 isidentifier = staticmethod(lambda s: s.isidentifier())
1028 coerce_str = staticmethod(lambda _,s: s)
1029
1030 def validate(self, obj, value):
1031 value = self.coerce_str(obj, value)
1032
1033 if isinstance(value, str) and self.isidentifier(value):
1034 return value
1035 self.error(obj, value)
1036
1037 class DottedObjectName(ObjectName):
1038 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1039 def validate(self, obj, value):
1040 value = self.coerce_str(obj, value)
1041
1042 if isinstance(value, str) and all(self.isidentifier(x) \
1043 for x in value.split('.')):
1044 return value
1045 self.error(obj, value)
1001 1046
1002 1047
1003 1048 class Bool(TraitType):
1004 1049 """A boolean (True, False) trait."""
1005 1050
1006 1051 default_value = False
1007 1052 info_text = 'a boolean'
1008 1053
1009 1054 def validate(self, obj, value):
1010 1055 if isinstance(value, bool):
1011 1056 return value
1012 1057 self.error(obj, value)
1013 1058
1014 1059
1015 1060 class CBool(Bool):
1016 1061 """A casting version of the boolean trait."""
1017 1062
1018 1063 def validate(self, obj, value):
1019 1064 try:
1020 1065 return bool(value)
1021 1066 except:
1022 1067 self.error(obj, value)
1023 1068
1024 1069
1025 1070 class Enum(TraitType):
1026 1071 """An enum that whose value must be in a given sequence."""
1027 1072
1028 1073 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1029 1074 self.values = values
1030 1075 self._allow_none = allow_none
1031 1076 super(Enum, self).__init__(default_value, **metadata)
1032 1077
1033 1078 def validate(self, obj, value):
1034 1079 if value is None:
1035 1080 if self._allow_none:
1036 1081 return value
1037 1082
1038 1083 if value in self.values:
1039 1084 return value
1040 1085 self.error(obj, value)
1041 1086
1042 1087 def info(self):
1043 1088 """ Returns a description of the trait."""
1044 1089 result = 'any of ' + repr(self.values)
1045 1090 if self._allow_none:
1046 1091 return result + ' or None'
1047 1092 return result
1048 1093
1049 1094 class CaselessStrEnum(Enum):
1050 1095 """An enum of strings that are caseless in validate."""
1051 1096
1052 1097 def validate(self, obj, value):
1053 1098 if value is None:
1054 1099 if self._allow_none:
1055 1100 return value
1056 1101
1057 1102 if not isinstance(value, str):
1058 1103 self.error(obj, value)
1059 1104
1060 1105 for v in self.values:
1061 1106 if v.lower() == value.lower():
1062 1107 return v
1063 1108 self.error(obj, value)
1064 1109
1065 1110 class Container(Instance):
1066 1111 """An instance of a container (list, set, etc.)
1067 1112
1068 1113 To be subclassed by overriding klass.
1069 1114 """
1070 1115 klass = None
1071 1116 _valid_defaults = SequenceTypes
1072 1117 _trait = None
1073 1118
1074 1119 def __init__(self, trait=None, default_value=None, allow_none=True,
1075 1120 **metadata):
1076 1121 """Create a container trait type from a list, set, or tuple.
1077 1122
1078 1123 The default value is created by doing ``List(default_value)``,
1079 1124 which creates a copy of the ``default_value``.
1080 1125
1081 1126 ``trait`` can be specified, which restricts the type of elements
1082 1127 in the container to that TraitType.
1083 1128
1084 1129 If only one arg is given and it is not a Trait, it is taken as
1085 1130 ``default_value``:
1086 1131
1087 1132 ``c = List([1,2,3])``
1088 1133
1089 1134 Parameters
1090 1135 ----------
1091 1136
1092 1137 trait : TraitType [ optional ]
1093 1138 the type for restricting the contents of the Container. If unspecified,
1094 1139 types are not checked.
1095 1140
1096 1141 default_value : SequenceType [ optional ]
1097 1142 The default value for the Trait. Must be list/tuple/set, and
1098 1143 will be cast to the container type.
1099 1144
1100 1145 allow_none : Bool [ default True ]
1101 1146 Whether to allow the value to be None
1102 1147
1103 1148 **metadata : any
1104 1149 further keys for extensions to the Trait (e.g. config)
1105 1150
1106 1151 """
1107 1152 istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType)
1108 1153
1109 1154 # allow List([values]):
1110 1155 if default_value is None and not istrait(trait):
1111 1156 default_value = trait
1112 1157 trait = None
1113 1158
1114 1159 if default_value is None:
1115 1160 args = ()
1116 1161 elif isinstance(default_value, self._valid_defaults):
1117 1162 args = (default_value,)
1118 1163 else:
1119 1164 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1120 1165
1121 1166 if istrait(trait):
1122 1167 self._trait = trait()
1123 1168 self._trait.name = 'element'
1124 1169 elif trait is not None:
1125 1170 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1126 1171
1127 1172 super(Container,self).__init__(klass=self.klass, args=args,
1128 1173 allow_none=allow_none, **metadata)
1129 1174
1130 1175 def element_error(self, obj, element, validator):
1131 1176 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1132 1177 % (self.name, class_of(obj), validator.info(), repr_type(element))
1133 1178 raise TraitError(e)
1134 1179
1135 1180 def validate(self, obj, value):
1136 1181 value = super(Container, self).validate(obj, value)
1137 1182 if value is None:
1138 1183 return value
1139 1184
1140 1185 value = self.validate_elements(obj, value)
1141 1186
1142 1187 return value
1143 1188
1144 1189 def validate_elements(self, obj, value):
1145 1190 validated = []
1146 1191 if self._trait is None or isinstance(self._trait, Any):
1147 1192 return value
1148 1193 for v in value:
1149 1194 try:
1150 1195 v = self._trait.validate(obj, v)
1151 1196 except TraitError:
1152 1197 self.element_error(obj, v, self._trait)
1153 1198 else:
1154 1199 validated.append(v)
1155 1200 return self.klass(validated)
1156 1201
1157 1202
1158 1203 class List(Container):
1159 1204 """An instance of a Python list."""
1160 1205 klass = list
1161 1206
1162 1207 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxint,
1163 1208 allow_none=True, **metadata):
1164 1209 """Create a List trait type from a list, set, or tuple.
1165 1210
1166 1211 The default value is created by doing ``List(default_value)``,
1167 1212 which creates a copy of the ``default_value``.
1168 1213
1169 1214 ``trait`` can be specified, which restricts the type of elements
1170 1215 in the container to that TraitType.
1171 1216
1172 1217 If only one arg is given and it is not a Trait, it is taken as
1173 1218 ``default_value``:
1174 1219
1175 1220 ``c = List([1,2,3])``
1176 1221
1177 1222 Parameters
1178 1223 ----------
1179 1224
1180 1225 trait : TraitType [ optional ]
1181 1226 the type for restricting the contents of the Container. If unspecified,
1182 1227 types are not checked.
1183 1228
1184 1229 default_value : SequenceType [ optional ]
1185 1230 The default value for the Trait. Must be list/tuple/set, and
1186 1231 will be cast to the container type.
1187 1232
1188 1233 minlen : Int [ default 0 ]
1189 1234 The minimum length of the input list
1190 1235
1191 1236 maxlen : Int [ default sys.maxint ]
1192 1237 The maximum length of the input list
1193 1238
1194 1239 allow_none : Bool [ default True ]
1195 1240 Whether to allow the value to be None
1196 1241
1197 1242 **metadata : any
1198 1243 further keys for extensions to the Trait (e.g. config)
1199 1244
1200 1245 """
1201 1246 self._minlen = minlen
1202 1247 self._maxlen = maxlen
1203 1248 super(List, self).__init__(trait=trait, default_value=default_value,
1204 1249 allow_none=allow_none, **metadata)
1205 1250
1206 1251 def length_error(self, obj, value):
1207 1252 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1208 1253 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1209 1254 raise TraitError(e)
1210 1255
1211 1256 def validate_elements(self, obj, value):
1212 1257 length = len(value)
1213 1258 if length < self._minlen or length > self._maxlen:
1214 1259 self.length_error(obj, value)
1215 1260
1216 1261 return super(List, self).validate_elements(obj, value)
1217 1262
1218 1263
1219 1264 class Set(Container):
1220 1265 """An instance of a Python set."""
1221 1266 klass = set
1222 1267
1223 1268 class Tuple(Container):
1224 1269 """An instance of a Python tuple."""
1225 1270 klass = tuple
1226 1271
1227 1272 def __init__(self, *traits, **metadata):
1228 1273 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1229 1274
1230 1275 Create a tuple from a list, set, or tuple.
1231 1276
1232 1277 Create a fixed-type tuple with Traits:
1233 1278
1234 1279 ``t = Tuple(Int, Str, CStr)``
1235 1280
1236 1281 would be length 3, with Int,Str,CStr for each element.
1237 1282
1238 1283 If only one arg is given and it is not a Trait, it is taken as
1239 1284 default_value:
1240 1285
1241 1286 ``t = Tuple((1,2,3))``
1242 1287
1243 1288 Otherwise, ``default_value`` *must* be specified by keyword.
1244 1289
1245 1290 Parameters
1246 1291 ----------
1247 1292
1248 1293 *traits : TraitTypes [ optional ]
1249 1294 the tsype for restricting the contents of the Tuple. If unspecified,
1250 1295 types are not checked. If specified, then each positional argument
1251 1296 corresponds to an element of the tuple. Tuples defined with traits
1252 1297 are of fixed length.
1253 1298
1254 1299 default_value : SequenceType [ optional ]
1255 1300 The default value for the Tuple. Must be list/tuple/set, and
1256 1301 will be cast to a tuple. If `traits` are specified, the
1257 1302 `default_value` must conform to the shape and type they specify.
1258 1303
1259 1304 allow_none : Bool [ default True ]
1260 1305 Whether to allow the value to be None
1261 1306
1262 1307 **metadata : any
1263 1308 further keys for extensions to the Trait (e.g. config)
1264 1309
1265 1310 """
1266 1311 default_value = metadata.pop('default_value', None)
1267 1312 allow_none = metadata.pop('allow_none', True)
1268 1313
1269 1314 istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType)
1270 1315
1271 1316 # allow Tuple((values,)):
1272 1317 if len(traits) == 1 and default_value is None and not istrait(traits[0]):
1273 1318 default_value = traits[0]
1274 1319 traits = ()
1275 1320
1276 1321 if default_value is None:
1277 1322 args = ()
1278 1323 elif isinstance(default_value, self._valid_defaults):
1279 1324 args = (default_value,)
1280 1325 else:
1281 1326 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1282 1327
1283 1328 self._traits = []
1284 1329 for trait in traits:
1285 1330 t = trait()
1286 1331 t.name = 'element'
1287 1332 self._traits.append(t)
1288 1333
1289 1334 if self._traits and default_value is None:
1290 1335 # don't allow default to be an empty container if length is specified
1291 1336 args = None
1292 1337 super(Container,self).__init__(klass=self.klass, args=args,
1293 1338 allow_none=allow_none, **metadata)
1294 1339
1295 1340 def validate_elements(self, obj, value):
1296 1341 if not self._traits:
1297 1342 # nothing to validate
1298 1343 return value
1299 1344 if len(value) != len(self._traits):
1300 1345 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1301 1346 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1302 1347 raise TraitError(e)
1303 1348
1304 1349 validated = []
1305 1350 for t,v in zip(self._traits, value):
1306 1351 try:
1307 1352 v = t.validate(obj, v)
1308 1353 except TraitError:
1309 1354 self.element_error(obj, v, t)
1310 1355 else:
1311 1356 validated.append(v)
1312 1357 return tuple(validated)
1313 1358
1314 1359
1315 1360 class Dict(Instance):
1316 1361 """An instance of a Python dict."""
1317 1362
1318 1363 def __init__(self, default_value=None, allow_none=True, **metadata):
1319 1364 """Create a dict trait type from a dict.
1320 1365
1321 1366 The default value is created by doing ``dict(default_value)``,
1322 1367 which creates a copy of the ``default_value``.
1323 1368 """
1324 1369 if default_value is None:
1325 1370 args = ((),)
1326 1371 elif isinstance(default_value, dict):
1327 1372 args = (default_value,)
1328 1373 elif isinstance(default_value, SequenceTypes):
1329 1374 args = (default_value,)
1330 1375 else:
1331 1376 raise TypeError('default value of Dict was %s' % default_value)
1332 1377
1333 1378 super(Dict,self).__init__(klass=dict, args=args,
1334 1379 allow_none=allow_none, **metadata)
1335 1380
1336 1381 class TCPAddress(TraitType):
1337 1382 """A trait for an (ip, port) tuple.
1338 1383
1339 1384 This allows for both IPv4 IP addresses as well as hostnames.
1340 1385 """
1341 1386
1342 1387 default_value = ('127.0.0.1', 0)
1343 1388 info_text = 'an (ip, port) tuple'
1344 1389
1345 1390 def validate(self, obj, value):
1346 1391 if isinstance(value, tuple):
1347 1392 if len(value) == 2:
1348 1393 if isinstance(value[0], basestring) and isinstance(value[1], int):
1349 1394 port = value[1]
1350 1395 if port >= 0 and port <= 65535:
1351 1396 return value
1352 1397 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now