Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/traitlets/traitlets.py: 13%
1705 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:05 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:05 +0000
1"""
2A lightweight Traits like module.
4This is designed to provide a lightweight, simple, pure Python version of
5many of the capabilities of enthought.traits. This includes:
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
13We don't support:
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
22There are also some important difference in our design:
24* enthought.traits does not validate default values. We do.
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.
30Inheritance diagram:
32.. inheritance-diagram:: traitlets.traitlets
33 :parts: 3
34"""
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.
41from __future__ import annotations
43import contextlib
44import enum
45import inspect
46import os
47import re
48import sys
49import types
50import typing as t
51from ast import literal_eval
53from .utils.bunch import Bunch
54from .utils.descriptions import add_article, class_of, describe, repr_type
55from .utils.getargspec import getargspec
56from .utils.importstring import import_item
57from .utils.sentinel import Sentinel
58from .utils.warnings import deprecated_method, should_warn, warn
60SequenceTypes = (list, tuple, set, frozenset)
62# backward compatibility, use to differ between Python 2 and 3.
63ClassTypes = (type,)
65# exports:
67__all__ = [
68 "All",
69 "Any",
70 "BaseDescriptor",
71 "Bool",
72 "Bytes",
73 "CBool",
74 "CBytes",
75 "CComplex",
76 "CFloat",
77 "CInt",
78 "CLong",
79 "CRegExp",
80 "CUnicode",
81 "Callable",
82 "CaselessStrEnum",
83 "ClassBasedTraitType",
84 "Complex",
85 "Container",
86 "DefaultHandler",
87 "Dict",
88 "DottedObjectName",
89 "Enum",
90 "EventHandler",
91 "Float",
92 "ForwardDeclaredInstance",
93 "ForwardDeclaredMixin",
94 "ForwardDeclaredType",
95 "FuzzyEnum",
96 "HasDescriptors",
97 "HasTraits",
98 "Instance",
99 "Int",
100 "Integer",
101 "List",
102 "Long",
103 "MetaHasDescriptors",
104 "MetaHasTraits",
105 "ObjectName",
106 "ObserveHandler",
107 "Set",
108 "TCPAddress",
109 "This",
110 "TraitError",
111 "TraitType",
112 "Tuple",
113 "Type",
114 "Unicode",
115 "Undefined",
116 "Union",
117 "UseEnum",
118 "ValidateHandler",
119 "default",
120 "directional_link",
121 "dlink",
122 "link",
123 "observe",
124 "observe_compat",
125 "parse_notifier_name",
126 "validate",
127]
129# any TraitType subclass (that doesn't start with _) will be added automatically
131# -----------------------------------------------------------------------------
132# Basic classes
133# -----------------------------------------------------------------------------
136Undefined = Sentinel(
137 "Undefined",
138 "traitlets",
139 """
140Used in Traitlets to specify that no defaults are set in kwargs
141""",
142)
144All = Sentinel(
145 "All",
146 "traitlets",
147 """
148Used in Traitlets to listen to all types of notification or to notifications
149from all trait attributes.
150""",
151)
153# Deprecated alias
154NoDefaultSpecified = Undefined
157class TraitError(Exception):
158 pass
161# -----------------------------------------------------------------------------
162# Utilities
163# -----------------------------------------------------------------------------
166def isidentifier(s):
167 return s.isidentifier()
170def _safe_literal_eval(s):
171 """Safely evaluate an expression
173 Returns original string if eval fails.
175 Use only where types are ambiguous.
176 """
177 try:
178 return literal_eval(s)
179 except (NameError, SyntaxError, ValueError):
180 return s
183def is_trait(t):
184 """Returns whether the given value is an instance or subclass of TraitType."""
185 return isinstance(t, TraitType) or (isinstance(t, type) and issubclass(t, TraitType))
188def parse_notifier_name(names: Sentinel | str | t.Iterable[Sentinel | str]) -> t.Iterable[t.Any]:
189 """Convert the name argument to a list of names.
191 Examples
192 --------
193 >>> parse_notifier_name([])
194 [traitlets.All]
195 >>> parse_notifier_name("a")
196 ['a']
197 >>> parse_notifier_name(["a", "b"])
198 ['a', 'b']
199 >>> parse_notifier_name(All)
200 [traitlets.All]
201 """
202 if names is All or isinstance(names, str):
203 return [names]
204 elif isinstance(names, Sentinel):
205 raise TypeError("`names` must be either `All`, a str, or a list of strs.")
206 else:
207 if not names or All in names:
208 return [All]
209 for n in names:
210 if not isinstance(n, str):
211 raise TypeError(f"names must be strings, not {type(n).__name__}({n!r})")
212 return names
215class _SimpleTest:
216 def __init__(self, value):
217 self.value = value
219 def __call__(self, test):
220 return test == self.value
222 def __repr__(self):
223 return "<SimpleTest(%r)" % self.value
225 def __str__(self):
226 return self.__repr__()
229def getmembers(object, predicate=None):
230 """A safe version of inspect.getmembers that handles missing attributes.
232 This is useful when there are descriptor based attributes that for
233 some reason raise AttributeError even though they exist. This happens
234 in zope.inteface with the __provides__ attribute.
235 """
236 results = []
237 for key in dir(object):
238 try:
239 value = getattr(object, key)
240 except AttributeError:
241 pass
242 else:
243 if not predicate or predicate(value):
244 results.append((key, value))
245 results.sort()
246 return results
249def _validate_link(*tuples):
250 """Validate arguments for traitlet link functions"""
251 for tup in tuples:
252 if not len(tup) == 2:
253 raise TypeError(
254 "Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t
255 )
256 obj, trait_name = tup
257 if not isinstance(obj, HasTraits):
258 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
259 if trait_name not in obj.traits():
260 raise TypeError(f"{obj!r} has no trait {trait_name!r}")
263class link:
264 """Link traits from different objects together so they remain in sync.
266 Parameters
267 ----------
268 source : (object / attribute name) pair
269 target : (object / attribute name) pair
270 transform: iterable with two callables (optional)
271 Data transformation between source and target and target and source.
273 Examples
274 --------
275 >>> class X(HasTraits):
276 ... value = Int()
278 >>> src = X(value=1)
279 >>> tgt = X(value=42)
280 >>> c = link((src, "value"), (tgt, "value"))
282 Setting source updates target objects:
283 >>> src.value = 5
284 >>> tgt.value
285 5
286 """
288 updating = False
290 def __init__(self, source, target, transform=None):
291 _validate_link(source, target)
292 self.source, self.target = source, target
293 self._transform, self._transform_inv = transform if transform else (lambda x: x,) * 2
295 self.link()
297 def link(self):
298 try:
299 setattr(
300 self.target[0],
301 self.target[1],
302 self._transform(getattr(self.source[0], self.source[1])),
303 )
305 finally:
306 self.source[0].observe(self._update_target, names=self.source[1])
307 self.target[0].observe(self._update_source, names=self.target[1])
309 @contextlib.contextmanager
310 def _busy_updating(self):
311 self.updating = True
312 try:
313 yield
314 finally:
315 self.updating = False
317 def _update_target(self, change):
318 if self.updating:
319 return
320 with self._busy_updating():
321 setattr(self.target[0], self.target[1], self._transform(change.new))
322 if getattr(self.source[0], self.source[1]) != change.new:
323 raise TraitError(
324 f"Broken link {self}: the source value changed while updating " "the target."
325 )
327 def _update_source(self, change):
328 if self.updating:
329 return
330 with self._busy_updating():
331 setattr(self.source[0], self.source[1], self._transform_inv(change.new))
332 if getattr(self.target[0], self.target[1]) != change.new:
333 raise TraitError(
334 f"Broken link {self}: the target value changed while updating " "the source."
335 )
337 def unlink(self):
338 self.source[0].unobserve(self._update_target, names=self.source[1])
339 self.target[0].unobserve(self._update_source, names=self.target[1])
342class directional_link:
343 """Link the trait of a source object with traits of target objects.
345 Parameters
346 ----------
347 source : (object, attribute name) pair
348 target : (object, attribute name) pair
349 transform: callable (optional)
350 Data transformation between source and target.
352 Examples
353 --------
354 >>> class X(HasTraits):
355 ... value = Int()
357 >>> src = X(value=1)
358 >>> tgt = X(value=42)
359 >>> c = directional_link((src, "value"), (tgt, "value"))
361 Setting source updates target objects:
362 >>> src.value = 5
363 >>> tgt.value
364 5
366 Setting target does not update source object:
367 >>> tgt.value = 6
368 >>> src.value
369 5
371 """
373 updating = False
375 def __init__(self, source, target, transform=None):
376 self._transform = transform if transform else lambda x: x
377 _validate_link(source, target)
378 self.source, self.target = source, target
379 self.link()
381 def link(self):
382 try:
383 setattr(
384 self.target[0],
385 self.target[1],
386 self._transform(getattr(self.source[0], self.source[1])),
387 )
388 finally:
389 self.source[0].observe(self._update, names=self.source[1])
391 @contextlib.contextmanager
392 def _busy_updating(self):
393 self.updating = True
394 try:
395 yield
396 finally:
397 self.updating = False
399 def _update(self, change):
400 if self.updating:
401 return
402 with self._busy_updating():
403 setattr(self.target[0], self.target[1], self._transform(change.new))
405 def unlink(self):
406 self.source[0].unobserve(self._update, names=self.source[1])
409dlink = directional_link
412# -----------------------------------------------------------------------------
413# Base Descriptor Class
414# -----------------------------------------------------------------------------
417class BaseDescriptor:
418 """Base descriptor class
420 Notes
421 -----
422 This implements Python's descriptor protocol.
424 This class is the base class for all such descriptors. The
425 only magic we use is a custom metaclass for the main :class:`HasTraits`
426 class that does the following:
428 1. Sets the :attr:`name` attribute of every :class:`BaseDescriptor`
429 instance in the class dict to the name of the attribute.
430 2. Sets the :attr:`this_class` attribute of every :class:`BaseDescriptor`
431 instance in the class dict to the *class* that declared the trait.
432 This is used by the :class:`This` trait to allow subclasses to
433 accept superclasses for :class:`This` values.
434 """
436 name: str | None = None
437 this_class: type[t.Any] | None = None
439 def class_init(self, cls, name):
440 """Part of the initialization which may depend on the underlying
441 HasDescriptors class.
443 It is typically overloaded for specific types.
445 This method is called by :meth:`MetaHasDescriptors.__init__`
446 passing the class (`cls`) and `name` under which the descriptor
447 has been assigned.
448 """
449 self.this_class = cls
450 self.name = name
452 def subclass_init(self, cls):
453 # Instead of HasDescriptors.setup_instance calling
454 # every instance_init, we opt in by default.
455 # This gives descriptors a change to opt out for
456 # performance reasons.
457 # Because most traits do not need instance_init,
458 # and it will otherwise be called for every HasTrait instance
459 # beging created, this otherwise gives a significant performance
460 # pentalty. Most TypeTraits in traitlets opt out.
461 cls._instance_inits.append(self.instance_init)
463 def instance_init(self, obj):
464 """Part of the initialization which may depend on the underlying
465 HasDescriptors instance.
467 It is typically overloaded for specific types.
469 This method is called by :meth:`HasTraits.__new__` and in the
470 :meth:`BaseDescriptor.instance_init` method of descriptors holding
471 other descriptors.
472 """
473 pass
476G = t.TypeVar("G")
477S = t.TypeVar("S")
478T = t.TypeVar("T")
481# Self from typing extension doesn't work well with mypy https://github.com/python/mypy/pull/14041
482# see https://peps.python.org/pep-0673/#use-in-generic-classes
483# Self = t.TypeVar("Self", bound="TraitType[Any, Any]")
484if t.TYPE_CHECKING:
485 from typing_extensions import Literal, Self
488# We use a type for the getter (G) and setter (G) because we allow
489# for traits to cast (for instance CInt will use G=int, S=t.Any)
490class TraitType(BaseDescriptor, t.Generic[G, S]):
491 """A base class for all trait types."""
493 metadata: dict[str, t.Any] = {}
494 allow_none: bool = False
495 read_only: bool = False
496 info_text: str = "any value"
497 default_value: t.Any | None = Undefined
499 def __init__(
500 self: TraitType[G, S],
501 default_value: t.Any = Undefined,
502 allow_none: bool = False,
503 read_only: bool | None = None,
504 help: str | None = None,
505 config: t.Any = None,
506 **kwargs: t.Any,
507 ):
508 """Declare a traitlet.
510 If *allow_none* is True, None is a valid value in addition to any
511 values that are normally valid. The default is up to the subclass.
512 For most trait types, the default value for ``allow_none`` is False.
514 If *read_only* is True, attempts to directly modify a trait attribute raises a TraitError.
516 If *help* is a string, it documents the attribute's purpose.
518 Extra metadata can be associated with the traitlet using the .tag() convenience method
519 or by using the traitlet instance's .metadata dictionary.
520 """
521 if default_value is not Undefined:
522 self.default_value = default_value
523 if allow_none:
524 self.allow_none = allow_none
525 if read_only is not None:
526 self.read_only = read_only
527 self.help = help if help is not None else ""
528 if self.help:
529 # define __doc__ so that inspectors like autodoc find traits
530 self.__doc__ = self.help
532 if len(kwargs) > 0:
533 stacklevel = 1
534 f = inspect.currentframe()
535 # count supers to determine stacklevel for warning
536 assert f is not None
537 while f.f_code.co_name == "__init__":
538 stacklevel += 1
539 f = f.f_back
540 assert f is not None
541 mod = f.f_globals.get("__name__") or ""
542 pkg = mod.split(".", 1)[0]
543 key = ("metadata-tag", pkg, *sorted(kwargs))
544 if should_warn(key):
545 warn(
546 f"metadata {kwargs} was set from the constructor. "
547 "With traitlets 4.1, metadata should be set using the .tag() method, "
548 "e.g., Int().tag(key1='value1', key2='value2')",
549 DeprecationWarning,
550 stacklevel=stacklevel,
551 )
552 if len(self.metadata) > 0:
553 self.metadata = self.metadata.copy()
554 self.metadata.update(kwargs)
555 else:
556 self.metadata = kwargs
557 else:
558 self.metadata = self.metadata.copy()
559 if config is not None:
560 self.metadata["config"] = config
562 # We add help to the metadata during a deprecation period so that
563 # code that looks for the help string there can find it.
564 if help is not None:
565 self.metadata["help"] = help
567 def from_string(self, s):
568 """Get a value from a config string
570 such as an environment variable or CLI arguments.
572 Traits can override this method to define their own
573 parsing of config strings.
575 .. seealso:: item_from_string
577 .. versionadded:: 5.0
578 """
579 if self.allow_none and s == "None":
580 return None
581 return s
583 def default(self, obj=None):
584 """The default generator for this trait
586 Notes
587 -----
588 This method is registered to HasTraits classes during ``class_init``
589 in the same way that dynamic defaults defined by ``@default`` are.
590 """
591 if self.default_value is not Undefined:
592 return self.default_value
593 elif hasattr(self, "make_dynamic_default"):
594 return self.make_dynamic_default()
595 else:
596 # Undefined will raise in TraitType.get
597 return self.default_value
599 def get_default_value(self):
600 """DEPRECATED: Retrieve the static default value for this trait.
601 Use self.default_value instead
602 """
603 warn(
604 "get_default_value is deprecated in traitlets 4.0: use the .default_value attribute",
605 DeprecationWarning,
606 stacklevel=2,
607 )
608 return self.default_value
610 def init_default_value(self, obj):
611 """DEPRECATED: Set the static default value for the trait type."""
612 warn(
613 "init_default_value is deprecated in traitlets 4.0, and may be removed in the future",
614 DeprecationWarning,
615 stacklevel=2,
616 )
617 value = self._validate(obj, self.default_value)
618 obj._trait_values[self.name] = value
619 return value
621 def get(self, obj: HasTraits, cls: t.Any = None) -> G | None:
622 try:
623 value = obj._trait_values[self.name] # type: ignore
624 except KeyError:
625 # Check for a dynamic initializer.
626 default = obj.trait_defaults(self.name)
627 if default is Undefined:
628 warn(
629 "Explicit using of Undefined as the default value "
630 "is deprecated in traitlets 5.0, and may cause "
631 "exceptions in the future.",
632 DeprecationWarning,
633 stacklevel=2,
634 )
635 # Using a context manager has a large runtime overhead, so we
636 # write out the obj.cross_validation_lock call here.
637 _cross_validation_lock = obj._cross_validation_lock
638 try:
639 obj._cross_validation_lock = True
640 value = self._validate(obj, default)
641 finally:
642 obj._cross_validation_lock = _cross_validation_lock
643 obj._trait_values[self.name] = value # type: ignore
644 obj._notify_observers(
645 Bunch(
646 name=self.name,
647 value=value,
648 owner=obj,
649 type="default",
650 )
651 )
652 return value # type: ignore
653 except Exception as e:
654 # This should never be reached.
655 raise TraitError("Unexpected error in TraitType: default value not set properly") from e
656 else:
657 return value # type: ignore
659 if t.TYPE_CHECKING:
660 # This gives ok type information, but not specific enough (e.g. it will)
661 # always be a TraitType, not a subclass, like Bool.
662 @t.overload
663 def __new__( # type: ignore[misc]
664 cls,
665 default_value: S | Sentinel = Undefined,
666 allow_none: Literal[False] = ...,
667 read_only: bool | None = None,
668 help: str | None = None,
669 config: t.Any = None,
670 **kwargs: t.Any,
671 ) -> TraitType[G, S]:
672 ...
674 @t.overload
675 def __new__(
676 cls,
677 default_value: S | None | Sentinel = Undefined,
678 allow_none: Literal[True] = ...,
679 read_only: bool | None = None,
680 help: str | None = None,
681 config: t.Any = None,
682 **kwargs: t.Any,
683 ) -> TraitType[G | None, S]:
684 ...
686 def __new__( # type: ignore[no-untyped-def, misc]
687 cls,
688 default_value: S | None | Sentinel = Undefined,
689 allow_none: Literal[True, False] = False,
690 read_only=None,
691 help=None,
692 config=None,
693 **kwargs,
694 ) -> TraitType[G | None, S] | TraitType[G, S]:
695 ...
697 @t.overload
698 def __get__(self, obj: None, cls: type[t.Any]) -> Self:
699 ...
701 @t.overload
702 def __get__(self, obj: t.Any, cls: type[t.Any]) -> G:
703 ...
705 def __get__(self, obj: HasTraits | None, cls: type[t.Any]) -> Self | G:
706 """Get the value of the trait by self.name for the instance.
708 Default values are instantiated when :meth:`HasTraits.__new__`
709 is called. Thus by the time this method gets called either the
710 default value or a user defined value (they called :meth:`__set__`)
711 is in the :class:`HasTraits` instance.
712 """
713 if obj is None:
714 return self
715 else:
716 return t.cast(G, self.get(obj, cls)) # the G should encode the Optional
718 def set(self, obj, value):
719 new_value = self._validate(obj, value)
720 try:
721 old_value = obj._trait_values[self.name]
722 except KeyError:
723 old_value = self.default_value
725 obj._trait_values[self.name] = new_value
726 try:
727 silent = bool(old_value == new_value)
728 except Exception:
729 # if there is an error in comparing, default to notify
730 silent = False
731 if silent is not True:
732 # we explicitly compare silent to True just in case the equality
733 # comparison above returns something other than True/False
734 obj._notify_trait(self.name, old_value, new_value)
736 def __set__(self, obj: HasTraits, value: S) -> None:
737 """Set the value of the trait by self.name for the instance.
739 Values pass through a validation stage where errors are raised when
740 impropper types, or types that cannot be coerced, are encountered.
741 """
742 if self.read_only:
743 raise TraitError('The "%s" trait is read-only.' % self.name)
744 else:
745 self.set(obj, value)
747 def _validate(self, obj, value):
748 if value is None and self.allow_none:
749 return value
750 if hasattr(self, "validate"):
751 value = self.validate(obj, value)
752 if obj._cross_validation_lock is False:
753 value = self._cross_validate(obj, value)
754 return value
756 def _cross_validate(self, obj, value):
757 if self.name in obj._trait_validators:
758 proposal = Bunch({"trait": self, "value": value, "owner": obj})
759 value = obj._trait_validators[self.name](obj, proposal)
760 elif hasattr(obj, "_%s_validate" % self.name):
761 meth_name = "_%s_validate" % self.name
762 cross_validate = getattr(obj, meth_name)
763 deprecated_method(
764 cross_validate,
765 obj.__class__,
766 meth_name,
767 "use @validate decorator instead.",
768 )
769 value = cross_validate(value, self)
770 return value
772 def __or__(self, other):
773 if isinstance(other, Union):
774 return Union([self, *other.trait_types])
775 else:
776 return Union([self, other])
778 def info(self):
779 return self.info_text
781 def error(self, obj, value, error=None, info=None):
782 """Raise a TraitError
784 Parameters
785 ----------
786 obj : HasTraits or None
787 The instance which owns the trait. If not
788 object is given, then an object agnostic
789 error will be raised.
790 value : any
791 The value that caused the error.
792 error : Exception (default: None)
793 An error that was raised by a child trait.
794 The arguments of this exception should be
795 of the form ``(value, info, *traits)``.
796 Where the ``value`` and ``info`` are the
797 problem value, and string describing the
798 expected value. The ``traits`` are a series
799 of :class:`TraitType` instances that are
800 "children" of this one (the first being
801 the deepest).
802 info : str (default: None)
803 A description of the expected value. By
804 default this is infered from this trait's
805 ``info`` method.
806 """
807 if error is not None:
808 # handle nested error
809 error.args += (self,)
810 if self.name is not None:
811 # this is the root trait that must format the final message
812 chain = " of ".join(describe("a", t) for t in error.args[2:])
813 if obj is not None:
814 error.args = (
815 "The '{}' trait of {} instance contains {} which "
816 "expected {}, not {}.".format(
817 self.name,
818 describe("an", obj),
819 chain,
820 error.args[1],
821 describe("the", error.args[0]),
822 ),
823 )
824 else:
825 error.args = (
826 "The '{}' trait contains {} which "
827 "expected {}, not {}.".format(
828 self.name,
829 chain,
830 error.args[1],
831 describe("the", error.args[0]),
832 ),
833 )
834 raise error
835 else:
836 # this trait caused an error
837 if self.name is None:
838 # this is not the root trait
839 raise TraitError(value, info or self.info(), self)
840 else:
841 # this is the root trait
842 if obj is not None:
843 e = "The '{}' trait of {} instance expected {}, not {}.".format(
844 self.name,
845 class_of(obj),
846 self.info(),
847 describe("the", value),
848 )
849 else:
850 e = "The '{}' trait expected {}, not {}.".format(
851 self.name,
852 self.info(),
853 describe("the", value),
854 )
855 raise TraitError(e)
857 def get_metadata(self, key, default=None):
858 """DEPRECATED: Get a metadata value.
860 Use .metadata[key] or .metadata.get(key, default) instead.
861 """
862 if key == "help":
863 msg = "use the instance .help string directly, like x.help"
864 else:
865 msg = "use the instance .metadata dictionary directly, like x.metadata[key] or x.metadata.get(key, default)"
866 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
867 return self.metadata.get(key, default)
869 def set_metadata(self, key, value):
870 """DEPRECATED: Set a metadata key/value.
872 Use .metadata[key] = value instead.
873 """
874 if key == "help":
875 msg = "use the instance .help string directly, like x.help = value"
876 else:
877 msg = "use the instance .metadata dictionary directly, like x.metadata[key] = value"
878 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
879 self.metadata[key] = value
881 def tag(self, **metadata: t.Any) -> Self:
882 """Sets metadata and returns self.
884 This allows convenient metadata tagging when initializing the trait, such as:
886 Examples
887 --------
888 >>> Int(0).tag(config=True, sync=True)
889 <traitlets.traitlets.Int object at ...>
891 """
892 maybe_constructor_keywords = set(metadata.keys()).intersection(
893 {"help", "allow_none", "read_only", "default_value"}
894 )
895 if maybe_constructor_keywords:
896 warn(
897 "The following attributes are set in using `tag`, but seem to be constructor keywords arguments: %s "
898 % maybe_constructor_keywords,
899 UserWarning,
900 stacklevel=2,
901 )
903 self.metadata.update(metadata)
904 return self
906 def default_value_repr(self):
907 return repr(self.default_value)
910# -----------------------------------------------------------------------------
911# The HasTraits implementation
912# -----------------------------------------------------------------------------
915class _CallbackWrapper:
916 """An object adapting a on_trait_change callback into an observe callback.
918 The comparison operator __eq__ is implemented to enable removal of wrapped
919 callbacks.
920 """
922 def __init__(self, cb):
923 self.cb = cb
924 # Bound methods have an additional 'self' argument.
925 offset = -1 if isinstance(self.cb, types.MethodType) else 0
926 self.nargs = len(getargspec(cb)[0]) + offset
927 if self.nargs > 4:
928 raise TraitError("a trait changed callback must have 0-4 arguments.")
930 def __eq__(self, other):
931 # The wrapper is equal to the wrapped element
932 if isinstance(other, _CallbackWrapper):
933 return self.cb == other.cb
934 else:
935 return self.cb == other
937 def __call__(self, change):
938 # The wrapper is callable
939 if self.nargs == 0:
940 self.cb()
941 elif self.nargs == 1:
942 self.cb(change.name)
943 elif self.nargs == 2:
944 self.cb(change.name, change.new)
945 elif self.nargs == 3:
946 self.cb(change.name, change.old, change.new)
947 elif self.nargs == 4:
948 self.cb(change.name, change.old, change.new, change.owner)
951def _callback_wrapper(cb):
952 if isinstance(cb, _CallbackWrapper):
953 return cb
954 else:
955 return _CallbackWrapper(cb)
958class MetaHasDescriptors(type):
959 """A metaclass for HasDescriptors.
961 This metaclass makes sure that any TraitType class attributes are
962 instantiated and sets their name attribute.
963 """
965 def __new__(mcls, name, bases, classdict): # noqa
966 """Create the HasDescriptors class."""
967 for k, v in classdict.items():
968 # ----------------------------------------------------------------
969 # Support of deprecated behavior allowing for TraitType types
970 # to be used instead of TraitType instances.
971 if inspect.isclass(v) and issubclass(v, TraitType):
972 warn(
973 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
974 " Passing types is deprecated in traitlets 4.1.",
975 DeprecationWarning,
976 stacklevel=2,
977 )
978 classdict[k] = v()
979 # ----------------------------------------------------------------
981 return super().__new__(mcls, name, bases, classdict)
983 def __init__(cls, name, bases, classdict):
984 """Finish initializing the HasDescriptors class."""
985 super().__init__(name, bases, classdict)
986 cls.setup_class(classdict)
988 def setup_class(cls, classdict):
989 """Setup descriptor instance on the class
991 This sets the :attr:`this_class` and :attr:`name` attributes of each
992 BaseDescriptor in the class dict of the newly created ``cls`` before
993 calling their :attr:`class_init` method.
994 """
995 cls._descriptors = []
996 cls._instance_inits = []
997 for k, v in classdict.items():
998 if isinstance(v, BaseDescriptor):
999 v.class_init(cls, k)
1001 for _, v in getmembers(cls):
1002 if isinstance(v, BaseDescriptor):
1003 v.subclass_init(cls)
1004 cls._descriptors.append(v)
1007class MetaHasTraits(MetaHasDescriptors):
1008 """A metaclass for HasTraits."""
1010 def setup_class(cls, classdict): # noqa
1011 # for only the current class
1012 cls._trait_default_generators = {}
1013 # also looking at base classes
1014 cls._all_trait_default_generators = {}
1015 cls._traits = {}
1016 cls._static_immutable_initial_values = {}
1018 super().setup_class(classdict)
1020 mro = cls.mro()
1022 for name in dir(cls):
1023 # Some descriptors raise AttributeError like zope.interface's
1024 # __provides__ attributes even though they exist. This causes
1025 # AttributeErrors even though they are listed in dir(cls).
1026 try:
1027 value = getattr(cls, name)
1028 except AttributeError:
1029 continue
1030 if isinstance(value, TraitType):
1031 cls._traits[name] = value
1032 trait = value
1033 default_method_name = "_%s_default" % name
1034 mro_trait = mro
1035 try:
1036 mro_trait = mro[: mro.index(trait.this_class) + 1] # type:ignore[arg-type]
1037 except ValueError:
1038 # this_class not in mro
1039 pass
1040 for c in mro_trait:
1041 if default_method_name in c.__dict__:
1042 cls._all_trait_default_generators[name] = c.__dict__[default_method_name]
1043 break
1044 if name in c.__dict__.get("_trait_default_generators", {}):
1045 cls._all_trait_default_generators[name] = c._trait_default_generators[name] # type: ignore[attr-defined]
1046 break
1047 else:
1048 # We don't have a dynamic default generator using @default etc.
1049 # Now if the default value is not dynamic and immutable (string, number)
1050 # and does not require any validation, we keep them in a dict
1051 # of initial values to speed up instance creation.
1052 # This is a very specific optimization, but a very common scenario in
1053 # for instance ipywidgets.
1054 none_ok = trait.default_value is None and trait.allow_none
1055 if (
1056 type(trait) in [CInt, Int]
1057 and trait.min is None # type: ignore[attr-defined]
1058 and trait.max is None # type: ignore[attr-defined]
1059 and (isinstance(trait.default_value, int) or none_ok)
1060 ):
1061 cls._static_immutable_initial_values[name] = trait.default_value
1062 elif (
1063 type(trait) in [CFloat, Float]
1064 and trait.min is None # type: ignore[attr-defined]
1065 and trait.max is None # type: ignore[attr-defined]
1066 and (isinstance(trait.default_value, float) or none_ok)
1067 ):
1068 cls._static_immutable_initial_values[name] = trait.default_value
1069 elif type(trait) in [CBool, Bool] and (
1070 isinstance(trait.default_value, bool) or none_ok
1071 ):
1072 cls._static_immutable_initial_values[name] = trait.default_value
1073 elif type(trait) in [CUnicode, Unicode] and (
1074 isinstance(trait.default_value, str) or none_ok
1075 ):
1076 cls._static_immutable_initial_values[name] = trait.default_value
1077 elif type(trait) == Any and (
1078 isinstance(trait.default_value, (str, int, float, bool)) or none_ok
1079 ):
1080 cls._static_immutable_initial_values[name] = trait.default_value
1081 elif type(trait) == Union and trait.default_value is None:
1082 cls._static_immutable_initial_values[name] = None
1083 elif (
1084 isinstance(trait, Instance)
1085 and trait.default_args is None
1086 and trait.default_kwargs is None
1087 and trait.allow_none
1088 ):
1089 cls._static_immutable_initial_values[name] = None
1091 # we always add it, because a class may change when we call add_trait
1092 # and then the instance may not have all the _static_immutable_initial_values
1093 cls._all_trait_default_generators[name] = trait.default
1096def observe(*names: Sentinel | str, type: str = "change") -> ObserveHandler:
1097 """A decorator which can be used to observe Traits on a class.
1099 The handler passed to the decorator will be called with one ``change``
1100 dict argument. The change dictionary at least holds a 'type' key and a
1101 'name' key, corresponding respectively to the type of notification and the
1102 name of the attribute that triggered the notification.
1104 Other keys may be passed depending on the value of 'type'. In the case
1105 where type is 'change', we also have the following keys:
1106 * ``owner`` : the HasTraits instance
1107 * ``old`` : the old value of the modified trait attribute
1108 * ``new`` : the new value of the modified trait attribute
1109 * ``name`` : the name of the modified trait attribute.
1111 Parameters
1112 ----------
1113 *names
1114 The str names of the Traits to observe on the object.
1115 type : str, kwarg-only
1116 The type of event to observe (e.g. 'change')
1117 """
1118 if not names:
1119 raise TypeError("Please specify at least one trait name to observe.")
1120 for name in names:
1121 if name is not All and not isinstance(name, str):
1122 raise TypeError("trait names to observe must be strings or All, not %r" % name)
1123 return ObserveHandler(names, type=type)
1126def observe_compat(func):
1127 """Backward-compatibility shim decorator for observers
1129 Use with:
1131 @observe('name')
1132 @observe_compat
1133 def _foo_changed(self, change):
1134 ...
1136 With this, `super()._foo_changed(self, name, old, new)` in subclasses will still work.
1137 Allows adoption of new observer API without breaking subclasses that override and super.
1138 """
1140 def compatible_observer(self, change_or_name, old=Undefined, new=Undefined):
1141 if isinstance(change_or_name, dict):
1142 change = change_or_name
1143 else:
1144 clsname = self.__class__.__name__
1145 warn(
1146 f"A parent of {clsname}._{change_or_name}_changed has adopted the new (traitlets 4.1) @observe(change) API",
1147 DeprecationWarning,
1148 stacklevel=2,
1149 )
1150 change = Bunch(
1151 type="change",
1152 old=old,
1153 new=new,
1154 name=change_or_name,
1155 owner=self,
1156 )
1157 return func(self, change)
1159 return compatible_observer
1162def validate(*names: Sentinel | str) -> ValidateHandler:
1163 """A decorator to register cross validator of HasTraits object's state
1164 when a Trait is set.
1166 The handler passed to the decorator must have one ``proposal`` dict argument.
1167 The proposal dictionary must hold the following keys:
1169 * ``owner`` : the HasTraits instance
1170 * ``value`` : the proposed value for the modified trait attribute
1171 * ``trait`` : the TraitType instance associated with the attribute
1173 Parameters
1174 ----------
1175 *names
1176 The str names of the Traits to validate.
1178 Notes
1179 -----
1180 Since the owner has access to the ``HasTraits`` instance via the 'owner' key,
1181 the registered cross validator could potentially make changes to attributes
1182 of the ``HasTraits`` instance. However, we recommend not to do so. The reason
1183 is that the cross-validation of attributes may run in arbitrary order when
1184 exiting the ``hold_trait_notifications`` context, and such changes may not
1185 commute.
1186 """
1187 if not names:
1188 raise TypeError("Please specify at least one trait name to validate.")
1189 for name in names:
1190 if name is not All and not isinstance(name, str):
1191 raise TypeError("trait names to validate must be strings or All, not %r" % name)
1192 return ValidateHandler(names)
1195def default(name: str) -> DefaultHandler:
1196 """A decorator which assigns a dynamic default for a Trait on a HasTraits object.
1198 Parameters
1199 ----------
1200 name
1201 The str name of the Trait on the object whose default should be generated.
1203 Notes
1204 -----
1205 Unlike observers and validators which are properties of the HasTraits
1206 instance, default value generators are class-level properties.
1208 Besides, default generators are only invoked if they are registered in
1209 subclasses of `this_type`.
1211 ::
1213 class A(HasTraits):
1214 bar = Int()
1216 @default('bar')
1217 def get_bar_default(self):
1218 return 11
1220 class B(A):
1221 bar = Float() # This trait ignores the default generator defined in
1222 # the base class A
1224 class C(B):
1226 @default('bar')
1227 def some_other_default(self): # This default generator should not be
1228 return 3.0 # ignored since it is defined in a
1229 # class derived from B.a.this_class.
1230 """
1231 if not isinstance(name, str):
1232 raise TypeError("Trait name must be a string or All, not %r" % name)
1233 return DefaultHandler(name)
1236class EventHandler(BaseDescriptor):
1237 def _init_call(self, func):
1238 self.func = func
1239 return self
1241 def __call__(self, *args, **kwargs):
1242 """Pass `*args` and `**kwargs` to the handler's function if it exists."""
1243 if hasattr(self, "func"):
1244 return self.func(*args, **kwargs)
1245 else:
1246 return self._init_call(*args, **kwargs)
1248 def __get__(self, inst, cls=None):
1249 if inst is None:
1250 return self
1251 return types.MethodType(self.func, inst)
1254class ObserveHandler(EventHandler):
1255 def __init__(self, names, type):
1256 self.trait_names = names
1257 self.type = type
1259 def instance_init(self, inst):
1260 inst.observe(self, self.trait_names, type=self.type)
1263class ValidateHandler(EventHandler):
1264 def __init__(self, names):
1265 self.trait_names = names
1267 def instance_init(self, inst):
1268 inst._register_validator(self, self.trait_names)
1271class DefaultHandler(EventHandler):
1272 def __init__(self, name):
1273 self.trait_name = name
1275 def class_init(self, cls, name):
1276 super().class_init(cls, name)
1277 cls._trait_default_generators[self.trait_name] = self
1280class HasDescriptors(metaclass=MetaHasDescriptors):
1281 """The base class for all classes that have descriptors."""
1283 def __new__(*args: t.Any, **kwargs: t.Any) -> t.Any:
1284 # Pass cls as args[0] to allow "cls" as keyword argument
1285 cls = args[0]
1286 args = args[1:]
1288 # This is needed because object.__new__ only accepts
1289 # the cls argument.
1290 new_meth = super(HasDescriptors, cls).__new__
1291 if new_meth is object.__new__:
1292 inst = new_meth(cls)
1293 else:
1294 inst = new_meth(cls, *args, **kwargs)
1295 inst.setup_instance(*args, **kwargs)
1296 return inst
1298 def setup_instance(*args, **kwargs):
1299 """
1300 This is called **before** self.__init__ is called.
1301 """
1302 # Pass self as args[0] to allow "self" as keyword argument
1303 self = args[0]
1304 args = args[1:]
1306 self._cross_validation_lock = False # type:ignore[attr-defined]
1307 cls = self.__class__
1308 # Let descriptors performance initialization when a HasDescriptor
1309 # instance is created. This allows registration of observers and
1310 # default creations or other bookkeepings.
1311 # Note that descriptors can opt-out of this behavior by overriding
1312 # subclass_init.
1313 for init in cls._instance_inits:
1314 init(self)
1317class HasTraits(HasDescriptors, metaclass=MetaHasTraits):
1318 _trait_values: dict[str, t.Any]
1319 _static_immutable_initial_values: dict[str, t.Any]
1320 _trait_notifiers: dict[str, t.Any]
1321 _trait_validators: dict[str, t.Any]
1322 _cross_validation_lock: bool
1323 _traits: dict[str, t.Any]
1324 _all_trait_default_generators: dict[str, t.Any]
1326 def setup_instance(*args, **kwargs):
1327 # Pass self as args[0] to allow "self" as keyword argument
1328 self = args[0]
1329 args = args[1:]
1331 # although we'd prefer to set only the initial values not present
1332 # in kwargs, we will overwrite them in `__init__`, and simply making
1333 # a copy of a dict is faster than checking for each key.
1334 self._trait_values = self._static_immutable_initial_values.copy()
1335 self._trait_notifiers = {}
1336 self._trait_validators = {}
1337 self._cross_validation_lock = False
1338 super(HasTraits, self).setup_instance(*args, **kwargs)
1340 def __init__(self, *args, **kwargs):
1341 # Allow trait values to be set using keyword arguments.
1342 # We need to use setattr for this to trigger validation and
1343 # notifications.
1344 super_args = args
1345 super_kwargs = {}
1347 if kwargs:
1348 # this is a simplified (and faster) version of
1349 # the hold_trait_notifications(self) context manager
1350 def ignore(*_ignore_args):
1351 pass
1353 self.notify_change = ignore # type:ignore[method-assign]
1354 self._cross_validation_lock = True
1355 changes = {}
1356 for key, value in kwargs.items():
1357 if self.has_trait(key):
1358 setattr(self, key, value)
1359 changes[key] = Bunch(
1360 name=key,
1361 old=None,
1362 new=value,
1363 owner=self,
1364 type="change",
1365 )
1366 else:
1367 # passthrough args that don't set traits to super
1368 super_kwargs[key] = value
1369 # notify and cross validate all trait changes that were set in kwargs
1370 changed = set(kwargs) & set(self._traits)
1371 for key in changed:
1372 value = self._traits[key]._cross_validate(self, getattr(self, key))
1373 self.set_trait(key, value)
1374 changes[key]['new'] = value
1375 self._cross_validation_lock = False
1376 # Restore method retrieval from class
1377 del self.notify_change
1378 for key in changed:
1379 self.notify_change(changes[key])
1381 try:
1382 super().__init__(*super_args, **super_kwargs)
1383 except TypeError as e:
1384 arg_s_list = [repr(arg) for arg in super_args]
1385 for k, v in super_kwargs.items():
1386 arg_s_list.append(f"{k}={v!r}")
1387 arg_s = ", ".join(arg_s_list)
1388 warn(
1389 "Passing unrecognized arguments to super({classname}).__init__({arg_s}).\n"
1390 "{error}\n"
1391 "This is deprecated in traitlets 4.2."
1392 "This error will be raised in a future release of traitlets.".format(
1393 arg_s=arg_s,
1394 classname=self.__class__.__name__,
1395 error=e,
1396 ),
1397 DeprecationWarning,
1398 stacklevel=2,
1399 )
1401 def __getstate__(self):
1402 d = self.__dict__.copy()
1403 # event handlers stored on an instance are
1404 # expected to be reinstantiated during a
1405 # recall of instance_init during __setstate__
1406 d["_trait_notifiers"] = {}
1407 d["_trait_validators"] = {}
1408 d["_trait_values"] = self._trait_values.copy()
1409 d["_cross_validation_lock"] = False # FIXME: raise if cloning locked!
1411 return d
1413 def __setstate__(self, state):
1414 self.__dict__ = state.copy()
1416 # event handlers are reassigned to self
1417 cls = self.__class__
1418 for key in dir(cls):
1419 # Some descriptors raise AttributeError like zope.interface's
1420 # __provides__ attributes even though they exist. This causes
1421 # AttributeErrors even though they are listed in dir(cls).
1422 try:
1423 value = getattr(cls, key)
1424 except AttributeError:
1425 pass
1426 else:
1427 if isinstance(value, EventHandler):
1428 value.instance_init(self)
1430 @property
1431 @contextlib.contextmanager
1432 def cross_validation_lock(self):
1433 """
1434 A contextmanager for running a block with our cross validation lock set
1435 to True.
1437 At the end of the block, the lock's value is restored to its value
1438 prior to entering the block.
1439 """
1440 if self._cross_validation_lock:
1441 yield
1442 return
1443 else:
1444 try:
1445 self._cross_validation_lock = True
1446 yield
1447 finally:
1448 self._cross_validation_lock = False
1450 @contextlib.contextmanager
1451 def hold_trait_notifications(self):
1452 """Context manager for bundling trait change notifications and cross
1453 validation.
1455 Use this when doing multiple trait assignments (init, config), to avoid
1456 race conditions in trait notifiers requesting other trait values.
1457 All trait notifications will fire after all values have been assigned.
1458 """
1459 if self._cross_validation_lock:
1460 yield
1461 return
1462 else:
1463 cache: dict[str, t.Any] = {}
1465 def compress(past_changes, change):
1466 """Merges the provided change with the last if possible."""
1467 if past_changes is None:
1468 return [change]
1469 else:
1470 if past_changes[-1]["type"] == "change" and change.type == "change":
1471 past_changes[-1]["new"] = change.new
1472 else:
1473 # In case of changes other than 'change', append the notification.
1474 past_changes.append(change)
1475 return past_changes
1477 def hold(change):
1478 name = change.name
1479 cache[name] = compress(cache.get(name), change)
1481 try:
1482 # Replace notify_change with `hold`, caching and compressing
1483 # notifications, disable cross validation and yield.
1484 self.notify_change = hold # type:ignore[method-assign]
1485 self._cross_validation_lock = True
1486 yield
1487 # Cross validate final values when context is released.
1488 for name in list(cache.keys()):
1489 trait = getattr(self.__class__, name)
1490 value = trait._cross_validate(self, getattr(self, name))
1491 self.set_trait(name, value)
1492 except TraitError as e:
1493 # Roll back in case of TraitError during final cross validation.
1494 self.notify_change = lambda x: None # type:ignore[method-assign]
1495 for name, changes in cache.items():
1496 for change in changes[::-1]:
1497 # TODO: Separate in a rollback function per notification type.
1498 if change.type == "change":
1499 if change.old is not Undefined:
1500 self.set_trait(name, change.old)
1501 else:
1502 self._trait_values.pop(name)
1503 cache = {}
1504 raise e
1505 finally:
1506 self._cross_validation_lock = False
1507 # Restore method retrieval from class
1508 del self.notify_change
1510 # trigger delayed notifications
1511 for changes in cache.values():
1512 for change in changes:
1513 self.notify_change(change)
1515 def _notify_trait(self, name, old_value, new_value):
1516 self.notify_change(
1517 Bunch(
1518 name=name,
1519 old=old_value,
1520 new=new_value,
1521 owner=self,
1522 type="change",
1523 )
1524 )
1526 def notify_change(self, change):
1527 """Notify observers of a change event"""
1528 return self._notify_observers(change)
1530 def _notify_observers(self, event):
1531 """Notify observers of any event"""
1532 if not isinstance(event, Bunch):
1533 # cast to bunch if given a dict
1534 event = Bunch(event)
1535 name, type = event['name'], event['type']
1537 callables = []
1538 if name in self._trait_notifiers:
1539 callables.extend(self._trait_notifiers.get(name, {}).get(type, []))
1540 callables.extend(self._trait_notifiers.get(name, {}).get(All, []))
1541 if All in self._trait_notifiers: # type:ignore[comparison-overlap]
1542 callables.extend(
1543 self._trait_notifiers.get(All, {}).get(type, []) # type:ignore[call-overload]
1544 )
1545 callables.extend(
1546 self._trait_notifiers.get(All, {}).get(All, []) # type:ignore[call-overload]
1547 )
1549 # Now static ones
1550 magic_name = "_%s_changed" % name
1551 if event['type'] == "change" and hasattr(self, magic_name):
1552 class_value = getattr(self.__class__, magic_name)
1553 if not isinstance(class_value, ObserveHandler):
1554 deprecated_method(
1555 class_value,
1556 self.__class__,
1557 magic_name,
1558 "use @observe and @unobserve instead.",
1559 )
1560 cb = getattr(self, magic_name)
1561 # Only append the magic method if it was not manually registered
1562 if cb not in callables:
1563 callables.append(_callback_wrapper(cb))
1565 # Call them all now
1566 # Traits catches and logs errors here. I allow them to raise
1567 for c in callables:
1568 # Bound methods have an additional 'self' argument.
1570 if isinstance(c, _CallbackWrapper):
1571 c = c.__call__
1572 elif isinstance(c, EventHandler) and c.name is not None:
1573 c = getattr(self, c.name)
1575 c(event)
1577 def _add_notifiers(self, handler, name, type):
1578 if name not in self._trait_notifiers:
1579 nlist: list[t.Any] = []
1580 self._trait_notifiers[name] = {type: nlist}
1581 else:
1582 if type not in self._trait_notifiers[name]:
1583 nlist = []
1584 self._trait_notifiers[name][type] = nlist
1585 else:
1586 nlist = self._trait_notifiers[name][type]
1587 if handler not in nlist:
1588 nlist.append(handler)
1590 def _remove_notifiers(self, handler, name, type):
1591 try:
1592 if handler is None:
1593 del self._trait_notifiers[name][type]
1594 else:
1595 self._trait_notifiers[name][type].remove(handler)
1596 except KeyError:
1597 pass
1599 def on_trait_change(self, handler=None, name=None, remove=False):
1600 """DEPRECATED: Setup a handler to be called when a trait changes.
1602 This is used to setup dynamic notifications of trait changes.
1604 Static handlers can be created by creating methods on a HasTraits
1605 subclass with the naming convention '_[traitname]_changed'. Thus,
1606 to create static handler for the trait 'a', create the method
1607 _a_changed(self, name, old, new) (fewer arguments can be used, see
1608 below).
1610 If `remove` is True and `handler` is not specified, all change
1611 handlers for the specified name are uninstalled.
1613 Parameters
1614 ----------
1615 handler : callable, None
1616 A callable that is called when a trait changes. Its
1617 signature can be handler(), handler(name), handler(name, new),
1618 handler(name, old, new), or handler(name, old, new, self).
1619 name : list, str, None
1620 If None, the handler will apply to all traits. If a list
1621 of str, handler will apply to all names in the list. If a
1622 str, the handler will apply just to that name.
1623 remove : bool
1624 If False (the default), then install the handler. If True
1625 then unintall it.
1626 """
1627 warn(
1628 "on_trait_change is deprecated in traitlets 4.1: use observe instead",
1629 DeprecationWarning,
1630 stacklevel=2,
1631 )
1632 if name is None:
1633 name = All
1634 if remove:
1635 self.unobserve(_callback_wrapper(handler), names=name)
1636 else:
1637 self.observe(_callback_wrapper(handler), names=name)
1639 def observe(
1640 self,
1641 handler: t.Callable[..., t.Any],
1642 names: Sentinel | str | t.Iterable[Sentinel | str] = All,
1643 type: Sentinel | str = "change",
1644 ) -> None:
1645 """Setup a handler to be called when a trait changes.
1647 This is used to setup dynamic notifications of trait changes.
1649 Parameters
1650 ----------
1651 handler : callable
1652 A callable that is called when a trait changes. Its
1653 signature should be ``handler(change)``, where ``change`` is a
1654 dictionary. The change dictionary at least holds a 'type' key.
1655 * ``type``: the type of notification.
1656 Other keys may be passed depending on the value of 'type'. In the
1657 case where type is 'change', we also have the following keys:
1658 * ``owner`` : the HasTraits instance
1659 * ``old`` : the old value of the modified trait attribute
1660 * ``new`` : the new value of the modified trait attribute
1661 * ``name`` : the name of the modified trait attribute.
1662 names : list, str, All
1663 If names is All, the handler will apply to all traits. If a list
1664 of str, handler will apply to all names in the list. If a
1665 str, the handler will apply just to that name.
1666 type : str, All (default: 'change')
1667 The type of notification to filter by. If equal to All, then all
1668 notifications are passed to the observe handler.
1669 """
1670 for name in parse_notifier_name(names):
1671 self._add_notifiers(handler, name, type)
1673 def unobserve(
1674 self,
1675 handler: t.Callable[..., t.Any],
1676 names: Sentinel | str | t.Iterable[Sentinel | str] = All,
1677 type: Sentinel | str = "change",
1678 ) -> None:
1679 """Remove a trait change handler.
1681 This is used to unregister handlers to trait change notifications.
1683 Parameters
1684 ----------
1685 handler : callable
1686 The callable called when a trait attribute changes.
1687 names : list, str, All (default: All)
1688 The names of the traits for which the specified handler should be
1689 uninstalled. If names is All, the specified handler is uninstalled
1690 from the list of notifiers corresponding to all changes.
1691 type : str or All (default: 'change')
1692 The type of notification to filter by. If All, the specified handler
1693 is uninstalled from the list of notifiers corresponding to all types.
1694 """
1695 for name in parse_notifier_name(names):
1696 self._remove_notifiers(handler, name, type)
1698 def unobserve_all(self, name: str | t.Any = All) -> None:
1699 """Remove trait change handlers of any type for the specified name.
1700 If name is not specified, removes all trait notifiers."""
1701 if name is All:
1702 self._trait_notifiers: dict[str, t.Any] = {}
1703 else:
1704 try:
1705 del self._trait_notifiers[name]
1706 except KeyError:
1707 pass
1709 def _register_validator(self, handler, names):
1710 """Setup a handler to be called when a trait should be cross validated.
1712 This is used to setup dynamic notifications for cross-validation.
1714 If a validator is already registered for any of the provided names, a
1715 TraitError is raised and no new validator is registered.
1717 Parameters
1718 ----------
1719 handler : callable
1720 A callable that is called when the given trait is cross-validated.
1721 Its signature is handler(proposal), where proposal is a Bunch (dictionary with attribute access)
1722 with the following attributes/keys:
1723 * ``owner`` : the HasTraits instance
1724 * ``value`` : the proposed value for the modified trait attribute
1725 * ``trait`` : the TraitType instance associated with the attribute
1726 names : List of strings
1727 The names of the traits that should be cross-validated
1728 """
1729 for name in names:
1730 magic_name = "_%s_validate" % name
1731 if hasattr(self, magic_name):
1732 class_value = getattr(self.__class__, magic_name)
1733 if not isinstance(class_value, ValidateHandler):
1734 deprecated_method(
1735 class_value,
1736 self.__class__,
1737 magic_name,
1738 "use @validate decorator instead.",
1739 )
1740 for name in names:
1741 self._trait_validators[name] = handler
1743 def add_traits(self, **traits):
1744 """Dynamically add trait attributes to the HasTraits instance."""
1745 cls = self.__class__
1746 attrs = {"__module__": cls.__module__}
1747 if hasattr(cls, "__qualname__"):
1748 # __qualname__ introduced in Python 3.3 (see PEP 3155)
1749 attrs["__qualname__"] = cls.__qualname__
1750 attrs.update(traits)
1751 self.__class__ = type(cls.__name__, (cls,), attrs)
1752 for trait in traits.values():
1753 trait.instance_init(self)
1755 def set_trait(self, name, value):
1756 """Forcibly sets trait attribute, including read-only attributes."""
1757 cls = self.__class__
1758 if not self.has_trait(name):
1759 raise TraitError(f"Class {cls.__name__} does not have a trait named {name}")
1760 else:
1761 getattr(cls, name).set(self, value)
1763 @classmethod
1764 def class_trait_names(cls, **metadata):
1765 """Get a list of all the names of this class' traits.
1767 This method is just like the :meth:`trait_names` method,
1768 but is unbound.
1769 """
1770 return list(cls.class_traits(**metadata))
1772 @classmethod
1773 def class_traits(cls, **metadata):
1774 """Get a ``dict`` of all the traits of this class. The dictionary
1775 is keyed on the name and the values are the TraitType objects.
1777 This method is just like the :meth:`traits` method, but is unbound.
1779 The TraitTypes returned don't know anything about the values
1780 that the various HasTrait's instances are holding.
1782 The metadata kwargs allow functions to be passed in which
1783 filter traits based on metadata values. The functions should
1784 take a single value as an argument and return a boolean. If
1785 any function returns False, then the trait is not included in
1786 the output. If a metadata key doesn't exist, None will be passed
1787 to the function.
1788 """
1789 traits = cls._traits.copy()
1791 if len(metadata) == 0:
1792 return traits
1794 result = {}
1795 for name, trait in traits.items():
1796 for meta_name, meta_eval in metadata.items():
1797 if not callable(meta_eval):
1798 meta_eval = _SimpleTest(meta_eval)
1799 if not meta_eval(trait.metadata.get(meta_name, None)):
1800 break
1801 else:
1802 result[name] = trait
1804 return result
1806 @classmethod
1807 def class_own_traits(cls, **metadata):
1808 """Get a dict of all the traitlets defined on this class, not a parent.
1810 Works like `class_traits`, except for excluding traits from parents.
1811 """
1812 sup = super(cls, cls)
1813 return {
1814 n: t
1815 for (n, t) in cls.class_traits(**metadata).items()
1816 if getattr(sup, n, None) is not t
1817 }
1819 def has_trait(self, name):
1820 """Returns True if the object has a trait with the specified name."""
1821 return name in self._traits
1823 def trait_has_value(self, name):
1824 """Returns True if the specified trait has a value.
1826 This will return false even if ``getattr`` would return a
1827 dynamically generated default value. These default values
1828 will be recognized as existing only after they have been
1829 generated.
1831 Example
1833 .. code-block:: python
1835 class MyClass(HasTraits):
1836 i = Int()
1838 mc = MyClass()
1839 assert not mc.trait_has_value("i")
1840 mc.i # generates a default value
1841 assert mc.trait_has_value("i")
1842 """
1843 return name in self._trait_values
1845 def trait_values(self, **metadata):
1846 """A ``dict`` of trait names and their values.
1848 The metadata kwargs allow functions to be passed in which
1849 filter traits based on metadata values. The functions should
1850 take a single value as an argument and return a boolean. If
1851 any function returns False, then the trait is not included in
1852 the output. If a metadata key doesn't exist, None will be passed
1853 to the function.
1855 Returns
1856 -------
1857 A ``dict`` of trait names and their values.
1859 Notes
1860 -----
1861 Trait values are retrieved via ``getattr``, any exceptions raised
1862 by traits or the operations they may trigger will result in the
1863 absence of a trait value in the result ``dict``.
1864 """
1865 return {name: getattr(self, name) for name in self.trait_names(**metadata)}
1867 def _get_trait_default_generator(self, name):
1868 """Return default generator for a given trait
1870 Walk the MRO to resolve the correct default generator according to inheritance.
1871 """
1872 method_name = "_%s_default" % name
1873 if method_name in self.__dict__:
1874 return getattr(self, method_name)
1875 if method_name in self.__class__.__dict__:
1876 return getattr(self.__class__, method_name)
1877 return self._all_trait_default_generators[name]
1879 def trait_defaults(self, *names, **metadata):
1880 """Return a trait's default value or a dictionary of them
1882 Notes
1883 -----
1884 Dynamically generated default values may
1885 depend on the current state of the object."""
1886 for n in names:
1887 if not self.has_trait(n):
1888 raise TraitError(f"'{n}' is not a trait of '{type(self).__name__}' instances")
1890 if len(names) == 1 and len(metadata) == 0:
1891 return self._get_trait_default_generator(names[0])(self)
1893 trait_names = self.trait_names(**metadata)
1894 trait_names.extend(names)
1896 defaults = {}
1897 for n in trait_names:
1898 defaults[n] = self._get_trait_default_generator(n)(self)
1899 return defaults
1901 def trait_names(self, **metadata):
1902 """Get a list of all the names of this class' traits."""
1903 return list(self.traits(**metadata))
1905 def traits(self, **metadata):
1906 """Get a ``dict`` of all the traits of this class. The dictionary
1907 is keyed on the name and the values are the TraitType objects.
1909 The TraitTypes returned don't know anything about the values
1910 that the various HasTrait's instances are holding.
1912 The metadata kwargs allow functions to be passed in which
1913 filter traits based on metadata values. The functions should
1914 take a single value as an argument and return a boolean. If
1915 any function returns False, then the trait is not included in
1916 the output. If a metadata key doesn't exist, None will be passed
1917 to the function.
1918 """
1919 traits = self._traits.copy()
1921 if len(metadata) == 0:
1922 return traits
1924 result = {}
1925 for name, trait in traits.items():
1926 for meta_name, meta_eval in metadata.items():
1927 if not callable(meta_eval):
1928 meta_eval = _SimpleTest(meta_eval)
1929 if not meta_eval(trait.metadata.get(meta_name, None)):
1930 break
1931 else:
1932 result[name] = trait
1934 return result
1936 def trait_metadata(self, traitname, key, default=None):
1937 """Get metadata values for trait by key."""
1938 try:
1939 trait = getattr(self.__class__, traitname)
1940 except AttributeError as e:
1941 raise TraitError(
1942 f"Class {self.__class__.__name__} does not have a trait named {traitname}"
1943 ) from e
1944 metadata_name = "_" + traitname + "_metadata"
1945 if hasattr(self, metadata_name) and key in getattr(self, metadata_name):
1946 return getattr(self, metadata_name).get(key, default)
1947 else:
1948 return trait.metadata.get(key, default)
1950 @classmethod
1951 def class_own_trait_events(cls, name):
1952 """Get a dict of all event handlers defined on this class, not a parent.
1954 Works like ``event_handlers``, except for excluding traits from parents.
1955 """
1956 sup = super(cls, cls)
1957 return {
1958 n: e
1959 for (n, e) in cls.events(name).items() # type:ignore[attr-defined]
1960 if getattr(sup, n, None) is not e
1961 }
1963 @classmethod
1964 def trait_events(cls, name=None):
1965 """Get a ``dict`` of all the event handlers of this class.
1967 Parameters
1968 ----------
1969 name : str (default: None)
1970 The name of a trait of this class. If name is ``None`` then all
1971 the event handlers of this class will be returned instead.
1973 Returns
1974 -------
1975 The event handlers associated with a trait name, or all event handlers.
1976 """
1977 events = {}
1978 for k, v in getmembers(cls):
1979 if isinstance(v, EventHandler):
1980 if name is None:
1981 events[k] = v
1982 elif name in v.trait_names: # type:ignore[attr-defined]
1983 events[k] = v
1984 elif hasattr(v, "tags"):
1985 if cls.trait_names(**v.tags):
1986 events[k] = v
1987 return events
1990# -----------------------------------------------------------------------------
1991# Actual TraitTypes implementations/subclasses
1992# -----------------------------------------------------------------------------
1994# -----------------------------------------------------------------------------
1995# TraitTypes subclasses for handling classes and instances of classes
1996# -----------------------------------------------------------------------------
1999class ClassBasedTraitType(TraitType[G, S]):
2000 """
2001 A trait with error reporting and string -> type resolution for Type,
2002 Instance and This.
2003 """
2005 def _resolve_string(self, string):
2006 """
2007 Resolve a string supplied for a type into an actual object.
2008 """
2009 return import_item(string)
2012class Type(ClassBasedTraitType[G, S]):
2013 """A trait whose value must be a subclass of a specified class."""
2015 if t.TYPE_CHECKING:
2017 @t.overload
2018 def __init__(
2019 self: Type[object, object],
2020 default_value: Sentinel | None | str = ...,
2021 klass: None | str = ...,
2022 allow_none: Literal[False] = ...,
2023 read_only: bool | None = ...,
2024 help: str | None = ...,
2025 config: t.Any | None = ...,
2026 **kwargs: t.Any,
2027 ):
2028 ...
2030 @t.overload
2031 def __init__(
2032 self: Type[object | None, object | None],
2033 default_value: S | Sentinel | None | str = ...,
2034 klass: None | str = ...,
2035 allow_none: Literal[True] = ...,
2036 read_only: bool | None = ...,
2037 help: str | None = ...,
2038 config: t.Any | None = ...,
2039 **kwargs: t.Any,
2040 ):
2041 ...
2043 @t.overload
2044 def __init__(
2045 self: Type[S, S],
2046 default_value: S | Sentinel | str = ...,
2047 klass: type[S] = ...,
2048 allow_none: Literal[False] = ...,
2049 read_only: bool | None = ...,
2050 help: str | None = ...,
2051 config: t.Any | None = ...,
2052 **kwargs: t.Any,
2053 ):
2054 ...
2056 @t.overload
2057 def __init__(
2058 self: Type[S | None, S | None],
2059 default_value: S | Sentinel | None | str = ...,
2060 klass: type[S] = ...,
2061 allow_none: Literal[True] = ...,
2062 read_only: bool | None = ...,
2063 help: str | None = ...,
2064 config: t.Any | None = ...,
2065 **kwargs: t.Any,
2066 ):
2067 ...
2069 def __init__(self, default_value=Undefined, klass=None, allow_none=False, **kwargs):
2070 """Construct a Type trait
2072 A Type trait specifies that its values must be subclasses of
2073 a particular class.
2075 If only ``default_value`` is given, it is used for the ``klass`` as
2076 well. If neither are given, both default to ``object``.
2078 Parameters
2079 ----------
2080 default_value : class, str or None
2081 The default value must be a subclass of klass. If an str,
2082 the str must be a fully specified class name, like 'foo.bar.Bah'.
2083 The string is resolved into real class, when the parent
2084 :class:`HasTraits` class is instantiated.
2085 klass : class, str [ default object ]
2086 Values of this trait must be a subclass of klass. The klass
2087 may be specified in a string like: 'foo.bar.MyClass'.
2088 The string is resolved into real class, when the parent
2089 :class:`HasTraits` class is instantiated.
2090 allow_none : bool [ default False ]
2091 Indicates whether None is allowed as an assignable value.
2092 **kwargs
2093 extra kwargs passed to `ClassBasedTraitType`
2094 """
2095 if default_value is Undefined:
2096 new_default_value = object if (klass is None) else klass
2097 else:
2098 new_default_value = default_value
2100 if klass is None:
2101 if (default_value is None) or (default_value is Undefined):
2102 klass = object
2103 else:
2104 klass = default_value
2106 if not (inspect.isclass(klass) or isinstance(klass, str)):
2107 raise TraitError("A Type trait must specify a class.")
2109 self.klass = klass
2111 super().__init__(new_default_value, allow_none=allow_none, **kwargs)
2113 def validate(self, obj, value):
2114 """Validates that the value is a valid object instance."""
2115 if isinstance(value, str):
2116 try:
2117 value = self._resolve_string(value)
2118 except ImportError as e:
2119 raise TraitError(
2120 f"The '{self.name}' trait of {obj} instance must be a type, but "
2121 f"{value!r} could not be imported"
2122 ) from e
2123 try:
2124 if issubclass(value, self.klass): # type:ignore[arg-type]
2125 return value
2126 except Exception:
2127 pass
2129 self.error(obj, value)
2131 def info(self):
2132 """Returns a description of the trait."""
2133 if isinstance(self.klass, str):
2134 klass = self.klass
2135 else:
2136 klass = self.klass.__module__ + "." + self.klass.__name__
2137 result = "a subclass of '%s'" % klass
2138 if self.allow_none:
2139 return result + " or None"
2140 return result
2142 def instance_init(self, obj):
2143 # we can't do this in subclass_init because that
2144 # might be called before all imports are done.
2145 self._resolve_classes()
2147 def _resolve_classes(self):
2148 if isinstance(self.klass, str):
2149 self.klass = self._resolve_string(self.klass)
2150 if isinstance(self.default_value, str):
2151 self.default_value = self._resolve_string(self.default_value)
2153 def default_value_repr(self):
2154 value = self.default_value
2155 assert value is not None
2156 if isinstance(value, str):
2157 return repr(value)
2158 else:
2159 return repr(f"{value.__module__}.{value.__name__}")
2162class Instance(ClassBasedTraitType[T, T]):
2163 """A trait whose value must be an instance of a specified class.
2165 The value can also be an instance of a subclass of the specified class.
2167 Subclasses can declare default classes by overriding the klass attribute
2168 """
2170 klass: str | type[T] | None = None
2172 if t.TYPE_CHECKING:
2174 @t.overload
2175 def __init__(
2176 self: Instance[T],
2177 klass: type[T] = ...,
2178 args: tuple[t.Any, ...] | None = ...,
2179 kw: dict[str, t.Any] | None = ...,
2180 allow_none: Literal[False] = ...,
2181 read_only: bool | None = ...,
2182 help: str | None = ...,
2183 **kwargs: t.Any,
2184 ) -> None:
2185 ...
2187 @t.overload
2188 def __init__(
2189 self: Instance[T | None],
2190 klass: type[T] = ...,
2191 args: tuple[t.Any, ...] | None = ...,
2192 kw: dict[str, t.Any] | None = ...,
2193 allow_none: Literal[True] = ...,
2194 read_only: bool | None = ...,
2195 help: str | None = ...,
2196 **kwargs: t.Any,
2197 ) -> None:
2198 ...
2200 @t.overload
2201 def __init__(
2202 self: Instance[t.Any],
2203 klass: str | None = ...,
2204 args: tuple[t.Any, ...] | None = ...,
2205 kw: dict[str, t.Any] | None = ...,
2206 allow_none: Literal[False] = ...,
2207 read_only: bool | None = ...,
2208 help: str | None = ...,
2209 **kwargs: t.Any,
2210 ) -> None:
2211 ...
2213 @t.overload
2214 def __init__(
2215 self: Instance[t.Any | None],
2216 klass: str | None = ...,
2217 args: tuple[t.Any, ...] | None = ...,
2218 kw: dict[str, t.Any] | None = ...,
2219 allow_none: Literal[True] = ...,
2220 read_only: bool | None = ...,
2221 help: str | None = ...,
2222 **kwargs: t.Any,
2223 ) -> None:
2224 ...
2226 def __init__(
2227 self,
2228 klass: str | type[T] | None = None,
2229 args: tuple[t.Any, ...] | None = None,
2230 kw: dict[str, t.Any] | None = None,
2231 allow_none: bool = False,
2232 read_only: bool | None = None,
2233 help: str | None = None,
2234 **kwargs: t.Any,
2235 ) -> None:
2236 """Construct an Instance trait.
2238 This trait allows values that are instances of a particular
2239 class or its subclasses. Our implementation is quite different
2240 from that of enthough.traits as we don't allow instances to be used
2241 for klass and we handle the ``args`` and ``kw`` arguments differently.
2243 Parameters
2244 ----------
2245 klass : class, str
2246 The class that forms the basis for the trait. Class names
2247 can also be specified as strings, like 'foo.bar.Bar'.
2248 args : tuple
2249 Positional arguments for generating the default value.
2250 kw : dict
2251 Keyword arguments for generating the default value.
2252 allow_none : bool [ default False ]
2253 Indicates whether None is allowed as a value.
2254 **kwargs
2255 Extra kwargs passed to `ClassBasedTraitType`
2257 Notes
2258 -----
2259 If both ``args`` and ``kw`` are None, then the default value is None.
2260 If ``args`` is a tuple and ``kw`` is a dict, then the default is
2261 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
2262 None, the None is replaced by ``()`` or ``{}``, respectively.
2263 """
2264 if klass is None:
2265 klass = self.klass
2267 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, str)):
2268 self.klass = klass
2269 else:
2270 raise TraitError("The klass attribute must be a class not: %r" % klass)
2272 if (kw is not None) and not isinstance(kw, dict):
2273 raise TraitError("The 'kw' argument must be a dict or None.")
2274 if (args is not None) and not isinstance(args, tuple):
2275 raise TraitError("The 'args' argument must be a tuple or None.")
2277 self.default_args = args
2278 self.default_kwargs = kw
2280 super().__init__(allow_none=allow_none, **kwargs)
2282 def validate(self, obj, value):
2283 assert self.klass is not None
2284 if isinstance(value, self.klass): # type:ignore[arg-type]
2285 return value
2286 else:
2287 self.error(obj, value)
2289 def info(self):
2290 if isinstance(self.klass, str):
2291 result = add_article(self.klass)
2292 else:
2293 result = describe("a", self.klass)
2294 if self.allow_none:
2295 result += " or None"
2296 return result
2298 def instance_init(self, obj):
2299 # we can't do this in subclass_init because that
2300 # might be called before all imports are done.
2301 self._resolve_classes()
2303 def _resolve_classes(self):
2304 if isinstance(self.klass, str):
2305 self.klass = self._resolve_string(self.klass)
2307 def make_dynamic_default(self):
2308 if (self.default_args is None) and (self.default_kwargs is None):
2309 return None
2310 assert self.klass is not None
2311 return self.klass(
2312 *(self.default_args or ()), **(self.default_kwargs or {})
2313 ) # type:ignore[operator]
2315 def default_value_repr(self):
2316 return repr(self.make_dynamic_default())
2318 def from_string(self, s):
2319 return _safe_literal_eval(s)
2322class ForwardDeclaredMixin:
2323 """
2324 Mixin for forward-declared versions of Instance and Type.
2325 """
2327 def _resolve_string(self, string):
2328 """
2329 Find the specified class name by looking for it in the module in which
2330 our this_class attribute was defined.
2331 """
2332 modname = self.this_class.__module__ # type:ignore[attr-defined]
2333 return import_item(".".join([modname, string]))
2336class ForwardDeclaredType(ForwardDeclaredMixin, Type[G, S]):
2337 """
2338 Forward-declared version of Type.
2339 """
2341 pass
2344class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance[T]):
2345 """
2346 Forward-declared version of Instance.
2347 """
2349 pass
2352class This(ClassBasedTraitType[t.Optional[T], t.Optional[T]]):
2353 """A trait for instances of the class containing this trait.
2355 Because how how and when class bodies are executed, the ``This``
2356 trait can only have a default value of None. This, and because we
2357 always validate default values, ``allow_none`` is *always* true.
2358 """
2360 info_text = "an instance of the same type as the receiver or None"
2362 def __init__(self, **kwargs):
2363 super().__init__(None, **kwargs)
2365 def validate(self, obj, value):
2366 # What if value is a superclass of obj.__class__? This is
2367 # complicated if it was the superclass that defined the This
2368 # trait.
2369 assert self.this_class is not None
2370 if isinstance(value, self.this_class) or (value is None):
2371 return value
2372 else:
2373 self.error(obj, value)
2376class Union(TraitType[t.Any, t.Any]):
2377 """A trait type representing a Union type."""
2379 def __init__(self, trait_types, **kwargs):
2380 """Construct a Union trait.
2382 This trait allows values that are allowed by at least one of the
2383 specified trait types. A Union traitlet cannot have metadata on
2384 its own, besides the metadata of the listed types.
2386 Parameters
2387 ----------
2388 trait_types : sequence
2389 The list of trait types of length at least 1.
2390 **kwargs
2391 Extra kwargs passed to `TraitType`
2393 Notes
2394 -----
2395 Union([Float(), Bool(), Int()]) attempts to validate the provided values
2396 with the validation function of Float, then Bool, and finally Int.
2398 Parsing from string is ambiguous for container types which accept other
2399 collection-like literals (e.g. List accepting both `[]` and `()`
2400 precludes Union from ever parsing ``Union([List(), Tuple()])`` as a tuple;
2401 you can modify behaviour of too permissive container traits by overriding
2402 ``_literal_from_string_pairs`` in subclasses.
2403 Similarly, parsing unions of numeric types is only unambiguous if
2404 types are provided in order of increasing permissiveness, e.g.
2405 ``Union([Int(), Float()])`` (since floats accept integer-looking values).
2406 """
2407 self.trait_types = list(trait_types)
2408 self.info_text = " or ".join([tt.info() for tt in self.trait_types])
2409 super().__init__(**kwargs)
2411 def default(self, obj=None):
2412 default = super().default(obj)
2413 for trait in self.trait_types:
2414 if default is Undefined:
2415 default = trait.default(obj)
2416 else:
2417 break
2418 return default
2420 def class_init(self, cls, name):
2421 for trait_type in reversed(self.trait_types):
2422 trait_type.class_init(cls, None)
2423 super().class_init(cls, name)
2425 def subclass_init(self, cls):
2426 for trait_type in reversed(self.trait_types):
2427 trait_type.subclass_init(cls)
2428 # explicitly not calling super().subclass_init(cls)
2429 # to opt out of instance_init
2431 def validate(self, obj, value):
2432 with obj.cross_validation_lock:
2433 for trait_type in self.trait_types:
2434 try:
2435 v = trait_type._validate(obj, value)
2436 # In the case of an element trait, the name is None
2437 if self.name is not None:
2438 setattr(obj, "_" + self.name + "_metadata", trait_type.metadata)
2439 return v
2440 except TraitError:
2441 continue
2442 self.error(obj, value)
2444 def __or__(self, other):
2445 if isinstance(other, Union):
2446 return Union(self.trait_types + other.trait_types)
2447 else:
2448 return Union([*self.trait_types, other])
2450 def from_string(self, s):
2451 for trait_type in self.trait_types:
2452 try:
2453 v = trait_type.from_string(s)
2454 return trait_type.validate(None, v)
2455 except (TraitError, ValueError):
2456 continue
2457 return super().from_string(s)
2460# -----------------------------------------------------------------------------
2461# Basic TraitTypes implementations/subclasses
2462# -----------------------------------------------------------------------------
2465class Any(TraitType[t.Optional[t.Any], t.Optional[t.Any]]):
2466 """A trait which allows any value."""
2468 if t.TYPE_CHECKING:
2470 @t.overload
2471 def __init__(
2472 self: Any,
2473 default_value: str = ...,
2474 *,
2475 allow_none: Literal[False],
2476 read_only: bool | None = ...,
2477 help: str | None = ...,
2478 config: t.Any | None = ...,
2479 **kwargs: t.Any,
2480 ):
2481 ...
2483 @t.overload
2484 def __init__(
2485 self: Any,
2486 default_value: str = ...,
2487 *,
2488 allow_none: Literal[True],
2489 read_only: bool | None = ...,
2490 help: str | None = ...,
2491 config: t.Any | None = ...,
2492 **kwargs: t.Any,
2493 ):
2494 ...
2496 @t.overload
2497 def __init__(
2498 self: Any,
2499 default_value: str = ...,
2500 *,
2501 allow_none: Literal[True, False] = ...,
2502 help: str | None = ...,
2503 read_only: bool | None = False,
2504 config: t.Any = None,
2505 **kwargs: t.Any,
2506 ):
2507 ...
2509 def __init__(
2510 self: Any,
2511 default_value: str = ...,
2512 *,
2513 allow_none: bool | None = False,
2514 help: str | None = "",
2515 read_only: bool | None = False,
2516 config: t.Any = None,
2517 **kwargs: t.Any,
2518 ):
2519 ...
2521 @t.overload
2522 def __get__(self, obj: None, cls: type[t.Any]) -> Any:
2523 ...
2525 @t.overload
2526 def __get__(self, obj: t.Any, cls: type[t.Any]) -> t.Any:
2527 ...
2529 def __get__(self, obj: t.Any | None, cls: type[t.Any]) -> t.Any | Any:
2530 ...
2532 default_value: t.Any | None = None
2533 allow_none = True
2534 info_text = "any value"
2536 def subclass_init(self, cls):
2537 pass # fully opt out of instance_init
2540def _validate_bounds(trait, obj, value):
2541 """
2542 Validate that a number to be applied to a trait is between bounds.
2544 If value is not between min_bound and max_bound, this raises a
2545 TraitError with an error message appropriate for this trait.
2546 """
2547 if trait.min is not None and value < trait.min:
2548 raise TraitError(
2549 f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
2550 f"not be less than {trait.min}, but a value of {value} was "
2551 "specified"
2552 )
2553 if trait.max is not None and value > trait.max:
2554 raise TraitError(
2555 f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
2556 f"not be greater than {trait.max}, but a value of {value} was "
2557 "specified"
2558 )
2559 return value
2562# I = t.TypeVar('I', t.Optional[int], int)
2565class Int(TraitType[G, S]):
2566 """An int trait."""
2568 default_value = 0
2569 info_text = "an int"
2571 @t.overload
2572 def __init__(
2573 self: Int[int, int],
2574 default_value: int | Sentinel = ...,
2575 allow_none: Literal[False] = ...,
2576 read_only: bool | None = ...,
2577 help: str | None = ...,
2578 config: t.Any | None = ...,
2579 **kwargs: t.Any,
2580 ):
2581 ...
2583 @t.overload
2584 def __init__(
2585 self: Int[int | None, int | None],
2586 default_value: int | Sentinel | None = ...,
2587 allow_none: Literal[True] = ...,
2588 read_only: bool | None = ...,
2589 help: str | None = ...,
2590 config: t.Any | None = ...,
2591 **kwargs: t.Any,
2592 ):
2593 ...
2595 def __init__(self, default_value=Undefined, allow_none=False, **kwargs):
2596 self.min = kwargs.pop("min", None)
2597 self.max = kwargs.pop("max", None)
2598 super().__init__(default_value=default_value, allow_none=allow_none, **kwargs)
2600 def validate(self, obj, value):
2601 if not isinstance(value, int):
2602 self.error(obj, value)
2603 return _validate_bounds(self, obj, value)
2605 def from_string(self, s):
2606 if self.allow_none and s == "None":
2607 return None
2608 return int(s)
2610 def subclass_init(self, cls):
2611 pass # fully opt out of instance_init
2614class CInt(Int[G, S]):
2615 """A casting version of the int trait."""
2617 if t.TYPE_CHECKING:
2619 @t.overload
2620 def __init__(
2621 self: CInt[int, t.Any],
2622 default_value: t.Any | Sentinel = ...,
2623 allow_none: Literal[False] = ...,
2624 read_only: bool | None = ...,
2625 help: str | None = ...,
2626 config: t.Any | None = ...,
2627 **kwargs: t.Any,
2628 ):
2629 ...
2631 @t.overload
2632 def __init__(
2633 self: CInt[int | None, t.Any],
2634 default_value: t.Any | Sentinel | None = ...,
2635 allow_none: Literal[True] = ...,
2636 read_only: bool | None = ...,
2637 help: str | None = ...,
2638 config: t.Any | None = ...,
2639 **kwargs: t.Any,
2640 ):
2641 ...
2643 def __init__(self, default_value=Undefined, allow_none=False, **kwargs):
2644 ...
2646 def validate(self, obj, value):
2647 try:
2648 value = int(value)
2649 except Exception:
2650 self.error(obj, value)
2651 return _validate_bounds(self, obj, value)
2654Long, CLong = Int, CInt
2655Integer = Int
2658class Float(TraitType[G, S]):
2659 """A float trait."""
2661 default_value = 0.0
2662 info_text = "a float"
2664 @t.overload
2665 def __init__(
2666 self: Float[float, int | float],
2667 default_value: float | Sentinel = ...,
2668 allow_none: Literal[False] = ...,
2669 read_only: bool | None = ...,
2670 help: str | None = ...,
2671 config: t.Any | None = ...,
2672 **kwargs: t.Any,
2673 ):
2674 ...
2676 @t.overload
2677 def __init__(
2678 self: Float[int | None, int | float | None],
2679 default_value: float | Sentinel | None = ...,
2680 allow_none: Literal[True] = ...,
2681 read_only: bool | None = ...,
2682 help: str | None = ...,
2683 config: t.Any | None = ...,
2684 **kwargs: t.Any,
2685 ):
2686 ...
2688 def __init__(self, default_value=Undefined, allow_none=False, **kwargs):
2689 self.min = kwargs.pop("min", -float("inf"))
2690 self.max = kwargs.pop("max", float("inf"))
2691 super().__init__(default_value=default_value, allow_none=allow_none, **kwargs)
2693 def validate(self, obj, value):
2694 if isinstance(value, int):
2695 value = float(value)
2696 if not isinstance(value, float):
2697 self.error(obj, value)
2698 return _validate_bounds(self, obj, value)
2700 def from_string(self, s):
2701 if self.allow_none and s == "None":
2702 return None
2703 return float(s)
2705 def subclass_init(self, cls):
2706 pass # fully opt out of instance_init
2709class CFloat(Float[G, S]):
2710 """A casting version of the float trait."""
2712 if t.TYPE_CHECKING:
2714 @t.overload
2715 def __init__(
2716 self: CFloat[float, t.Any],
2717 default_value: t.Any = ...,
2718 allow_none: Literal[False] = ...,
2719 read_only: bool | None = ...,
2720 help: str | None = ...,
2721 config: t.Any | None = ...,
2722 **kwargs: t.Any,
2723 ):
2724 ...
2726 @t.overload
2727 def __init__(
2728 self: CFloat[float | None, t.Any],
2729 default_value: t.Any = ...,
2730 allow_none: Literal[True] = ...,
2731 read_only: bool | None = ...,
2732 help: str | None = ...,
2733 config: t.Any | None = ...,
2734 **kwargs: t.Any,
2735 ):
2736 ...
2738 def __init__(self, default_value=Undefined, allow_none=False, **kwargs):
2739 ...
2741 def validate(self, obj, value):
2742 try:
2743 value = float(value)
2744 except Exception:
2745 self.error(obj, value)
2746 return _validate_bounds(self, obj, value)
2749class Complex(TraitType[complex, t.Union[complex, float, int]]):
2750 """A trait for complex numbers."""
2752 default_value = 0.0 + 0.0j
2753 info_text = "a complex number"
2755 def validate(self, obj, value):
2756 if isinstance(value, complex):
2757 return value
2758 if isinstance(value, (float, int)):
2759 return complex(value)
2760 self.error(obj, value)
2762 def from_string(self, s):
2763 if self.allow_none and s == "None":
2764 return None
2765 return complex(s)
2767 def subclass_init(self, cls):
2768 pass # fully opt out of instance_init
2771class CComplex(Complex, TraitType[complex, t.Any]):
2772 """A casting version of the complex number trait."""
2774 def validate(self, obj, value):
2775 try:
2776 return complex(value)
2777 except Exception:
2778 self.error(obj, value)
2781# We should always be explicit about whether we're using bytes or unicode, both
2782# for Python 3 conversion and for reliable unicode behaviour on Python 2. So
2783# we don't have a Str type.
2784class Bytes(TraitType[bytes, bytes]):
2785 """A trait for byte strings."""
2787 default_value = b""
2788 info_text = "a bytes object"
2790 def validate(self, obj, value):
2791 if isinstance(value, bytes):
2792 return value
2793 self.error(obj, value)
2795 def from_string(self, s):
2796 if self.allow_none and s == "None":
2797 return None
2798 if len(s) >= 3:
2799 # handle deprecated b"string"
2800 for quote in ('"', "'"):
2801 if s[:2] == f"b{quote}" and s[-1] == quote:
2802 old_s = s
2803 s = s[2:-1]
2804 warn(
2805 "Supporting extra quotes around Bytes is deprecated in traitlets 5.0. "
2806 f"Use {s!r} instead of {old_s!r}.",
2807 DeprecationWarning,
2808 stacklevel=2,
2809 )
2810 break
2811 return s.encode("utf8")
2813 def subclass_init(self, cls):
2814 pass # fully opt out of instance_init
2817class CBytes(Bytes, TraitType[bytes, t.Any]):
2818 """A casting version of the byte string trait."""
2820 def validate(self, obj, value):
2821 try:
2822 return bytes(value)
2823 except Exception:
2824 self.error(obj, value)
2827class Unicode(TraitType[G, S]):
2828 """A trait for unicode strings."""
2830 default_value = ""
2831 info_text = "a unicode string"
2833 if t.TYPE_CHECKING:
2835 @t.overload
2836 def __init__(
2837 self: Unicode[str, str | bytes],
2838 default_value: str | Sentinel = ...,
2839 allow_none: Literal[False] = ...,
2840 read_only: bool | None = ...,
2841 help: str | None = ...,
2842 config: t.Any = ...,
2843 **kwargs: t.Any,
2844 ):
2845 ...
2847 @t.overload
2848 def __init__(
2849 self: Unicode[str | None, str | bytes | None],
2850 default_value: str | Sentinel | None = ...,
2851 allow_none: Literal[True] = ...,
2852 read_only: bool | None = ...,
2853 help: str | None = ...,
2854 config: t.Any = ...,
2855 **kwargs: t.Any,
2856 ):
2857 ...
2859 def __init__(self, **kwargs):
2860 ...
2862 def validate(self, obj, value):
2863 if isinstance(value, str):
2864 return value
2865 if isinstance(value, bytes):
2866 try:
2867 return value.decode("ascii", "strict")
2868 except UnicodeDecodeError as e:
2869 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
2870 raise TraitError(msg.format(value, self.name, class_of(obj))) from e
2871 self.error(obj, value)
2873 def from_string(self, s):
2874 if self.allow_none and s == "None":
2875 return None
2876 s = os.path.expanduser(s)
2877 if len(s) >= 2:
2878 # handle deprecated "1"
2879 for c in ('"', "'"):
2880 if s[0] == s[-1] == c:
2881 old_s = s
2882 s = s[1:-1]
2883 warn(
2884 "Supporting extra quotes around strings is deprecated in traitlets 5.0. "
2885 f"You can use {s!r} instead of {old_s!r} if you require traitlets >=5.",
2886 DeprecationWarning,
2887 stacklevel=2,
2888 )
2889 return s
2891 def subclass_init(self, cls):
2892 pass # fully opt out of instance_init
2895class CUnicode(Unicode[G, S], TraitType[str, t.Any]):
2896 """A casting version of the unicode trait."""
2898 if t.TYPE_CHECKING:
2900 @t.overload
2901 def __init__(
2902 self: CUnicode[str, t.Any],
2903 default_value: str | Sentinel = ...,
2904 allow_none: Literal[False] = ...,
2905 read_only: bool | None = ...,
2906 help: str | None = ...,
2907 config: t.Any = ...,
2908 **kwargs: t.Any,
2909 ):
2910 ...
2912 @t.overload
2913 def __init__(
2914 self: CUnicode[str | None, t.Any],
2915 default_value: str | Sentinel | None = ...,
2916 allow_none: Literal[True] = ...,
2917 read_only: bool | None = ...,
2918 help: str | None = ...,
2919 config: t.Any = ...,
2920 **kwargs: t.Any,
2921 ):
2922 ...
2924 def __init__(self, **kwargs):
2925 ...
2927 def validate(self, obj, value):
2928 try:
2929 return str(value)
2930 except Exception:
2931 self.error(obj, value)
2934class ObjectName(TraitType[str, str]):
2935 """A string holding a valid object name in this version of Python.
2937 This does not check that the name exists in any scope."""
2939 info_text = "a valid object identifier in Python"
2941 coerce_str = staticmethod(lambda _, s: s)
2943 def validate(self, obj, value):
2944 value = self.coerce_str(obj, value)
2946 if isinstance(value, str) and isidentifier(value):
2947 return value
2948 self.error(obj, value)
2950 def from_string(self, s):
2951 if self.allow_none and s == "None":
2952 return None
2953 return s
2956class DottedObjectName(ObjectName):
2957 """A string holding a valid dotted object name in Python, such as A.b3._c"""
2959 def validate(self, obj, value):
2960 value = self.coerce_str(obj, value)
2962 if isinstance(value, str) and all(isidentifier(a) for a in value.split(".")):
2963 return value
2964 self.error(obj, value)
2967class Bool(TraitType[G, S]):
2968 """A boolean (True, False) trait."""
2970 default_value = False
2971 info_text = "a boolean"
2973 if t.TYPE_CHECKING:
2975 @t.overload
2976 def __init__(
2977 self: Bool[bool, bool | int],
2978 default_value: bool | Sentinel = ...,
2979 allow_none: Literal[False] = ...,
2980 read_only: bool | None = ...,
2981 help: str | None = ...,
2982 config: t.Any = ...,
2983 **kwargs: t.Any,
2984 ):
2985 ...
2987 @t.overload
2988 def __init__(
2989 self: Bool[bool | None, bool | int | None],
2990 default_value: bool | Sentinel | None = ...,
2991 allow_none: Literal[True] = ...,
2992 read_only: bool | None = ...,
2993 help: str | None = ...,
2994 config: t.Any = ...,
2995 **kwargs: t.Any,
2996 ):
2997 ...
2999 def __init__(self, **kwargs):
3000 ...
3002 def validate(self, obj, value):
3003 if isinstance(value, bool):
3004 return value
3005 elif isinstance(value, int):
3006 if value == 1:
3007 return True
3008 elif value == 0:
3009 return False
3010 self.error(obj, value)
3012 def from_string(self, s):
3013 if self.allow_none and s == "None":
3014 return None
3015 s = s.lower()
3016 if s in {"true", "1"}:
3017 return True
3018 elif s in {"false", "0"}:
3019 return False
3020 else:
3021 raise ValueError("%r is not 1, 0, true, or false")
3023 def subclass_init(self, cls):
3024 pass # fully opt out of instance_init
3026 def argcompleter(self, **kwargs):
3027 """Completion hints for argcomplete"""
3028 completions = ["true", "1", "false", "0"]
3029 if self.allow_none:
3030 completions.append("None")
3031 return completions
3034class CBool(Bool[G, S]):
3035 """A casting version of the boolean trait."""
3037 if t.TYPE_CHECKING:
3039 @t.overload
3040 def __init__(
3041 self: CBool[bool, t.Any],
3042 default_value: bool | Sentinel = ...,
3043 allow_none: Literal[False] = ...,
3044 read_only: bool | None = ...,
3045 help: str | None = ...,
3046 config: t.Any = ...,
3047 **kwargs: t.Any,
3048 ):
3049 ...
3051 @t.overload
3052 def __init__(
3053 self: CBool[bool | None, t.Any],
3054 default_value: bool | Sentinel | None = ...,
3055 allow_none: Literal[True] = ...,
3056 read_only: bool | None = ...,
3057 help: str | None = ...,
3058 config: t.Any = ...,
3059 **kwargs: t.Any,
3060 ):
3061 ...
3063 def __init__(self, **kwargs):
3064 ...
3066 def validate(self, obj, value):
3067 try:
3068 return bool(value)
3069 except Exception:
3070 self.error(obj, value)
3073class Enum(TraitType[G, S]):
3074 """An enum whose value must be in a given sequence."""
3076 def __init__(
3077 self: Enum[t.Any, t.Any], values: t.Any, default_value: t.Any = Undefined, **kwargs: t.Any
3078 ):
3079 self.values = values
3080 if kwargs.get("allow_none", False) and default_value is Undefined:
3081 default_value = None
3082 super().__init__(default_value, **kwargs)
3084 def validate(self, obj, value):
3085 if value in self.values:
3086 return value
3087 self.error(obj, value)
3089 def _choices_str(self, as_rst=False):
3090 """Returns a description of the trait choices (not none)."""
3091 choices = self.values
3092 if as_rst:
3093 choices = "|".join("``%r``" % x for x in choices)
3094 else:
3095 choices = repr(list(choices))
3096 return choices
3098 def _info(self, as_rst=False):
3099 """Returns a description of the trait."""
3100 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3101 return f"any of {self._choices_str(as_rst)}{none}"
3103 def info(self):
3104 return self._info(as_rst=False)
3106 def info_rst(self):
3107 return self._info(as_rst=True)
3109 def from_string(self, s):
3110 try:
3111 return self.validate(None, s)
3112 except TraitError:
3113 return _safe_literal_eval(s)
3115 def subclass_init(self, cls):
3116 pass # fully opt out of instance_init
3118 def argcompleter(self, **kwargs):
3119 """Completion hints for argcomplete"""
3120 return [str(v) for v in self.values]
3123class CaselessStrEnum(Enum[G, S]):
3124 """An enum of strings where the case should be ignored."""
3126 def __init__(
3127 self: CaselessStrEnum[t.Any, t.Any],
3128 values: t.Any,
3129 default_value: t.Any = Undefined,
3130 **kwargs: t.Any,
3131 ):
3132 super().__init__(values, default_value=default_value, **kwargs)
3134 def validate(self, obj, value):
3135 if not isinstance(value, str):
3136 self.error(obj, value)
3138 for v in self.values:
3139 if v.lower() == value.lower():
3140 return v
3141 self.error(obj, value)
3143 def _info(self, as_rst=False):
3144 """Returns a description of the trait."""
3145 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3146 return f"any of {self._choices_str(as_rst)} (case-insensitive){none}"
3148 def info(self):
3149 return self._info(as_rst=False)
3151 def info_rst(self):
3152 return self._info(as_rst=True)
3155class FuzzyEnum(Enum[G, S]):
3156 """An case-ignoring enum matching choices by unique prefixes/substrings."""
3158 case_sensitive = False
3159 #: If True, choices match anywhere in the string, otherwise match prefixes.
3160 substring_matching = False
3162 def __init__(
3163 self: FuzzyEnum[t.Any, t.Any],
3164 values: t.Any,
3165 default_value: t.Any = Undefined,
3166 case_sensitive: bool = False,
3167 substring_matching: bool = False,
3168 **kwargs: t.Any,
3169 ):
3170 self.case_sensitive = case_sensitive
3171 self.substring_matching = substring_matching
3172 super().__init__(values, default_value=default_value, **kwargs)
3174 def validate(self, obj, value):
3175 if not isinstance(value, str):
3176 self.error(obj, value)
3178 conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower()
3179 substring_matching = self.substring_matching
3180 match_func = (lambda v, c: v in c) if substring_matching else (lambda v, c: c.startswith(v))
3181 value = conv_func(value)
3182 choices = self.values
3183 matches = [match_func(value, conv_func(c)) for c in choices]
3184 if sum(matches) == 1:
3185 for v, m in zip(choices, matches):
3186 if m:
3187 return v
3189 self.error(obj, value)
3191 def _info(self, as_rst=False):
3192 """Returns a description of the trait."""
3193 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3194 case = "sensitive" if self.case_sensitive else "insensitive"
3195 substr = "substring" if self.substring_matching else "prefix"
3196 return f"any case-{case} {substr} of {self._choices_str(as_rst)}{none}"
3198 def info(self):
3199 return self._info(as_rst=False)
3201 def info_rst(self):
3202 return self._info(as_rst=True)
3205class Container(Instance[T]):
3206 """An instance of a container (list, set, etc.)
3208 To be subclassed by overriding klass.
3209 """
3211 klass: type[T] | None = None
3212 _cast_types: t.Any = ()
3213 _valid_defaults = SequenceTypes
3214 _trait = None
3215 _literal_from_string_pairs: t.Any = ("[]", "()")
3217 @t.overload
3218 def __init__(
3219 self: Container[T],
3220 kind: type[T],
3221 *,
3222 allow_none: Literal[False],
3223 read_only: bool | None = ...,
3224 help: str | None = ...,
3225 config: t.Any | None = ...,
3226 **kwargs: t.Any,
3227 ):
3228 ...
3230 @t.overload
3231 def __init__(
3232 self: Container[T | None],
3233 kind: type[T],
3234 *,
3235 allow_none: Literal[True],
3236 read_only: bool | None = ...,
3237 help: str | None = ...,
3238 config: t.Any | None = ...,
3239 **kwargs: t.Any,
3240 ):
3241 ...
3243 @t.overload
3244 def __init__(
3245 self: Container[T],
3246 kind: type[T],
3247 *,
3248 help: str = ...,
3249 read_only: bool = ...,
3250 config: t.Any = ...,
3251 trait: t.Any = ...,
3252 default_value: t.Any = ...,
3253 **kwargs: t.Any,
3254 ):
3255 ...
3257 def __init__(self, trait=None, default_value=Undefined, **kwargs):
3258 """Create a container trait type from a list, set, or tuple.
3260 The default value is created by doing ``List(default_value)``,
3261 which creates a copy of the ``default_value``.
3263 ``trait`` can be specified, which restricts the type of elements
3264 in the container to that TraitType.
3266 If only one arg is given and it is not a Trait, it is taken as
3267 ``default_value``:
3269 ``c = List([1, 2, 3])``
3271 Parameters
3272 ----------
3273 trait : TraitType [ optional ]
3274 the type for restricting the contents of the Container. If unspecified,
3275 types are not checked.
3276 default_value : SequenceType [ optional ]
3277 The default value for the Trait. Must be list/tuple/set, and
3278 will be cast to the container type.
3279 allow_none : bool [ default False ]
3280 Whether to allow the value to be None
3281 **kwargs : any
3282 further keys for extensions to the Trait (e.g. config)
3284 """
3286 # allow List([values]):
3287 if trait is not None and default_value is Undefined and not is_trait(trait):
3288 default_value = trait
3289 trait = None
3291 if default_value is None and not kwargs.get("allow_none", False):
3292 # improve backward-compatibility for possible subclasses
3293 # specifying default_value=None as default,
3294 # keeping 'unspecified' behavior (i.e. empty container)
3295 warn(
3296 f"Specifying {self.__class__.__name__}(default_value=None)"
3297 " for no default is deprecated in traitlets 5.0.5."
3298 " Use default_value=Undefined",
3299 DeprecationWarning,
3300 stacklevel=2,
3301 )
3302 default_value = Undefined
3304 if default_value is Undefined:
3305 args: t.Any = ()
3306 elif default_value is None:
3307 # default_value back on kwargs for super() to handle
3308 args = ()
3309 kwargs["default_value"] = None
3310 elif isinstance(default_value, self._valid_defaults):
3311 args = (default_value,)
3312 else:
3313 raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
3315 if is_trait(trait):
3316 if isinstance(trait, type):
3317 warn(
3318 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
3319 " Passing types is deprecated in traitlets 4.1.",
3320 DeprecationWarning,
3321 stacklevel=3,
3322 )
3323 self._trait = trait() if isinstance(trait, type) else trait
3324 elif trait is not None:
3325 raise TypeError("`trait` must be a Trait or None, got %s" % repr_type(trait))
3327 super().__init__(klass=self.klass, args=args, **kwargs)
3329 def validate(self, obj, value):
3330 if isinstance(value, self._cast_types):
3331 assert self.klass is not None
3332 value = self.klass(value) # type:ignore[call-arg]
3333 value = super().validate(obj, value)
3334 if value is None:
3335 return value
3337 value = self.validate_elements(obj, value)
3339 return value
3341 def validate_elements(self, obj, value):
3342 validated = []
3343 if self._trait is None or isinstance(self._trait, Any):
3344 return value
3345 for v in value:
3346 try:
3347 v = self._trait._validate(obj, v)
3348 except TraitError as error:
3349 self.error(obj, v, error)
3350 else:
3351 validated.append(v)
3352 assert self.klass is not None
3353 return self.klass(validated) # type:ignore[call-arg]
3355 def class_init(self, cls, name):
3356 if isinstance(self._trait, TraitType):
3357 self._trait.class_init(cls, None)
3358 super().class_init(cls, name)
3360 def subclass_init(self, cls):
3361 if isinstance(self._trait, TraitType):
3362 self._trait.subclass_init(cls)
3363 # explicitly not calling super().subclass_init(cls)
3364 # to opt out of instance_init
3366 def from_string(self, s):
3367 """Load value from a single string"""
3368 if not isinstance(s, str):
3369 raise TraitError(f"Expected string, got {s!r}")
3370 try:
3371 test = literal_eval(s)
3372 except Exception:
3373 test = None
3374 return self.validate(None, test)
3376 def from_string_list(self, s_list):
3377 """Return the value from a list of config strings
3379 This is where we parse CLI configuration
3380 """
3381 assert self.klass is not None
3382 if len(s_list) == 1:
3383 # check for deprecated --Class.trait="['a', 'b', 'c']"
3384 r = s_list[0]
3385 if r == "None" and self.allow_none:
3386 return None
3387 if len(r) >= 2 and any(
3388 r.startswith(start) and r.endswith(end)
3389 for start, end in self._literal_from_string_pairs
3390 ):
3391 if self.this_class:
3392 clsname = self.this_class.__name__ + "."
3393 else:
3394 clsname = ""
3395 assert self.name is not None
3396 warn(
3397 "--{0}={1} for containers is deprecated in traitlets 5.0. "
3398 "You can pass `--{0} item` ... multiple times to add items to a list.".format(
3399 clsname + self.name, r
3400 ),
3401 DeprecationWarning,
3402 stacklevel=2,
3403 )
3404 return self.klass(literal_eval(r)) # type:ignore[call-arg]
3405 sig = inspect.signature(self.item_from_string)
3406 if "index" in sig.parameters:
3407 item_from_string = self.item_from_string
3408 else:
3409 # backward-compat: allow item_from_string to ignore index arg
3410 def item_from_string(s, index=None):
3411 return self.item_from_string(s)
3413 return self.klass( # type:ignore[call-arg]
3414 [item_from_string(s, index=idx) for idx, s in enumerate(s_list)]
3415 )
3417 def item_from_string(self, s, index=None):
3418 """Cast a single item from a string
3420 Evaluated when parsing CLI configuration from a string
3421 """
3422 if self._trait:
3423 return self._trait.from_string(s)
3424 else:
3425 return s
3428class List(Container[t.List[t.Any]]):
3429 """An instance of a Python list."""
3431 klass = list
3432 _cast_types: t.Any = (tuple,)
3434 def __init__(
3435 self,
3436 trait=None,
3437 default_value=Undefined,
3438 minlen=0,
3439 maxlen=sys.maxsize,
3440 **kwargs,
3441 ):
3442 """Create a List trait type from a list, set, or tuple.
3444 The default value is created by doing ``list(default_value)``,
3445 which creates a copy of the ``default_value``.
3447 ``trait`` can be specified, which restricts the type of elements
3448 in the container to that TraitType.
3450 If only one arg is given and it is not a Trait, it is taken as
3451 ``default_value``:
3453 ``c = List([1, 2, 3])``
3455 Parameters
3456 ----------
3457 trait : TraitType [ optional ]
3458 the type for restricting the contents of the Container.
3459 If unspecified, types are not checked.
3460 default_value : SequenceType [ optional ]
3461 The default value for the Trait. Must be list/tuple/set, and
3462 will be cast to the container type.
3463 minlen : Int [ default 0 ]
3464 The minimum length of the input list
3465 maxlen : Int [ default sys.maxsize ]
3466 The maximum length of the input list
3467 """
3468 self._minlen = minlen
3469 self._maxlen = maxlen
3470 super().__init__(trait=trait, default_value=default_value, **kwargs)
3472 def length_error(self, obj, value):
3473 e = (
3474 "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified."
3475 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
3476 )
3477 raise TraitError(e)
3479 def validate_elements(self, obj, value):
3480 length = len(value)
3481 if length < self._minlen or length > self._maxlen:
3482 self.length_error(obj, value)
3484 return super().validate_elements(obj, value)
3486 def set(self, obj, value):
3487 if isinstance(value, str):
3488 return super().set(obj, [value])
3489 else:
3490 return super().set(obj, value)
3493class Set(List):
3494 """An instance of a Python set."""
3496 klass = set # type:ignore[assignment]
3497 _cast_types = (tuple, list)
3499 _literal_from_string_pairs = ("[]", "()", "{}")
3501 # Redefine __init__ just to make the docstring more accurate.
3502 def __init__(
3503 self,
3504 trait=None,
3505 default_value=Undefined,
3506 minlen=0,
3507 maxlen=sys.maxsize,
3508 **kwargs,
3509 ):
3510 """Create a Set trait type from a list, set, or tuple.
3512 The default value is created by doing ``set(default_value)``,
3513 which creates a copy of the ``default_value``.
3515 ``trait`` can be specified, which restricts the type of elements
3516 in the container to that TraitType.
3518 If only one arg is given and it is not a Trait, it is taken as
3519 ``default_value``:
3521 ``c = Set({1, 2, 3})``
3523 Parameters
3524 ----------
3525 trait : TraitType [ optional ]
3526 the type for restricting the contents of the Container.
3527 If unspecified, types are not checked.
3528 default_value : SequenceType [ optional ]
3529 The default value for the Trait. Must be list/tuple/set, and
3530 will be cast to the container type.
3531 minlen : Int [ default 0 ]
3532 The minimum length of the input list
3533 maxlen : Int [ default sys.maxsize ]
3534 The maximum length of the input list
3535 """
3536 super().__init__(trait, default_value, minlen, maxlen, **kwargs)
3538 def default_value_repr(self):
3539 # Ensure default value is sorted for a reproducible build
3540 list_repr = repr(sorted(self.make_dynamic_default()))
3541 if list_repr == "[]":
3542 return "set()"
3543 return "{" + list_repr[1:-1] + "}"
3546class Tuple(Container[t.Tuple[t.Any, ...]]):
3547 """An instance of a Python tuple."""
3549 klass = tuple
3550 _cast_types = (list,)
3552 def __init__(self, *traits, **kwargs):
3553 """Create a tuple from a list, set, or tuple.
3555 Create a fixed-type tuple with Traits:
3557 ``t = Tuple(Int(), Str(), CStr())``
3559 would be length 3, with Int,Str,CStr for each element.
3561 If only one arg is given and it is not a Trait, it is taken as
3562 default_value:
3564 ``t = Tuple((1, 2, 3))``
3566 Otherwise, ``default_value`` *must* be specified by keyword.
3568 Parameters
3569 ----------
3570 *traits : TraitTypes [ optional ]
3571 the types for restricting the contents of the Tuple. If unspecified,
3572 types are not checked. If specified, then each positional argument
3573 corresponds to an element of the tuple. Tuples defined with traits
3574 are of fixed length.
3575 default_value : SequenceType [ optional ]
3576 The default value for the Tuple. Must be list/tuple/set, and
3577 will be cast to a tuple. If ``traits`` are specified,
3578 ``default_value`` must conform to the shape and type they specify.
3579 **kwargs
3580 Other kwargs passed to `Container`
3581 """
3582 default_value = kwargs.pop("default_value", Undefined)
3583 # allow Tuple((values,)):
3584 if len(traits) == 1 and default_value is Undefined and not is_trait(traits[0]):
3585 default_value = traits[0]
3586 traits = ()
3588 if default_value is None and not kwargs.get("allow_none", False):
3589 # improve backward-compatibility for possible subclasses
3590 # specifying default_value=None as default,
3591 # keeping 'unspecified' behavior (i.e. empty container)
3592 warn(
3593 f"Specifying {self.__class__.__name__}(default_value=None)"
3594 " for no default is deprecated in traitlets 5.0.5."
3595 " Use default_value=Undefined",
3596 DeprecationWarning,
3597 stacklevel=2,
3598 )
3599 default_value = Undefined
3601 if default_value is Undefined:
3602 args: t.Any = ()
3603 elif default_value is None:
3604 # default_value back on kwargs for super() to handle
3605 args = ()
3606 kwargs["default_value"] = None
3607 elif isinstance(default_value, self._valid_defaults):
3608 args = (default_value,)
3609 else:
3610 raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
3612 self._traits = []
3613 for trait in traits:
3614 if isinstance(trait, type):
3615 warn(
3616 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3617 " Passing types is deprecated in traitlets 4.1.",
3618 DeprecationWarning,
3619 stacklevel=2,
3620 )
3621 trait = trait()
3622 self._traits.append(trait)
3624 if self._traits and (default_value is None or default_value is Undefined):
3625 # don't allow default to be an empty container if length is specified
3626 args = None
3627 super(Container, self).__init__(klass=self.klass, args=args, **kwargs)
3629 def item_from_string(self, s, index):
3630 """Cast a single item from a string
3632 Evaluated when parsing CLI configuration from a string
3633 """
3634 if not self._traits or index >= len(self._traits):
3635 # return s instead of raising index error
3636 # length errors will be raised later on validation
3637 return s
3638 return self._traits[index].from_string(s)
3640 def validate_elements(self, obj, value):
3641 if not self._traits:
3642 # nothing to validate
3643 return value
3644 if len(value) != len(self._traits):
3645 e = (
3646 "The '%s' trait of %s instance requires %i elements, but a value of %s was specified."
3647 % (self.name, class_of(obj), len(self._traits), repr_type(value))
3648 )
3649 raise TraitError(e)
3651 validated = []
3652 for trait, v in zip(self._traits, value):
3653 try:
3654 v = trait._validate(obj, v)
3655 except TraitError as error:
3656 self.error(obj, v, error)
3657 else:
3658 validated.append(v)
3659 return tuple(validated)
3661 def class_init(self, cls, name):
3662 for trait in self._traits:
3663 if isinstance(trait, TraitType):
3664 trait.class_init(cls, None)
3665 super(Container, self).class_init(cls, name)
3667 def subclass_init(self, cls):
3668 for trait in self._traits:
3669 if isinstance(trait, TraitType):
3670 trait.subclass_init(cls)
3671 # explicitly not calling super().subclass_init(cls)
3672 # to opt out of instance_init
3675class Dict(Instance[t.Dict[t.Any, t.Any]]):
3676 """An instance of a Python dict.
3678 One or more traits can be passed to the constructor
3679 to validate the keys and/or values of the dict.
3680 If you need more detailed validation,
3681 you may use a custom validator method.
3683 .. versionchanged:: 5.0
3684 Added key_trait for validating dict keys.
3686 .. versionchanged:: 5.0
3687 Deprecated ambiguous ``trait``, ``traits`` args in favor of ``value_trait``, ``per_key_traits``.
3688 """
3690 _value_trait = None
3691 _key_trait = None
3693 def __init__(
3694 self,
3695 value_trait=None,
3696 per_key_traits=None,
3697 key_trait=None,
3698 default_value=Undefined,
3699 **kwargs,
3700 ):
3701 """Create a dict trait type from a Python dict.
3703 The default value is created by doing ``dict(default_value)``,
3704 which creates a copy of the ``default_value``.
3706 Parameters
3707 ----------
3708 value_trait : TraitType [ optional ]
3709 The specified trait type to check and use to restrict the values of
3710 the dict. If unspecified, values are not checked.
3711 per_key_traits : Dictionary of {keys:trait types} [ optional, keyword-only ]
3712 A Python dictionary containing the types that are valid for
3713 restricting the values of the dict on a per-key basis.
3714 Each value in this dict should be a Trait for validating
3715 key_trait : TraitType [ optional, keyword-only ]
3716 The type for restricting the keys of the dict. If
3717 unspecified, the types of the keys are not checked.
3718 default_value : SequenceType [ optional, keyword-only ]
3719 The default value for the Dict. Must be dict, tuple, or None, and
3720 will be cast to a dict if not None. If any key or value traits are specified,
3721 the `default_value` must conform to the constraints.
3723 Examples
3724 --------
3725 a dict whose values must be text
3726 >>> d = Dict(Unicode())
3728 d2['n'] must be an integer
3729 d2['s'] must be text
3730 >>> d2 = Dict(per_key_traits={"n": Integer(), "s": Unicode()})
3732 d3's keys must be text
3733 d3's values must be integers
3734 >>> d3 = Dict(value_trait=Integer(), key_trait=Unicode())
3736 """
3738 # handle deprecated keywords
3739 trait = kwargs.pop("trait", None)
3740 if trait is not None:
3741 if value_trait is not None:
3742 raise TypeError(
3743 "Found a value for both `value_trait` and its deprecated alias `trait`."
3744 )
3745 value_trait = trait
3746 warn(
3747 "Keyword `trait` is deprecated in traitlets 5.0, use `value_trait` instead",
3748 DeprecationWarning,
3749 stacklevel=2,
3750 )
3751 traits = kwargs.pop("traits", None)
3752 if traits is not None:
3753 if per_key_traits is not None:
3754 raise TypeError(
3755 "Found a value for both `per_key_traits` and its deprecated alias `traits`."
3756 )
3757 per_key_traits = traits
3758 warn(
3759 "Keyword `traits` is deprecated in traitlets 5.0, use `per_key_traits` instead",
3760 DeprecationWarning,
3761 stacklevel=2,
3762 )
3764 # Handling positional arguments
3765 if default_value is Undefined and value_trait is not None:
3766 if not is_trait(value_trait):
3767 default_value = value_trait
3768 value_trait = None
3770 if key_trait is None and per_key_traits is not None:
3771 if is_trait(per_key_traits):
3772 key_trait = per_key_traits
3773 per_key_traits = None
3775 # Handling default value
3776 if default_value is Undefined:
3777 default_value = {}
3778 if default_value is None:
3779 args: t.Any = None
3780 elif isinstance(default_value, dict):
3781 args = (default_value,)
3782 elif isinstance(default_value, SequenceTypes):
3783 args = (default_value,)
3784 else:
3785 raise TypeError("default value of Dict was %s" % default_value)
3787 # Case where a type of TraitType is provided rather than an instance
3788 if is_trait(value_trait):
3789 if isinstance(value_trait, type):
3790 warn(
3791 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3792 " Passing types is deprecated in traitlets 4.1.",
3793 DeprecationWarning,
3794 stacklevel=2,
3795 )
3796 value_trait = value_trait()
3797 self._value_trait = value_trait
3798 elif value_trait is not None:
3799 raise TypeError(
3800 "`value_trait` must be a Trait or None, got %s" % repr_type(value_trait)
3801 )
3803 if is_trait(key_trait):
3804 if isinstance(key_trait, type):
3805 warn(
3806 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3807 " Passing types is deprecated in traitlets 4.1.",
3808 DeprecationWarning,
3809 stacklevel=2,
3810 )
3811 key_trait = key_trait()
3812 self._key_trait = key_trait
3813 elif key_trait is not None:
3814 raise TypeError("`key_trait` must be a Trait or None, got %s" % repr_type(key_trait))
3816 self._per_key_traits = per_key_traits
3818 super().__init__(klass=dict, args=args, **kwargs)
3820 def element_error(self, obj, element, validator, side="Values"):
3821 e = (
3822 side
3823 + f" of the '{self.name}' trait of {class_of(obj)} instance must be {validator.info()}, but a value of {repr_type(element)} was specified."
3824 )
3825 raise TraitError(e)
3827 def validate(self, obj, value):
3828 value = super().validate(obj, value)
3829 if value is None:
3830 return value
3831 value = self.validate_elements(obj, value)
3832 return value
3834 def validate_elements(self, obj, value):
3835 per_key_override = self._per_key_traits or {}
3836 key_trait = self._key_trait
3837 value_trait = self._value_trait
3838 if not (key_trait or value_trait or per_key_override):
3839 return value
3841 validated = {}
3842 for key in value:
3843 v = value[key]
3844 if key_trait:
3845 try:
3846 key = key_trait._validate(obj, key)
3847 except TraitError:
3848 self.element_error(obj, key, key_trait, "Keys")
3849 active_value_trait = per_key_override.get(key, value_trait)
3850 if active_value_trait:
3851 try:
3852 v = active_value_trait._validate(obj, v)
3853 except TraitError:
3854 self.element_error(obj, v, active_value_trait, "Values")
3855 validated[key] = v
3857 return self.klass(validated) # type:ignore
3859 def class_init(self, cls, name):
3860 if isinstance(self._value_trait, TraitType):
3861 self._value_trait.class_init(cls, None)
3862 if isinstance(self._key_trait, TraitType):
3863 self._key_trait.class_init(cls, None)
3864 if self._per_key_traits is not None:
3865 for trait in self._per_key_traits.values():
3866 trait.class_init(cls, None)
3867 super().class_init(cls, name)
3869 def subclass_init(self, cls):
3870 if isinstance(self._value_trait, TraitType):
3871 self._value_trait.subclass_init(cls)
3872 if isinstance(self._key_trait, TraitType):
3873 self._key_trait.subclass_init(cls)
3874 if self._per_key_traits is not None:
3875 for trait in self._per_key_traits.values():
3876 trait.subclass_init(cls)
3877 # explicitly not calling super().subclass_init(cls)
3878 # to opt out of instance_init
3880 def from_string(self, s):
3881 """Load value from a single string"""
3882 if not isinstance(s, str):
3883 raise TypeError(f"from_string expects a string, got {s!r} of type {type(s)}")
3884 try:
3885 return self.from_string_list([s])
3886 except Exception:
3887 test = _safe_literal_eval(s)
3888 if isinstance(test, dict):
3889 return test
3890 raise
3892 def from_string_list(self, s_list):
3893 """Return a dict from a list of config strings.
3895 This is where we parse CLI configuration.
3897 Each item should have the form ``"key=value"``.
3899 item parsing is done in :meth:`.item_from_string`.
3900 """
3901 if len(s_list) == 1 and s_list[0] == "None" and self.allow_none:
3902 return None
3903 if len(s_list) == 1 and s_list[0].startswith("{") and s_list[0].endswith("}"):
3904 warn(
3905 f"--{self.name}={s_list[0]} for dict-traits is deprecated in traitlets 5.0. "
3906 f"You can pass --{self.name} <key=value> ... multiple times to add items to a dict.",
3907 DeprecationWarning,
3908 stacklevel=2,
3909 )
3911 return literal_eval(s_list[0])
3913 combined = {}
3914 for d in [self.item_from_string(s) for s in s_list]:
3915 combined.update(d)
3916 return combined
3918 def item_from_string(self, s):
3919 """Cast a single-key dict from a string.
3921 Evaluated when parsing CLI configuration from a string.
3923 Dicts expect strings of the form key=value.
3925 Returns a one-key dictionary,
3926 which will be merged in :meth:`.from_string_list`.
3927 """
3929 if "=" not in s:
3930 raise TraitError(
3931 f"'{self.__class__.__name__}' options must have the form 'key=value', got {s!r}"
3932 )
3933 key, value = s.split("=", 1)
3935 # cast key with key trait, if defined
3936 if self._key_trait:
3937 key = self._key_trait.from_string(key)
3939 # cast value with value trait, if defined (per-key or global)
3940 value_trait = (self._per_key_traits or {}).get(key, self._value_trait)
3941 if value_trait:
3942 value = value_trait.from_string(value)
3943 return {key: value}
3946class TCPAddress(TraitType[G, S]):
3947 """A trait for an (ip, port) tuple.
3949 This allows for both IPv4 IP addresses as well as hostnames.
3950 """
3952 default_value = ("127.0.0.1", 0)
3953 info_text = "an (ip, port) tuple"
3955 if t.TYPE_CHECKING:
3957 @t.overload
3958 def __init__(
3959 self: TCPAddress[tuple[str, int], tuple[str, int]],
3960 default_value: bool | Sentinel = ...,
3961 allow_none: Literal[False] = ...,
3962 read_only: bool | None = ...,
3963 help: str | None = ...,
3964 config: t.Any = ...,
3965 **kwargs: t.Any,
3966 ):
3967 ...
3969 @t.overload
3970 def __init__(
3971 self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None],
3972 default_value: bool | None | Sentinel = ...,
3973 allow_none: Literal[True] = ...,
3974 read_only: bool | None = ...,
3975 help: str | None = ...,
3976 config: t.Any = ...,
3977 **kwargs: t.Any,
3978 ):
3979 ...
3981 def __init__(
3982 self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None]
3983 | TCPAddress[tuple[str, int], tuple[str, int]],
3984 default_value: bool | None | Sentinel = Undefined,
3985 allow_none: Literal[True, False] = False,
3986 read_only: bool | None = None,
3987 help: str | None = None,
3988 config: t.Any = None,
3989 **kwargs: t.Any,
3990 ):
3991 ...
3993 def validate(self, obj, value):
3994 if isinstance(value, tuple):
3995 if len(value) == 2:
3996 if isinstance(value[0], str) and isinstance(value[1], int):
3997 port = value[1]
3998 if port >= 0 and port <= 65535:
3999 return value
4000 self.error(obj, value)
4002 def from_string(self, s):
4003 if self.allow_none and s == "None":
4004 return None
4005 if ":" not in s:
4006 raise ValueError("Require `ip:port`, got %r" % s)
4007 ip, port = s.split(":", 1)
4008 port = int(port)
4009 return (ip, port)
4012class CRegExp(TraitType["re.Pattern[t.Any]", t.Union["re.Pattern[t.Any]", str]]):
4013 """A casting compiled regular expression trait.
4015 Accepts both strings and compiled regular expressions. The resulting
4016 attribute will be a compiled regular expression."""
4018 info_text = "a regular expression"
4020 def validate(self, obj, value):
4021 try:
4022 return re.compile(value)
4023 except Exception:
4024 self.error(obj, value)
4027class UseEnum(TraitType[t.Any, t.Any]):
4028 """Use a Enum class as model for the data type description.
4029 Note that if no default-value is provided, the first enum-value is used
4030 as default-value.
4032 .. sourcecode:: python
4034 # -- SINCE: Python 3.4 (or install backport: pip install enum34)
4035 import enum
4036 from traitlets import HasTraits, UseEnum
4038 class Color(enum.Enum):
4039 red = 1 # -- IMPLICIT: default_value
4040 blue = 2
4041 green = 3
4043 class MyEntity(HasTraits):
4044 color = UseEnum(Color, default_value=Color.blue)
4046 entity = MyEntity(color=Color.red)
4047 entity.color = Color.green # USE: Enum-value (preferred)
4048 entity.color = "green" # USE: name (as string)
4049 entity.color = "Color.green" # USE: scoped-name (as string)
4050 entity.color = 3 # USE: number (as int)
4051 assert entity.color is Color.green
4052 """
4054 default_value: enum.Enum | None = None
4055 info_text = "Trait type adapter to a Enum class"
4057 def __init__(self, enum_class, default_value=None, **kwargs):
4058 assert issubclass(enum_class, enum.Enum), "REQUIRE: enum.Enum, but was: %r" % enum_class
4059 allow_none = kwargs.get("allow_none", False)
4060 if default_value is None and not allow_none:
4061 default_value = next(iter(enum_class.__members__.values()))
4062 super().__init__(default_value=default_value, **kwargs)
4063 self.enum_class = enum_class
4064 self.name_prefix = enum_class.__name__ + "."
4066 def select_by_number(self, value, default=Undefined):
4067 """Selects enum-value by using its number-constant."""
4068 assert isinstance(value, int)
4069 enum_members = self.enum_class.__members__
4070 for enum_item in enum_members.values():
4071 if enum_item.value == value:
4072 return enum_item
4073 # -- NOT FOUND:
4074 return default
4076 def select_by_name(self, value, default=Undefined):
4077 """Selects enum-value by using its name or scoped-name."""
4078 assert isinstance(value, str)
4079 if value.startswith(self.name_prefix):
4080 # -- SUPPORT SCOPED-NAMES, like: "Color.red" => "red"
4081 value = value.replace(self.name_prefix, "", 1)
4082 return self.enum_class.__members__.get(value, default)
4084 def validate(self, obj, value):
4085 if isinstance(value, self.enum_class):
4086 return value
4087 elif isinstance(value, int):
4088 # -- CONVERT: number => enum_value (item)
4089 value2 = self.select_by_number(value)
4090 if value2 is not Undefined:
4091 return value2
4092 elif isinstance(value, str):
4093 # -- CONVERT: name or scoped_name (as string) => enum_value (item)
4094 value2 = self.select_by_name(value)
4095 if value2 is not Undefined:
4096 return value2
4097 elif value is None:
4098 if self.allow_none:
4099 return None
4100 else:
4101 return self.default_value
4102 self.error(obj, value)
4104 def _choices_str(self, as_rst=False):
4105 """Returns a description of the trait choices (not none)."""
4106 choices = self.enum_class.__members__.keys()
4107 if as_rst:
4108 return "|".join("``%r``" % x for x in choices)
4109 else:
4110 return repr(list(choices)) # Listify because py3.4- prints odict-class
4112 def _info(self, as_rst=False):
4113 """Returns a description of the trait."""
4114 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
4115 return f"any of {self._choices_str(as_rst)}{none}"
4117 def info(self):
4118 return self._info(as_rst=False)
4120 def info_rst(self):
4121 return self._info(as_rst=True)
4124class Callable(TraitType[t.Callable[..., t.Any], t.Callable[..., t.Any]]):
4125 """A trait which is callable.
4127 Notes
4128 -----
4129 Classes are callable, as are instances
4130 with a __call__() method."""
4132 info_text = "a callable"
4134 def validate(self, obj, value):
4135 if callable(value):
4136 return value
4137 else:
4138 self.error(obj, value)