1""" 2A lightweight Traits like module. 3 4This is designed to provide a lightweight, simple, pure Python version of 5many of the capabilities of enthought.traits. This includes: 6 7* Validation 8* Type specification with defaults 9* Static and dynamic notification 10* Basic predefined types 11* An API that is similar to enthought.traits 12 13We don't support: 14 15* Delegation 16* Automatic GUI generation 17* A full set of trait types. Most importantly, we don't provide container 18 traits (list, dict, tuple) that can trigger notifications if their 19 contents change. 20* API compatibility with enthought.traits 21 22There are also some important difference in our design: 23 24* enthought.traits does not validate default values. We do. 25 26We choose to create this module because we need these capabilities, but 27we need them to be pure Python so they work in all Python implementations, 28including Jython and IronPython. 29 30Inheritance diagram: 31 32.. inheritance-diagram:: traitlets.traitlets 33 :parts: 3 34""" 35 36# Copyright (c) IPython Development Team. 37# Distributed under the terms of the Modified BSD License. 38# 39# Adapted from enthought.traits, Copyright (c) Enthought, Inc., 40# also under the terms of the Modified BSD License. 41 42from ast import literal_eval 43import contextlib 44import inspect 45import os 46import re 47import sys 48import types 49import enum 50from warnings import warn, warn_explicit 51 52from .utils.getargspec import getargspec 53from .utils.importstring import import_item 54from .utils.sentinel import Sentinel 55from .utils.bunch import Bunch 56from .utils.descriptions import describe, class_of, add_article, repr_type 57 58SequenceTypes = (list, tuple, set, frozenset) 59 60# backward compatibility, use to differ between Python 2 and 3. 61ClassTypes = (type,) 62 63# exports: 64 65__all__ = [ 66 "default", 67 "validate", 68 "observe", 69 "observe_compat", 70 "link", 71 "directional_link", 72 "dlink", 73 "Undefined", 74 "All", 75 "NoDefaultSpecified", 76 "TraitError", 77 "HasDescriptors", 78 "HasTraits", 79 "MetaHasDescriptors", 80 "MetaHasTraits", 81 "BaseDescriptor", 82 "TraitType", 83 "parse_notifier_name", 84] 85 86# any TraitType subclass (that doesn't start with _) will be added automatically 87 88#----------------------------------------------------------------------------- 89# Basic classes 90#----------------------------------------------------------------------------- 91 92 93Undefined = Sentinel('Undefined', 'traitlets', 94''' 95Used in Traitlets to specify that no defaults are set in kwargs 96''' 97) 98 99All = Sentinel('All', 'traitlets', 100''' 101Used in Traitlets to listen to all types of notification or to notifications 102from all trait attributes. 103''' 104) 105 106# Deprecated alias 107NoDefaultSpecified = Undefined 108 109class TraitError(Exception): 110 pass 111 112#----------------------------------------------------------------------------- 113# Utilities 114#----------------------------------------------------------------------------- 115 116_name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") 117 118def isidentifier(s): 119 return s.isidentifier() 120 121_deprecations_shown = set() 122def _should_warn(key): 123 """Add our own checks for too many deprecation warnings. 124 125 Limit to once per package. 126 """ 127 env_flag = os.environ.get('TRAITLETS_ALL_DEPRECATIONS') 128 if env_flag and env_flag != '0': 129 return True 130 131 if key not in _deprecations_shown: 132 _deprecations_shown.add(key) 133 return True 134 else: 135 return False 136 137def _deprecated_method(method, cls, method_name, msg): 138 """Show deprecation warning about a magic method definition. 139 140 Uses warn_explicit to bind warning to method definition instead of triggering code, 141 which isn't relevant. 142 """ 143 warn_msg = "{classname}.{method_name} is deprecated in traitlets 4.1: {msg}".format( 144 classname=cls.__name__, method_name=method_name, msg=msg 145 ) 146 147 for parent in inspect.getmro(cls): 148 if method_name in parent.__dict__: 149 cls = parent 150 break 151 # limit deprecation messages to once per package 152 package_name = cls.__module__.split('.', 1)[0] 153 key = (package_name, msg) 154 if not _should_warn(key): 155 return 156 try: 157 fname = inspect.getsourcefile(method) or "<unknown>" 158 lineno = inspect.getsourcelines(method)[1] or 0 159 except (OSError, TypeError) as e: 160 # Failed to inspect for some reason 161 warn(warn_msg + ('\n(inspection failed) %s' % e), DeprecationWarning) 162 else: 163 warn_explicit(warn_msg, DeprecationWarning, fname, lineno) 164 165def _safe_literal_eval(s): 166 """Safely evaluate an expression 167 168 Returns original string if eval fails. 169 170 Use only where types are ambiguous. 171 """ 172 try: 173 return literal_eval(s) 174 except (NameError, SyntaxError, ValueError): 175 return s 176 177def is_trait(t): 178 """ Returns whether the given value is an instance or subclass of TraitType. 179 """ 180 return (isinstance(t, TraitType) or 181 (isinstance(t, type) and issubclass(t, TraitType))) 182 183 184def parse_notifier_name(names): 185 """Convert the name argument to a list of names. 186 187 Examples 188 -------- 189 >>> parse_notifier_name([]) 190 [All] 191 >>> parse_notifier_name("a") 192 ['a'] 193 >>> parse_notifier_name(["a", "b"]) 194 ['a', 'b'] 195 >>> parse_notifier_name(All) 196 [All] 197 """ 198 if names is All or isinstance(names, str): 199 return [names] 200 else: 201 if not names or All in names: 202 return [All] 203 for n in names: 204 if not isinstance(n, str): 205 raise TypeError("names must be strings, not %r" % n) 206 return names 207 208 209class _SimpleTest: 210 def __init__ ( self, value ): self.value = value 211 def __call__ ( self, test ): 212 return test == self.value 213 def __repr__(self): 214 return "<SimpleTest(%r)" % self.value 215 def __str__(self): 216 return self.__repr__() 217 218 219def getmembers(object, predicate=None): 220 """A safe version of inspect.getmembers that handles missing attributes. 221 222 This is useful when there are descriptor based attributes that for 223 some reason raise AttributeError even though they exist. This happens 224 in zope.inteface with the __provides__ attribute. 225 """ 226 results = [] 227 for key in dir(object): 228 try: 229 value = getattr(object, key) 230 except AttributeError: 231 pass 232 else: 233 if not predicate or predicate(value): 234 results.append((key, value)) 235 results.sort() 236 return results 237 238def _validate_link(*tuples): 239 """Validate arguments for traitlet link functions""" 240 for t in tuples: 241 if not len(t) == 2: 242 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t) 243 obj, trait_name = t 244 if not isinstance(obj, HasTraits): 245 raise TypeError("Each object must be HasTraits, not %r" % type(obj)) 246 if not trait_name in obj.traits(): 247 raise TypeError("%r has no trait %r" % (obj, trait_name)) 248 249class link(object): 250 """Link traits from different objects together so they remain in sync. 251 252 Parameters 253 ---------- 254 source : (object / attribute name) pair 255 target : (object / attribute name) pair 256 transform: iterable with two callables (optional) 257 Data transformation between source and target and target and source. 258 259 Examples 260 -------- 261 >>> c = link((src, "value"), (tgt, "value")) 262 >>> src.value = 5 # updates other objects as well 263 """ 264 updating = False 265 266 def __init__(self, source, target, transform=None): 267 _validate_link(source, target) 268 self.source, self.target = source, target 269 self._transform, self._transform_inv = ( 270 transform if transform else (lambda x: x,) * 2) 271 272 self.link() 273 274 def link(self): 275 try: 276 setattr(self.target[0], self.target[1], 277 self._transform(getattr(self.source[0], self.source[1]))) 278 279 finally: 280 self.source[0].observe(self._update_target, names=self.source[1]) 281 self.target[0].observe(self._update_source, names=self.target[1]) 282 283 @contextlib.contextmanager 284 def _busy_updating(self): 285 self.updating = True 286 try: 287 yield 288 finally: 289 self.updating = False 290 291 def _update_target(self, change): 292 if self.updating: 293 return 294 with self._busy_updating(): 295 setattr(self.target[0], self.target[1], self._transform(change.new)) 296 if getattr(self.source[0], self.source[1]) != change.new: 297 raise TraitError( 298 "Broken link {}: the source value changed while updating " 299 "the target.".format(self)) 300 301 def _update_source(self, change): 302 if self.updating: 303 return 304 with self._busy_updating(): 305 setattr(self.source[0], self.source[1], 306 self._transform_inv(change.new)) 307 if getattr(self.target[0], self.target[1]) != change.new: 308 raise TraitError( 309 "Broken link {}: the target value changed while updating " 310 "the source.".format(self)) 311 312 def unlink(self): 313 self.source[0].unobserve(self._update_target, names=self.source[1]) 314 self.target[0].unobserve(self._update_source, names=self.target[1]) 315 316 317class directional_link(object): 318 """Link the trait of a source object with traits of target objects. 319 320 Parameters 321 ---------- 322 source : (object, attribute name) pair 323 target : (object, attribute name) pair 324 transform: callable (optional) 325 Data transformation between source and target. 326 327 Examples 328 -------- 329 >>> c = directional_link((src, "value"), (tgt, "value")) 330 >>> src.value = 5 # updates target objects 331 >>> tgt.value = 6 # does not update source object 332 """ 333 updating = False 334 335 def __init__(self, source, target, transform=None): 336 self._transform = transform if transform else lambda x: x 337 _validate_link(source, target) 338 self.source, self.target = source, target 339 self.link() 340 341 def link(self): 342 try: 343 setattr(self.target[0], self.target[1], 344 self._transform(getattr(self.source[0], self.source[1]))) 345 finally: 346 self.source[0].observe(self._update, names=self.source[1]) 347 348 @contextlib.contextmanager 349 def _busy_updating(self): 350 self.updating = True 351 try: 352 yield 353 finally: 354 self.updating = False 355 356 def _update(self, change): 357 if self.updating: 358 return 359 with self._busy_updating(): 360 setattr(self.target[0], self.target[1], 361 self._transform(change.new)) 362 363 def unlink(self): 364 self.source[0].unobserve(self._update, names=self.source[1]) 365 366dlink = directional_link 367 368 369#----------------------------------------------------------------------------- 370# Base Descriptor Class 371#----------------------------------------------------------------------------- 372 373 374class BaseDescriptor(object): 375 """Base descriptor class 376 377 Notes 378 ----- 379 This implements Python's descriptor protocol. 380 381 This class is the base class for all such descriptors. The 382 only magic we use is a custom metaclass for the main :class:`HasTraits` 383 class that does the following: 384 385 1. Sets the :attr:`name` attribute of every :class:`BaseDescriptor` 386 instance in the class dict to the name of the attribute. 387 2. Sets the :attr:`this_class` attribute of every :class:`BaseDescriptor` 388 instance in the class dict to the *class* that declared the trait. 389 This is used by the :class:`This` trait to allow subclasses to 390 accept superclasses for :class:`This` values. 391 """ 392 393 name = None 394 this_class = None 395 396 def class_init(self, cls, name): 397 """Part of the initialization which may depend on the underlying 398 HasDescriptors class. 399 400 It is typically overloaded for specific types. 401 402 This method is called by :meth:`MetaHasDescriptors.__init__` 403 passing the class (`cls`) and `name` under which the descriptor 404 has been assigned. 405 """ 406 self.this_class = cls 407 self.name = name 408 409 def subclass_init(self, cls): 410 pass 411 412 def instance_init(self, obj): 413 """Part of the initialization which may depend on the underlying 414 HasDescriptors instance. 415 416 It is typically overloaded for specific types. 417 418 This method is called by :meth:`HasTraits.__new__` and in the 419 :meth:`BaseDescriptor.instance_init` method of descriptors holding 420 other descriptors. 421 """ 422 pass 423 424 425class TraitType(BaseDescriptor): 426 """A base class for all trait types. 427 """ 428 429 metadata = {} 430 allow_none = False 431 read_only = False 432 info_text = 'any value' 433 default_value = Undefined 434 435 def __init__(self, default_value=Undefined, allow_none=False, read_only=None, help=None, 436 config=None, **kwargs): 437 """Declare a traitlet. 438 439 If *allow_none* is True, None is a valid value in addition to any 440 values that are normally valid. The default is up to the subclass. 441 For most trait types, the default value for ``allow_none`` is False. 442 443 If *read_only* is True, attempts to directly modify a trait attribute raises a TraitError. 444 445 Extra metadata can be associated with the traitlet using the .tag() convenience method 446 or by using the traitlet instance's .metadata dictionary. 447 """ 448 if default_value is not Undefined: 449 self.default_value = default_value 450 if allow_none: 451 self.allow_none = allow_none 452 if read_only is not None: 453 self.read_only = read_only 454 self.help = help if help is not None else '' 455 456 if len(kwargs) > 0: 457 stacklevel = 1 458 f = inspect.currentframe() 459 # count supers to determine stacklevel for warning 460 while f.f_code.co_name == '__init__': 461 stacklevel += 1 462 f = f.f_back 463 mod = f.f_globals.get('__name__') or '' 464 pkg = mod.split('.', 1)[0] 465 key = tuple(['metadata-tag', pkg] + sorted(kwargs)) 466 if _should_warn(key): 467 warn("metadata %s was set from the constructor. " 468 "With traitlets 4.1, metadata should be set using the .tag() method, " 469 "e.g., Int().tag(key1='value1', key2='value2')" % (kwargs,), 470 DeprecationWarning, stacklevel=stacklevel) 471 if len(self.metadata) > 0: 472 self.metadata = self.metadata.copy() 473 self.metadata.update(kwargs) 474 else: 475 self.metadata = kwargs 476 else: 477 self.metadata = self.metadata.copy() 478 if config is not None: 479 self.metadata['config'] = config 480 481 # We add help to the metadata during a deprecation period so that 482 # code that looks for the help string there can find it. 483 if help is not None: 484 self.metadata['help'] = help 485 486 def from_string(self, s): 487 """Get a value from a config string 488 489 such as an environment variable or CLI arguments. 490 491 Traits can override this method to define their own 492 parsing of config strings. 493 494 .. seealso:: item_from_string 495 496 .. versionadded:: 5.0 497 """ 498 if self.allow_none and s == 'None': 499 return None 500 return s 501 502 def default(self, obj=None): 503 """The default generator for this trait 504 505 Notes 506 ----- 507 This method is registered to HasTraits classes during ``class_init`` 508 in the same way that dynamic defaults defined by ``@default`` are. 509 """ 510 if self.default_value is not Undefined: 511 return self.default_value 512 elif hasattr(self, 'make_dynamic_default'): 513 return self.make_dynamic_default() 514 else: 515 # Undefined will raise in TraitType.get 516 return self.default_value 517 518 def get_default_value(self): 519 """DEPRECATED: Retrieve the static default value for this trait. 520 Use self.default_value instead 521 """ 522 warn("get_default_value is deprecated in traitlets 4.0: use the .default_value attribute", DeprecationWarning, 523 stacklevel=2) 524 return self.default_value 525 526 def init_default_value(self, obj): 527 """DEPRECATED: Set the static default value for the trait type. 528 """ 529 warn("init_default_value is deprecated in traitlets 4.0, and may be removed in the future", DeprecationWarning, 530 stacklevel=2) 531 value = self._validate(obj, self.default_value) 532 obj._trait_values[self.name] = value 533 return value 534 535 def get(self, obj, cls=None): 536 try: 537 value = obj._trait_values[self.name] 538 except KeyError: 539 # Check for a dynamic initializer. 540 default = obj.trait_defaults(self.name) 541 if default is Undefined: 542 warn( 543 "Explicit using of Undefined as the default value " 544 "is deprecated in traitlets 5.0, and may cause " 545 "exceptions in the future.", 546 DeprecationWarning, 547 stacklevel=2 548 ) 549 with obj.cross_validation_lock: 550 value = self._validate(obj, default) 551 obj._trait_values[self.name] = value 552 obj._notify_observers(Bunch( 553 name=self.name, 554 value=value, 555 owner=obj, 556 type='default', 557 )) 558 return value 559 except Exception: 560 # This should never be reached. 561 raise TraitError('Unexpected error in TraitType: ' 562 'default value not set properly') 563 else: 564 return value 565 566 def __get__(self, obj, cls=None): 567 """Get the value of the trait by self.name for the instance. 568 569 Default values are instantiated when :meth:`HasTraits.__new__` 570 is called. Thus by the time this method gets called either the 571 default value or a user defined value (they called :meth:`__set__`) 572 is in the :class:`HasTraits` instance. 573 """ 574 if obj is None: 575 return self 576 else: 577 return self.get(obj, cls) 578 579 def set(self, obj, value): 580 new_value = self._validate(obj, value) 581 try: 582 old_value = obj._trait_values[self.name] 583 except KeyError: 584 old_value = self.default_value 585 586 obj._trait_values[self.name] = new_value 587 try: 588 silent = bool(old_value == new_value) 589 except Exception: 590 # if there is an error in comparing, default to notify 591 silent = False 592 if silent is not True: 593 # we explicitly compare silent to True just in case the equality 594 # comparison above returns something other than True/False 595 obj._notify_trait(self.name, old_value, new_value) 596 597 def __set__(self, obj, value): 598 """Set the value of the trait by self.name for the instance. 599 600 Values pass through a validation stage where errors are raised when 601 impropper types, or types that cannot be coerced, are encountered. 602 """ 603 if self.read_only: 604 raise TraitError('The "%s" trait is read-only.' % self.name) 605 else: 606 self.set(obj, value) 607 608 def _validate(self, obj, value): 609 if value is None and self.allow_none: 610 return value 611 if hasattr(self, 'validate'): 612 value = self.validate(obj, value) 613 if obj._cross_validation_lock is False: 614 value = self._cross_validate(obj, value) 615 return value 616 617 def _cross_validate(self, obj, value): 618 if self.name in obj._trait_validators: 619 proposal = Bunch({'trait': self, 'value': value, 'owner': obj}) 620 value = obj._trait_validators[self.name](obj, proposal) 621 elif hasattr(obj, '_%s_validate' % self.name): 622 meth_name = '_%s_validate' % self.name 623 cross_validate = getattr(obj, meth_name) 624 _deprecated_method(cross_validate, obj.__class__, meth_name, 625 "use @validate decorator instead.") 626 value = cross_validate(value, self) 627 return value 628 629 def __or__(self, other): 630 if isinstance(other, Union): 631 return Union([self] + other.trait_types) 632 else: 633 return Union([self, other]) 634 635 def info(self): 636 return self.info_text 637 638 def error(self, obj, value, error=None, info=None): 639 """Raise a TraitError 640 641 Parameters 642 ---------- 643 obj : HasTraits or None 644 The instance which owns the trait. If not 645 object is given, then an object agnostic 646 error will be raised. 647 value : any 648 The value that caused the error. 649 error : Exception (default: None) 650 An error that was raised by a child trait. 651 The arguments of this exception should be 652 of the form ``(value, info, *traits)``. 653 Where the ``value`` and ``info`` are the 654 problem value, and string describing the 655 expected value. The ``traits`` are a series 656 of :class:`TraitType` instances that are 657 "children" of this one (the first being 658 the deepest). 659 info : str (default: None) 660 A description of the expected value. By 661 default this is infered from this trait's 662 ``info`` method. 663 """ 664 if error is not None: 665 # handle nested error 666 error.args += (self,) 667 if self.name is not None: 668 # this is the root trait that must format the final message 669 chain = " of ".join(describe("a", t) for t in error.args[2:]) 670 if obj is not None: 671 error.args = ("The '%s' trait of %s instance contains %s which " 672 "expected %s, not %s." % (self.name, describe("an", obj), 673 chain, error.args[1], describe("the", error.args[0])),) 674 else: 675 error.args = ("The '%s' trait contains %s which " 676 "expected %s, not %s." % (self.name, chain, 677 error.args[1], describe("the", error.args[0])),) 678 raise error 679 else: 680 # this trait caused an error 681 if self.name is None: 682 # this is not the root trait 683 raise TraitError(value, info or self.info(), self) 684 else: 685 # this is the root trait 686 if obj is not None: 687 e = "The '%s' trait of %s instance expected %s, not %s." % ( 688 self.name, class_of(obj), self.info(), describe("the", value)) 689 else: 690 e = "The '%s' trait expected %s, not %s." % ( 691 self.name, self.info(), describe("the", value)) 692 raise TraitError(e) 693 694 def get_metadata(self, key, default=None): 695 """DEPRECATED: Get a metadata value. 696 697 Use .metadata[key] or .metadata.get(key, default) instead. 698 """ 699 if key == 'help': 700 msg = "use the instance .help string directly, like x.help" 701 else: 702 msg = "use the instance .metadata dictionary directly, like x.metadata[key] or x.metadata.get(key, default)" 703 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2) 704 return self.metadata.get(key, default) 705 706 def set_metadata(self, key, value): 707 """DEPRECATED: Set a metadata key/value. 708 709 Use .metadata[key] = value instead. 710 """ 711 if key == 'help': 712 msg = "use the instance .help string directly, like x.help = value" 713 else: 714 msg = "use the instance .metadata dictionary directly, like x.metadata[key] = value" 715 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2) 716 self.metadata[key] = value 717 718 def tag(self, **metadata): 719 """Sets metadata and returns self. 720 721 This allows convenient metadata tagging when initializing the trait, such as: 722 723 >>> Int(0).tag(config=True, sync=True) 724 """ 725 maybe_constructor_keywords = set(metadata.keys()).intersection({'help','allow_none', 'read_only', 'default_value'}) 726 if maybe_constructor_keywords: 727 warn('The following attributes are set in using `tag`, but seem to be constructor keywords arguments: %s '% 728 maybe_constructor_keywords, UserWarning, stacklevel=2) 729 730 self.metadata.update(metadata) 731 return self 732 733 def default_value_repr(self): 734 return repr(self.default_value) 735 736#----------------------------------------------------------------------------- 737# The HasTraits implementation 738#----------------------------------------------------------------------------- 739 740class _CallbackWrapper(object): 741 """An object adapting a on_trait_change callback into an observe callback. 742 743 The comparison operator __eq__ is implemented to enable removal of wrapped 744 callbacks. 745 """ 746 747 def __init__(self, cb): 748 self.cb = cb 749 # Bound methods have an additional 'self' argument. 750 offset = -1 if isinstance(self.cb, types.MethodType) else 0 751 self.nargs = len(getargspec(cb)[0]) + offset 752 if (self.nargs > 4): 753 raise TraitError('a trait changed callback must have 0-4 arguments.') 754 755 def __eq__(self, other): 756 # The wrapper is equal to the wrapped element 757 if isinstance(other, _CallbackWrapper): 758 return self.cb == other.cb 759 else: 760 return self.cb == other 761 762 def __call__(self, change): 763 # The wrapper is callable 764 if self.nargs == 0: 765 self.cb() 766 elif self.nargs == 1: 767 self.cb(change.name) 768 elif self.nargs == 2: 769 self.cb(change.name, change.new) 770 elif self.nargs == 3: 771 self.cb(change.name, change.old, change.new) 772 elif self.nargs == 4: 773 self.cb(change.name, change.old, change.new, change.owner) 774 775def _callback_wrapper(cb): 776 if isinstance(cb, _CallbackWrapper): 777 return cb 778 else: 779 return _CallbackWrapper(cb) 780 781 782class MetaHasDescriptors(type): 783 """A metaclass for HasDescriptors. 784 785 This metaclass makes sure that any TraitType class attributes are 786 instantiated and sets their name attribute. 787 """ 788 789 def __new__(mcls, name, bases, classdict): 790 """Create the HasDescriptors class.""" 791 for k, v in classdict.items(): 792 # ---------------------------------------------------------------- 793 # Support of deprecated behavior allowing for TraitType types 794 # to be used instead of TraitType instances. 795 if inspect.isclass(v) and issubclass(v, TraitType): 796 warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)." 797 " Passing types is deprecated in traitlets 4.1.", 798 DeprecationWarning, stacklevel=2) 799 classdict[k] = v() 800 # ---------------------------------------------------------------- 801 802 return super(MetaHasDescriptors, mcls).__new__(mcls, name, bases, classdict) 803 804 def __init__(cls, name, bases, classdict): 805 """Finish initializing the HasDescriptors class.""" 806 super(MetaHasDescriptors, cls).__init__(name, bases, classdict) 807 cls.setup_class(classdict) 808 809 def setup_class(cls, classdict): 810 """Setup descriptor instance on the class 811 812 This sets the :attr:`this_class` and :attr:`name` attributes of each 813 BaseDescriptor in the class dict of the newly created ``cls`` before 814 calling their :attr:`class_init` method. 815 """ 816 for k, v in classdict.items(): 817 if isinstance(v, BaseDescriptor): 818 v.class_init(cls, k) 819 820 for k, v in getmembers(cls): 821 if isinstance(v, BaseDescriptor): 822 v.subclass_init(cls) 823 824 825class MetaHasTraits(MetaHasDescriptors): 826 """A metaclass for HasTraits.""" 827 828 def setup_class(cls, classdict): 829 cls._trait_default_generators = {} 830 super(MetaHasTraits, cls).setup_class(classdict) 831 832 833def observe(*names, type="change"): 834 """A decorator which can be used to observe Traits on a class. 835 836 The handler passed to the decorator will be called with one ``change`` 837 dict argument. The change dictionary at least holds a 'type' key and a 838 'name' key, corresponding respectively to the type of notification and the 839 name of the attribute that triggered the notification. 840 841 Other keys may be passed depending on the value of 'type'. In the case 842 where type is 'change', we also have the following keys: 843 * ``owner`` : the HasTraits instance 844 * ``old`` : the old value of the modified trait attribute 845 * ``new`` : the new value of the modified trait attribute 846 * ``name`` : the name of the modified trait attribute. 847 848 Parameters 849 ---------- 850 *names 851 The str names of the Traits to observe on the object. 852 type : str, kwarg-only 853 The type of event to observe (e.g. 'change') 854 """ 855 if not names: 856 raise TypeError("Please specify at least one trait name to observe.") 857 for name in names: 858 if name is not All and not isinstance(name, str): 859 raise TypeError("trait names to observe must be strings or All, not %r" % name) 860 return ObserveHandler(names, type=type) 861 862 863def observe_compat(func): 864 """Backward-compatibility shim decorator for observers 865 866 Use with: 867 868 @observe('name') 869 @observe_compat 870 def _foo_changed(self, change): 871 ... 872 873 With this, `super()._foo_changed(self, name, old, new)` in subclasses will still work. 874 Allows adoption of new observer API without breaking subclasses that override and super. 875 """ 876 def compatible_observer(self, change_or_name, old=Undefined, new=Undefined): 877 if isinstance(change_or_name, dict): 878 change = change_or_name 879 else: 880 clsname = self.__class__.__name__ 881 warn("A parent of %s._%s_changed has adopted the new (traitlets 4.1) @observe(change) API" % ( 882 clsname, change_or_name), DeprecationWarning) 883 change = Bunch( 884 type='change', 885 old=old, 886 new=new, 887 name=change_or_name, 888 owner=self, 889 ) 890 return func(self, change) 891 return compatible_observer 892 893 894def validate(*names): 895 """A decorator to register cross validator of HasTraits object's state 896 when a Trait is set. 897 898 The handler passed to the decorator must have one ``proposal`` dict argument. 899 The proposal dictionary must hold the following keys: 900 901 * ``owner`` : the HasTraits instance 902 * ``value`` : the proposed value for the modified trait attribute 903 * ``trait`` : the TraitType instance associated with the attribute 904 905 Parameters 906 ---------- 907 *names 908 The str names of the Traits to validate. 909 910 Notes 911 ----- 912 Since the owner has access to the ``HasTraits`` instance via the 'owner' key, 913 the registered cross validator could potentially make changes to attributes 914 of the ``HasTraits`` instance. However, we recommend not to do so. The reason 915 is that the cross-validation of attributes may run in arbitrary order when 916 exiting the ``hold_trait_notifications`` context, and such changes may not 917 commute. 918 """ 919 if not names: 920 raise TypeError("Please specify at least one trait name to validate.") 921 for name in names: 922 if name is not All and not isinstance(name, str): 923 raise TypeError("trait names to validate must be strings or All, not %r" % name) 924 return ValidateHandler(names) 925 926 927def default(name): 928 """ A decorator which assigns a dynamic default for a Trait on a HasTraits object. 929 930 Parameters 931 ---------- 932 name 933 The str name of the Trait on the object whose default should be generated. 934 935 Notes 936 ----- 937 Unlike observers and validators which are properties of the HasTraits 938 instance, default value generators are class-level properties. 939 940 Besides, default generators are only invoked if they are registered in 941 subclasses of `this_type`. 942 943 :: 944 945 class A(HasTraits): 946 bar = Int() 947 948 @default('bar') 949 def get_bar_default(self): 950 return 11 951 952 class B(A): 953 bar = Float() # This trait ignores the default generator defined in 954 # the base class A 955 956 class C(B): 957 958 @default('bar') 959 def some_other_default(self): # This default generator should not be 960 return 3.0 # ignored since it is defined in a 961 # class derived from B.a.this_class. 962 """ 963 if not isinstance(name, str): 964 raise TypeError("Trait name must be a string or All, not %r" % name) 965 return DefaultHandler(name) 966 967 968class EventHandler(BaseDescriptor): 969 970 def _init_call(self, func): 971 self.func = func 972 return self 973 974 def __call__(self, *args, **kwargs): 975 """Pass `*args` and `**kwargs` to the handler's function if it exists.""" 976 if hasattr(self, 'func'): 977 return self.func(*args, **kwargs) 978 else: 979 return self._init_call(*args, **kwargs) 980 981 def __get__(self, inst, cls=None): 982 if inst is None: 983 return self 984 return types.MethodType(self.func, inst) 985 986 987class ObserveHandler(EventHandler): 988 989 def __init__(self, names, type): 990 self.trait_names = names 991 self.type = type 992 993 def instance_init(self, inst): 994 inst.observe(self, self.trait_names, type=self.type) 995 996 997class ValidateHandler(EventHandler): 998 999 def __init__(self, names): 1000 self.trait_names = names 1001 1002 def instance_init(self, inst): 1003 inst._register_validator(self, self.trait_names) 1004 1005 1006class DefaultHandler(EventHandler): 1007 1008 def __init__(self, name): 1009 self.trait_name = name 1010 1011 def class_init(self, cls, name): 1012 super().class_init(cls, name) 1013 cls._trait_default_generators[self.trait_name] = self 1014 1015 1016class HasDescriptors(metaclass=MetaHasDescriptors): 1017 """The base class for all classes that have descriptors. 1018 """ 1019 1020 def __new__(*args, **kwargs): 1021 # Pass cls as args[0] to allow "cls" as keyword argument 1022 cls = args[0] 1023 args = args[1:] 1024 1025 # This is needed because object.__new__ only accepts 1026 # the cls argument. 1027 new_meth = super(HasDescriptors, cls).__new__ 1028 if new_meth is object.__new__: 1029 inst = new_meth(cls) 1030 else: 1031 inst = new_meth(cls, *args, **kwargs) 1032 inst.setup_instance(*args, **kwargs) 1033 return inst 1034 1035 def setup_instance(*args, **kwargs): 1036 """ 1037 This is called **before** self.__init__ is called. 1038 """ 1039 # Pass self as args[0] to allow "self" as keyword argument 1040 self = args[0] 1041 args = args[1:] 1042 1043 self._cross_validation_lock = False 1044 cls = self.__class__ 1045 for key in dir(cls): 1046 # Some descriptors raise AttributeError like zope.interface's 1047 # __provides__ attributes even though they exist. This causes 1048 # AttributeErrors even though they are listed in dir(cls). 1049 try: 1050 value = getattr(cls, key) 1051 except AttributeError: 1052 pass 1053 else: 1054 if isinstance(value, BaseDescriptor): 1055 value.instance_init(self) 1056 1057 1058class HasTraits(HasDescriptors, metaclass=MetaHasTraits): 1059 1060 def setup_instance(*args, **kwargs): 1061 # Pass self as args[0] to allow "self" as keyword argument 1062 self = args[0] 1063 args = args[1:] 1064 1065 self._trait_values = {} 1066 self._trait_notifiers = {} 1067 self._trait_validators = {} 1068 super(HasTraits, self).setup_instance(*args, **kwargs) 1069 1070 def __init__(self, *args, **kwargs): 1071 # Allow trait values to be set using keyword arguments. 1072 # We need to use setattr for this to trigger validation and 1073 # notifications. 1074 super_args = args 1075 super_kwargs = {} 1076 with self.hold_trait_notifications(): 1077 for key, value in kwargs.items(): 1078 if self.has_trait(key): 1079 setattr(self, key, value) 1080 else: 1081 # passthrough args that don't set traits to super 1082 super_kwargs[key] = value 1083 try: 1084 super(HasTraits, self).__init__(*super_args, **super_kwargs) 1085 except TypeError as e: 1086 arg_s_list = [ repr(arg) for arg in super_args ] 1087 for k, v in super_kwargs.items(): 1088 arg_s_list.append("%s=%r" % (k, v)) 1089 arg_s = ', '.join(arg_s_list) 1090 warn( 1091 "Passing unrecognized arguments to super({classname}).__init__({arg_s}).\n" 1092 "{error}\n" 1093 "This is deprecated in traitlets 4.2." 1094 "This error will be raised in a future release of traitlets." 1095 .format( 1096 arg_s=arg_s, classname=self.__class__.__name__, 1097 error=e, 1098 ), 1099 DeprecationWarning, 1100 stacklevel=2, 1101 ) 1102 1103 def __getstate__(self): 1104 d = self.__dict__.copy() 1105 # event handlers stored on an instance are 1106 # expected to be reinstantiated during a 1107 # recall of instance_init during __setstate__ 1108 d['_trait_notifiers'] = {} 1109 d['_trait_validators'] = {} 1110 d['_trait_values'] = self._trait_values.copy() 1111 d['_cross_validation_lock'] = False # FIXME: raise if cloning locked! 1112 1113 return d 1114 1115 def __setstate__(self, state): 1116 self.__dict__ = state.copy() 1117 1118 # event handlers are reassigned to self 1119 cls = self.__class__ 1120 for key in dir(cls): 1121 # Some descriptors raise AttributeError like zope.interface's 1122 # __provides__ attributes even though they exist. This causes 1123 # AttributeErrors even though they are listed in dir(cls). 1124 try: 1125 value = getattr(cls, key) 1126 except AttributeError: 1127 pass 1128 else: 1129 if isinstance(value, EventHandler): 1130 value.instance_init(self) 1131 1132 @property 1133 @contextlib.contextmanager 1134 def cross_validation_lock(self): 1135 """ 1136 A contextmanager for running a block with our cross validation lock set 1137 to True. 1138 1139 At the end of the block, the lock's value is restored to its value 1140 prior to entering the block. 1141 """ 1142 if self._cross_validation_lock: 1143 yield 1144 return 1145 else: 1146 try: 1147 self._cross_validation_lock = True 1148 yield 1149 finally: 1150 self._cross_validation_lock = False 1151 1152 @contextlib.contextmanager 1153 def hold_trait_notifications(self): 1154 """Context manager for bundling trait change notifications and cross 1155 validation. 1156 1157 Use this when doing multiple trait assignments (init, config), to avoid 1158 race conditions in trait notifiers requesting other trait values. 1159 All trait notifications will fire after all values have been assigned. 1160 """ 1161 if self._cross_validation_lock: 1162 yield 1163 return 1164 else: 1165 cache = {} 1166 notify_change = self.notify_change 1167 1168 def compress(past_changes, change): 1169 """Merges the provided change with the last if possible.""" 1170 if past_changes is None: 1171 return [change] 1172 else: 1173 if past_changes[-1]['type'] == 'change' and change.type == 'change': 1174 past_changes[-1]['new'] = change.new 1175 else: 1176 # In case of changes other than 'change', append the notification. 1177 past_changes.append(change) 1178 return past_changes 1179 1180 def hold(change): 1181 name = change.name 1182 cache[name] = compress(cache.get(name), change) 1183 1184 try: 1185 # Replace notify_change with `hold`, caching and compressing 1186 # notifications, disable cross validation and yield. 1187 self.notify_change = hold 1188 self._cross_validation_lock = True 1189 yield 1190 # Cross validate final values when context is released. 1191 for name in list(cache.keys()): 1192 trait = getattr(self.__class__, name) 1193 value = trait._cross_validate(self, getattr(self, name)) 1194 self.set_trait(name, value) 1195 except TraitError as e: 1196 # Roll back in case of TraitError during final cross validation. 1197 self.notify_change = lambda x: None 1198 for name, changes in cache.items(): 1199 for change in changes[::-1]: 1200 # TODO: Separate in a rollback function per notification type. 1201 if change.type == 'change': 1202 if change.old is not Undefined: 1203 self.set_trait(name, change.old) 1204 else: 1205 self._trait_values.pop(name) 1206 cache = {} 1207 raise e 1208 finally: 1209 self._cross_validation_lock = False 1210 # Restore method retrieval from class 1211 del self.notify_change 1212 1213 # trigger delayed notifications 1214 for changes in cache.values(): 1215 for change in changes: 1216 self.notify_change(change) 1217 1218 def _notify_trait(self, name, old_value, new_value): 1219 self.notify_change(Bunch( 1220 name=name, 1221 old=old_value, 1222 new=new_value, 1223 owner=self, 1224 type='change', 1225 )) 1226 1227 def notify_change(self, change): 1228 """Notify observers of a change event""" 1229 return self._notify_observers(change) 1230 1231 def _notify_observers(self, event): 1232 """Notify observers of any event""" 1233 if not isinstance(event, Bunch): 1234 # cast to bunch if given a dict 1235 event = Bunch(event) 1236 name, type = event.name, event.type 1237 1238 callables = [] 1239 callables.extend(self._trait_notifiers.get(name, {}).get(type, [])) 1240 callables.extend(self._trait_notifiers.get(name, {}).get(All, [])) 1241 callables.extend(self._trait_notifiers.get(All, {}).get(type, [])) 1242 callables.extend(self._trait_notifiers.get(All, {}).get(All, [])) 1243 1244 # Now static ones 1245 magic_name = '_%s_changed' % name 1246 if event.type == "change" and hasattr(self, magic_name): 1247 class_value = getattr(self.__class__, magic_name) 1248 if not isinstance(class_value, ObserveHandler): 1249 _deprecated_method(class_value, self.__class__, magic_name, 1250 "use @observe and @unobserve instead.") 1251 cb = getattr(self, magic_name) 1252 # Only append the magic method if it was not manually registered 1253 if cb not in callables: 1254 callables.append(_callback_wrapper(cb)) 1255 1256 # Call them all now 1257 # Traits catches and logs errors here. I allow them to raise 1258 for c in callables: 1259 # Bound methods have an additional 'self' argument. 1260 1261 if isinstance(c, _CallbackWrapper): 1262 c = c.__call__ 1263 elif isinstance(c, EventHandler) and c.name is not None: 1264 c = getattr(self, c.name) 1265 1266 c(event) 1267 1268 def _add_notifiers(self, handler, name, type): 1269 if name not in self._trait_notifiers: 1270 nlist = [] 1271 self._trait_notifiers[name] = {type: nlist} 1272 else: 1273 if type not in self._trait_notifiers[name]: 1274 nlist = [] 1275 self._trait_notifiers[name][type] = nlist 1276 else: 1277 nlist = self._trait_notifiers[name][type] 1278 if handler not in nlist: 1279 nlist.append(handler) 1280 1281 def _remove_notifiers(self, handler, name, type): 1282 try: 1283 if handler is None: 1284 del self._trait_notifiers[name][type] 1285 else: 1286 self._trait_notifiers[name][type].remove(handler) 1287 except KeyError: 1288 pass 1289 1290 def on_trait_change(self, handler=None, name=None, remove=False): 1291 """DEPRECATED: Setup a handler to be called when a trait changes. 1292 1293 This is used to setup dynamic notifications of trait changes. 1294 1295 Static handlers can be created by creating methods on a HasTraits 1296 subclass with the naming convention '_[traitname]_changed'. Thus, 1297 to create static handler for the trait 'a', create the method 1298 _a_changed(self, name, old, new) (fewer arguments can be used, see 1299 below). 1300 1301 If `remove` is True and `handler` is not specified, all change 1302 handlers for the specified name are uninstalled. 1303 1304 Parameters 1305 ---------- 1306 handler : callable, None 1307 A callable that is called when a trait changes. Its 1308 signature can be handler(), handler(name), handler(name, new), 1309 handler(name, old, new), or handler(name, old, new, self). 1310 name : list, str, None 1311 If None, the handler will apply to all traits. If a list 1312 of str, handler will apply to all names in the list. If a 1313 str, the handler will apply just to that name. 1314 remove : bool 1315 If False (the default), then install the handler. If True 1316 then unintall it. 1317 """ 1318 warn("on_trait_change is deprecated in traitlets 4.1: use observe instead", 1319 DeprecationWarning, stacklevel=2) 1320 if name is None: 1321 name = All 1322 if remove: 1323 self.unobserve(_callback_wrapper(handler), names=name) 1324 else: 1325 self.observe(_callback_wrapper(handler), names=name) 1326 1327 def observe(self, handler, names=All, type='change'): 1328 """Setup a handler to be called when a trait changes. 1329 1330 This is used to setup dynamic notifications of trait changes. 1331 1332 Parameters 1333 ---------- 1334 handler : callable 1335 A callable that is called when a trait changes. Its 1336 signature should be ``handler(change)``, where ``change`` is a 1337 dictionary. The change dictionary at least holds a 'type' key. 1338 * ``type``: the type of notification. 1339 Other keys may be passed depending on the value of 'type'. In the 1340 case where type is 'change', we also have the following keys: 1341 * ``owner`` : the HasTraits instance 1342 * ``old`` : the old value of the modified trait attribute 1343 * ``new`` : the new value of the modified trait attribute 1344 * ``name`` : the name of the modified trait attribute. 1345 names : list, str, All 1346 If names is All, the handler will apply to all traits. If a list 1347 of str, handler will apply to all names in the list. If a 1348 str, the handler will apply just to that name. 1349 type : str, All (default: 'change') 1350 The type of notification to filter by. If equal to All, then all 1351 notifications are passed to the observe handler. 1352 """ 1353 names = parse_notifier_name(names) 1354 for n in names: 1355 self._add_notifiers(handler, n, type) 1356 1357 def unobserve(self, handler, names=All, type='change'): 1358 """Remove a trait change handler. 1359 1360 This is used to unregister handlers to trait change notifications. 1361 1362 Parameters 1363 ---------- 1364 handler : callable 1365 The callable called when a trait attribute changes. 1366 names : list, str, All (default: All) 1367 The names of the traits for which the specified handler should be 1368 uninstalled. If names is All, the specified handler is uninstalled 1369 from the list of notifiers corresponding to all changes. 1370 type : str or All (default: 'change') 1371 The type of notification to filter by. If All, the specified handler 1372 is uninstalled from the list of notifiers corresponding to all types. 1373 """ 1374 names = parse_notifier_name(names) 1375 for n in names: 1376 self._remove_notifiers(handler, n, type) 1377 1378 def unobserve_all(self, name=All): 1379 """Remove trait change handlers of any type for the specified name. 1380 If name is not specified, removes all trait notifiers.""" 1381 if name is All: 1382 self._trait_notifiers = {} 1383 else: 1384 try: 1385 del self._trait_notifiers[name] 1386 except KeyError: 1387 pass 1388 1389 def _register_validator(self, handler, names): 1390 """Setup a handler to be called when a trait should be cross validated. 1391 1392 This is used to setup dynamic notifications for cross-validation. 1393 1394 If a validator is already registered for any of the provided names, a 1395 TraitError is raised and no new validator is registered. 1396 1397 Parameters 1398 ---------- 1399 handler : callable 1400 A callable that is called when the given trait is cross-validated. 1401 Its signature is handler(proposal), where proposal is a Bunch (dictionary with attribute access) 1402 with the following attributes/keys: 1403 * ``owner`` : the HasTraits instance 1404 * ``value`` : the proposed value for the modified trait attribute 1405 * ``trait`` : the TraitType instance associated with the attribute 1406 names : List of strings 1407 The names of the traits that should be cross-validated 1408 """ 1409 for name in names: 1410 magic_name = '_%s_validate' % name 1411 if hasattr(self, magic_name): 1412 class_value = getattr(self.__class__, magic_name) 1413 if not isinstance(class_value, ValidateHandler): 1414 _deprecated_method(class_value, self.__class__, magic_name, 1415 "use @validate decorator instead.") 1416 for name in names: 1417 self._trait_validators[name] = handler 1418 1419 def add_traits(self, **traits): 1420 """Dynamically add trait attributes to the HasTraits instance.""" 1421 cls = self.__class__ 1422 attrs = {"__module__": cls.__module__} 1423 if hasattr(cls, "__qualname__"): 1424 # __qualname__ introduced in Python 3.3 (see PEP 3155) 1425 attrs["__qualname__"] = cls.__qualname__ 1426 attrs.update(traits) 1427 self.__class__ = type(cls.__name__, (cls,), attrs) 1428 for trait in traits.values(): 1429 trait.instance_init(self) 1430 1431 def set_trait(self, name, value): 1432 """Forcibly sets trait attribute, including read-only attributes.""" 1433 cls = self.__class__ 1434 if not self.has_trait(name): 1435 raise TraitError("Class %s does not have a trait named %s" % 1436 (cls.__name__, name)) 1437 else: 1438 getattr(cls, name).set(self, value) 1439 1440 @classmethod 1441 def class_trait_names(cls, **metadata): 1442 """Get a list of all the names of this class' traits. 1443 1444 This method is just like the :meth:`trait_names` method, 1445 but is unbound. 1446 """ 1447 return list(cls.class_traits(**metadata)) 1448 1449 @classmethod 1450 def class_traits(cls, **metadata): 1451 """Get a ``dict`` of all the traits of this class. The dictionary 1452 is keyed on the name and the values are the TraitType objects. 1453 1454 This method is just like the :meth:`traits` method, but is unbound. 1455 1456 The TraitTypes returned don't know anything about the values 1457 that the various HasTrait's instances are holding. 1458 1459 The metadata kwargs allow functions to be passed in which 1460 filter traits based on metadata values. The functions should 1461 take a single value as an argument and return a boolean. If 1462 any function returns False, then the trait is not included in 1463 the output. If a metadata key doesn't exist, None will be passed 1464 to the function. 1465 """ 1466 traits = dict([memb for memb in getmembers(cls) if 1467 isinstance(memb[1], TraitType)]) 1468 1469 if len(metadata) == 0: 1470 return traits 1471 1472 result = {} 1473 for name, trait in traits.items(): 1474 for meta_name, meta_eval in metadata.items(): 1475 if not callable(meta_eval): 1476 meta_eval = _SimpleTest(meta_eval) 1477 if not meta_eval(trait.metadata.get(meta_name, None)): 1478 break 1479 else: 1480 result[name] = trait 1481 1482 return result 1483 1484 @classmethod 1485 def class_own_traits(cls, **metadata): 1486 """Get a dict of all the traitlets defined on this class, not a parent. 1487 1488 Works like `class_traits`, except for excluding traits from parents. 1489 """ 1490 sup = super(cls, cls) 1491 return {n: t for (n, t) in cls.class_traits(**metadata).items() 1492 if getattr(sup, n, None) is not t} 1493 1494 def has_trait(self, name): 1495 """Returns True if the object has a trait with the specified name.""" 1496 return isinstance(getattr(self.__class__, name, None), TraitType) 1497 1498 def trait_has_value(self, name): 1499 """Returns True if the specified trait has a value. 1500 1501 This will return false even if ``getattr`` would return a 1502 dynamically generated default value. These default values 1503 will be recognized as existing only after they have been 1504 generated. 1505 1506 Example 1507 1508 .. code-block:: python 1509 1510 class MyClass(HasTraits): 1511 i = Int() 1512 1513 mc = MyClass() 1514 assert not mc.trait_has_value("i") 1515 mc.i # generates a default value 1516 assert mc.trait_has_value("i") 1517 """ 1518 return name in self._trait_values 1519 1520 def trait_values(self, **metadata): 1521 """A ``dict`` of trait names and their values. 1522 1523 The metadata kwargs allow functions to be passed in which 1524 filter traits based on metadata values. The functions should 1525 take a single value as an argument and return a boolean. If 1526 any function returns False, then the trait is not included in 1527 the output. If a metadata key doesn't exist, None will be passed 1528 to the function. 1529 1530 Returns 1531 ------- 1532 A ``dict`` of trait names and their values. 1533 1534 Notes 1535 ----- 1536 Trait values are retrieved via ``getattr``, any exceptions raised 1537 by traits or the operations they may trigger will result in the 1538 absence of a trait value in the result ``dict``. 1539 """ 1540 return {name: getattr(self, name) for name in self.trait_names(**metadata)} 1541 1542 def _get_trait_default_generator(self, name): 1543 """Return default generator for a given trait 1544 1545 Walk the MRO to resolve the correct default generator according to inheritance. 1546 """ 1547 method_name = '_%s_default' % name 1548 if method_name in self.__dict__: 1549 return getattr(self, method_name) 1550 cls = self.__class__ 1551 trait = getattr(cls, name) 1552 assert isinstance(trait, TraitType) 1553 # truncate mro to the class on which the trait is defined 1554 mro = cls.mro() 1555 try: 1556 mro = mro[:mro.index(trait.this_class) + 1] 1557 except ValueError: 1558 # this_class not in mro 1559 pass 1560 for c in mro: 1561 if method_name in c.__dict__: 1562 return getattr(c, method_name) 1563 if name in c.__dict__.get('_trait_default_generators', {}): 1564 return c._trait_default_generators[name] 1565 return trait.default 1566 1567 def trait_defaults(self, *names, **metadata): 1568 """Return a trait's default value or a dictionary of them 1569 1570 Notes 1571 ----- 1572 Dynamically generated default values may 1573 depend on the current state of the object.""" 1574 for n in names: 1575 if not self.has_trait(n): 1576 raise TraitError("'%s' is not a trait of '%s' " 1577 "instances" % (n, type(self).__name__)) 1578 1579 if len(names) == 1 and len(metadata) == 0: 1580 return self._get_trait_default_generator(names[0])(self) 1581 1582 trait_names = self.trait_names(**metadata) 1583 trait_names.extend(names) 1584 1585 defaults = {} 1586 for n in trait_names: 1587 defaults[n] = self._get_trait_default_generator(n)(self) 1588 return defaults 1589 1590 def trait_names(self, **metadata): 1591 """Get a list of all the names of this class' traits.""" 1592 return list(self.traits(**metadata)) 1593 1594 def traits(self, **metadata): 1595 """Get a ``dict`` of all the traits of this class. The dictionary 1596 is keyed on the name and the values are the TraitType objects. 1597 1598 The TraitTypes returned don't know anything about the values 1599 that the various HasTrait's instances are holding. 1600 1601 The metadata kwargs allow functions to be passed in which 1602 filter traits based on metadata values. The functions should 1603 take a single value as an argument and return a boolean. If 1604 any function returns False, then the trait is not included in 1605 the output. If a metadata key doesn't exist, None will be passed 1606 to the function. 1607 """ 1608 traits = dict([memb for memb in getmembers(self.__class__) if 1609 isinstance(memb[1], TraitType)]) 1610 1611 if len(metadata) == 0: 1612 return traits 1613 1614 result = {} 1615 for name, trait in traits.items(): 1616 for meta_name, meta_eval in metadata.items(): 1617 if not callable(meta_eval): 1618 meta_eval = _SimpleTest(meta_eval) 1619 if not meta_eval(trait.metadata.get(meta_name, None)): 1620 break 1621 else: 1622 result[name] = trait 1623 1624 return result 1625 1626 def trait_metadata(self, traitname, key, default=None): 1627 """Get metadata values for trait by key.""" 1628 try: 1629 trait = getattr(self.__class__, traitname) 1630 except AttributeError: 1631 raise TraitError("Class %s does not have a trait named %s" % 1632 (self.__class__.__name__, traitname)) 1633 metadata_name = '_' + traitname + '_metadata' 1634 if hasattr(self, metadata_name) and key in getattr(self, metadata_name): 1635 return getattr(self, metadata_name).get(key, default) 1636 else: 1637 return trait.metadata.get(key, default) 1638 1639 @classmethod 1640 def class_own_trait_events(cls, name): 1641 """Get a dict of all event handlers defined on this class, not a parent. 1642 1643 Works like ``event_handlers``, except for excluding traits from parents. 1644 """ 1645 sup = super(cls, cls) 1646 return {n: e for (n, e) in cls.events(name).items() 1647 if getattr(sup, n, None) is not e} 1648 1649 @classmethod 1650 def trait_events(cls, name=None): 1651 """Get a ``dict`` of all the event handlers of this class. 1652 1653 Parameters 1654 ---------- 1655 name : str (default: None) 1656 The name of a trait of this class. If name is ``None`` then all 1657 the event handlers of this class will be returned instead. 1658 1659 Returns 1660 ------- 1661 The event handlers associated with a trait name, or all event handlers. 1662 """ 1663 events = {} 1664 for k, v in getmembers(cls): 1665 if isinstance(v, EventHandler): 1666 if name is None: 1667 events[k] = v 1668 elif name in v.trait_names: 1669 events[k] = v 1670 elif hasattr(v, 'tags'): 1671 if cls.trait_names(**v.tags): 1672 events[k] = v 1673 return events 1674 1675#----------------------------------------------------------------------------- 1676# Actual TraitTypes implementations/subclasses 1677#----------------------------------------------------------------------------- 1678 1679#----------------------------------------------------------------------------- 1680# TraitTypes subclasses for handling classes and instances of classes 1681#----------------------------------------------------------------------------- 1682 1683 1684class ClassBasedTraitType(TraitType): 1685 """ 1686 A trait with error reporting and string -> type resolution for Type, 1687 Instance and This. 1688 """ 1689 1690 def _resolve_string(self, string): 1691 """ 1692 Resolve a string supplied for a type into an actual object. 1693 """ 1694 return import_item(string) 1695 1696 1697class Type(ClassBasedTraitType): 1698 """A trait whose value must be a subclass of a specified class.""" 1699 1700 def __init__(self, default_value=Undefined, klass=None, **kwargs): 1701 """Construct a Type trait 1702 1703 A Type trait specifies that its values must be subclasses of 1704 a particular class. 1705 1706 If only ``default_value`` is given, it is used for the ``klass`` as 1707 well. If neither are given, both default to ``object``. 1708 1709 Parameters 1710 ---------- 1711 default_value : class, str or None 1712 The default value must be a subclass of klass. If an str, 1713 the str must be a fully specified class name, like 'foo.bar.Bah'. 1714 The string is resolved into real class, when the parent 1715 :class:`HasTraits` class is instantiated. 1716 klass : class, str [ default object ] 1717 Values of this trait must be a subclass of klass. The klass 1718 may be specified in a string like: 'foo.bar.MyClass'. 1719 The string is resolved into real class, when the parent 1720 :class:`HasTraits` class is instantiated. 1721 allow_none : bool [ default False ] 1722 Indicates whether None is allowed as an assignable value. 1723 **kwargs 1724 extra kwargs passed to `ClassBasedTraitType` 1725 """ 1726 if default_value is Undefined: 1727 new_default_value = object if (klass is None) else klass 1728 else: 1729 new_default_value = default_value 1730 1731 if klass is None: 1732 if (default_value is None) or (default_value is Undefined): 1733 klass = object 1734 else: 1735 klass = default_value 1736 1737 if not (inspect.isclass(klass) or isinstance(klass, str)): 1738 raise TraitError("A Type trait must specify a class.") 1739 1740 self.klass = klass 1741 1742 super().__init__(new_default_value, **kwargs) 1743 1744 def validate(self, obj, value): 1745 """Validates that the value is a valid object instance.""" 1746 if isinstance(value, str): 1747 try: 1748 value = self._resolve_string(value) 1749 except ImportError: 1750 raise TraitError("The '%s' trait of %s instance must be a type, but " 1751 "%r could not be imported" % (self.name, obj, value)) 1752 try: 1753 if issubclass(value, self.klass): 1754 return value 1755 except Exception: 1756 pass 1757 1758 self.error(obj, value) 1759 1760 def info(self): 1761 """ Returns a description of the trait.""" 1762 if isinstance(self.klass, str): 1763 klass = self.klass 1764 else: 1765 klass = self.klass.__module__ + '.' + self.klass.__name__ 1766 result = "a subclass of '%s'" % klass 1767 if self.allow_none: 1768 return result + ' or None' 1769 return result 1770 1771 def instance_init(self, obj): 1772 self._resolve_classes() 1773 super().instance_init(obj) 1774 1775 def _resolve_classes(self): 1776 if isinstance(self.klass, str): 1777 self.klass = self._resolve_string(self.klass) 1778 if isinstance(self.default_value, str): 1779 self.default_value = self._resolve_string(self.default_value) 1780 1781 def default_value_repr(self): 1782 value = self.default_value 1783 if isinstance(value, str): 1784 return repr(value) 1785 else: 1786 return repr(f'{value.__module__}.{value.__name__}') 1787 1788 1789class Instance(ClassBasedTraitType): 1790 """A trait whose value must be an instance of a specified class. 1791 1792 The value can also be an instance of a subclass of the specified class. 1793 1794 Subclasses can declare default classes by overriding the klass attribute 1795 """ 1796 1797 klass = None 1798 1799 def __init__(self, klass=None, args=None, kw=None, **kwargs): 1800 """Construct an Instance trait. 1801 1802 This trait allows values that are instances of a particular 1803 class or its subclasses. Our implementation is quite different 1804 from that of enthough.traits as we don't allow instances to be used 1805 for klass and we handle the ``args`` and ``kw`` arguments differently. 1806 1807 Parameters 1808 ---------- 1809 klass : class, str 1810 The class that forms the basis for the trait. Class names 1811 can also be specified as strings, like 'foo.bar.Bar'. 1812 args : tuple 1813 Positional arguments for generating the default value. 1814 kw : dict 1815 Keyword arguments for generating the default value. 1816 allow_none : bool [ default False ] 1817 Indicates whether None is allowed as a value. 1818 **kwargs 1819 Extra kwargs passed to `ClassBasedTraitType` 1820 1821 Notes 1822 ----- 1823 If both ``args`` and ``kw`` are None, then the default value is None. 1824 If ``args`` is a tuple and ``kw`` is a dict, then the default is 1825 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is 1826 None, the None is replaced by ``()`` or ``{}``, respectively. 1827 """ 1828 if klass is None: 1829 klass = self.klass 1830 1831 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, str)): 1832 self.klass = klass 1833 else: 1834 raise TraitError('The klass attribute must be a class' 1835 ' not: %r' % klass) 1836 1837 if (kw is not None) and not isinstance(kw, dict): 1838 raise TraitError("The 'kw' argument must be a dict or None.") 1839 if (args is not None) and not isinstance(args, tuple): 1840 raise TraitError("The 'args' argument must be a tuple or None.") 1841 1842 self.default_args = args 1843 self.default_kwargs = kw 1844 1845 super(Instance, self).__init__(**kwargs) 1846 1847 def validate(self, obj, value): 1848 if isinstance(value, self.klass): 1849 return value 1850 else: 1851 self.error(obj, value) 1852 1853 def info(self): 1854 if isinstance(self.klass, str): 1855 result = add_article(self.klass) 1856 else: 1857 result = describe("a", self.klass) 1858 if self.allow_none: 1859 result += ' or None' 1860 return result 1861 1862 def instance_init(self, obj): 1863 self._resolve_classes() 1864 super().instance_init(obj) 1865 1866 def _resolve_classes(self): 1867 if isinstance(self.klass, str): 1868 self.klass = self._resolve_string(self.klass) 1869 1870 def make_dynamic_default(self): 1871 if (self.default_args is None) and (self.default_kwargs is None): 1872 return None 1873 return self.klass(*(self.default_args or ()), 1874 **(self.default_kwargs or {})) 1875 1876 def default_value_repr(self): 1877 return repr(self.make_dynamic_default()) 1878 1879 def from_string(self, s): 1880 return _safe_literal_eval(s) 1881 1882 1883class ForwardDeclaredMixin(object): 1884 """ 1885 Mixin for forward-declared versions of Instance and Type. 1886 """ 1887 def _resolve_string(self, string): 1888 """ 1889 Find the specified class name by looking for it in the module in which 1890 our this_class attribute was defined. 1891 """ 1892 modname = self.this_class.__module__ 1893 return import_item('.'.join([modname, string])) 1894 1895 1896class ForwardDeclaredType(ForwardDeclaredMixin, Type): 1897 """ 1898 Forward-declared version of Type. 1899 """ 1900 pass 1901 1902 1903class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance): 1904 """ 1905 Forward-declared version of Instance. 1906 """ 1907 pass 1908 1909 1910class This(ClassBasedTraitType): 1911 """A trait for instances of the class containing this trait. 1912 1913 Because how how and when class bodies are executed, the ``This`` 1914 trait can only have a default value of None. This, and because we 1915 always validate default values, ``allow_none`` is *always* true. 1916 """ 1917 1918 info_text = 'an instance of the same type as the receiver or None' 1919 1920 def __init__(self, **kwargs): 1921 super(This, self).__init__(None, **kwargs) 1922 1923 def validate(self, obj, value): 1924 # What if value is a superclass of obj.__class__? This is 1925 # complicated if it was the superclass that defined the This 1926 # trait. 1927 if isinstance(value, self.this_class) or (value is None): 1928 return value 1929 else: 1930 self.error(obj, value) 1931 1932 1933class Union(TraitType): 1934 """A trait type representing a Union type.""" 1935 1936 def __init__(self, trait_types, **kwargs): 1937 """Construct a Union trait. 1938 1939 This trait allows values that are allowed by at least one of the 1940 specified trait types. A Union traitlet cannot have metadata on 1941 its own, besides the metadata of the listed types. 1942 1943 Parameters 1944 ---------- 1945 trait_types : sequence 1946 The list of trait types of length at least 1. 1947 1948 Notes 1949 ----- 1950 Union([Float(), Bool(), Int()]) attempts to validate the provided values 1951 with the validation function of Float, then Bool, and finally Int. 1952 """ 1953 self.trait_types = list(trait_types) 1954 self.info_text = " or ".join([tt.info() for tt in self.trait_types]) 1955 super(Union, self).__init__(**kwargs) 1956 1957 def default(self, obj=None): 1958 default = super(Union, self).default(obj) 1959 for t in self.trait_types: 1960 if default is Undefined: 1961 default = t.default(obj) 1962 else: 1963 break 1964 return default 1965 1966 def class_init(self, cls, name): 1967 for trait_type in reversed(self.trait_types): 1968 trait_type.class_init(cls, None) 1969 super(Union, self).class_init(cls, name) 1970 1971 def instance_init(self, obj): 1972 for trait_type in reversed(self.trait_types): 1973 trait_type.instance_init(obj) 1974 super(Union, self).instance_init(obj) 1975 1976 def validate(self, obj, value): 1977 with obj.cross_validation_lock: 1978 for trait_type in self.trait_types: 1979 try: 1980 v = trait_type._validate(obj, value) 1981 # In the case of an element trait, the name is None 1982 if self.name is not None: 1983 setattr(obj, '_' + self.name + '_metadata', trait_type.metadata) 1984 return v 1985 except TraitError: 1986 continue 1987 self.error(obj, value) 1988 1989 def __or__(self, other): 1990 if isinstance(other, Union): 1991 return Union(self.trait_types + other.trait_types) 1992 else: 1993 return Union(self.trait_types + [other]) 1994 1995 1996#----------------------------------------------------------------------------- 1997# Basic TraitTypes implementations/subclasses 1998#----------------------------------------------------------------------------- 1999 2000 2001class Any(TraitType): 2002 """A trait which allows any value.""" 2003 default_value = None 2004 allow_none = True 2005 info_text = 'any value' 2006 2007 2008def _validate_bounds(trait, obj, value): 2009 """ 2010 Validate that a number to be applied to a trait is between bounds. 2011 2012 If value is not between min_bound and max_bound, this raises a 2013 TraitError with an error message appropriate for this trait. 2014 """ 2015 if trait.min is not None and value < trait.min: 2016 raise TraitError( 2017 "The value of the '{name}' trait of {klass} instance should " 2018 "not be less than {min_bound}, but a value of {value} was " 2019 "specified".format( 2020 name=trait.name, klass=class_of(obj), 2021 value=value, min_bound=trait.min)) 2022 if trait.max is not None and value > trait.max: 2023 raise TraitError( 2024 "The value of the '{name}' trait of {klass} instance should " 2025 "not be greater than {max_bound}, but a value of {value} was " 2026 "specified".format( 2027 name=trait.name, klass=class_of(obj), 2028 value=value, max_bound=trait.max)) 2029 return value 2030 2031 2032class Int(TraitType): 2033 """An int trait.""" 2034 2035 default_value = 0 2036 info_text = 'an int' 2037 2038 def __init__(self, default_value=Undefined, allow_none=False, **kwargs): 2039 self.min = kwargs.pop('min', None) 2040 self.max = kwargs.pop('max', None) 2041 super(Int, self).__init__(default_value=default_value, 2042 allow_none=allow_none, **kwargs) 2043 2044 def validate(self, obj, value): 2045 if not isinstance(value, int): 2046 self.error(obj, value) 2047 return _validate_bounds(self, obj, value) 2048 2049 def from_string(self, s): 2050 if self.allow_none and s == 'None': 2051 return None 2052 return int(s) 2053 2054 2055class CInt(Int): 2056 """A casting version of the int trait.""" 2057 2058 def validate(self, obj, value): 2059 try: 2060 value = int(value) 2061 except Exception: 2062 self.error(obj, value) 2063 return _validate_bounds(self, obj, value) 2064 2065 2066Long, CLong = Int, CInt 2067Integer = Int 2068 2069 2070class Float(TraitType): 2071 """A float trait.""" 2072 2073 default_value = 0.0 2074 info_text = 'a float' 2075 2076 def __init__(self, default_value=Undefined, allow_none=False, **kwargs): 2077 self.min = kwargs.pop('min', -float('inf')) 2078 self.max = kwargs.pop('max', float('inf')) 2079 super(Float, self).__init__(default_value=default_value, 2080 allow_none=allow_none, **kwargs) 2081 2082 def validate(self, obj, value): 2083 if isinstance(value, int): 2084 value = float(value) 2085 if not isinstance(value, float): 2086 self.error(obj, value) 2087 return _validate_bounds(self, obj, value) 2088 2089 def from_string(self, s): 2090 if self.allow_none and s == 'None': 2091 return None 2092 return float(s) 2093 2094 2095class CFloat(Float): 2096 """A casting version of the float trait.""" 2097 2098 def validate(self, obj, value): 2099 try: 2100 value = float(value) 2101 except Exception: 2102 self.error(obj, value) 2103 return _validate_bounds(self, obj, value) 2104 2105 2106class Complex(TraitType): 2107 """A trait for complex numbers.""" 2108 2109 default_value = 0.0 + 0.0j 2110 info_text = 'a complex number' 2111 2112 def validate(self, obj, value): 2113 if isinstance(value, complex): 2114 return value 2115 if isinstance(value, (float, int)): 2116 return complex(value) 2117 self.error(obj, value) 2118 2119 def from_string(self, s): 2120 if self.allow_none and s == 'None': 2121 return None 2122 return complex(s) 2123 2124 2125class CComplex(Complex): 2126 """A casting version of the complex number trait.""" 2127 2128 def validate (self, obj, value): 2129 try: 2130 return complex(value) 2131 except Exception: 2132 self.error(obj, value) 2133 2134# We should always be explicit about whether we're using bytes or unicode, both 2135# for Python 3 conversion and for reliable unicode behaviour on Python 2. So 2136# we don't have a Str type. 2137class Bytes(TraitType): 2138 """A trait for byte strings.""" 2139 2140 default_value = b'' 2141 info_text = 'a bytes object' 2142 2143 def validate(self, obj, value): 2144 if isinstance(value, bytes): 2145 return value 2146 self.error(obj, value) 2147 2148 def from_string(self, s): 2149 if self.allow_none and s == "None": 2150 return None 2151 if len(s) >= 3: 2152 # handle deprecated b"string" 2153 for quote in ('"', "'"): 2154 if s[:2] == f"b{quote}" and s[-1] == quote: 2155 old_s = s 2156 s = s[2:-1] 2157 warn( 2158 "Supporting extra quotes around Bytes is deprecated in traitlets 5.0. " 2159 "Use %r instead of %r." % (s, old_s), 2160 FutureWarning) 2161 break 2162 return s.encode("utf8") 2163 2164 2165class CBytes(Bytes): 2166 """A casting version of the byte string trait.""" 2167 2168 def validate(self, obj, value): 2169 try: 2170 return bytes(value) 2171 except Exception: 2172 self.error(obj, value) 2173 2174 2175class Unicode(TraitType): 2176 """A trait for unicode strings.""" 2177 2178 default_value = '' 2179 info_text = 'a unicode string' 2180 2181 def validate(self, obj, value): 2182 if isinstance(value, str): 2183 return value 2184 if isinstance(value, bytes): 2185 try: 2186 return value.decode('ascii', 'strict') 2187 except UnicodeDecodeError: 2188 msg = "Could not decode {!r} for unicode trait '{}' of {} instance." 2189 raise TraitError(msg.format(value, self.name, class_of(obj))) 2190 self.error(obj, value) 2191 2192 def from_string(self, s): 2193 if self.allow_none and s == 'None': 2194 return None 2195 s = os.path.expanduser(s) 2196 if len(s) >= 2: 2197 # handle deprecated "1" 2198 for c in ('"', "'"): 2199 if s[0] == s[-1] == c: 2200 old_s = s 2201 s = s[1:-1] 2202 warn( 2203 "Supporting extra quotes around strings is deprecated in traitlets 5.0. " 2204 "You can use %r instead of %r if you require traitlets >=5." % (s, old_s), 2205 FutureWarning) 2206 return s 2207 2208 2209 2210class CUnicode(Unicode): 2211 """A casting version of the unicode trait.""" 2212 2213 def validate(self, obj, value): 2214 try: 2215 return str(value) 2216 except Exception: 2217 self.error(obj, value) 2218 2219 2220class ObjectName(TraitType): 2221 """A string holding a valid object name in this version of Python. 2222 2223 This does not check that the name exists in any scope.""" 2224 info_text = "a valid object identifier in Python" 2225 2226 coerce_str = staticmethod(lambda _,s: s) 2227 2228 def validate(self, obj, value): 2229 value = self.coerce_str(obj, value) 2230 2231 if isinstance(value, str) and isidentifier(value): 2232 return value 2233 self.error(obj, value) 2234 2235 def from_string(self, s): 2236 if self.allow_none and s == 'None': 2237 return None 2238 return s 2239 2240class DottedObjectName(ObjectName): 2241 """A string holding a valid dotted object name in Python, such as A.b3._c""" 2242 def validate(self, obj, value): 2243 value = self.coerce_str(obj, value) 2244 2245 if isinstance(value, str) and all(isidentifier(a) 2246 for a in value.split('.')): 2247 return value 2248 self.error(obj, value) 2249 2250 2251class Bool(TraitType): 2252 """A boolean (True, False) trait.""" 2253 2254 default_value = False 2255 info_text = 'a boolean' 2256 2257 def validate(self, obj, value): 2258 if isinstance(value, bool): 2259 return value 2260 elif isinstance(value, int): 2261 if value == 1: 2262 return True 2263 elif value == 0: 2264 return False 2265 self.error(obj, value) 2266 2267 def from_string(self, s): 2268 if self.allow_none and s == 'None': 2269 return None 2270 s = s.lower() 2271 if s in {'true', '1'}: 2272 return True 2273 elif s in {'false', '0'}: 2274 return False 2275 else: 2276 raise ValueError("%r is not 1, 0, true, or false") 2277 2278 2279class CBool(Bool): 2280 """A casting version of the boolean trait.""" 2281 2282 def validate(self, obj, value): 2283 try: 2284 return bool(value) 2285 except Exception: 2286 self.error(obj, value) 2287 2288 2289class Enum(TraitType): 2290 """An enum whose value must be in a given sequence.""" 2291 2292 def __init__(self, values, default_value=Undefined, **kwargs): 2293 self.values = values 2294 if kwargs.get('allow_none', False) and default_value is Undefined: 2295 default_value = None 2296 super(Enum, self).__init__(default_value, **kwargs) 2297 2298 def validate(self, obj, value): 2299 if value in self.values: 2300 return value 2301 self.error(obj, value) 2302 2303 def _choices_str(self, as_rst=False): 2304 """ Returns a description of the trait choices (not none).""" 2305 choices = self.values 2306 if as_rst: 2307 choices = '|'.join('``%r``' % x for x in choices) 2308 else: 2309 choices = repr(list(choices)) 2310 return choices 2311 2312 def _info(self, as_rst=False): 2313 """ Returns a description of the trait.""" 2314 none = (' or %s' % ('`None`' if as_rst else 'None') 2315 if self.allow_none else 2316 '') 2317 return 'any of %s%s' % (self._choices_str(as_rst), none) 2318 2319 def info(self): 2320 return self._info(as_rst=False) 2321 2322 def info_rst(self): 2323 return self._info(as_rst=True) 2324 2325 def from_string(self, s): 2326 try: 2327 return self.validate(None, s) 2328 except TraitError: 2329 return _safe_literal_eval(s) 2330 2331 2332class CaselessStrEnum(Enum): 2333 """An enum of strings where the case should be ignored.""" 2334 2335 def __init__(self, values, default_value=Undefined, **kwargs): 2336 super().__init__(values, default_value=default_value, **kwargs) 2337 2338 def validate(self, obj, value): 2339 if not isinstance(value, str): 2340 self.error(obj, value) 2341 2342 for v in self.values: 2343 if v.lower() == value.lower(): 2344 return v 2345 self.error(obj, value) 2346 2347 def _info(self, as_rst=False): 2348 """ Returns a description of the trait.""" 2349 none = (' or %s' % ('`None`' if as_rst else 'None') 2350 if self.allow_none else 2351 '') 2352 return 'any of %s (case-insensitive)%s' % (self._choices_str(as_rst), none) 2353 2354 def info(self): 2355 return self._info(as_rst=False) 2356 2357 def info_rst(self): 2358 return self._info(as_rst=True) 2359 2360 2361class FuzzyEnum(Enum): 2362 """An case-ignoring enum matching choices by unique prefixes/substrings.""" 2363 2364 case_sensitive = False 2365 #: If True, choices match anywhere in the string, otherwise match prefixes. 2366 substring_matching = False 2367 2368 def __init__(self, values, default_value=Undefined, 2369 case_sensitive=False, substring_matching=False, **kwargs): 2370 self.case_sensitive = case_sensitive 2371 self.substring_matching = substring_matching 2372 super().__init__(values, default_value=default_value, **kwargs) 2373 2374 def validate(self, obj, value): 2375 if not isinstance(value, str): 2376 self.error(obj, value) 2377 2378 conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower() 2379 substring_matching = self.substring_matching 2380 match_func = ((lambda v, c: v in c) 2381 if substring_matching 2382 else (lambda v, c: c.startswith(v))) 2383 value = conv_func(value) 2384 choices = self.values 2385 matches = [match_func(value, conv_func(c)) for c in choices] 2386 if sum(matches) == 1: 2387 for v, m in zip(choices, matches): 2388 if m: 2389 return v 2390 2391 self.error(obj, value) 2392 2393 def _info(self, as_rst=False): 2394 """ Returns a description of the trait.""" 2395 none = (' or %s' % ('`None`' if as_rst else 'None') 2396 if self.allow_none else 2397 '') 2398 case = 'sensitive' if self.case_sensitive else 'insensitive' 2399 substr = 'substring' if self.substring_matching else 'prefix' 2400 return 'any case-%s %s of %s%s' % (case, substr, 2401 self._choices_str(as_rst), 2402 none) 2403 2404 def info(self): 2405 return self._info(as_rst=False) 2406 2407 def info_rst(self): 2408 return self._info(as_rst=True) 2409 2410 2411class Container(Instance): 2412 """An instance of a container (list, set, etc.) 2413 2414 To be subclassed by overriding klass. 2415 """ 2416 2417 klass = None 2418 _cast_types = () 2419 _valid_defaults = SequenceTypes 2420 _trait = None 2421 _literal_from_string_pairs = ("[]", "()") 2422 2423 def __init__(self, trait=None, default_value=Undefined, **kwargs): 2424 """Create a container trait type from a list, set, or tuple. 2425 2426 The default value is created by doing ``List(default_value)``, 2427 which creates a copy of the ``default_value``. 2428 2429 ``trait`` can be specified, which restricts the type of elements 2430 in the container to that TraitType. 2431 2432 If only one arg is given and it is not a Trait, it is taken as 2433 ``default_value``: 2434 2435 ``c = List([1, 2, 3])`` 2436 2437 Parameters 2438 ---------- 2439 trait : TraitType [ optional ] 2440 the type for restricting the contents of the Container. If unspecified, 2441 types are not checked. 2442 default_value : SequenceType [ optional ] 2443 The default value for the Trait. Must be list/tuple/set, and 2444 will be cast to the container type. 2445 allow_none : bool [ default False ] 2446 Whether to allow the value to be None 2447 **kwargs : any 2448 further keys for extensions to the Trait (e.g. config) 2449 2450 """ 2451 2452 # allow List([values]): 2453 if trait is not None and default_value is Undefined and not is_trait(trait): 2454 default_value = trait 2455 trait = None 2456 2457 if default_value is None and not kwargs.get("allow_none", False): 2458 # improve backward-compatibility for possible subclasses 2459 # specifying default_value=None as default, 2460 # keeping 'unspecified' behavior (i.e. empty container) 2461 warn( 2462 f"Specifying {self.__class__.__name__}(default_value=None)" 2463 " for no default is deprecated in traitlets 5.0.5." 2464 " Use default_value=Undefined", 2465 DeprecationWarning, 2466 stacklevel=2, 2467 ) 2468 default_value = Undefined 2469 2470 if default_value is Undefined: 2471 args = () 2472 elif default_value is None: 2473 # default_value back on kwargs for super() to handle 2474 args = () 2475 kwargs["default_value"] = None 2476 elif isinstance(default_value, self._valid_defaults): 2477 args = (default_value,) 2478 else: 2479 raise TypeError( 2480 "default value of %s was %s" % (self.__class__.__name__, default_value) 2481 ) 2482 2483 if is_trait(trait): 2484 if isinstance(trait, type): 2485 warn( 2486 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)." 2487 " Passing types is deprecated in traitlets 4.1.", 2488 DeprecationWarning, 2489 stacklevel=3, 2490 ) 2491 self._trait = trait() if isinstance(trait, type) else trait 2492 elif trait is not None: 2493 raise TypeError( 2494 "`trait` must be a Trait or None, got %s" % repr_type(trait) 2495 ) 2496 2497 super(Container, self).__init__(klass=self.klass, args=args, **kwargs) 2498 2499 def validate(self, obj, value): 2500 if isinstance(value, self._cast_types): 2501 value = self.klass(value) 2502 value = super(Container, self).validate(obj, value) 2503 if value is None: 2504 return value 2505 2506 value = self.validate_elements(obj, value) 2507 2508 return value 2509 2510 def validate_elements(self, obj, value): 2511 validated = [] 2512 if self._trait is None or isinstance(self._trait, Any): 2513 return value 2514 for v in value: 2515 try: 2516 v = self._trait._validate(obj, v) 2517 except TraitError as error: 2518 self.error(obj, v, error) 2519 else: 2520 validated.append(v) 2521 return self.klass(validated) 2522 2523 def class_init(self, cls, name): 2524 if isinstance(self._trait, TraitType): 2525 self._trait.class_init(cls, None) 2526 super(Container, self).class_init(cls, name) 2527 2528 def instance_init(self, obj): 2529 if isinstance(self._trait, TraitType): 2530 self._trait.instance_init(obj) 2531 super(Container, self).instance_init(obj) 2532 2533 def from_string(self, s): 2534 """Load value from a single string""" 2535 if not isinstance(s, str): 2536 raise TraitError(f"Expected string, got {s!r}") 2537 try: 2538 test = literal_eval(s) 2539 except Exception: 2540 test = None 2541 return self.validate(None, test) 2542 2543 def from_string_list(self, s_list): 2544 """Return the value from a list of config strings 2545 2546 This is where we parse CLI configuration 2547 """ 2548 if len(s_list) == 1: 2549 # check for deprecated --Class.trait="['a', 'b', 'c']" 2550 r = s_list[0] 2551 if r == "None" and self.allow_none: 2552 return None 2553 if len(r) >= 2 and any( 2554 r.startswith(start) and r.endswith(end) 2555 for start, end in self._literal_from_string_pairs 2556 ): 2557 if self.this_class: 2558 clsname = self.this_class.__name__ + "." 2559 else: 2560 clsname = "" 2561 2562 warn( 2563 "--{0}={1} for containers is deprecated in traitlets 5.0. " 2564 "You can pass `--{0} item` ... multiple times to add items to a list.".format( 2565 clsname + self.name, r 2566 ), 2567 FutureWarning, 2568 ) 2569 return self.klass(literal_eval(r)) 2570 sig = inspect.signature(self.item_from_string) 2571 if "index" in sig.parameters: 2572 item_from_string = self.item_from_string 2573 else: 2574 # backward-compat: allow item_from_string to ignore index arg 2575 item_from_string = lambda s, index=None: self.item_from_string(s) 2576 return self.klass( 2577 [item_from_string(s, index=idx) for idx, s in enumerate(s_list)] 2578 ) 2579 2580 def item_from_string(self, s, index=None): 2581 """Cast a single item from a string 2582 2583 Evaluated when parsing CLI configuration from a string 2584 """ 2585 if self._trait: 2586 return self._trait.from_string(s) 2587 else: 2588 return s 2589 2590 2591class List(Container): 2592 """An instance of a Python list.""" 2593 klass = list 2594 _cast_types = (tuple,) 2595 2596 def __init__( 2597 self, 2598 trait=None, 2599 default_value=Undefined, 2600 minlen=0, 2601 maxlen=sys.maxsize, 2602 **kwargs, 2603 ): 2604 """Create a List trait type from a list, set, or tuple. 2605 2606 The default value is created by doing ``list(default_value)``, 2607 which creates a copy of the ``default_value``. 2608 2609 ``trait`` can be specified, which restricts the type of elements 2610 in the container to that TraitType. 2611 2612 If only one arg is given and it is not a Trait, it is taken as 2613 ``default_value``: 2614 2615 ``c = List([1, 2, 3])`` 2616 2617 Parameters 2618 ---------- 2619 trait : TraitType [ optional ] 2620 the type for restricting the contents of the Container. 2621 If unspecified, types are not checked. 2622 default_value : SequenceType [ optional ] 2623 The default value for the Trait. Must be list/tuple/set, and 2624 will be cast to the container type. 2625 minlen : Int [ default 0 ] 2626 The minimum length of the input list 2627 maxlen : Int [ default sys.maxsize ] 2628 The maximum length of the input list 2629 """ 2630 self._minlen = minlen 2631 self._maxlen = maxlen 2632 super(List, self).__init__(trait=trait, default_value=default_value, 2633 **kwargs) 2634 2635 def length_error(self, obj, value): 2636 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \ 2637 % (self.name, class_of(obj), self._minlen, self._maxlen, value) 2638 raise TraitError(e) 2639 2640 def validate_elements(self, obj, value): 2641 length = len(value) 2642 if length < self._minlen or length > self._maxlen: 2643 self.length_error(obj, value) 2644 2645 return super(List, self).validate_elements(obj, value) 2646 2647 def set(self, obj, value): 2648 if isinstance(value, str): 2649 return super().set(obj, [value]) 2650 else: 2651 return super().set(obj, value) 2652 2653 2654class Set(List): 2655 """An instance of a Python set.""" 2656 klass = set 2657 _cast_types = (tuple, list) 2658 2659 _literal_from_string_pairs = ("[]", "()", "{}") 2660 2661 # Redefine __init__ just to make the docstring more accurate. 2662 def __init__( 2663 self, 2664 trait=None, 2665 default_value=Undefined, 2666 minlen=0, 2667 maxlen=sys.maxsize, 2668 **kwargs, 2669 ): 2670 """Create a Set trait type from a list, set, or tuple. 2671 2672 The default value is created by doing ``set(default_value)``, 2673 which creates a copy of the ``default_value``. 2674 2675 ``trait`` can be specified, which restricts the type of elements 2676 in the container to that TraitType. 2677 2678 If only one arg is given and it is not a Trait, it is taken as 2679 ``default_value``: 2680 2681 ``c = Set({1, 2, 3})`` 2682 2683 Parameters 2684 ---------- 2685 trait : TraitType [ optional ] 2686 the type for restricting the contents of the Container. 2687 If unspecified, types are not checked. 2688 default_value : SequenceType [ optional ] 2689 The default value for the Trait. Must be list/tuple/set, and 2690 will be cast to the container type. 2691 minlen : Int [ default 0 ] 2692 The minimum length of the input list 2693 maxlen : Int [ default sys.maxsize ] 2694 The maximum length of the input list 2695 """ 2696 super(Set, self).__init__(trait, default_value, minlen, maxlen, **kwargs) 2697 2698 def default_value_repr(self): 2699 # Ensure default value is sorted for a reproducible build 2700 list_repr = repr(sorted(self.make_dynamic_default())) 2701 if list_repr == '[]': 2702 return 'set()' 2703 return '{'+list_repr[1:-1]+'}' 2704 2705 2706class Tuple(Container): 2707 """An instance of a Python tuple.""" 2708 2709 klass = tuple 2710 _cast_types = (list,) 2711 2712 def __init__(self, *traits, **kwargs): 2713 """Create a tuple from a list, set, or tuple. 2714 2715 Create a fixed-type tuple with Traits: 2716 2717 ``t = Tuple(Int(), Str(), CStr())`` 2718 2719 would be length 3, with Int,Str,CStr for each element. 2720 2721 If only one arg is given and it is not a Trait, it is taken as 2722 default_value: 2723 2724 ``t = Tuple((1, 2, 3))`` 2725 2726 Otherwise, ``default_value`` *must* be specified by keyword. 2727 2728 Parameters 2729 ---------- 2730 *traits : TraitTypes [ optional ] 2731 the types for restricting the contents of the Tuple. If unspecified, 2732 types are not checked. If specified, then each positional argument 2733 corresponds to an element of the tuple. Tuples defined with traits 2734 are of fixed length. 2735 default_value : SequenceType [ optional ] 2736 The default value for the Tuple. Must be list/tuple/set, and 2737 will be cast to a tuple. If ``traits`` are specified, 2738 ``default_value`` must conform to the shape and type they specify. 2739 **kwargs 2740 Other kwargs passed to `Container` 2741 """ 2742 default_value = kwargs.pop("default_value", Undefined) 2743 # allow Tuple((values,)): 2744 if len(traits) == 1 and default_value is Undefined and not is_trait(traits[0]): 2745 default_value = traits[0] 2746 traits = () 2747 2748 if default_value is None and not kwargs.get("allow_none", False): 2749 # improve backward-compatibility for possible subclasses 2750 # specifying default_value=None as default, 2751 # keeping 'unspecified' behavior (i.e. empty container) 2752 warn( 2753 f"Specifying {self.__class__.__name__}(default_value=None)" 2754 " for no default is deprecated in traitlets 5.0.5." 2755 " Use default_value=Undefined", 2756 DeprecationWarning, 2757 stacklevel=2, 2758 ) 2759 default_value = Undefined 2760 2761 if default_value is Undefined: 2762 args = () 2763 elif default_value is None: 2764 # default_value back on kwargs for super() to handle 2765 args = () 2766 kwargs["default_value"] = None 2767 elif isinstance(default_value, self._valid_defaults): 2768 args = (default_value,) 2769 else: 2770 raise TypeError( 2771 "default value of %s was %s" % (self.__class__.__name__, default_value) 2772 ) 2773 2774 self._traits = [] 2775 for trait in traits: 2776 if isinstance(trait, type): 2777 warn( 2778 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)" 2779 " Passing types is deprecated in traitlets 4.1.", 2780 DeprecationWarning, 2781 stacklevel=2, 2782 ) 2783 trait = trait() 2784 self._traits.append(trait) 2785 2786 if self._traits and (default_value is None or default_value is Undefined): 2787 # don't allow default to be an empty container if length is specified 2788 args = None 2789 super(Container, self).__init__(klass=self.klass, args=args, **kwargs) 2790 2791 def item_from_string(self, s, index): 2792 """Cast a single item from a string 2793 2794 Evaluated when parsing CLI configuration from a string 2795 """ 2796 if not self._traits or index >= len(self._traits): 2797 # return s instead of raising index error 2798 # length errors will be raised later on validation 2799 return s 2800 return self._traits[index].from_string(s) 2801 2802 def validate_elements(self, obj, value): 2803 if not self._traits: 2804 # nothing to validate 2805 return value 2806 if len(value) != len(self._traits): 2807 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \ 2808 % (self.name, class_of(obj), len(self._traits), repr_type(value)) 2809 raise TraitError(e) 2810 2811 validated = [] 2812 for t, v in zip(self._traits, value): 2813 try: 2814 v = t._validate(obj, v) 2815 except TraitError as error: 2816 self.error(obj, v, error) 2817 else: 2818 validated.append(v) 2819 return tuple(validated) 2820 2821 def class_init(self, cls, name): 2822 for trait in self._traits: 2823 if isinstance(trait, TraitType): 2824 trait.class_init(cls, None) 2825 super(Container, self).class_init(cls, name) 2826 2827 def instance_init(self, obj): 2828 for trait in self._traits: 2829 if isinstance(trait, TraitType): 2830 trait.instance_init(obj) 2831 super(Container, self).instance_init(obj) 2832 2833 2834class Dict(Instance): 2835 """An instance of a Python dict. 2836 2837 One or more traits can be passed to the constructor 2838 to validate the keys and/or values of the dict. 2839 If you need more detailed validation, 2840 you may use a custom validator method. 2841 2842 .. versionchanged:: 5.0 2843 Added key_trait for validating dict keys. 2844 2845 .. versionchanged:: 5.0 2846 Deprecated ambiguous ``trait``, ``traits`` args in favor of ``value_trait``, ``per_key_traits``. 2847 """ 2848 _value_trait = None 2849 _key_trait = None 2850 2851 def __init__(self, value_trait=None, per_key_traits=None, key_trait=None, default_value=Undefined, 2852 **kwargs): 2853 """Create a dict trait type from a Python dict. 2854 2855 The default value is created by doing ``dict(default_value)``, 2856 which creates a copy of the ``default_value``. 2857 2858 Parameters 2859 ---------- 2860 value_trait : TraitType [ optional ] 2861 The specified trait type to check and use to restrict the values of 2862 the dict. If unspecified, values are not checked. 2863 per_key_traits : Dictionary of {keys:trait types} [ optional, keyword-only ] 2864 A Python dictionary containing the types that are valid for 2865 restricting the values of the dict on a per-key basis. 2866 Each value in this dict should be a Trait for validating 2867 key_trait : TraitType [ optional, keyword-only ] 2868 The type for restricting the keys of the dict. If 2869 unspecified, the types of the keys are not checked. 2870 default_value : SequenceType [ optional, keyword-only ] 2871 The default value for the Dict. Must be dict, tuple, or None, and 2872 will be cast to a dict if not None. If any key or value traits are specified, 2873 the `default_value` must conform to the constraints. 2874 2875 Examples 2876 -------- 2877 >>> d = Dict(Unicode()) 2878 a dict whose values must be text 2879 2880 >>> d2 = Dict(per_key_traits={"n": Integer(), "s": Unicode()}) 2881 d2['n'] must be an integer 2882 d2['s'] must be text 2883 2884 >>> d3 = Dict(value_trait=Integer(), key_trait=Unicode()) 2885 d3's keys must be text 2886 d3's values must be integers 2887 """ 2888 2889 # handle deprecated keywords 2890 trait = kwargs.pop('trait', None) 2891 if trait is not None: 2892 if value_trait is not None: 2893 raise TypeError("Found a value for both `value_trait` and its deprecated alias `trait`.") 2894 value_trait = trait 2895 warn( 2896 "Keyword `trait` is deprecated in traitlets 5.0, use `value_trait` instead", 2897 DeprecationWarning, 2898 stacklevel=2, 2899 ) 2900 traits = kwargs.pop("traits", None) 2901 if traits is not None: 2902 if per_key_traits is not None: 2903 raise TypeError("Found a value for both `per_key_traits` and its deprecated alias `traits`.") 2904 per_key_traits = traits 2905 warn( 2906 "Keyword `traits` is deprecated in traitlets 5.0, use `per_key_traits` instead", 2907 DeprecationWarning, 2908 stacklevel=2, 2909 ) 2910 2911 # Handling positional arguments 2912 if default_value is Undefined and value_trait is not None: 2913 if not is_trait(value_trait): 2914 default_value = value_trait 2915 value_trait = None 2916 2917 if key_trait is None and per_key_traits is not None: 2918 if is_trait(per_key_traits): 2919 key_trait = per_key_traits 2920 per_key_traits = None 2921 2922 # Handling default value 2923 if default_value is Undefined: 2924 default_value = {} 2925 if default_value is None: 2926 args = None 2927 elif isinstance(default_value, dict): 2928 args = (default_value,) 2929 elif isinstance(default_value, SequenceTypes): 2930 args = (default_value,) 2931 else: 2932 raise TypeError('default value of Dict was %s' % default_value) 2933 2934 # Case where a type of TraitType is provided rather than an instance 2935 if is_trait(value_trait): 2936 if isinstance(value_trait, type): 2937 warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)" 2938 " Passing types is deprecated in traitlets 4.1.", 2939 DeprecationWarning, stacklevel=2) 2940 value_trait = value_trait() 2941 self._value_trait = value_trait 2942 elif value_trait is not None: 2943 raise TypeError("`value_trait` must be a Trait or None, got %s" % repr_type(value_trait)) 2944 2945 if is_trait(key_trait): 2946 if isinstance(key_trait, type): 2947 warn("Traits should be given as instances, not types (for example, `Int()`, not `Int`)" 2948 " Passing types is deprecated in traitlets 4.1.", 2949 DeprecationWarning, stacklevel=2) 2950 key_trait = key_trait() 2951 self._key_trait = key_trait 2952 elif key_trait is not None: 2953 raise TypeError("`key_trait` must be a Trait or None, got %s" % repr_type(key_trait)) 2954 2955 self._per_key_traits = per_key_traits 2956 2957 super(Dict, self).__init__(klass=dict, args=args, **kwargs) 2958 2959 def element_error(self, obj, element, validator, side='Values'): 2960 e = side + " of the '%s' trait of %s instance must be %s, but a value of %s was specified." \ 2961 % (self.name, class_of(obj), validator.info(), repr_type(element)) 2962 raise TraitError(e) 2963 2964 def validate(self, obj, value): 2965 value = super(Dict, self).validate(obj, value) 2966 if value is None: 2967 return value 2968 value = self.validate_elements(obj, value) 2969 return value 2970 2971 def validate_elements(self, obj, value): 2972 per_key_override = self._per_key_traits or {} 2973 key_trait = self._key_trait 2974 value_trait = self._value_trait 2975 if not (key_trait or value_trait or per_key_override): 2976 return value 2977 2978 validated = {} 2979 for key in value: 2980 v = value[key] 2981 if key_trait: 2982 try: 2983 key = key_trait._validate(obj, key) 2984 except TraitError as error: 2985 self.element_error(obj, key, key_trait, 'Keys') 2986 active_value_trait = per_key_override.get(key, value_trait) 2987 if active_value_trait: 2988 try: 2989 v = active_value_trait._validate(obj, v) 2990 except TraitError: 2991 self.element_error(obj, v, active_value_trait, 'Values') 2992 validated[key] = v 2993 2994 return self.klass(validated) 2995 2996 def class_init(self, cls, name): 2997 if isinstance(self._value_trait, TraitType): 2998 self._value_trait.class_init(cls, None) 2999 if isinstance(self._key_trait, TraitType): 3000 self._key_trait.class_init(cls, None) 3001 if self._per_key_traits is not None: 3002 for trait in self._per_key_traits.values(): 3003 trait.class_init(cls, None) 3004 super(Dict, self).class_init(cls, name) 3005 3006 def instance_init(self, obj): 3007 if isinstance(self._value_trait, TraitType): 3008 self._value_trait.instance_init(obj) 3009 if isinstance(self._key_trait, TraitType): 3010 self._key_trait.instance_init(obj) 3011 if self._per_key_traits is not None: 3012 for trait in self._per_key_traits.values(): 3013 trait.instance_init(obj) 3014 super(Dict, self).instance_init(obj) 3015 3016 def from_string(self, s): 3017 """Load value from a single string""" 3018 if not isinstance(s, str): 3019 raise TypeError(f"from_string expects a string, got {repr(s)} of type {type(s)}") 3020 try: 3021 return self.from_string_list([s]) 3022 except Exception: 3023 test = _safe_literal_eval(s) 3024 if isinstance(test, dict): 3025 return test 3026 raise 3027 3028 def from_string_list(self, s_list): 3029 """Return a dict from a list of config strings. 3030 3031 This is where we parse CLI configuration. 3032 3033 Each item should have the form ``"key=value"``. 3034 3035 item parsing is done in :meth:`.item_from_string`. 3036 """ 3037 if len(s_list) == 1 and s_list[0] == "None" and self.allow_none: 3038 return None 3039 if ( 3040 len(s_list) == 1 3041 and s_list[0].startswith("{") 3042 and s_list[0].endswith("}") 3043 ): 3044 warn( 3045 "--{0}={1} for dict-traits is deprecated in traitlets 5.0. " 3046 "You can pass --{0} <key=value> ... multiple times to add items to a dict.".format( 3047 self.name, 3048 s_list[0], 3049 ), 3050 FutureWarning, 3051 ) 3052 3053 return literal_eval(s_list[0]) 3054 3055 combined = {} 3056 for d in [self.item_from_string(s) for s in s_list]: 3057 combined.update(d) 3058 return combined 3059 3060 def item_from_string(self, s): 3061 """Cast a single-key dict from a string. 3062 3063 Evaluated when parsing CLI configuration from a string. 3064 3065 Dicts expect strings of the form key=value. 3066 3067 Returns a one-key dictionary, 3068 which will be merged in :meth:`.from_string_list`. 3069 """ 3070 3071 if '=' not in s: 3072 raise TraitError( 3073 "'%s' options must have the form 'key=value', got %s" 3074 % (self.__class__.__name__, repr(s),) 3075 ) 3076 key, value = s.split("=", 1) 3077 3078 # cast key with key trait, if defined 3079 if self._key_trait: 3080 key = self._key_trait.from_string(key) 3081 3082 # cast value with value trait, if defined (per-key or global) 3083 value_trait = (self._per_key_traits or {}).get(key, self._value_trait) 3084 if value_trait: 3085 value = value_trait.from_string(value) 3086 return {key: value} 3087 3088 3089class TCPAddress(TraitType): 3090 """A trait for an (ip, port) tuple. 3091 3092 This allows for both IPv4 IP addresses as well as hostnames. 3093 """ 3094 3095 default_value = ('127.0.0.1', 0) 3096 info_text = 'an (ip, port) tuple' 3097 3098 def validate(self, obj, value): 3099 if isinstance(value, tuple): 3100 if len(value) == 2: 3101 if isinstance(value[0], str) and isinstance(value[1], int): 3102 port = value[1] 3103 if port >= 0 and port <= 65535: 3104 return value 3105 self.error(obj, value) 3106 3107 def from_string(self, s): 3108 if self.allow_none and s == 'None': 3109 return None 3110 if ':' not in s: 3111 raise ValueError('Require `ip:port`, got %r' % s) 3112 ip, port = s.split(':', 1) 3113 port = int(port) 3114 return (ip, port) 3115 3116 3117class CRegExp(TraitType): 3118 """A casting compiled regular expression trait. 3119 3120 Accepts both strings and compiled regular expressions. The resulting 3121 attribute will be a compiled regular expression.""" 3122 3123 info_text = 'a regular expression' 3124 3125 def validate(self, obj, value): 3126 try: 3127 return re.compile(value) 3128 except Exception: 3129 self.error(obj, value) 3130 3131 3132class UseEnum(TraitType): 3133 """Use a Enum class as model for the data type description. 3134 Note that if no default-value is provided, the first enum-value is used 3135 as default-value. 3136 3137 .. sourcecode:: python 3138 3139 # -- SINCE: Python 3.4 (or install backport: pip install enum34) 3140 import enum 3141 from traitlets import HasTraits, UseEnum 3142 3143 class Color(enum.Enum): 3144 red = 1 # -- IMPLICIT: default_value 3145 blue = 2 3146 green = 3 3147 3148 class MyEntity(HasTraits): 3149 color = UseEnum(Color, default_value=Color.blue) 3150 3151 entity = MyEntity(color=Color.red) 3152 entity.color = Color.green # USE: Enum-value (preferred) 3153 entity.color = "green" # USE: name (as string) 3154 entity.color = "Color.green" # USE: scoped-name (as string) 3155 entity.color = 3 # USE: number (as int) 3156 assert entity.color is Color.green 3157 """ 3158 default_value = None 3159 info_text = "Trait type adapter to a Enum class" 3160 3161 def __init__(self, enum_class, default_value=None, **kwargs): 3162 assert issubclass(enum_class, enum.Enum), \ 3163 "REQUIRE: enum.Enum, but was: %r" % enum_class 3164 allow_none = kwargs.get("allow_none", False) 3165 if default_value is None and not allow_none: 3166 default_value = list(enum_class.__members__.values())[0] 3167 super(UseEnum, self).__init__(default_value=default_value, **kwargs) 3168 self.enum_class = enum_class 3169 self.name_prefix = enum_class.__name__ + "." 3170 3171 def select_by_number(self, value, default=Undefined): 3172 """Selects enum-value by using its number-constant.""" 3173 assert isinstance(value, int) 3174 enum_members = self.enum_class.__members__ 3175 for enum_item in enum_members.values(): 3176 if enum_item.value == value: 3177 return enum_item 3178 # -- NOT FOUND: 3179 return default 3180 3181 def select_by_name(self, value, default=Undefined): 3182 """Selects enum-value by using its name or scoped-name.""" 3183 assert isinstance(value, str) 3184 if value.startswith(self.name_prefix): 3185 # -- SUPPORT SCOPED-NAMES, like: "Color.red" => "red" 3186 value = value.replace(self.name_prefix, "", 1) 3187 return self.enum_class.__members__.get(value, default) 3188 3189 def validate(self, obj, value): 3190 if isinstance(value, self.enum_class): 3191 return value 3192 elif isinstance(value, int): 3193 # -- CONVERT: number => enum_value (item) 3194 value2 = self.select_by_number(value) 3195 if value2 is not Undefined: 3196 return value2 3197 elif isinstance(value, str): 3198 # -- CONVERT: name or scoped_name (as string) => enum_value (item) 3199 value2 = self.select_by_name(value) 3200 if value2 is not Undefined: 3201 return value2 3202 elif value is None: 3203 if self.allow_none: 3204 return None 3205 else: 3206 return self.default_value 3207 self.error(obj, value) 3208 3209 def _choices_str(self, as_rst=False): 3210 """ Returns a description of the trait choices (not none).""" 3211 choices = self.enum_class.__members__.keys() 3212 if as_rst: 3213 return '|'.join('``%r``' % x for x in choices) 3214 else: 3215 return repr(list(choices)) # Listify because py3.4- prints odict-class 3216 3217 def _info(self, as_rst=False): 3218 """ Returns a description of the trait.""" 3219 none = (' or %s' % ('`None`' if as_rst else 'None') 3220 if self.allow_none else 3221 '') 3222 return 'any of %s%s' % (self._choices_str(as_rst), none) 3223 3224 def info(self): 3225 return self._info(as_rst=False) 3226 3227 def info_rst(self): 3228 return self._info(as_rst=True) 3229 3230 3231 3232class Callable(TraitType): 3233 """A trait which is callable. 3234 3235 Notes 3236 ----- 3237 Classes are callable, as are instances 3238 with a __call__() method.""" 3239 3240 info_text = 'a callable' 3241 3242 def validate(self, obj, value): 3243 if callable(value): 3244 return value 3245 else: 3246 self.error(obj, value) 3247 3248 3249def _add_all(): 3250 """add all trait types to `__all__` 3251 3252 do in a function to avoid iterating through globals while defining local variables 3253 """ 3254 for _name, _value in globals().items(): 3255 if not _name.startswith('_') and isinstance(_value, type) and issubclass(_value, TraitType): 3256 __all__.append(_name) 3257 3258_add_all() 3259