Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/traitlets/traitlets.py: 13%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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.
42from __future__ import annotations
44import contextlib
45import enum
46import inspect
47import numbers
48import os
49import re
50import sys
51import types
52import typing as t
53from ast import literal_eval
55from .utils.bunch import Bunch
56from .utils.descriptions import add_article, class_of, describe, repr_type
57from .utils.getargspec import getargspec
58from .utils.importstring import import_item
59from .utils.sentinel import Sentinel
60from .utils.warnings import deprecated_method, should_warn, warn
62SequenceTypes = (list, tuple, set, frozenset)
64# backward compatibility, use to differ between Python 2 and 3.
65ClassTypes = (type,)
67if t.TYPE_CHECKING:
68 from typing_extensions import TypeVar
69else:
70 from typing import TypeVar
72# exports:
74__all__ = [
75 "All",
76 "Any",
77 "BaseDescriptor",
78 "Bool",
79 "Bytes",
80 "CBool",
81 "CBytes",
82 "CComplex",
83 "CFloat",
84 "CInt",
85 "CLong",
86 "CRegExp",
87 "CUnicode",
88 "Callable",
89 "CaselessStrEnum",
90 "ClassBasedTraitType",
91 "Complex",
92 "Container",
93 "DefaultHandler",
94 "Dict",
95 "DottedObjectName",
96 "Enum",
97 "EventHandler",
98 "Float",
99 "ForwardDeclaredInstance",
100 "ForwardDeclaredMixin",
101 "ForwardDeclaredType",
102 "FuzzyEnum",
103 "HasDescriptors",
104 "HasTraits",
105 "Instance",
106 "Int",
107 "Integer",
108 "List",
109 "Long",
110 "MetaHasDescriptors",
111 "MetaHasTraits",
112 "ObjectName",
113 "ObserveHandler",
114 "Set",
115 "TCPAddress",
116 "This",
117 "TraitError",
118 "TraitType",
119 "Tuple",
120 "Type",
121 "Unicode",
122 "Undefined",
123 "Union",
124 "UseEnum",
125 "ValidateHandler",
126 "default",
127 "directional_link",
128 "dlink",
129 "link",
130 "observe",
131 "observe_compat",
132 "parse_notifier_name",
133 "validate",
134]
136# any TraitType subclass (that doesn't start with _) will be added automatically
138# -----------------------------------------------------------------------------
139# Basic classes
140# -----------------------------------------------------------------------------
143Undefined = Sentinel(
144 "Undefined",
145 "traitlets",
146 """
147Used in Traitlets to specify that no defaults are set in kwargs
148""",
149)
151All = Sentinel(
152 "All",
153 "traitlets",
154 """
155Used in Traitlets to listen to all types of notification or to notifications
156from all trait attributes.
157""",
158)
160# Deprecated alias
161NoDefaultSpecified = Undefined
164class TraitError(Exception):
165 pass
168# -----------------------------------------------------------------------------
169# Utilities
170# -----------------------------------------------------------------------------
173def isidentifier(s: str) -> bool:
174 warn(
175 "traitlets.traitlets.isidentifier(s) is deprecated since traitlets 5.14.4 Use `s.isidentifier()`.",
176 DeprecationWarning,
177 stacklevel=2,
178 )
179 return s.isidentifier()
182def _safe_literal_eval(s: str) -> t.Any:
183 """Safely evaluate an expression
185 Returns original string if eval fails.
187 Use only where types are ambiguous.
188 """
189 try:
190 return literal_eval(s)
191 except (NameError, SyntaxError, ValueError):
192 return s
195def is_trait(t: t.Any) -> bool:
196 """Returns whether the given value is an instance or subclass of TraitType."""
197 return isinstance(t, TraitType) or (isinstance(t, type) and issubclass(t, TraitType))
200def parse_notifier_name(names: Sentinel | str | t.Iterable[Sentinel | str]) -> t.Iterable[t.Any]:
201 """Convert the name argument to a list of names.
203 Examples
204 --------
205 >>> parse_notifier_name([])
206 [traitlets.All]
207 >>> parse_notifier_name("a")
208 ['a']
209 >>> parse_notifier_name(["a", "b"])
210 ['a', 'b']
211 >>> parse_notifier_name(All)
212 [traitlets.All]
213 """
214 if names is All or isinstance(names, str):
215 return [names]
216 elif isinstance(names, Sentinel):
217 raise TypeError("`names` must be either `All`, a str, or a list of strs.")
218 else:
219 if not names or All in names:
220 return [All]
221 for n in names:
222 if not isinstance(n, str):
223 raise TypeError(f"names must be strings, not {type(n).__name__}({n!r})")
224 return names
227class _SimpleTest:
228 def __init__(self, value: t.Any) -> None:
229 self.value = value
231 def __call__(self, test: t.Any) -> bool:
232 return bool(test == self.value)
234 def __repr__(self) -> str:
235 return "<SimpleTest(%r)" % self.value
237 def __str__(self) -> str:
238 return self.__repr__()
241def getmembers(object: t.Any, predicate: t.Any = None) -> list[tuple[str, t.Any]]:
242 """A safe version of inspect.getmembers that handles missing attributes.
244 This is useful when there are descriptor based attributes that for
245 some reason raise AttributeError even though they exist. This happens
246 in zope.interface with the __provides__ attribute.
247 """
248 results = []
249 for key in dir(object):
250 try:
251 value = getattr(object, key)
252 except AttributeError:
253 pass
254 else:
255 if not predicate or predicate(value):
256 results.append((key, value))
257 results.sort()
258 return results
261def _validate_link(*tuples: t.Any) -> None:
262 """Validate arguments for traitlet link functions"""
263 for tup in tuples:
264 if not len(tup) == 2:
265 raise TypeError(
266 "Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t
267 )
268 obj, trait_name = tup
269 if not isinstance(obj, HasTraits):
270 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
271 if trait_name not in obj.traits():
272 raise TypeError(f"{obj!r} has no trait {trait_name!r}")
275class link:
276 """Link traits from different objects together so they remain in sync.
278 Parameters
279 ----------
280 source : (object / attribute name) pair
281 target : (object / attribute name) pair
282 transform: iterable with two callables (optional)
283 Data transformation between source and target and target and source.
285 Examples
286 --------
287 >>> class X(HasTraits):
288 ... value = Int()
290 >>> src = X(value=1)
291 >>> tgt = X(value=42)
292 >>> c = link((src, "value"), (tgt, "value"))
294 Setting source updates target objects:
295 >>> src.value = 5
296 >>> tgt.value
297 5
298 """
300 updating = False
302 def __init__(
303 self, source: t.Any, target: t.Any, transform: t.Iterable[FuncT] | None = None
304 ) -> None:
305 _validate_link(source, target)
306 self.source, self.target = source, target
307 if transform:
308 self._transform, self._transform_inv = transform # type:ignore[method-assign]
309 self.link()
311 def _transform(self, x: T) -> T:
312 """default transform: no-op"""
313 return x
315 _transform_inv = _transform
317 def link(self) -> None:
318 try:
319 setattr(
320 self.target[0],
321 self.target[1],
322 self._transform(getattr(self.source[0], self.source[1])),
323 )
325 finally:
326 self.source[0].observe(self._update_target, names=self.source[1])
327 self.target[0].observe(self._update_source, names=self.target[1])
329 @contextlib.contextmanager
330 def _busy_updating(self) -> t.Any:
331 self.updating = True
332 try:
333 yield
334 finally:
335 self.updating = False
337 def _update_target(self, change: t.Any) -> None:
338 if self.updating:
339 return
340 with self._busy_updating():
341 setattr(self.target[0], self.target[1], self._transform(change.new))
342 if getattr(self.source[0], self.source[1]) != change.new:
343 raise TraitError(
344 f"Broken link {self}: the source value changed while updating " "the target."
345 )
347 def _update_source(self, change: t.Any) -> None:
348 if self.updating:
349 return
350 with self._busy_updating():
351 setattr(self.source[0], self.source[1], self._transform_inv(change.new))
352 if getattr(self.target[0], self.target[1]) != change.new:
353 raise TraitError(
354 f"Broken link {self}: the target value changed while updating " "the source."
355 )
357 def unlink(self) -> None:
358 self.source[0].unobserve(self._update_target, names=self.source[1])
359 self.target[0].unobserve(self._update_source, names=self.target[1])
362class directional_link:
363 """Link the trait of a source object with traits of target objects.
365 Parameters
366 ----------
367 source : (object, attribute name) pair
368 target : (object, attribute name) pair
369 transform: callable (optional)
370 Data transformation between source and target.
372 Examples
373 --------
374 >>> class X(HasTraits):
375 ... value = Int()
377 >>> src = X(value=1)
378 >>> tgt = X(value=42)
379 >>> c = directional_link((src, "value"), (tgt, "value"))
381 Setting source updates target objects:
382 >>> src.value = 5
383 >>> tgt.value
384 5
386 Setting target does not update source object:
387 >>> tgt.value = 6
388 >>> src.value
389 5
391 """
393 updating = False
395 def __init__(self, source: t.Any, target: t.Any, transform: t.Any = None) -> None:
396 self._transform = transform if transform else lambda x: x
397 _validate_link(source, target)
398 self.source, self.target = source, target
399 self.link()
401 def link(self) -> None:
402 try:
403 setattr(
404 self.target[0],
405 self.target[1],
406 self._transform(getattr(self.source[0], self.source[1])),
407 )
408 finally:
409 self.source[0].observe(self._update, names=self.source[1])
411 @contextlib.contextmanager
412 def _busy_updating(self) -> t.Any:
413 self.updating = True
414 try:
415 yield
416 finally:
417 self.updating = False
419 def _update(self, change: t.Any) -> None:
420 if self.updating:
421 return
422 with self._busy_updating():
423 setattr(self.target[0], self.target[1], self._transform(change.new))
425 def unlink(self) -> None:
426 self.source[0].unobserve(self._update, names=self.source[1])
429dlink = directional_link
432# -----------------------------------------------------------------------------
433# Base Descriptor Class
434# -----------------------------------------------------------------------------
437class BaseDescriptor:
438 """Base descriptor class
440 Notes
441 -----
442 This implements Python's descriptor protocol.
444 This class is the base class for all such descriptors. The
445 only magic we use is a custom metaclass for the main :class:`HasTraits`
446 class that does the following:
448 1. Sets the :attr:`name` attribute of every :class:`BaseDescriptor`
449 instance in the class dict to the name of the attribute.
450 2. Sets the :attr:`this_class` attribute of every :class:`BaseDescriptor`
451 instance in the class dict to the *class* that declared the trait.
452 This is used by the :class:`This` trait to allow subclasses to
453 accept superclasses for :class:`This` values.
454 """
456 name: str | None = None
457 this_class: type[HasTraits] | None = None
459 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
460 """Part of the initialization which may depend on the underlying
461 HasDescriptors class.
463 It is typically overloaded for specific types.
465 This method is called by :meth:`MetaHasDescriptors.__init__`
466 passing the class (`cls`) and `name` under which the descriptor
467 has been assigned.
468 """
469 self.this_class = cls
470 self.name = name
472 def subclass_init(self, cls: type[HasTraits]) -> None:
473 # Instead of HasDescriptors.setup_instance calling
474 # every instance_init, we opt in by default.
475 # This gives descriptors a change to opt out for
476 # performance reasons.
477 # Because most traits do not need instance_init,
478 # and it will otherwise be called for every HasTrait instance
479 # being created, this otherwise gives a significant performance
480 # pentalty. Most TypeTraits in traitlets opt out.
481 cls._instance_inits.append(self.instance_init)
483 def instance_init(self, obj: t.Any) -> None:
484 """Part of the initialization which may depend on the underlying
485 HasDescriptors instance.
487 It is typically overloaded for specific types.
489 This method is called by :meth:`HasTraits.__new__` and in the
490 :meth:`BaseDescriptor.instance_init` method of descriptors holding
491 other descriptors.
492 """
495G = TypeVar("G")
496S = TypeVar("S")
497T = TypeVar("T")
500# Self from typing extension doesn't work well with mypy https://github.com/python/mypy/pull/14041
501# see https://peps.python.org/pep-0673/#use-in-generic-classes
502# Self = t.TypeVar("Self", bound="TraitType[Any, Any]")
503if t.TYPE_CHECKING:
504 from typing_extensions import Literal, Self
506 K = TypeVar("K", default=str)
507 V = TypeVar("V", default=t.Any)
508else:
509 # This is required to avoid warnings about unresolved references when generating
510 # the documentation of downstream projects.
511 K = TypeVar("K")
512 V = TypeVar("V")
515# We use a type for the getter (G) and setter (G) because we allow
516# for traits to cast (for instance CInt will use G=int, S=t.Any)
517class TraitType(BaseDescriptor, t.Generic[G, S]):
518 """A base class for all trait types."""
520 metadata: dict[str, t.Any] = {}
521 allow_none: bool = False
522 read_only: bool = False
523 info_text: str = "any value"
524 default_value: t.Any = Undefined
526 def __init__(
527 self: TraitType[G, S],
528 default_value: t.Any = Undefined,
529 allow_none: bool = False,
530 read_only: bool | None = None,
531 help: str | None = None,
532 config: t.Any = None,
533 **kwargs: t.Any,
534 ) -> None:
535 """Declare a traitlet.
537 If *allow_none* is True, None is a valid value in addition to any
538 values that are normally valid. The default is up to the subclass.
539 For most trait types, the default value for ``allow_none`` is False.
541 If *read_only* is True, attempts to directly modify a trait attribute raises a TraitError.
543 If *help* is a string, it documents the attribute's purpose.
545 Extra metadata can be associated with the traitlet using the .tag() convenience method
546 or by using the traitlet instance's .metadata dictionary.
547 """
548 if default_value is not Undefined:
549 self.default_value = default_value
550 if allow_none:
551 self.allow_none = allow_none
552 if read_only is not None:
553 self.read_only = read_only
554 self.help = help if help is not None else ""
555 if self.help:
556 # define __doc__ so that inspectors like autodoc find traits
557 self.__doc__ = self.help
559 if len(kwargs) > 0:
560 stacklevel = 1
561 f = inspect.currentframe()
562 # count supers to determine stacklevel for warning
563 assert f is not None
564 while f.f_code.co_name == "__init__":
565 stacklevel += 1
566 f = f.f_back
567 assert f is not None
568 mod = f.f_globals.get("__name__") or ""
569 pkg = mod.split(".", 1)[0]
570 key = ("metadata-tag", pkg, *sorted(kwargs))
571 if should_warn(key):
572 warn(
573 f"metadata {kwargs} was set from the constructor. "
574 "With traitlets 4.1, metadata should be set using the .tag() method, "
575 "e.g., Int().tag(key1='value1', key2='value2')",
576 DeprecationWarning,
577 stacklevel=stacklevel,
578 )
579 if len(self.metadata) > 0:
580 self.metadata = self.metadata.copy()
581 self.metadata.update(kwargs)
582 else:
583 self.metadata = kwargs
584 else:
585 self.metadata = self.metadata.copy()
586 if config is not None:
587 self.metadata["config"] = config
589 # We add help to the metadata during a deprecation period so that
590 # code that looks for the help string there can find it.
591 if help is not None:
592 self.metadata["help"] = help
594 def from_string(self, s: str) -> G | None:
595 """Get a value from a config string
597 such as an environment variable or CLI arguments.
599 Traits can override this method to define their own
600 parsing of config strings.
602 .. seealso:: item_from_string
604 .. versionadded:: 5.0
605 """
606 if self.allow_none and s == "None":
607 return None
608 return s # type:ignore[return-value]
610 def default(self, obj: t.Any = None) -> G | None:
611 """The default generator for this trait
613 Notes
614 -----
615 This method is registered to HasTraits classes during ``class_init``
616 in the same way that dynamic defaults defined by ``@default`` are.
617 """
618 if self.default_value is not Undefined:
619 return self.default_value # type:ignore[no-any-return]
620 elif hasattr(self, "make_dynamic_default"):
621 return self.make_dynamic_default() # type:ignore[no-any-return]
622 else:
623 # Undefined will raise in TraitType.get
624 return self.default_value # type:ignore[no-any-return]
626 def get_default_value(self) -> G | None:
627 """DEPRECATED: Retrieve the static default value for this trait.
628 Use self.default_value instead
629 """
630 warn(
631 "get_default_value is deprecated in traitlets 4.0: use the .default_value attribute",
632 DeprecationWarning,
633 stacklevel=2,
634 )
635 return self.default_value # type:ignore[no-any-return]
637 def init_default_value(self, obj: t.Any) -> G | None:
638 """DEPRECATED: Set the static default value for the trait type."""
639 warn(
640 "init_default_value is deprecated in traitlets 4.0, and may be removed in the future",
641 DeprecationWarning,
642 stacklevel=2,
643 )
644 value = self._validate(obj, self.default_value)
645 obj._trait_values[self.name] = value
646 return value
648 def get(self, obj: HasTraits, cls: type[t.Any] | None = None) -> G | None:
649 assert self.name is not None
650 try:
651 value = obj._trait_values[self.name]
652 except KeyError:
653 # Check for a dynamic initializer.
654 default = obj.trait_defaults(self.name)
655 if default is Undefined:
656 warn(
657 "Explicit using of Undefined as the default value "
658 "is deprecated in traitlets 5.0, and may cause "
659 "exceptions in the future.",
660 DeprecationWarning,
661 stacklevel=2,
662 )
663 # Using a context manager has a large runtime overhead, so we
664 # write out the obj.cross_validation_lock call here.
665 _cross_validation_lock = obj._cross_validation_lock
666 try:
667 obj._cross_validation_lock = True
668 value = self._validate(obj, default)
669 finally:
670 obj._cross_validation_lock = _cross_validation_lock
671 obj._trait_values[self.name] = value
672 obj._notify_observers(
673 Bunch(
674 name=self.name,
675 value=value,
676 owner=obj,
677 type="default",
678 )
679 )
680 return value # type:ignore[no-any-return]
681 except Exception as e:
682 # This should never be reached.
683 raise TraitError("Unexpected error in TraitType: default value not set properly") from e
684 else:
685 return value # type:ignore[no-any-return]
687 @t.overload
688 def __get__(self, obj: None, cls: type[t.Any]) -> Self:
689 ...
691 @t.overload
692 def __get__(self, obj: t.Any, cls: type[t.Any]) -> G:
693 ...
695 def __get__(self, obj: HasTraits | None, cls: type[t.Any]) -> Self | G:
696 """Get the value of the trait by self.name for the instance.
698 Default values are instantiated when :meth:`HasTraits.__new__`
699 is called. Thus by the time this method gets called either the
700 default value or a user defined value (they called :meth:`__set__`)
701 is in the :class:`HasTraits` instance.
702 """
703 if obj is None:
704 return self
705 else:
706 return self.get(obj, cls) # type:ignore[return-value]
708 def set(self, obj: HasTraits, value: S) -> None:
709 new_value = self._validate(obj, value)
710 assert self.name is not None
711 try:
712 old_value = obj._trait_values[self.name]
713 except KeyError:
714 old_value = self.default_value
716 obj._trait_values[self.name] = new_value
717 try:
718 silent = bool(old_value == new_value)
719 except Exception:
720 # if there is an error in comparing, default to notify
721 silent = False
722 if silent is not True:
723 # we explicitly compare silent to True just in case the equality
724 # comparison above returns something other than True/False
725 obj._notify_trait(self.name, old_value, new_value)
727 def __set__(self, obj: HasTraits, value: S) -> None:
728 """Set the value of the trait by self.name for the instance.
730 Values pass through a validation stage where errors are raised when
731 impropper types, or types that cannot be coerced, are encountered.
732 """
733 if self.read_only:
734 raise TraitError('The "%s" trait is read-only.' % self.name)
735 self.set(obj, value)
737 def _validate(self, obj: t.Any, value: t.Any) -> G | None:
738 if value is None and self.allow_none:
739 return value
740 if hasattr(self, "validate"):
741 value = self.validate(obj, value)
742 if obj._cross_validation_lock is False:
743 value = self._cross_validate(obj, value)
744 return value # type:ignore[no-any-return]
746 def _cross_validate(self, obj: t.Any, value: t.Any) -> G | None:
747 if self.name in obj._trait_validators:
748 proposal = Bunch({"trait": self, "value": value, "owner": obj})
749 value = obj._trait_validators[self.name](obj, proposal)
750 elif hasattr(obj, "_%s_validate" % self.name):
751 meth_name = "_%s_validate" % self.name
752 cross_validate = getattr(obj, meth_name)
753 deprecated_method(
754 cross_validate,
755 obj.__class__,
756 meth_name,
757 "use @validate decorator instead.",
758 )
759 value = cross_validate(value, self)
760 return value # type:ignore[no-any-return]
762 def __or__(self, other: TraitType[t.Any, t.Any]) -> Union:
763 if isinstance(other, Union):
764 return Union([self, *other.trait_types])
765 else:
766 return Union([self, other])
768 def info(self) -> str:
769 return self.info_text
771 def error(
772 self,
773 obj: HasTraits | None,
774 value: t.Any,
775 error: Exception | None = None,
776 info: str | None = None,
777 ) -> t.NoReturn:
778 """Raise a TraitError
780 Parameters
781 ----------
782 obj : HasTraits or None
783 The instance which owns the trait. If not
784 object is given, then an object agnostic
785 error will be raised.
786 value : any
787 The value that caused the error.
788 error : Exception (default: None)
789 An error that was raised by a child trait.
790 The arguments of this exception should be
791 of the form ``(value, info, *traits)``.
792 Where the ``value`` and ``info`` are the
793 problem value, and string describing the
794 expected value. The ``traits`` are a series
795 of :class:`TraitType` instances that are
796 "children" of this one (the first being
797 the deepest).
798 info : str (default: None)
799 A description of the expected value. By
800 default this is inferred from this trait's
801 ``info`` method.
802 """
803 if error is not None:
804 # handle nested error
805 error.args += (self,)
806 if self.name is not None:
807 # this is the root trait that must format the final message
808 chain = " of ".join(describe("a", t) for t in error.args[2:])
809 if obj is not None:
810 error.args = (
811 "The '{}' trait of {} instance contains {} which "
812 "expected {}, not {}.".format(
813 self.name,
814 describe("an", obj),
815 chain,
816 error.args[1],
817 describe("the", error.args[0]),
818 ),
819 )
820 else:
821 error.args = (
822 "The '{}' trait contains {} which " "expected {}, not {}.".format(
823 self.name,
824 chain,
825 error.args[1],
826 describe("the", error.args[0]),
827 ),
828 )
829 raise error
831 # this trait caused an error
832 if self.name is None:
833 # this is not the root trait
834 raise TraitError(value, info or self.info(), self)
836 # this is the root trait
837 if obj is not None:
838 e = "The '{}' trait of {} instance expected {}, not {}.".format(
839 self.name,
840 class_of(obj),
841 info or self.info(),
842 describe("the", value),
843 )
844 else:
845 e = "The '{}' trait expected {}, not {}.".format(
846 self.name,
847 info or self.info(),
848 describe("the", value),
849 )
850 raise TraitError(e)
852 def get_metadata(self, key: str, default: t.Any = None) -> t.Any:
853 """DEPRECATED: Get a metadata value.
855 Use .metadata[key] or .metadata.get(key, default) instead.
856 """
857 if key == "help":
858 msg = "use the instance .help string directly, like x.help"
859 else:
860 msg = "use the instance .metadata dictionary directly, like x.metadata[key] or x.metadata.get(key, default)"
861 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
862 return self.metadata.get(key, default)
864 def set_metadata(self, key: str, value: t.Any) -> None:
865 """DEPRECATED: Set a metadata key/value.
867 Use .metadata[key] = value instead.
868 """
869 if key == "help":
870 msg = "use the instance .help string directly, like x.help = value"
871 else:
872 msg = "use the instance .metadata dictionary directly, like x.metadata[key] = value"
873 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
874 self.metadata[key] = value
876 def tag(self, **metadata: t.Any) -> Self:
877 """Sets metadata and returns self.
879 This allows convenient metadata tagging when initializing the trait, such as:
881 Examples
882 --------
883 >>> Int(0).tag(config=True, sync=True)
884 <traitlets.traitlets.Int object at ...>
886 """
887 maybe_constructor_keywords = set(metadata.keys()).intersection(
888 {"help", "allow_none", "read_only", "default_value"}
889 )
890 if maybe_constructor_keywords:
891 warn(
892 "The following attributes are set in using `tag`, but seem to be constructor keywords arguments: %s "
893 % maybe_constructor_keywords,
894 UserWarning,
895 stacklevel=2,
896 )
898 self.metadata.update(metadata)
899 return self
901 def default_value_repr(self) -> str:
902 return repr(self.default_value)
905# -----------------------------------------------------------------------------
906# The HasTraits implementation
907# -----------------------------------------------------------------------------
910class _CallbackWrapper:
911 """An object adapting a on_trait_change callback into an observe callback.
913 The comparison operator __eq__ is implemented to enable removal of wrapped
914 callbacks.
915 """
917 def __init__(self, cb: t.Any) -> None:
918 self.cb = cb
919 # Bound methods have an additional 'self' argument.
920 offset = -1 if isinstance(self.cb, types.MethodType) else 0
921 self.nargs = len(getargspec(cb)[0]) + offset
922 if self.nargs > 4:
923 raise TraitError("a trait changed callback must have 0-4 arguments.")
925 def __eq__(self, other: object) -> bool:
926 # The wrapper is equal to the wrapped element
927 if isinstance(other, _CallbackWrapper):
928 return bool(self.cb == other.cb)
929 else:
930 return bool(self.cb == other)
932 def __call__(self, change: Bunch) -> None:
933 # The wrapper is callable
934 if self.nargs == 0:
935 self.cb()
936 elif self.nargs == 1:
937 self.cb(change.name)
938 elif self.nargs == 2:
939 self.cb(change.name, change.new)
940 elif self.nargs == 3:
941 self.cb(change.name, change.old, change.new)
942 elif self.nargs == 4:
943 self.cb(change.name, change.old, change.new, change.owner)
946def _callback_wrapper(cb: t.Any) -> _CallbackWrapper:
947 if isinstance(cb, _CallbackWrapper):
948 return cb
949 else:
950 return _CallbackWrapper(cb)
953class MetaHasDescriptors(type):
954 """A metaclass for HasDescriptors.
956 This metaclass makes sure that any TraitType class attributes are
957 instantiated and sets their name attribute.
958 """
960 def __new__(
961 mcls: type[MetaHasDescriptors],
962 name: str,
963 bases: tuple[type, ...],
964 classdict: dict[str, t.Any],
965 **kwds: t.Any,
966 ) -> MetaHasDescriptors:
967 """Create the HasDescriptors class."""
968 for k, v in classdict.items():
969 # ----------------------------------------------------------------
970 # Support of deprecated behavior allowing for TraitType types
971 # to be used instead of TraitType instances.
972 if inspect.isclass(v) and issubclass(v, TraitType):
973 warn(
974 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
975 " Passing types is deprecated in traitlets 4.1.",
976 DeprecationWarning,
977 stacklevel=2,
978 )
979 classdict[k] = v()
980 # ----------------------------------------------------------------
982 return super().__new__(mcls, name, bases, classdict, **kwds)
984 def __init__(
985 cls, name: str, bases: tuple[type, ...], classdict: dict[str, t.Any], **kwds: t.Any
986 ) -> None:
987 """Finish initializing the HasDescriptors class."""
988 super().__init__(name, bases, classdict, **kwds)
989 cls.setup_class(classdict)
991 def setup_class(cls: MetaHasDescriptors, classdict: dict[str, t.Any]) -> None:
992 """Setup descriptor instance on the class
994 This sets the :attr:`this_class` and :attr:`name` attributes of each
995 BaseDescriptor in the class dict of the newly created ``cls`` before
996 calling their :attr:`class_init` method.
997 """
998 cls._descriptors = []
999 cls._instance_inits: list[t.Any] = []
1000 for k, v in classdict.items():
1001 if isinstance(v, BaseDescriptor):
1002 v.class_init(cls, k) # type:ignore[arg-type]
1004 for _, v in getmembers(cls):
1005 if isinstance(v, BaseDescriptor):
1006 v.subclass_init(cls) # type:ignore[arg-type]
1007 cls._descriptors.append(v)
1010class MetaHasTraits(MetaHasDescriptors):
1011 """A metaclass for HasTraits."""
1013 def setup_class(cls: MetaHasTraits, classdict: dict[str, t.Any]) -> None:
1014 # for only the current class
1015 cls._trait_default_generators: dict[str, t.Any] = {}
1016 # also looking at base classes
1017 cls._all_trait_default_generators = {}
1018 cls._traits = {}
1019 cls._static_immutable_initial_values = {}
1021 super().setup_class(classdict)
1023 mro = cls.mro()
1025 for name in dir(cls):
1026 # Some descriptors raise AttributeError like zope.interface's
1027 # __provides__ attributes even though they exist. This causes
1028 # AttributeErrors even though they are listed in dir(cls).
1029 try:
1030 value = getattr(cls, name)
1031 except AttributeError:
1032 continue
1033 if isinstance(value, TraitType):
1034 cls._traits[name] = value
1035 trait = value
1036 default_method_name = "_%s_default" % name
1037 mro_trait = mro
1038 try:
1039 mro_trait = mro[: mro.index(trait.this_class) + 1] # type:ignore[arg-type]
1040 except ValueError:
1041 # this_class not in mro
1042 pass
1043 for c in mro_trait:
1044 if default_method_name in c.__dict__:
1045 cls._all_trait_default_generators[name] = c.__dict__[default_method_name]
1046 break
1047 if name in c.__dict__.get("_trait_default_generators", {}):
1048 cls._all_trait_default_generators[name] = c._trait_default_generators[name] # type: ignore[attr-defined]
1049 break
1050 else:
1051 # We don't have a dynamic default generator using @default etc.
1052 # Now if the default value is not dynamic and immutable (string, number)
1053 # and does not require any validation, we keep them in a dict
1054 # of initial values to speed up instance creation.
1055 # This is a very specific optimization, but a very common scenario in
1056 # for instance ipywidgets.
1057 none_ok = trait.default_value is None and trait.allow_none
1058 if (
1059 type(trait) in [CInt, Int]
1060 and trait.min is None # type: ignore[attr-defined]
1061 and trait.max is None # type: ignore[attr-defined]
1062 and (isinstance(trait.default_value, int) or none_ok)
1063 ):
1064 cls._static_immutable_initial_values[name] = trait.default_value
1065 elif (
1066 type(trait) in [CFloat, Float]
1067 and trait.min is None # type: ignore[attr-defined]
1068 and trait.max is None # type: ignore[attr-defined]
1069 and (isinstance(trait.default_value, float) or none_ok)
1070 ):
1071 cls._static_immutable_initial_values[name] = trait.default_value
1072 elif type(trait) in [CBool, Bool] and (
1073 isinstance(trait.default_value, bool) or none_ok
1074 ):
1075 cls._static_immutable_initial_values[name] = trait.default_value
1076 elif type(trait) in [CUnicode, Unicode] and (
1077 isinstance(trait.default_value, str) or none_ok
1078 ):
1079 cls._static_immutable_initial_values[name] = trait.default_value
1080 elif type(trait) == Any and (
1081 isinstance(trait.default_value, (str, int, float, bool)) or none_ok
1082 ):
1083 cls._static_immutable_initial_values[name] = trait.default_value
1084 elif type(trait) == Union and trait.default_value is None:
1085 cls._static_immutable_initial_values[name] = None
1086 elif (
1087 isinstance(trait, Instance)
1088 and trait.default_args is None
1089 and trait.default_kwargs is None
1090 and trait.allow_none
1091 ):
1092 cls._static_immutable_initial_values[name] = None
1094 # we always add it, because a class may change when we call add_trait
1095 # and then the instance may not have all the _static_immutable_initial_values
1096 cls._all_trait_default_generators[name] = trait.default
1099def observe(*names: Sentinel | str, type: str = "change") -> ObserveHandler:
1100 """A decorator which can be used to observe Traits on a class.
1102 The handler passed to the decorator will be called with one ``change``
1103 dict argument. The change dictionary at least holds a 'type' key and a
1104 'name' key, corresponding respectively to the type of notification and the
1105 name of the attribute that triggered the notification.
1107 Other keys may be passed depending on the value of 'type'. In the case
1108 where type is 'change', we also have the following keys:
1109 * ``owner`` : the HasTraits instance
1110 * ``old`` : the old value of the modified trait attribute
1111 * ``new`` : the new value of the modified trait attribute
1112 * ``name`` : the name of the modified trait attribute.
1114 Parameters
1115 ----------
1116 *names
1117 The str names of the Traits to observe on the object.
1118 type : str, kwarg-only
1119 The type of event to observe (e.g. 'change')
1120 """
1121 if not names:
1122 raise TypeError("Please specify at least one trait name to observe.")
1123 for name in names:
1124 if name is not All and not isinstance(name, str):
1125 raise TypeError("trait names to observe must be strings or All, not %r" % name)
1126 return ObserveHandler(names, type=type)
1129def observe_compat(func: FuncT) -> FuncT:
1130 """Backward-compatibility shim decorator for observers
1132 Use with:
1134 @observe('name')
1135 @observe_compat
1136 def _foo_changed(self, change):
1137 ...
1139 With this, `super()._foo_changed(self, name, old, new)` in subclasses will still work.
1140 Allows adoption of new observer API without breaking subclasses that override and super.
1141 """
1143 def compatible_observer(
1144 self: t.Any, change_or_name: str, old: t.Any = Undefined, new: t.Any = Undefined
1145 ) -> t.Any:
1146 if isinstance(change_or_name, dict): # type:ignore[unreachable]
1147 change = Bunch(change_or_name) # type:ignore[unreachable]
1148 else:
1149 clsname = self.__class__.__name__
1150 warn(
1151 f"A parent of {clsname}._{change_or_name}_changed has adopted the new (traitlets 4.1) @observe(change) API",
1152 DeprecationWarning,
1153 stacklevel=2,
1154 )
1155 change = Bunch(
1156 type="change",
1157 old=old,
1158 new=new,
1159 name=change_or_name,
1160 owner=self,
1161 )
1162 return func(self, change)
1164 return compatible_observer # type:ignore[return-value]
1167def validate(*names: Sentinel | str) -> ValidateHandler:
1168 """A decorator to register cross validator of HasTraits object's state
1169 when a Trait is set.
1171 The handler passed to the decorator must have one ``proposal`` dict argument.
1172 The proposal dictionary must hold the following keys:
1174 * ``owner`` : the HasTraits instance
1175 * ``value`` : the proposed value for the modified trait attribute
1176 * ``trait`` : the TraitType instance associated with the attribute
1178 Parameters
1179 ----------
1180 *names
1181 The str names of the Traits to validate.
1183 Notes
1184 -----
1185 Since the owner has access to the ``HasTraits`` instance via the 'owner' key,
1186 the registered cross validator could potentially make changes to attributes
1187 of the ``HasTraits`` instance. However, we recommend not to do so. The reason
1188 is that the cross-validation of attributes may run in arbitrary order when
1189 exiting the ``hold_trait_notifications`` context, and such changes may not
1190 commute.
1191 """
1192 if not names:
1193 raise TypeError("Please specify at least one trait name to validate.")
1194 for name in names:
1195 if name is not All and not isinstance(name, str):
1196 raise TypeError("trait names to validate must be strings or All, not %r" % name)
1197 return ValidateHandler(names)
1200def default(name: str) -> DefaultHandler:
1201 """A decorator which assigns a dynamic default for a Trait on a HasTraits object.
1203 Parameters
1204 ----------
1205 name
1206 The str name of the Trait on the object whose default should be generated.
1208 Notes
1209 -----
1210 Unlike observers and validators which are properties of the HasTraits
1211 instance, default value generators are class-level properties.
1213 Besides, default generators are only invoked if they are registered in
1214 subclasses of `this_type`.
1216 ::
1218 class A(HasTraits):
1219 bar = Int()
1221 @default('bar')
1222 def get_bar_default(self):
1223 return 11
1225 class B(A):
1226 bar = Float() # This trait ignores the default generator defined in
1227 # the base class A
1229 class C(B):
1231 @default('bar')
1232 def some_other_default(self): # This default generator should not be
1233 return 3.0 # ignored since it is defined in a
1234 # class derived from B.a.this_class.
1235 """
1236 if not isinstance(name, str):
1237 raise TypeError("Trait name must be a string or All, not %r" % name)
1238 return DefaultHandler(name)
1241FuncT = t.TypeVar("FuncT", bound=t.Callable[..., t.Any])
1244class EventHandler(BaseDescriptor):
1245 def _init_call(self, func: FuncT) -> EventHandler:
1246 self.func = func
1247 return self
1249 @t.overload
1250 def __call__(self, func: FuncT, *args: t.Any, **kwargs: t.Any) -> FuncT:
1251 ...
1253 @t.overload
1254 def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
1255 ...
1257 def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
1258 """Pass `*args` and `**kwargs` to the handler's function if it exists."""
1259 if hasattr(self, "func"):
1260 return self.func(*args, **kwargs)
1261 else:
1262 return self._init_call(*args, **kwargs)
1264 def __get__(self, inst: t.Any, cls: t.Any = None) -> types.MethodType | EventHandler:
1265 if inst is None:
1266 return self
1267 return types.MethodType(self.func, inst)
1270class ObserveHandler(EventHandler):
1271 def __init__(self, names: tuple[Sentinel | str, ...], type: str = "") -> None:
1272 self.trait_names = names
1273 self.type = type
1275 def instance_init(self, inst: HasTraits) -> None:
1276 inst.observe(self, self.trait_names, type=self.type)
1279class ValidateHandler(EventHandler):
1280 def __init__(self, names: tuple[Sentinel | str, ...]) -> None:
1281 self.trait_names = names
1283 def instance_init(self, inst: HasTraits) -> None:
1284 inst._register_validator(self, self.trait_names)
1287class DefaultHandler(EventHandler):
1288 def __init__(self, name: str) -> None:
1289 self.trait_name = name
1291 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
1292 super().class_init(cls, name)
1293 cls._trait_default_generators[self.trait_name] = self
1296class HasDescriptors(metaclass=MetaHasDescriptors):
1297 """The base class for all classes that have descriptors."""
1299 def __new__(cls, /, *args: t.Any, **kwargs: t.Any) -> Self:
1300 # This is needed because object.__new__ only accepts
1301 # the cls argument.
1302 new_meth = super(HasDescriptors, cls).__new__
1303 if new_meth is object.__new__:
1304 inst = new_meth(cls)
1305 else:
1306 inst = new_meth(cls, *args, **kwargs)
1307 inst.setup_instance(*args, **kwargs)
1308 return inst
1310 def setup_instance(self, /, *args: t.Any, **kwargs: t.Any) -> None:
1311 """
1312 This is called **before** self.__init__ is called.
1313 """
1315 self._cross_validation_lock = False
1316 cls = self.__class__
1317 # Let descriptors performance initialization when a HasDescriptor
1318 # instance is created. This allows registration of observers and
1319 # default creations or other bookkeepings.
1320 # Note that descriptors can opt-out of this behavior by overriding
1321 # subclass_init.
1322 for init in cls._instance_inits:
1323 init(self)
1326class HasTraits(HasDescriptors, metaclass=MetaHasTraits):
1327 _trait_values: dict[str, t.Any]
1328 _static_immutable_initial_values: dict[str, t.Any]
1329 _trait_notifiers: dict[str | Sentinel, t.Any]
1330 _trait_validators: dict[str | Sentinel, t.Any]
1331 _cross_validation_lock: bool
1332 _traits: dict[str, t.Any]
1333 _all_trait_default_generators: dict[str, t.Any]
1335 def setup_instance(self, /, *args: t.Any, **kwargs: t.Any) -> None:
1336 # although we'd prefer to set only the initial values not present
1337 # in kwargs, we will overwrite them in `__init__`, and simply making
1338 # a copy of a dict is faster than checking for each key.
1339 self._trait_values = self._static_immutable_initial_values.copy()
1340 self._trait_notifiers = {}
1341 self._trait_validators = {}
1342 self._cross_validation_lock = False
1343 super(HasTraits, self).setup_instance(*args, **kwargs)
1345 def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
1346 # Allow trait values to be set using keyword arguments.
1347 # We need to use setattr for this to trigger validation and
1348 # notifications.
1349 super_args = args
1350 super_kwargs = {}
1352 if kwargs:
1353 # this is a simplified (and faster) version of
1354 # the hold_trait_notifications(self) context manager
1355 def ignore(change: Bunch) -> None:
1356 pass
1358 self.notify_change = ignore # type:ignore[method-assign]
1359 self._cross_validation_lock = True
1360 changes = {}
1361 for key, value in kwargs.items():
1362 if self.has_trait(key):
1363 setattr(self, key, value)
1364 changes[key] = Bunch(
1365 name=key,
1366 old=None,
1367 new=value,
1368 owner=self,
1369 type="change",
1370 )
1371 else:
1372 # passthrough args that don't set traits to super
1373 super_kwargs[key] = value
1374 # notify and cross validate all trait changes that were set in kwargs
1375 changed = set(kwargs) & set(self._traits)
1376 for key in changed:
1377 value = self._traits[key]._cross_validate(self, getattr(self, key))
1378 self.set_trait(key, value)
1379 changes[key]["new"] = value
1380 self._cross_validation_lock = False
1381 # Restore method retrieval from class
1382 del self.notify_change
1383 for key in changed:
1384 self.notify_change(changes[key])
1386 try:
1387 super().__init__(*super_args, **super_kwargs)
1388 except TypeError as e:
1389 arg_s_list = [repr(arg) for arg in super_args]
1390 for k, v in super_kwargs.items():
1391 arg_s_list.append(f"{k}={v!r}")
1392 arg_s = ", ".join(arg_s_list)
1393 warn(
1394 "Passing unrecognized arguments to super({classname}).__init__({arg_s}).\n"
1395 "{error}\n"
1396 "This is deprecated in traitlets 4.2."
1397 "This error will be raised in a future release of traitlets.".format(
1398 arg_s=arg_s,
1399 classname=self.__class__.__name__,
1400 error=e,
1401 ),
1402 DeprecationWarning,
1403 stacklevel=2,
1404 )
1406 def __getstate__(self) -> dict[str, t.Any]:
1407 d = self.__dict__.copy()
1408 # event handlers stored on an instance are
1409 # expected to be reinstantiated during a
1410 # recall of instance_init during __setstate__
1411 d["_trait_notifiers"] = {}
1412 d["_trait_validators"] = {}
1413 d["_trait_values"] = self._trait_values.copy()
1414 d["_cross_validation_lock"] = False # FIXME: raise if cloning locked!
1416 return d
1418 def __setstate__(self, state: dict[str, t.Any]) -> None:
1419 self.__dict__ = state.copy()
1421 # event handlers are reassigned to self
1422 cls = self.__class__
1423 for key in dir(cls):
1424 # Some descriptors raise AttributeError like zope.interface's
1425 # __provides__ attributes even though they exist. This causes
1426 # AttributeErrors even though they are listed in dir(cls).
1427 try:
1428 value = getattr(cls, key)
1429 except AttributeError:
1430 pass
1431 else:
1432 if isinstance(value, EventHandler):
1433 value.instance_init(self)
1435 @property
1436 @contextlib.contextmanager
1437 def cross_validation_lock(self) -> t.Any:
1438 """
1439 A contextmanager for running a block with our cross validation lock set
1440 to True.
1442 At the end of the block, the lock's value is restored to its value
1443 prior to entering the block.
1444 """
1445 if self._cross_validation_lock:
1446 yield
1447 return
1448 else:
1449 try:
1450 self._cross_validation_lock = True
1451 yield
1452 finally:
1453 self._cross_validation_lock = False
1455 @contextlib.contextmanager
1456 def hold_trait_notifications(self) -> t.Any:
1457 """Context manager for bundling trait change notifications and cross
1458 validation.
1460 Use this when doing multiple trait assignments (init, config), to avoid
1461 race conditions in trait notifiers requesting other trait values.
1462 All trait notifications will fire after all values have been assigned.
1463 """
1464 if self._cross_validation_lock:
1465 yield
1466 return
1467 else:
1468 cache: dict[str, list[Bunch]] = {}
1470 def compress(past_changes: list[Bunch] | None, change: Bunch) -> list[Bunch]:
1471 """Merges the provided change with the last if possible."""
1472 if past_changes is None:
1473 return [change]
1474 else:
1475 if past_changes[-1]["type"] == "change" and change.type == "change":
1476 past_changes[-1]["new"] = change.new
1477 else:
1478 # In case of changes other than 'change', append the notification.
1479 past_changes.append(change)
1480 return past_changes
1482 def hold(change: Bunch) -> None:
1483 name = change.name
1484 cache[name] = compress(cache.get(name), change)
1486 try:
1487 # Replace notify_change with `hold`, caching and compressing
1488 # notifications, disable cross validation and yield.
1489 self.notify_change = hold # type:ignore[method-assign]
1490 self._cross_validation_lock = True
1491 yield
1492 # Cross validate final values when context is released.
1493 for name in list(cache.keys()):
1494 trait = getattr(self.__class__, name)
1495 value = trait._cross_validate(self, getattr(self, name))
1496 self.set_trait(name, value)
1497 except TraitError as e:
1498 # Roll back in case of TraitError during final cross validation.
1499 self.notify_change = lambda x: None # type:ignore[method-assign, assignment] # noqa: ARG005
1500 for name, changes in cache.items():
1501 for change in changes[::-1]:
1502 # TODO: Separate in a rollback function per notification type.
1503 if change.type == "change":
1504 if change.old is not Undefined:
1505 self.set_trait(name, change.old)
1506 else:
1507 self._trait_values.pop(name)
1508 cache = {}
1509 raise e
1510 finally:
1511 self._cross_validation_lock = False
1512 # Restore method retrieval from class
1513 del self.notify_change
1515 # trigger delayed notifications
1516 for changes in cache.values():
1517 for change in changes:
1518 self.notify_change(change)
1520 def _notify_trait(self, name: str, old_value: t.Any, new_value: t.Any) -> None:
1521 self.notify_change(
1522 Bunch(
1523 name=name,
1524 old=old_value,
1525 new=new_value,
1526 owner=self,
1527 type="change",
1528 )
1529 )
1531 def notify_change(self, change: Bunch) -> None:
1532 """Notify observers of a change event"""
1533 return self._notify_observers(change)
1535 def _notify_observers(self, event: Bunch) -> None:
1536 """Notify observers of any event"""
1537 if not isinstance(event, Bunch):
1538 # cast to bunch if given a dict
1539 event = Bunch(event) # type:ignore[unreachable]
1540 name, type = event["name"], event["type"]
1542 callables = []
1543 if name in self._trait_notifiers:
1544 callables.extend(self._trait_notifiers.get(name, {}).get(type, []))
1545 callables.extend(self._trait_notifiers.get(name, {}).get(All, []))
1546 if All in self._trait_notifiers:
1547 callables.extend(self._trait_notifiers.get(All, {}).get(type, []))
1548 callables.extend(self._trait_notifiers.get(All, {}).get(All, []))
1550 # Now static ones
1551 magic_name = "_%s_changed" % name
1552 if event["type"] == "change" and hasattr(self, magic_name):
1553 class_value = getattr(self.__class__, magic_name)
1554 if not isinstance(class_value, ObserveHandler):
1555 deprecated_method(
1556 class_value,
1557 self.__class__,
1558 magic_name,
1559 "use @observe and @unobserve instead.",
1560 )
1561 cb = getattr(self, magic_name)
1562 # Only append the magic method if it was not manually registered
1563 if cb not in callables:
1564 callables.append(_callback_wrapper(cb))
1566 # Call them all now
1567 # Traits catches and logs errors here. I allow them to raise
1568 for c in callables:
1569 # Bound methods have an additional 'self' argument.
1571 if isinstance(c, _CallbackWrapper):
1572 c = c.__call__
1573 elif isinstance(c, EventHandler) and c.name is not None:
1574 c = getattr(self, c.name)
1576 c(event)
1578 def _add_notifiers(
1579 self, handler: t.Callable[..., t.Any], name: Sentinel | str, type: str | Sentinel
1580 ) -> None:
1581 if name not in self._trait_notifiers:
1582 nlist: list[t.Any] = []
1583 self._trait_notifiers[name] = {type: nlist}
1584 else:
1585 if type not in self._trait_notifiers[name]:
1586 nlist = []
1587 self._trait_notifiers[name][type] = nlist
1588 else:
1589 nlist = self._trait_notifiers[name][type]
1590 if handler not in nlist:
1591 nlist.append(handler)
1593 def _remove_notifiers(
1594 self, handler: t.Callable[..., t.Any] | None, name: Sentinel | str, type: str | Sentinel
1595 ) -> None:
1596 try:
1597 if handler is None:
1598 del self._trait_notifiers[name][type]
1599 else:
1600 self._trait_notifiers[name][type].remove(handler)
1601 except KeyError:
1602 pass
1604 def on_trait_change(
1605 self,
1606 handler: EventHandler | None = None,
1607 name: Sentinel | str | None = None,
1608 remove: bool = False,
1609 ) -> None:
1610 """DEPRECATED: Setup a handler to be called when a trait changes.
1612 This is used to setup dynamic notifications of trait changes.
1614 Static handlers can be created by creating methods on a HasTraits
1615 subclass with the naming convention '_[traitname]_changed'. Thus,
1616 to create static handler for the trait 'a', create the method
1617 _a_changed(self, name, old, new) (fewer arguments can be used, see
1618 below).
1620 If `remove` is True and `handler` is not specified, all change
1621 handlers for the specified name are uninstalled.
1623 Parameters
1624 ----------
1625 handler : callable, None
1626 A callable that is called when a trait changes. Its
1627 signature can be handler(), handler(name), handler(name, new),
1628 handler(name, old, new), or handler(name, old, new, self).
1629 name : list, str, None
1630 If None, the handler will apply to all traits. If a list
1631 of str, handler will apply to all names in the list. If a
1632 str, the handler will apply just to that name.
1633 remove : bool
1634 If False (the default), then install the handler. If True
1635 then unintall it.
1636 """
1637 warn(
1638 "on_trait_change is deprecated in traitlets 4.1: use observe instead",
1639 DeprecationWarning,
1640 stacklevel=2,
1641 )
1642 if name is None:
1643 name = All
1644 if remove:
1645 self.unobserve(_callback_wrapper(handler), names=name)
1646 else:
1647 self.observe(_callback_wrapper(handler), names=name)
1649 def observe(
1650 self,
1651 handler: t.Callable[..., t.Any],
1652 names: Sentinel | str | t.Iterable[Sentinel | str] = All,
1653 type: Sentinel | str = "change",
1654 ) -> None:
1655 """Setup a handler to be called when a trait changes.
1657 This is used to setup dynamic notifications of trait changes.
1659 Parameters
1660 ----------
1661 handler : callable
1662 A callable that is called when a trait changes. Its
1663 signature should be ``handler(change)``, where ``change`` is a
1664 dictionary. The change dictionary at least holds a 'type' key.
1665 * ``type``: the type of notification.
1666 Other keys may be passed depending on the value of 'type'. In the
1667 case where type is 'change', we also have the following keys:
1668 * ``owner`` : the HasTraits instance
1669 * ``old`` : the old value of the modified trait attribute
1670 * ``new`` : the new value of the modified trait attribute
1671 * ``name`` : the name of the modified trait attribute.
1672 names : list, str, All
1673 If names is All, the handler will apply to all traits. If a list
1674 of str, handler will apply to all names in the list. If a
1675 str, the handler will apply just to that name.
1676 type : str, All (default: 'change')
1677 The type of notification to filter by. If equal to All, then all
1678 notifications are passed to the observe handler.
1679 """
1680 for name in parse_notifier_name(names):
1681 self._add_notifiers(handler, name, type)
1683 def unobserve(
1684 self,
1685 handler: t.Callable[..., t.Any],
1686 names: Sentinel | str | t.Iterable[Sentinel | str] = All,
1687 type: Sentinel | str = "change",
1688 ) -> None:
1689 """Remove a trait change handler.
1691 This is used to unregister handlers to trait change notifications.
1693 Parameters
1694 ----------
1695 handler : callable
1696 The callable called when a trait attribute changes.
1697 names : list, str, All (default: All)
1698 The names of the traits for which the specified handler should be
1699 uninstalled. If names is All, the specified handler is uninstalled
1700 from the list of notifiers corresponding to all changes.
1701 type : str or All (default: 'change')
1702 The type of notification to filter by. If All, the specified handler
1703 is uninstalled from the list of notifiers corresponding to all types.
1704 """
1705 for name in parse_notifier_name(names):
1706 self._remove_notifiers(handler, name, type)
1708 def unobserve_all(self, name: str | t.Any = All) -> None:
1709 """Remove trait change handlers of any type for the specified name.
1710 If name is not specified, removes all trait notifiers."""
1711 if name is All:
1712 self._trait_notifiers = {}
1713 else:
1714 try:
1715 del self._trait_notifiers[name]
1716 except KeyError:
1717 pass
1719 def _register_validator(
1720 self, handler: t.Callable[..., None], names: tuple[str | Sentinel, ...]
1721 ) -> None:
1722 """Setup a handler to be called when a trait should be cross validated.
1724 This is used to setup dynamic notifications for cross-validation.
1726 If a validator is already registered for any of the provided names, a
1727 TraitError is raised and no new validator is registered.
1729 Parameters
1730 ----------
1731 handler : callable
1732 A callable that is called when the given trait is cross-validated.
1733 Its signature is handler(proposal), where proposal is a Bunch (dictionary with attribute access)
1734 with the following attributes/keys:
1735 * ``owner`` : the HasTraits instance
1736 * ``value`` : the proposed value for the modified trait attribute
1737 * ``trait`` : the TraitType instance associated with the attribute
1738 names : List of strings
1739 The names of the traits that should be cross-validated
1740 """
1741 for name in names:
1742 magic_name = "_%s_validate" % name
1743 if hasattr(self, magic_name):
1744 class_value = getattr(self.__class__, magic_name)
1745 if not isinstance(class_value, ValidateHandler):
1746 deprecated_method(
1747 class_value,
1748 self.__class__,
1749 magic_name,
1750 "use @validate decorator instead.",
1751 )
1752 for name in names:
1753 self._trait_validators[name] = handler
1755 def add_traits(self, **traits: t.Any) -> None:
1756 """Dynamically add trait attributes to the HasTraits instance."""
1757 cls = self.__class__
1758 attrs = {"__module__": cls.__module__}
1759 if hasattr(cls, "__qualname__"):
1760 # __qualname__ introduced in Python 3.3 (see PEP 3155)
1761 attrs["__qualname__"] = cls.__qualname__
1762 attrs.update(traits)
1763 self.__class__ = type(cls.__name__, (cls,), attrs)
1764 for trait in traits.values():
1765 trait.instance_init(self)
1767 def set_trait(self, name: str, value: t.Any) -> None:
1768 """Forcibly sets trait attribute, including read-only attributes."""
1769 cls = self.__class__
1770 if not self.has_trait(name):
1771 raise TraitError(f"Class {cls.__name__} does not have a trait named {name}")
1772 getattr(cls, name).set(self, value)
1774 @classmethod
1775 def class_trait_names(cls: type[HasTraits], **metadata: t.Any) -> list[str]:
1776 """Get a list of all the names of this class' traits.
1778 This method is just like the :meth:`trait_names` method,
1779 but is unbound.
1780 """
1781 return list(cls.class_traits(**metadata))
1783 @classmethod
1784 def class_traits(cls: type[HasTraits], **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]:
1785 """Get a ``dict`` of all the traits of this class. The dictionary
1786 is keyed on the name and the values are the TraitType objects.
1788 This method is just like the :meth:`traits` method, but is unbound.
1790 The TraitTypes returned don't know anything about the values
1791 that the various HasTrait's instances are holding.
1793 The metadata kwargs allow functions to be passed in which
1794 filter traits based on metadata values. The functions should
1795 take a single value as an argument and return a boolean. If
1796 any function returns False, then the trait is not included in
1797 the output. If a metadata key doesn't exist, None will be passed
1798 to the function.
1799 """
1800 traits = cls._traits.copy()
1802 if len(metadata) == 0:
1803 return traits
1805 result = {}
1806 for name, trait in traits.items():
1807 for meta_name, meta_eval in metadata.items():
1808 if not callable(meta_eval):
1809 meta_eval = _SimpleTest(meta_eval)
1810 if not meta_eval(trait.metadata.get(meta_name, None)):
1811 break
1812 else:
1813 result[name] = trait
1815 return result
1817 @classmethod
1818 def class_own_traits(
1819 cls: type[HasTraits], **metadata: t.Any
1820 ) -> dict[str, TraitType[t.Any, t.Any]]:
1821 """Get a dict of all the traitlets defined on this class, not a parent.
1823 Works like `class_traits`, except for excluding traits from parents.
1824 """
1825 sup = super(cls, cls)
1826 return {
1827 n: t
1828 for (n, t) in cls.class_traits(**metadata).items()
1829 if getattr(sup, n, None) is not t
1830 }
1832 def has_trait(self, name: str) -> bool:
1833 """Returns True if the object has a trait with the specified name."""
1834 return name in self._traits
1836 def trait_has_value(self, name: str) -> bool:
1837 """Returns True if the specified trait has a value.
1839 This will return false even if ``getattr`` would return a
1840 dynamically generated default value. These default values
1841 will be recognized as existing only after they have been
1842 generated.
1844 Example
1846 .. code-block:: python
1848 class MyClass(HasTraits):
1849 i = Int()
1852 mc = MyClass()
1853 assert not mc.trait_has_value("i")
1854 mc.i # generates a default value
1855 assert mc.trait_has_value("i")
1856 """
1857 return name in self._trait_values
1859 def trait_values(self, **metadata: t.Any) -> dict[str, t.Any]:
1860 """A ``dict`` of trait names and their values.
1862 The metadata kwargs allow functions to be passed in which
1863 filter traits based on metadata values. The functions should
1864 take a single value as an argument and return a boolean. If
1865 any function returns False, then the trait is not included in
1866 the output. If a metadata key doesn't exist, None will be passed
1867 to the function.
1869 Returns
1870 -------
1871 A ``dict`` of trait names and their values.
1873 Notes
1874 -----
1875 Trait values are retrieved via ``getattr``, any exceptions raised
1876 by traits or the operations they may trigger will result in the
1877 absence of a trait value in the result ``dict``.
1878 """
1879 return {name: getattr(self, name) for name in self.trait_names(**metadata)}
1881 def _get_trait_default_generator(self, name: str) -> t.Any:
1882 """Return default generator for a given trait
1884 Walk the MRO to resolve the correct default generator according to inheritance.
1885 """
1886 method_name = "_%s_default" % name
1887 if method_name in self.__dict__:
1888 return getattr(self, method_name)
1889 if method_name in self.__class__.__dict__:
1890 return getattr(self.__class__, method_name)
1891 return self._all_trait_default_generators[name]
1893 def trait_defaults(self, *names: str, **metadata: t.Any) -> dict[str, t.Any] | Sentinel:
1894 """Return a trait's default value or a dictionary of them
1896 Notes
1897 -----
1898 Dynamically generated default values may
1899 depend on the current state of the object."""
1900 for n in names:
1901 if not self.has_trait(n):
1902 raise TraitError(f"'{n}' is not a trait of '{type(self).__name__}' instances")
1904 if len(names) == 1 and len(metadata) == 0:
1905 return self._get_trait_default_generator(names[0])(self) # type:ignore[no-any-return]
1907 trait_names = self.trait_names(**metadata)
1908 trait_names.extend(names)
1910 defaults = {}
1911 for n in trait_names:
1912 defaults[n] = self._get_trait_default_generator(n)(self)
1913 return defaults
1915 def trait_names(self, **metadata: t.Any) -> list[str]:
1916 """Get a list of all the names of this class' traits."""
1917 return list(self.traits(**metadata))
1919 def traits(self, **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]:
1920 """Get a ``dict`` of all the traits of this class. The dictionary
1921 is keyed on the name and the values are the TraitType objects.
1923 The TraitTypes returned don't know anything about the values
1924 that the various HasTrait's instances are holding.
1926 The metadata kwargs allow functions to be passed in which
1927 filter traits based on metadata values. The functions should
1928 take a single value as an argument and return a boolean. If
1929 any function returns False, then the trait is not included in
1930 the output. If a metadata key doesn't exist, None will be passed
1931 to the function.
1932 """
1933 traits = self._traits.copy()
1935 if len(metadata) == 0:
1936 return traits
1938 result = {}
1939 for name, trait in traits.items():
1940 for meta_name, meta_eval in metadata.items():
1941 if not callable(meta_eval):
1942 meta_eval = _SimpleTest(meta_eval)
1943 if not meta_eval(trait.metadata.get(meta_name, None)):
1944 break
1945 else:
1946 result[name] = trait
1948 return result
1950 def trait_metadata(self, traitname: str, key: str, default: t.Any = None) -> t.Any:
1951 """Get metadata values for trait by key."""
1952 try:
1953 trait = getattr(self.__class__, traitname)
1954 except AttributeError as e:
1955 raise TraitError(
1956 f"Class {self.__class__.__name__} does not have a trait named {traitname}"
1957 ) from e
1958 metadata_name = "_" + traitname + "_metadata"
1959 if hasattr(self, metadata_name) and key in getattr(self, metadata_name):
1960 return getattr(self, metadata_name).get(key, default)
1961 else:
1962 return trait.metadata.get(key, default)
1964 @classmethod
1965 def class_own_trait_events(cls: type[HasTraits], name: str) -> dict[str, EventHandler]:
1966 """Get a dict of all event handlers defined on this class, not a parent.
1968 Works like ``event_handlers``, except for excluding traits from parents.
1969 """
1970 sup = super(cls, cls)
1971 return {
1972 n: e
1973 for (n, e) in cls.events(name).items() # type:ignore[attr-defined]
1974 if getattr(sup, n, None) is not e
1975 }
1977 @classmethod
1978 def trait_events(cls: type[HasTraits], name: str | None = None) -> dict[str, EventHandler]:
1979 """Get a ``dict`` of all the event handlers of this class.
1981 Parameters
1982 ----------
1983 name : str (default: None)
1984 The name of a trait of this class. If name is ``None`` then all
1985 the event handlers of this class will be returned instead.
1987 Returns
1988 -------
1989 The event handlers associated with a trait name, or all event handlers.
1990 """
1991 events = {}
1992 for k, v in getmembers(cls):
1993 if isinstance(v, EventHandler):
1994 if name is None:
1995 events[k] = v
1996 elif name in v.trait_names: # type:ignore[attr-defined]
1997 events[k] = v
1998 elif hasattr(v, "tags"):
1999 if cls.trait_names(**v.tags):
2000 events[k] = v
2001 return events
2004# -----------------------------------------------------------------------------
2005# Actual TraitTypes implementations/subclasses
2006# -----------------------------------------------------------------------------
2008# -----------------------------------------------------------------------------
2009# TraitTypes subclasses for handling classes and instances of classes
2010# -----------------------------------------------------------------------------
2013class ClassBasedTraitType(TraitType[G, S]):
2014 """
2015 A trait with error reporting and string -> type resolution for Type,
2016 Instance and This.
2017 """
2019 def _resolve_string(self, string: str) -> t.Any:
2020 """
2021 Resolve a string supplied for a type into an actual object.
2022 """
2023 return import_item(string)
2026class Type(ClassBasedTraitType[G, S]):
2027 """A trait whose value must be a subclass of a specified class."""
2029 if t.TYPE_CHECKING:
2031 @t.overload
2032 def __init__(
2033 self: Type[type, type],
2034 default_value: Sentinel | None | str = ...,
2035 klass: None | str = ...,
2036 allow_none: Literal[False] = ...,
2037 read_only: bool | None = ...,
2038 help: str | None = ...,
2039 config: t.Any | None = ...,
2040 **kwargs: t.Any,
2041 ) -> None:
2042 ...
2044 @t.overload
2045 def __init__(
2046 self: Type[type | None, type | None],
2047 default_value: Sentinel | None | str = ...,
2048 klass: None | str = ...,
2049 allow_none: Literal[True] = ...,
2050 read_only: bool | None = ...,
2051 help: str | None = ...,
2052 config: t.Any | None = ...,
2053 **kwargs: t.Any,
2054 ) -> None:
2055 ...
2057 @t.overload
2058 def __init__(
2059 self: Type[S, S],
2060 default_value: S = ...,
2061 klass: S = ...,
2062 allow_none: Literal[False] = ...,
2063 read_only: bool | None = ...,
2064 help: str | None = ...,
2065 config: t.Any | None = ...,
2066 **kwargs: t.Any,
2067 ) -> None:
2068 ...
2070 @t.overload
2071 def __init__(
2072 self: Type[S | None, S | None],
2073 default_value: S | None = ...,
2074 klass: S = ...,
2075 allow_none: Literal[True] = ...,
2076 read_only: bool | None = ...,
2077 help: str | None = ...,
2078 config: t.Any | None = ...,
2079 **kwargs: t.Any,
2080 ) -> None:
2081 ...
2083 def __init__(
2084 self,
2085 default_value: t.Any = Undefined,
2086 klass: t.Any = None,
2087 allow_none: bool = False,
2088 read_only: bool | None = None,
2089 help: str | None = None,
2090 config: t.Any | None = None,
2091 **kwargs: t.Any,
2092 ) -> None:
2093 """Construct a Type trait
2095 A Type trait specifies that its values must be subclasses of
2096 a particular class.
2098 If only ``default_value`` is given, it is used for the ``klass`` as
2099 well. If neither are given, both default to ``object``.
2101 Parameters
2102 ----------
2103 default_value : class, str or None
2104 The default value must be a subclass of klass. If an str,
2105 the str must be a fully specified class name, like 'foo.bar.Bah'.
2106 The string is resolved into real class, when the parent
2107 :class:`HasTraits` class is instantiated.
2108 klass : class, str [ default object ]
2109 Values of this trait must be a subclass of klass. The klass
2110 may be specified in a string like: 'foo.bar.MyClass'.
2111 The string is resolved into real class, when the parent
2112 :class:`HasTraits` class is instantiated.
2113 allow_none : bool [ default False ]
2114 Indicates whether None is allowed as an assignable value.
2115 **kwargs
2116 extra kwargs passed to `ClassBasedTraitType`
2117 """
2118 if default_value is Undefined:
2119 new_default_value = object if (klass is None) else klass
2120 else:
2121 new_default_value = default_value
2123 if klass is None:
2124 if (default_value is None) or (default_value is Undefined):
2125 klass = object
2126 else:
2127 klass = default_value
2129 if not (inspect.isclass(klass) or isinstance(klass, str)):
2130 raise TraitError("A Type trait must specify a class.")
2132 self.klass = klass
2134 super().__init__(
2135 new_default_value,
2136 allow_none=allow_none,
2137 read_only=read_only,
2138 help=help,
2139 config=config,
2140 **kwargs,
2141 )
2143 def validate(self, obj: t.Any, value: t.Any) -> G:
2144 """Validates that the value is a valid object instance."""
2145 if isinstance(value, str):
2146 try:
2147 value = self._resolve_string(value)
2148 except ImportError as e:
2149 raise TraitError(
2150 f"The '{self.name}' trait of {obj} instance must be a type, but "
2151 f"{value!r} could not be imported"
2152 ) from e
2153 try:
2154 if issubclass(value, self.klass): # type:ignore[arg-type]
2155 return value # type:ignore[no-any-return]
2156 except Exception:
2157 pass
2159 self.error(obj, value)
2161 def info(self) -> str:
2162 """Returns a description of the trait."""
2163 if isinstance(self.klass, str):
2164 klass = self.klass
2165 else:
2166 klass = self.klass.__module__ + "." + self.klass.__name__
2167 result = "a subclass of '%s'" % klass
2168 if self.allow_none:
2169 return result + " or None"
2170 return result
2172 def instance_init(self, obj: t.Any) -> None:
2173 # we can't do this in subclass_init because that
2174 # might be called before all imports are done.
2175 self._resolve_classes()
2177 def _resolve_classes(self) -> None:
2178 if isinstance(self.klass, str):
2179 self.klass = self._resolve_string(self.klass)
2180 if isinstance(self.default_value, str):
2181 self.default_value = self._resolve_string(self.default_value)
2183 def default_value_repr(self) -> str:
2184 value = self.default_value
2185 assert value is not None
2186 if isinstance(value, str):
2187 return repr(value)
2188 else:
2189 return repr(f"{value.__module__}.{value.__name__}")
2192class Instance(ClassBasedTraitType[T, T]):
2193 """A trait whose value must be an instance of a specified class.
2195 The value can also be an instance of a subclass of the specified class.
2197 Subclasses can declare default classes by overriding the klass attribute
2198 """
2200 klass: str | type[T] | None = None
2202 if t.TYPE_CHECKING:
2204 @t.overload
2205 def __init__(
2206 self: Instance[T],
2207 klass: type[T] = ...,
2208 args: tuple[t.Any, ...] | None = ...,
2209 kw: dict[str, t.Any] | None = ...,
2210 allow_none: Literal[False] = ...,
2211 read_only: bool | None = ...,
2212 help: str | None = ...,
2213 **kwargs: t.Any,
2214 ) -> None:
2215 ...
2217 @t.overload
2218 def __init__(
2219 self: Instance[T | None],
2220 klass: type[T] = ...,
2221 args: tuple[t.Any, ...] | None = ...,
2222 kw: dict[str, t.Any] | None = ...,
2223 allow_none: Literal[True] = ...,
2224 read_only: bool | None = ...,
2225 help: str | None = ...,
2226 **kwargs: t.Any,
2227 ) -> None:
2228 ...
2230 @t.overload
2231 def __init__(
2232 self: Instance[t.Any],
2233 klass: str | None = ...,
2234 args: tuple[t.Any, ...] | None = ...,
2235 kw: dict[str, t.Any] | None = ...,
2236 allow_none: Literal[False] = ...,
2237 read_only: bool | None = ...,
2238 help: str | None = ...,
2239 **kwargs: t.Any,
2240 ) -> None:
2241 ...
2243 @t.overload
2244 def __init__(
2245 self: Instance[t.Any | None],
2246 klass: str | None = ...,
2247 args: tuple[t.Any, ...] | None = ...,
2248 kw: dict[str, t.Any] | None = ...,
2249 allow_none: Literal[True] = ...,
2250 read_only: bool | None = ...,
2251 help: str | None = ...,
2252 **kwargs: t.Any,
2253 ) -> None:
2254 ...
2256 def __init__(
2257 self,
2258 klass: str | type[T] | None = None,
2259 args: tuple[t.Any, ...] | None = None,
2260 kw: dict[str, t.Any] | None = None,
2261 allow_none: bool = False,
2262 read_only: bool | None = None,
2263 help: str | None = None,
2264 **kwargs: t.Any,
2265 ) -> None:
2266 """Construct an Instance trait.
2268 This trait allows values that are instances of a particular
2269 class or its subclasses. Our implementation is quite different
2270 from that of enthough.traits as we don't allow instances to be used
2271 for klass and we handle the ``args`` and ``kw`` arguments differently.
2273 Parameters
2274 ----------
2275 klass : class, str
2276 The class that forms the basis for the trait. Class names
2277 can also be specified as strings, like 'foo.bar.Bar'.
2278 args : tuple
2279 Positional arguments for generating the default value.
2280 kw : dict
2281 Keyword arguments for generating the default value.
2282 allow_none : bool [ default False ]
2283 Indicates whether None is allowed as a value.
2284 **kwargs
2285 Extra kwargs passed to `ClassBasedTraitType`
2287 Notes
2288 -----
2289 If both ``args`` and ``kw`` are None, then the default value is None.
2290 If ``args`` is a tuple and ``kw`` is a dict, then the default is
2291 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
2292 None, the None is replaced by ``()`` or ``{}``, respectively.
2293 """
2294 if klass is None:
2295 klass = self.klass
2297 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, str)):
2298 self.klass = klass
2299 else:
2300 raise TraitError("The klass attribute must be a class not: %r" % klass)
2302 if (kw is not None) and not isinstance(kw, dict):
2303 raise TraitError("The 'kw' argument must be a dict or None.")
2304 if (args is not None) and not isinstance(args, tuple):
2305 raise TraitError("The 'args' argument must be a tuple or None.")
2307 self.default_args = args
2308 self.default_kwargs = kw
2310 super().__init__(allow_none=allow_none, read_only=read_only, help=help, **kwargs)
2312 def validate(self, obj: t.Any, value: t.Any) -> T | None:
2313 assert self.klass is not None
2314 if self.allow_none and value is None:
2315 return value
2316 if isinstance(value, self.klass): # type:ignore[arg-type]
2317 return value # type:ignore[no-any-return]
2318 else:
2319 self.error(obj, value)
2321 def info(self) -> str:
2322 if isinstance(self.klass, str):
2323 result = add_article(self.klass)
2324 else:
2325 result = describe("a", self.klass)
2326 if self.allow_none:
2327 result += " or None"
2328 return result
2330 def instance_init(self, obj: t.Any) -> None:
2331 # we can't do this in subclass_init because that
2332 # might be called before all imports are done.
2333 self._resolve_classes()
2335 def _resolve_classes(self) -> None:
2336 if isinstance(self.klass, str):
2337 self.klass = self._resolve_string(self.klass)
2339 def make_dynamic_default(self) -> T | None:
2340 if (self.default_args is None) and (self.default_kwargs is None):
2341 return None
2342 assert self.klass is not None
2343 return self.klass(*(self.default_args or ()), **(self.default_kwargs or {})) # type:ignore[operator]
2345 def default_value_repr(self) -> str:
2346 return repr(self.make_dynamic_default())
2348 def from_string(self, s: str) -> T | None:
2349 return _safe_literal_eval(s) # type:ignore[no-any-return]
2352class ForwardDeclaredMixin:
2353 """
2354 Mixin for forward-declared versions of Instance and Type.
2355 """
2357 def _resolve_string(self, string: str) -> t.Any:
2358 """
2359 Find the specified class name by looking for it in the module in which
2360 our this_class attribute was defined.
2361 """
2362 modname = self.this_class.__module__ # type:ignore[attr-defined]
2363 return import_item(".".join([modname, string]))
2366class ForwardDeclaredType(ForwardDeclaredMixin, Type[G, S]):
2367 """
2368 Forward-declared version of Type.
2369 """
2372class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance[T]):
2373 """
2374 Forward-declared version of Instance.
2375 """
2378class This(ClassBasedTraitType[t.Optional[T], t.Optional[T]]):
2379 """A trait for instances of the class containing this trait.
2381 Because how how and when class bodies are executed, the ``This``
2382 trait can only have a default value of None. This, and because we
2383 always validate default values, ``allow_none`` is *always* true.
2384 """
2386 info_text = "an instance of the same type as the receiver or None"
2388 def __init__(self, **kwargs: t.Any) -> None:
2389 super().__init__(None, **kwargs)
2391 def validate(self, obj: t.Any, value: t.Any) -> HasTraits | None:
2392 # What if value is a superclass of obj.__class__? This is
2393 # complicated if it was the superclass that defined the This
2394 # trait.
2395 assert self.this_class is not None
2396 if isinstance(value, self.this_class) or (value is None):
2397 return value
2398 else:
2399 self.error(obj, value)
2402class Union(TraitType[t.Any, t.Any]):
2403 """A trait type representing a Union type."""
2405 def __init__(self, trait_types: t.Any, **kwargs: t.Any) -> None:
2406 """Construct a Union trait.
2408 This trait allows values that are allowed by at least one of the
2409 specified trait types. A Union traitlet cannot have metadata on
2410 its own, besides the metadata of the listed types.
2412 Parameters
2413 ----------
2414 trait_types : sequence
2415 The list of trait types of length at least 1.
2416 **kwargs
2417 Extra kwargs passed to `TraitType`
2419 Notes
2420 -----
2421 Union([Float(), Bool(), Int()]) attempts to validate the provided values
2422 with the validation function of Float, then Bool, and finally Int.
2424 Parsing from string is ambiguous for container types which accept other
2425 collection-like literals (e.g. List accepting both `[]` and `()`
2426 precludes Union from ever parsing ``Union([List(), Tuple()])`` as a tuple;
2427 you can modify behaviour of too permissive container traits by overriding
2428 ``_literal_from_string_pairs`` in subclasses.
2429 Similarly, parsing unions of numeric types is only unambiguous if
2430 types are provided in order of increasing permissiveness, e.g.
2431 ``Union([Int(), Float()])`` (since floats accept integer-looking values).
2432 """
2433 self.trait_types = list(trait_types)
2434 self.info_text = " or ".join([tt.info() for tt in self.trait_types])
2435 super().__init__(**kwargs)
2437 def default(self, obj: t.Any = None) -> t.Any:
2438 default = super().default(obj)
2439 for trait in self.trait_types:
2440 if default is Undefined:
2441 default = trait.default(obj)
2442 else:
2443 break
2444 return default
2446 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
2447 for trait_type in reversed(self.trait_types):
2448 trait_type.class_init(cls, None)
2449 super().class_init(cls, name)
2451 def subclass_init(self, cls: type[t.Any]) -> None:
2452 for trait_type in reversed(self.trait_types):
2453 trait_type.subclass_init(cls)
2454 # explicitly not calling super().subclass_init(cls)
2455 # to opt out of instance_init
2457 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
2458 with obj.cross_validation_lock:
2459 for trait_type in self.trait_types:
2460 try:
2461 v = trait_type._validate(obj, value)
2462 # In the case of an element trait, the name is None
2463 if self.name is not None:
2464 setattr(obj, "_" + self.name + "_metadata", trait_type.metadata)
2465 return v
2466 except TraitError:
2467 continue
2468 self.error(obj, value)
2470 def __or__(self, other: t.Any) -> Union:
2471 if isinstance(other, Union):
2472 return Union(self.trait_types + other.trait_types)
2473 else:
2474 return Union([*self.trait_types, other])
2476 def from_string(self, s: str) -> t.Any:
2477 for trait_type in self.trait_types:
2478 try:
2479 v = trait_type.from_string(s)
2480 return trait_type.validate(None, v)
2481 except (TraitError, ValueError):
2482 continue
2483 return super().from_string(s)
2486# -----------------------------------------------------------------------------
2487# Basic TraitTypes implementations/subclasses
2488# -----------------------------------------------------------------------------
2491class Any(TraitType[t.Optional[t.Any], t.Optional[t.Any]]):
2492 """A trait which allows any value."""
2494 if t.TYPE_CHECKING:
2496 @t.overload
2497 def __init__(
2498 self: Any,
2499 default_value: t.Any = ...,
2500 *,
2501 allow_none: Literal[False],
2502 read_only: bool | None = ...,
2503 help: str | None = ...,
2504 config: t.Any | None = ...,
2505 **kwargs: t.Any,
2506 ) -> None:
2507 ...
2509 @t.overload
2510 def __init__(
2511 self: Any,
2512 default_value: t.Any = ...,
2513 *,
2514 allow_none: Literal[True],
2515 read_only: bool | None = ...,
2516 help: str | None = ...,
2517 config: t.Any | None = ...,
2518 **kwargs: t.Any,
2519 ) -> None:
2520 ...
2522 @t.overload
2523 def __init__(
2524 self: Any,
2525 default_value: t.Any = ...,
2526 *,
2527 allow_none: Literal[True, False] = ...,
2528 help: str | None = ...,
2529 read_only: bool | None = False,
2530 config: t.Any = None,
2531 **kwargs: t.Any,
2532 ) -> None:
2533 ...
2535 def __init__(
2536 self: Any,
2537 default_value: t.Any = ...,
2538 *,
2539 allow_none: bool = False,
2540 help: str | None = "",
2541 read_only: bool | None = False,
2542 config: t.Any = None,
2543 **kwargs: t.Any,
2544 ) -> None:
2545 ...
2547 @t.overload
2548 def __get__(self, obj: None, cls: type[t.Any]) -> Any:
2549 ...
2551 @t.overload
2552 def __get__(self, obj: t.Any, cls: type[t.Any]) -> t.Any:
2553 ...
2555 def __get__(self, obj: t.Any | None, cls: type[t.Any]) -> t.Any | Any:
2556 ...
2558 default_value: t.Any | None = None
2559 allow_none = True
2560 info_text = "any value"
2562 def subclass_init(self, cls: type[t.Any]) -> None:
2563 pass # fully opt out of instance_init
2566def _validate_bounds(
2567 trait: Int[t.Any, t.Any] | Float[t.Any, t.Any], obj: t.Any, value: t.Any
2568) -> t.Any:
2569 """
2570 Validate that a number to be applied to a trait is between bounds.
2572 If value is not between min_bound and max_bound, this raises a
2573 TraitError with an error message appropriate for this trait.
2574 """
2575 if trait.min is not None and value < trait.min:
2576 raise TraitError(
2577 f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
2578 f"not be less than {trait.min}, but a value of {value} was "
2579 "specified"
2580 )
2581 if trait.max is not None and value > trait.max:
2582 raise TraitError(
2583 f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
2584 f"not be greater than {trait.max}, but a value of {value} was "
2585 "specified"
2586 )
2587 return value
2590# I = t.TypeVar('I', t.Optional[int], int)
2593class Int(TraitType[G, S]):
2594 """An int trait."""
2596 default_value = 0
2597 info_text = "an int"
2599 @t.overload
2600 def __init__(
2601 self: Int[int, int],
2602 default_value: int | Sentinel = ...,
2603 allow_none: Literal[False] = ...,
2604 read_only: bool | None = ...,
2605 help: str | None = ...,
2606 config: t.Any | None = ...,
2607 **kwargs: t.Any,
2608 ) -> None:
2609 ...
2611 @t.overload
2612 def __init__(
2613 self: Int[int | None, int | None],
2614 default_value: int | Sentinel | None = ...,
2615 allow_none: Literal[True] = ...,
2616 read_only: bool | None = ...,
2617 help: str | None = ...,
2618 config: t.Any | None = ...,
2619 **kwargs: t.Any,
2620 ) -> None:
2621 ...
2623 def __init__(
2624 self,
2625 default_value: t.Any = Undefined,
2626 allow_none: bool = False,
2627 read_only: bool | None = None,
2628 help: str | None = None,
2629 config: t.Any | None = None,
2630 **kwargs: t.Any,
2631 ) -> None:
2632 self.min = kwargs.pop("min", None)
2633 self.max = kwargs.pop("max", None)
2634 super().__init__(
2635 default_value=default_value,
2636 allow_none=allow_none,
2637 read_only=read_only,
2638 help=help,
2639 config=config,
2640 **kwargs,
2641 )
2643 def validate(self, obj: t.Any, value: t.Any) -> G:
2644 if not isinstance(value, int) and isinstance(value, numbers.Number):
2645 # allow casting integer-valued numbers to int
2646 # allows for more concise assignment like `4e9` which is a float
2647 try:
2648 int_value = int(value)
2649 if int_value == value:
2650 value = int_value
2651 except Exception:
2652 pass
2653 if not isinstance(value, int):
2654 self.error(obj, value)
2655 return _validate_bounds(self, obj, value) # type:ignore[no-any-return]
2657 def from_string(self, s: str) -> G:
2658 if self.allow_none and s == "None":
2659 return None # type:ignore[return-value]
2660 return int(s) # type:ignore[return-value]
2662 def subclass_init(self, cls: type[t.Any]) -> None:
2663 pass # fully opt out of instance_init
2666class CInt(Int[G, S]):
2667 """A casting version of the int trait."""
2669 if t.TYPE_CHECKING:
2671 @t.overload
2672 def __init__(
2673 self: CInt[int, t.Any],
2674 default_value: t.Any | Sentinel = ...,
2675 allow_none: Literal[False] = ...,
2676 read_only: bool | None = ...,
2677 help: str | None = ...,
2678 config: t.Any | None = ...,
2679 **kwargs: t.Any,
2680 ) -> None:
2681 ...
2683 @t.overload
2684 def __init__(
2685 self: CInt[int | None, t.Any],
2686 default_value: t.Any | Sentinel | None = ...,
2687 allow_none: Literal[True] = ...,
2688 read_only: bool | None = ...,
2689 help: str | None = ...,
2690 config: t.Any | None = ...,
2691 **kwargs: t.Any,
2692 ) -> None:
2693 ...
2695 def __init__(
2696 self: CInt[int | None, t.Any],
2697 default_value: t.Any | Sentinel | None = ...,
2698 allow_none: bool = ...,
2699 read_only: bool | None = ...,
2700 help: str | None = ...,
2701 config: t.Any | None = ...,
2702 **kwargs: t.Any,
2703 ) -> None:
2704 ...
2706 def validate(self, obj: t.Any, value: t.Any) -> G:
2707 try:
2708 value = int(value)
2709 except Exception:
2710 self.error(obj, value)
2711 return _validate_bounds(self, obj, value) # type:ignore[no-any-return]
2714Long, CLong = Int, CInt
2715Integer = Int
2718class Float(TraitType[G, S]):
2719 """A float trait."""
2721 default_value = 0.0
2722 info_text = "a float"
2724 @t.overload
2725 def __init__(
2726 self: Float[float, int | float],
2727 default_value: float | Sentinel = ...,
2728 allow_none: Literal[False] = ...,
2729 read_only: bool | None = ...,
2730 help: str | None = ...,
2731 config: t.Any | None = ...,
2732 **kwargs: t.Any,
2733 ) -> None:
2734 ...
2736 @t.overload
2737 def __init__(
2738 self: Float[int | None, int | float | None],
2739 default_value: float | Sentinel | None = ...,
2740 allow_none: Literal[True] = ...,
2741 read_only: bool | None = ...,
2742 help: str | None = ...,
2743 config: t.Any | None = ...,
2744 **kwargs: t.Any,
2745 ) -> None:
2746 ...
2748 def __init__(
2749 self: Float[int | None, int | float | None],
2750 default_value: float | Sentinel | None = Undefined,
2751 allow_none: bool = False,
2752 read_only: bool | None = False,
2753 help: str | None = None,
2754 config: t.Any | None = None,
2755 **kwargs: t.Any,
2756 ) -> None:
2757 self.min = kwargs.pop("min", -float("inf"))
2758 self.max = kwargs.pop("max", float("inf"))
2759 super().__init__(
2760 default_value=default_value,
2761 allow_none=allow_none,
2762 read_only=read_only,
2763 help=help,
2764 config=config,
2765 **kwargs,
2766 )
2768 def validate(self, obj: t.Any, value: t.Any) -> G:
2769 if isinstance(value, int):
2770 value = float(value)
2771 if not isinstance(value, float):
2772 self.error(obj, value)
2773 return _validate_bounds(self, obj, value) # type:ignore[no-any-return]
2775 def from_string(self, s: str) -> G:
2776 if self.allow_none and s == "None":
2777 return None # type:ignore[return-value]
2778 return float(s) # type:ignore[return-value]
2780 def subclass_init(self, cls: type[t.Any]) -> None:
2781 pass # fully opt out of instance_init
2784class CFloat(Float[G, S]):
2785 """A casting version of the float trait."""
2787 if t.TYPE_CHECKING:
2789 @t.overload
2790 def __init__(
2791 self: CFloat[float, t.Any],
2792 default_value: t.Any = ...,
2793 allow_none: Literal[False] = ...,
2794 read_only: bool | None = ...,
2795 help: str | None = ...,
2796 config: t.Any | None = ...,
2797 **kwargs: t.Any,
2798 ) -> None:
2799 ...
2801 @t.overload
2802 def __init__(
2803 self: CFloat[float | None, t.Any],
2804 default_value: t.Any = ...,
2805 allow_none: Literal[True] = ...,
2806 read_only: bool | None = ...,
2807 help: str | None = ...,
2808 config: t.Any | None = ...,
2809 **kwargs: t.Any,
2810 ) -> None:
2811 ...
2813 def __init__(
2814 self: CFloat[float | None, t.Any],
2815 default_value: t.Any = ...,
2816 allow_none: bool = ...,
2817 read_only: bool | None = ...,
2818 help: str | None = ...,
2819 config: t.Any | None = ...,
2820 **kwargs: t.Any,
2821 ) -> None:
2822 ...
2824 def validate(self, obj: t.Any, value: t.Any) -> G:
2825 try:
2826 value = float(value)
2827 except Exception:
2828 self.error(obj, value)
2829 return _validate_bounds(self, obj, value) # type:ignore[no-any-return]
2832class Complex(TraitType[complex, t.Union[complex, float, int]]):
2833 """A trait for complex numbers."""
2835 default_value = 0.0 + 0.0j
2836 info_text = "a complex number"
2838 def validate(self, obj: t.Any, value: t.Any) -> complex | None:
2839 if isinstance(value, complex):
2840 return value
2841 if isinstance(value, (float, int)):
2842 return complex(value)
2843 self.error(obj, value)
2845 def from_string(self, s: str) -> complex | None:
2846 if self.allow_none and s == "None":
2847 return None
2848 return complex(s)
2850 def subclass_init(self, cls: type[t.Any]) -> None:
2851 pass # fully opt out of instance_init
2854class CComplex(Complex, TraitType[complex, t.Any]):
2855 """A casting version of the complex number trait."""
2857 def validate(self, obj: t.Any, value: t.Any) -> complex | None:
2858 try:
2859 return complex(value)
2860 except Exception:
2861 self.error(obj, value)
2864# We should always be explicit about whether we're using bytes or unicode, both
2865# for Python 3 conversion and for reliable unicode behaviour on Python 2. So
2866# we don't have a Str type.
2867class Bytes(TraitType[bytes, bytes]):
2868 """A trait for byte strings."""
2870 default_value = b""
2871 info_text = "a bytes object"
2873 def validate(self, obj: t.Any, value: t.Any) -> bytes | None:
2874 if isinstance(value, bytes):
2875 return value
2876 self.error(obj, value)
2878 def from_string(self, s: str) -> bytes | None:
2879 if self.allow_none and s == "None":
2880 return None
2881 if len(s) >= 3:
2882 # handle deprecated b"string"
2883 for quote in ('"', "'"):
2884 if s[:2] == f"b{quote}" and s[-1] == quote:
2885 old_s = s
2886 s = s[2:-1]
2887 warn(
2888 "Supporting extra quotes around Bytes is deprecated in traitlets 5.0. "
2889 f"Use {s!r} instead of {old_s!r}.",
2890 DeprecationWarning,
2891 stacklevel=2,
2892 )
2893 break
2894 return s.encode("utf8")
2896 def subclass_init(self, cls: type[t.Any]) -> None:
2897 pass # fully opt out of instance_init
2900class CBytes(Bytes, TraitType[bytes, t.Any]):
2901 """A casting version of the byte string trait."""
2903 def validate(self, obj: t.Any, value: t.Any) -> bytes | None:
2904 try:
2905 return bytes(value)
2906 except Exception:
2907 self.error(obj, value)
2910class Unicode(TraitType[G, S]):
2911 """A trait for unicode strings."""
2913 default_value = ""
2914 info_text = "a unicode string"
2916 if t.TYPE_CHECKING:
2918 @t.overload
2919 def __init__(
2920 self: Unicode[str, str | bytes],
2921 default_value: str | Sentinel = ...,
2922 allow_none: Literal[False] = ...,
2923 read_only: bool | None = ...,
2924 help: str | None = ...,
2925 config: t.Any = ...,
2926 **kwargs: t.Any,
2927 ) -> None:
2928 ...
2930 @t.overload
2931 def __init__(
2932 self: Unicode[str | None, str | bytes | None],
2933 default_value: str | Sentinel | None = ...,
2934 allow_none: Literal[True] = ...,
2935 read_only: bool | None = ...,
2936 help: str | None = ...,
2937 config: t.Any = ...,
2938 **kwargs: t.Any,
2939 ) -> None:
2940 ...
2942 def __init__(
2943 self: Unicode[str | None, str | bytes | None],
2944 default_value: str | Sentinel | None = ...,
2945 allow_none: bool = ...,
2946 read_only: bool | None = ...,
2947 help: str | None = ...,
2948 config: t.Any = ...,
2949 **kwargs: t.Any,
2950 ) -> None:
2951 ...
2953 def validate(self, obj: t.Any, value: t.Any) -> G:
2954 if isinstance(value, str):
2955 return value # type:ignore[return-value]
2956 if isinstance(value, bytes):
2957 try:
2958 return value.decode("ascii", "strict") # type:ignore[return-value]
2959 except UnicodeDecodeError as e:
2960 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
2961 raise TraitError(msg.format(value, self.name, class_of(obj))) from e
2962 self.error(obj, value)
2964 def from_string(self, s: str) -> G:
2965 if self.allow_none and s == "None":
2966 return None # type:ignore[return-value]
2967 s = os.path.expanduser(s)
2968 if len(s) >= 2:
2969 # handle deprecated "1"
2970 for c in ('"', "'"):
2971 if s[0] == s[-1] == c:
2972 old_s = s
2973 s = s[1:-1]
2974 warn(
2975 "Supporting extra quotes around strings is deprecated in traitlets 5.0. "
2976 f"You can use {s!r} instead of {old_s!r} if you require traitlets >=5.",
2977 DeprecationWarning,
2978 stacklevel=2,
2979 )
2980 return s # type:ignore[return-value]
2982 def subclass_init(self, cls: type[t.Any]) -> None:
2983 pass # fully opt out of instance_init
2986class CUnicode(Unicode[G, S], TraitType[str, t.Any]):
2987 """A casting version of the unicode trait."""
2989 if t.TYPE_CHECKING:
2991 @t.overload
2992 def __init__(
2993 self: CUnicode[str, t.Any],
2994 default_value: str | Sentinel = ...,
2995 allow_none: Literal[False] = ...,
2996 read_only: bool | None = ...,
2997 help: str | None = ...,
2998 config: t.Any = ...,
2999 **kwargs: t.Any,
3000 ) -> None:
3001 ...
3003 @t.overload
3004 def __init__(
3005 self: CUnicode[str | None, t.Any],
3006 default_value: str | Sentinel | None = ...,
3007 allow_none: Literal[True] = ...,
3008 read_only: bool | None = ...,
3009 help: str | None = ...,
3010 config: t.Any = ...,
3011 **kwargs: t.Any,
3012 ) -> None:
3013 ...
3015 def __init__(
3016 self: CUnicode[str | None, t.Any],
3017 default_value: str | Sentinel | None = ...,
3018 allow_none: bool = ...,
3019 read_only: bool | None = ...,
3020 help: str | None = ...,
3021 config: t.Any = ...,
3022 **kwargs: t.Any,
3023 ) -> None:
3024 ...
3026 def validate(self, obj: t.Any, value: t.Any) -> G:
3027 try:
3028 return str(value) # type:ignore[return-value]
3029 except Exception:
3030 self.error(obj, value)
3033class ObjectName(TraitType[str, str]):
3034 """A string holding a valid object name in this version of Python.
3036 This does not check that the name exists in any scope."""
3038 info_text = "a valid object identifier in Python"
3040 coerce_str = staticmethod(lambda _, s: s)
3042 def validate(self, obj: t.Any, value: t.Any) -> str:
3043 value = self.coerce_str(obj, value)
3045 if isinstance(value, str) and value.isidentifier():
3046 return value
3047 self.error(obj, value)
3049 def from_string(self, s: str) -> str | None:
3050 if self.allow_none and s == "None":
3051 return None
3052 return s
3055class DottedObjectName(ObjectName):
3056 """A string holding a valid dotted object name in Python, such as A.b3._c"""
3058 def validate(self, obj: t.Any, value: t.Any) -> str:
3059 value = self.coerce_str(obj, value)
3061 if isinstance(value, str) and all(a.isidentifier() for a in value.split(".")):
3062 return value
3063 self.error(obj, value)
3066class Bool(TraitType[G, S]):
3067 """A boolean (True, False) trait."""
3069 default_value = False
3070 info_text = "a boolean"
3072 if t.TYPE_CHECKING:
3074 @t.overload
3075 def __init__(
3076 self: Bool[bool, bool | int],
3077 default_value: bool | Sentinel = ...,
3078 allow_none: Literal[False] = ...,
3079 read_only: bool | None = ...,
3080 help: str | None = ...,
3081 config: t.Any = ...,
3082 **kwargs: t.Any,
3083 ) -> None:
3084 ...
3086 @t.overload
3087 def __init__(
3088 self: Bool[bool | None, bool | int | None],
3089 default_value: bool | Sentinel | None = ...,
3090 allow_none: Literal[True] = ...,
3091 read_only: bool | None = ...,
3092 help: str | None = ...,
3093 config: t.Any = ...,
3094 **kwargs: t.Any,
3095 ) -> None:
3096 ...
3098 def __init__(
3099 self: Bool[bool | None, bool | int | None],
3100 default_value: bool | Sentinel | None = ...,
3101 allow_none: bool = ...,
3102 read_only: bool | None = ...,
3103 help: str | None = ...,
3104 config: t.Any = ...,
3105 **kwargs: t.Any,
3106 ) -> None:
3107 ...
3109 def validate(self, obj: t.Any, value: t.Any) -> G:
3110 if isinstance(value, bool):
3111 return value # type:ignore[return-value]
3112 elif isinstance(value, int):
3113 if value == 1:
3114 return True # type:ignore[return-value]
3115 elif value == 0:
3116 return False # type:ignore[return-value]
3117 self.error(obj, value)
3119 def from_string(self, s: str) -> G:
3120 if self.allow_none and s == "None":
3121 return None # type:ignore[return-value]
3122 s = s.lower()
3123 if s in {"true", "1"}:
3124 return True # type:ignore[return-value]
3125 elif s in {"false", "0"}:
3126 return False # type:ignore[return-value]
3127 else:
3128 raise ValueError("%r is not 1, 0, true, or false")
3130 def subclass_init(self, cls: type[t.Any]) -> None:
3131 pass # fully opt out of instance_init
3133 def argcompleter(self, **kwargs: t.Any) -> list[str]:
3134 """Completion hints for argcomplete"""
3135 completions = ["true", "1", "false", "0"]
3136 if self.allow_none:
3137 completions.append("None")
3138 return completions
3141class CBool(Bool[G, S]):
3142 """A casting version of the boolean trait."""
3144 if t.TYPE_CHECKING:
3146 @t.overload
3147 def __init__(
3148 self: CBool[bool, t.Any],
3149 default_value: bool | Sentinel = ...,
3150 allow_none: Literal[False] = ...,
3151 read_only: bool | None = ...,
3152 help: str | None = ...,
3153 config: t.Any = ...,
3154 **kwargs: t.Any,
3155 ) -> None:
3156 ...
3158 @t.overload
3159 def __init__(
3160 self: CBool[bool | None, t.Any],
3161 default_value: bool | Sentinel | None = ...,
3162 allow_none: Literal[True] = ...,
3163 read_only: bool | None = ...,
3164 help: str | None = ...,
3165 config: t.Any = ...,
3166 **kwargs: t.Any,
3167 ) -> None:
3168 ...
3170 def __init__(
3171 self: CBool[bool | None, t.Any],
3172 default_value: bool | Sentinel | None = ...,
3173 allow_none: bool = ...,
3174 read_only: bool | None = ...,
3175 help: str | None = ...,
3176 config: t.Any = ...,
3177 **kwargs: t.Any,
3178 ) -> None:
3179 ...
3181 def validate(self, obj: t.Any, value: t.Any) -> G:
3182 try:
3183 return bool(value) # type:ignore[return-value]
3184 except Exception:
3185 self.error(obj, value)
3188class Enum(TraitType[G, G]):
3189 """An enum whose value must be in a given sequence."""
3191 if t.TYPE_CHECKING:
3193 @t.overload
3194 def __init__(
3195 self: Enum[G],
3196 values: t.Sequence[G],
3197 default_value: G | Sentinel = ...,
3198 allow_none: Literal[False] = ...,
3199 read_only: bool | None = ...,
3200 help: str | None = ...,
3201 config: t.Any = ...,
3202 **kwargs: t.Any,
3203 ) -> None:
3204 ...
3206 @t.overload
3207 def __init__(
3208 self: Enum[G | None],
3209 values: t.Sequence[G] | None,
3210 default_value: G | Sentinel | None = ...,
3211 allow_none: Literal[True] = ...,
3212 read_only: bool | None = ...,
3213 help: str | None = ...,
3214 config: t.Any = ...,
3215 **kwargs: t.Any,
3216 ) -> None:
3217 ...
3219 def __init__(
3220 self: Enum[G],
3221 values: t.Sequence[G] | None,
3222 default_value: G | Sentinel | None = Undefined,
3223 allow_none: bool = False,
3224 read_only: bool | None = None,
3225 help: str | None = None,
3226 config: t.Any = None,
3227 **kwargs: t.Any,
3228 ) -> None:
3229 self.values = values
3230 if allow_none is True and default_value is Undefined:
3231 default_value = None
3232 kwargs["allow_none"] = allow_none
3233 kwargs["read_only"] = read_only
3234 kwargs["help"] = help
3235 kwargs["config"] = config
3236 super().__init__(default_value, **kwargs)
3238 def validate(self, obj: t.Any, value: t.Any) -> G:
3239 if self.values and value in self.values:
3240 return value # type:ignore[no-any-return]
3241 self.error(obj, value)
3243 def _choices_str(self, as_rst: bool = False) -> str:
3244 """Returns a description of the trait choices (not none)."""
3245 choices = self.values or []
3246 if as_rst:
3247 choice_str = "|".join("``%r``" % x for x in choices)
3248 else:
3249 choice_str = repr(list(choices))
3250 return choice_str
3252 def _info(self, as_rst: bool = False) -> str:
3253 """Returns a description of the trait."""
3254 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3255 return f"any of {self._choices_str(as_rst)}{none}"
3257 def info(self) -> str:
3258 return self._info(as_rst=False)
3260 def info_rst(self) -> str:
3261 return self._info(as_rst=True)
3263 def from_string(self, s: str) -> G:
3264 try:
3265 return self.validate(None, s)
3266 except TraitError:
3267 return _safe_literal_eval(s) # type:ignore[no-any-return]
3269 def subclass_init(self, cls: type[t.Any]) -> None:
3270 pass # fully opt out of instance_init
3272 def argcompleter(self, **kwargs: t.Any) -> list[str]:
3273 """Completion hints for argcomplete"""
3274 return [str(v) for v in self.values or []]
3277class CaselessStrEnum(Enum[G]):
3278 """An enum of strings where the case should be ignored."""
3280 def __init__(
3281 self: CaselessStrEnum[t.Any],
3282 values: t.Any,
3283 default_value: t.Any = Undefined,
3284 **kwargs: t.Any,
3285 ) -> None:
3286 super().__init__(values, default_value=default_value, **kwargs)
3288 def validate(self, obj: t.Any, value: t.Any) -> G:
3289 if not isinstance(value, str):
3290 self.error(obj, value)
3292 for v in self.values or []:
3293 assert isinstance(v, str)
3294 if v.lower() == value.lower():
3295 return v # type:ignore[return-value]
3296 self.error(obj, value)
3298 def _info(self, as_rst: bool = False) -> str:
3299 """Returns a description of the trait."""
3300 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3301 return f"any of {self._choices_str(as_rst)} (case-insensitive){none}"
3303 def info(self) -> str:
3304 return self._info(as_rst=False)
3306 def info_rst(self) -> str:
3307 return self._info(as_rst=True)
3310class FuzzyEnum(Enum[G]):
3311 """An case-ignoring enum matching choices by unique prefixes/substrings."""
3313 case_sensitive = False
3314 #: If True, choices match anywhere in the string, otherwise match prefixes.
3315 substring_matching = False
3317 def __init__(
3318 self: FuzzyEnum[t.Any],
3319 values: t.Any,
3320 default_value: t.Any = Undefined,
3321 case_sensitive: bool = False,
3322 substring_matching: bool = False,
3323 **kwargs: t.Any,
3324 ) -> None:
3325 self.case_sensitive = case_sensitive
3326 self.substring_matching = substring_matching
3327 super().__init__(values, default_value=default_value, **kwargs)
3329 def validate(self, obj: t.Any, value: t.Any) -> G:
3330 if not isinstance(value, str):
3331 self.error(obj, value)
3333 conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower()
3334 substring_matching = self.substring_matching
3335 match_func = (lambda v, c: v in c) if substring_matching else (lambda v, c: c.startswith(v))
3336 value = conv_func(value) # type:ignore[no-untyped-call]
3337 choices = self.values or []
3338 matches = [match_func(value, conv_func(c)) for c in choices] # type:ignore[no-untyped-call]
3339 if sum(matches) == 1:
3340 for v, m in zip(choices, matches):
3341 if m:
3342 return v
3344 self.error(obj, value)
3346 def _info(self, as_rst: bool = False) -> str:
3347 """Returns a description of the trait."""
3348 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3349 case = "sensitive" if self.case_sensitive else "insensitive"
3350 substr = "substring" if self.substring_matching else "prefix"
3351 return f"any case-{case} {substr} of {self._choices_str(as_rst)}{none}"
3353 def info(self) -> str:
3354 return self._info(as_rst=False)
3356 def info_rst(self) -> str:
3357 return self._info(as_rst=True)
3360class Container(Instance[T]):
3361 """An instance of a container (list, set, etc.)
3363 To be subclassed by overriding klass.
3364 """
3366 klass: type[T] | None = None
3367 _cast_types: t.Any = ()
3368 _valid_defaults = SequenceTypes
3369 _trait: t.Any = None
3370 _literal_from_string_pairs: t.Any = ("[]", "()")
3372 @t.overload
3373 def __init__(
3374 self: Container[T],
3375 *,
3376 allow_none: Literal[False],
3377 read_only: bool | None = ...,
3378 help: str | None = ...,
3379 config: t.Any | None = ...,
3380 **kwargs: t.Any,
3381 ) -> None:
3382 ...
3384 @t.overload
3385 def __init__(
3386 self: Container[T | None],
3387 *,
3388 allow_none: Literal[True],
3389 read_only: bool | None = ...,
3390 help: str | None = ...,
3391 config: t.Any | None = ...,
3392 **kwargs: t.Any,
3393 ) -> None:
3394 ...
3396 @t.overload
3397 def __init__(
3398 self: Container[T],
3399 *,
3400 trait: t.Any = ...,
3401 default_value: t.Any = ...,
3402 help: str = ...,
3403 read_only: bool = ...,
3404 config: t.Any = ...,
3405 **kwargs: t.Any,
3406 ) -> None:
3407 ...
3409 def __init__(
3410 self,
3411 trait: t.Any | None = None,
3412 default_value: t.Any = Undefined,
3413 help: str | None = None,
3414 read_only: bool | None = None,
3415 config: t.Any | None = None,
3416 **kwargs: t.Any,
3417 ) -> None:
3418 """Create a container trait type from a list, set, or tuple.
3420 The default value is created by doing ``List(default_value)``,
3421 which creates a copy of the ``default_value``.
3423 ``trait`` can be specified, which restricts the type of elements
3424 in the container to that TraitType.
3426 If only one arg is given and it is not a Trait, it is taken as
3427 ``default_value``:
3429 ``c = List([1, 2, 3])``
3431 Parameters
3432 ----------
3433 trait : TraitType [ optional ]
3434 the type for restricting the contents of the Container. If unspecified,
3435 types are not checked.
3436 default_value : SequenceType [ optional ]
3437 The default value for the Trait. Must be list/tuple/set, and
3438 will be cast to the container type.
3439 allow_none : bool [ default False ]
3440 Whether to allow the value to be None
3441 **kwargs : any
3442 further keys for extensions to the Trait (e.g. config)
3444 """
3446 # allow List([values]):
3447 if trait is not None and default_value is Undefined and not is_trait(trait):
3448 default_value = trait
3449 trait = None
3451 if default_value is None and not kwargs.get("allow_none", False):
3452 # improve backward-compatibility for possible subclasses
3453 # specifying default_value=None as default,
3454 # keeping 'unspecified' behavior (i.e. empty container)
3455 warn(
3456 f"Specifying {self.__class__.__name__}(default_value=None)"
3457 " for no default is deprecated in traitlets 5.0.5."
3458 " Use default_value=Undefined",
3459 DeprecationWarning,
3460 stacklevel=2,
3461 )
3462 default_value = Undefined
3464 if default_value is Undefined:
3465 args: t.Any = ()
3466 elif default_value is None:
3467 # default_value back on kwargs for super() to handle
3468 args = ()
3469 kwargs["default_value"] = None
3470 elif isinstance(default_value, self._valid_defaults):
3471 args = (default_value,)
3472 else:
3473 raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
3475 if is_trait(trait):
3476 if isinstance(trait, type):
3477 warn(
3478 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
3479 " Passing types is deprecated in traitlets 4.1.",
3480 DeprecationWarning,
3481 stacklevel=3,
3482 )
3483 self._trait = trait() if isinstance(trait, type) else trait
3484 elif trait is not None:
3485 raise TypeError("`trait` must be a Trait or None, got %s" % repr_type(trait))
3487 super().__init__(
3488 klass=self.klass, args=args, help=help, read_only=read_only, config=config, **kwargs
3489 )
3491 def validate(self, obj: t.Any, value: t.Any) -> T | None:
3492 if isinstance(value, self._cast_types):
3493 assert self.klass is not None
3494 value = self.klass(value) # type:ignore[call-arg]
3495 value = super().validate(obj, value)
3496 if value is None:
3497 return value
3499 return self.validate_elements(obj, value)
3501 def validate_elements(self, obj: t.Any, value: t.Any) -> T | None:
3502 validated = []
3503 if self._trait is None or isinstance(self._trait, Any):
3504 return value # type:ignore[no-any-return]
3505 for v in value:
3506 try:
3507 v = self._trait._validate(obj, v)
3508 except TraitError as error:
3509 self.error(obj, v, error)
3510 else:
3511 validated.append(v)
3512 assert self.klass is not None
3513 return self.klass(validated) # type:ignore[call-arg]
3515 def class_init(self, cls: type[t.Any], name: str | None) -> None:
3516 if isinstance(self._trait, TraitType):
3517 self._trait.class_init(cls, None)
3518 super().class_init(cls, name)
3520 def subclass_init(self, cls: type[t.Any]) -> None:
3521 if isinstance(self._trait, TraitType):
3522 self._trait.subclass_init(cls)
3523 # explicitly not calling super().subclass_init(cls)
3524 # to opt out of instance_init
3526 def from_string(self, s: str) -> T | None:
3527 """Load value from a single string"""
3528 if not isinstance(s, str):
3529 raise TraitError(f"Expected string, got {s!r}")
3530 try:
3531 test = literal_eval(s)
3532 except Exception:
3533 test = None
3534 return self.validate(None, test)
3536 def from_string_list(self, s_list: list[str]) -> T | None:
3537 """Return the value from a list of config strings
3539 This is where we parse CLI configuration
3540 """
3541 assert self.klass is not None
3542 if len(s_list) == 1:
3543 # check for deprecated --Class.trait="['a', 'b', 'c']"
3544 r = s_list[0]
3545 if r == "None" and self.allow_none:
3546 return None
3547 if len(r) >= 2 and any(
3548 r.startswith(start) and r.endswith(end)
3549 for start, end in self._literal_from_string_pairs
3550 ):
3551 if self.this_class:
3552 clsname = self.this_class.__name__ + "."
3553 else:
3554 clsname = ""
3555 assert self.name is not None
3556 warn(
3557 "--{0}={1} for containers is deprecated in traitlets 5.0. "
3558 "You can pass `--{0} item` ... multiple times to add items to a list.".format(
3559 clsname + self.name, r
3560 ),
3561 DeprecationWarning,
3562 stacklevel=2,
3563 )
3564 return self.klass(literal_eval(r)) # type:ignore[call-arg]
3565 sig = inspect.signature(self.item_from_string)
3566 if "index" in sig.parameters:
3567 item_from_string = self.item_from_string
3568 else:
3569 # backward-compat: allow item_from_string to ignore index arg
3570 def item_from_string(s: str, index: int | None = None) -> T | str:
3571 return self.item_from_string(s)
3573 return self.klass( # type:ignore[call-arg]
3574 [item_from_string(s, index=idx) for idx, s in enumerate(s_list)]
3575 )
3577 def item_from_string(self, s: str, index: int | None = None) -> T | str:
3578 """Cast a single item from a string
3580 Evaluated when parsing CLI configuration from a string
3581 """
3582 if self._trait:
3583 return self._trait.from_string(s) # type:ignore[no-any-return]
3584 else:
3585 return s
3588class List(Container[t.List[T]]):
3589 """An instance of a Python list."""
3591 klass = list # type:ignore[assignment]
3592 _cast_types: t.Any = (tuple,)
3594 def __init__(
3595 self,
3596 trait: t.List[T] | t.Tuple[T] | t.Set[T] | Sentinel | TraitType[T, t.Any] | None = None,
3597 default_value: t.List[T] | t.Tuple[T] | t.Set[T] | Sentinel | None = Undefined,
3598 minlen: int = 0,
3599 maxlen: int = sys.maxsize,
3600 **kwargs: t.Any,
3601 ) -> None:
3602 """Create a List trait type from a list, set, or tuple.
3604 The default value is created by doing ``list(default_value)``,
3605 which creates a copy of the ``default_value``.
3607 ``trait`` can be specified, which restricts the type of elements
3608 in the container to that TraitType.
3610 If only one arg is given and it is not a Trait, it is taken as
3611 ``default_value``:
3613 ``c = List([1, 2, 3])``
3615 Parameters
3616 ----------
3617 trait : TraitType [ optional ]
3618 the type for restricting the contents of the Container.
3619 If unspecified, types are not checked.
3620 default_value : SequenceType [ optional ]
3621 The default value for the Trait. Must be list/tuple/set, and
3622 will be cast to the container type.
3623 minlen : Int [ default 0 ]
3624 The minimum length of the input list
3625 maxlen : Int [ default sys.maxsize ]
3626 The maximum length of the input list
3627 """
3628 self._maxlen = maxlen
3629 self._minlen = minlen
3630 super().__init__(trait=trait, default_value=default_value, **kwargs)
3632 def length_error(self, obj: t.Any, value: t.Any) -> None:
3633 e = (
3634 "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified."
3635 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
3636 )
3637 raise TraitError(e)
3639 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3640 length = len(value)
3641 if length < self._minlen or length > self._maxlen:
3642 self.length_error(obj, value)
3644 return super().validate_elements(obj, value)
3646 def set(self, obj: t.Any, value: t.Any) -> None:
3647 if isinstance(value, str):
3648 return super().set(obj, [value]) # type:ignore[list-item]
3649 else:
3650 return super().set(obj, value)
3653class Set(Container[t.Set[t.Any]]):
3654 """An instance of a Python set."""
3656 klass = set
3657 _cast_types = (tuple, list)
3659 _literal_from_string_pairs = ("[]", "()", "{}")
3661 # Redefine __init__ just to make the docstring more accurate.
3662 def __init__(
3663 self,
3664 trait: t.Any = None,
3665 default_value: t.Any = Undefined,
3666 minlen: int = 0,
3667 maxlen: int = sys.maxsize,
3668 **kwargs: t.Any,
3669 ) -> None:
3670 """Create a Set trait type from a list, set, or tuple.
3672 The default value is created by doing ``set(default_value)``,
3673 which creates a copy of the ``default_value``.
3675 ``trait`` can be specified, which restricts the type of elements
3676 in the container to that TraitType.
3678 If only one arg is given and it is not a Trait, it is taken as
3679 ``default_value``:
3681 ``c = Set({1, 2, 3})``
3683 Parameters
3684 ----------
3685 trait : TraitType [ optional ]
3686 the type for restricting the contents of the Container.
3687 If unspecified, types are not checked.
3688 default_value : SequenceType [ optional ]
3689 The default value for the Trait. Must be list/tuple/set, and
3690 will be cast to the container type.
3691 minlen : Int [ default 0 ]
3692 The minimum length of the input list
3693 maxlen : Int [ default sys.maxsize ]
3694 The maximum length of the input list
3695 """
3696 self._maxlen = maxlen
3697 self._minlen = minlen
3698 super().__init__(trait=trait, default_value=default_value, **kwargs)
3700 def length_error(self, obj: t.Any, value: t.Any) -> None:
3701 e = (
3702 "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified."
3703 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
3704 )
3705 raise TraitError(e)
3707 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3708 length = len(value)
3709 if length < self._minlen or length > self._maxlen:
3710 self.length_error(obj, value)
3712 return super().validate_elements(obj, value)
3714 def set(self, obj: t.Any, value: t.Any) -> None:
3715 if isinstance(value, str):
3716 return super().set(obj, {value})
3717 else:
3718 return super().set(obj, value)
3720 def default_value_repr(self) -> str:
3721 # Ensure default value is sorted for a reproducible build
3722 list_repr = repr(sorted(self.make_dynamic_default() or []))
3723 if list_repr == "[]":
3724 return "set()"
3725 return "{" + list_repr[1:-1] + "}"
3728class Tuple(Container[t.Tuple[t.Any, ...]]):
3729 """An instance of a Python tuple."""
3731 klass = tuple
3732 _cast_types = (list,)
3734 def __init__(self, *traits: t.Any, **kwargs: t.Any) -> None:
3735 """Create a tuple from a list, set, or tuple.
3737 Create a fixed-type tuple with Traits:
3739 ``t = Tuple(Int(), Str(), CStr())``
3741 would be length 3, with Int,Str,CStr for each element.
3743 If only one arg is given and it is not a Trait, it is taken as
3744 default_value:
3746 ``t = Tuple((1, 2, 3))``
3748 Otherwise, ``default_value`` *must* be specified by keyword.
3750 Parameters
3751 ----------
3752 *traits : TraitTypes [ optional ]
3753 the types for restricting the contents of the Tuple. If unspecified,
3754 types are not checked. If specified, then each positional argument
3755 corresponds to an element of the tuple. Tuples defined with traits
3756 are of fixed length.
3757 default_value : SequenceType [ optional ]
3758 The default value for the Tuple. Must be list/tuple/set, and
3759 will be cast to a tuple. If ``traits`` are specified,
3760 ``default_value`` must conform to the shape and type they specify.
3761 **kwargs
3762 Other kwargs passed to `Container`
3763 """
3764 default_value = kwargs.pop("default_value", Undefined)
3765 # allow Tuple((values,)):
3766 if len(traits) == 1 and default_value is Undefined and not is_trait(traits[0]):
3767 default_value = traits[0]
3768 traits = ()
3770 if default_value is None and not kwargs.get("allow_none", False):
3771 # improve backward-compatibility for possible subclasses
3772 # specifying default_value=None as default,
3773 # keeping 'unspecified' behavior (i.e. empty container)
3774 warn(
3775 f"Specifying {self.__class__.__name__}(default_value=None)"
3776 " for no default is deprecated in traitlets 5.0.5."
3777 " Use default_value=Undefined",
3778 DeprecationWarning,
3779 stacklevel=2,
3780 )
3781 default_value = Undefined
3783 if default_value is Undefined:
3784 args: t.Any = ()
3785 elif default_value is None:
3786 # default_value back on kwargs for super() to handle
3787 args = ()
3788 kwargs["default_value"] = None
3789 elif isinstance(default_value, self._valid_defaults):
3790 args = (default_value,)
3791 else:
3792 raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
3794 self._traits = []
3795 for trait in traits:
3796 if isinstance(trait, type):
3797 warn(
3798 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3799 " Passing types is deprecated in traitlets 4.1.",
3800 DeprecationWarning,
3801 stacklevel=2,
3802 )
3803 trait = trait()
3804 self._traits.append(trait)
3806 if self._traits and (default_value is None or default_value is Undefined):
3807 # don't allow default to be an empty container if length is specified
3808 args = None
3809 super(Container, self).__init__(klass=self.klass, args=args, **kwargs)
3811 def item_from_string(self, s: str, index: int) -> t.Any: # type:ignore[override]
3812 """Cast a single item from a string
3814 Evaluated when parsing CLI configuration from a string
3815 """
3816 if not self._traits or index >= len(self._traits):
3817 # return s instead of raising index error
3818 # length errors will be raised later on validation
3819 return s
3820 return self._traits[index].from_string(s)
3822 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3823 if not self._traits:
3824 # nothing to validate
3825 return value
3826 if len(value) != len(self._traits):
3827 e = (
3828 "The '%s' trait of %s instance requires %i elements, but a value of %s was specified."
3829 % (self.name, class_of(obj), len(self._traits), repr_type(value))
3830 )
3831 raise TraitError(e)
3833 validated = []
3834 for trait, v in zip(self._traits, value):
3835 try:
3836 v = trait._validate(obj, v)
3837 except TraitError as error:
3838 self.error(obj, v, error)
3839 else:
3840 validated.append(v)
3841 return tuple(validated)
3843 def class_init(self, cls: type[t.Any], name: str | None) -> None:
3844 for trait in self._traits:
3845 if isinstance(trait, TraitType):
3846 trait.class_init(cls, None)
3847 super(Container, self).class_init(cls, name)
3849 def subclass_init(self, cls: type[t.Any]) -> None:
3850 for trait in self._traits:
3851 if isinstance(trait, TraitType):
3852 trait.subclass_init(cls)
3853 # explicitly not calling super().subclass_init(cls)
3854 # to opt out of instance_init
3857class Dict(Instance["dict[K, V]"]):
3858 """An instance of a Python dict.
3860 One or more traits can be passed to the constructor
3861 to validate the keys and/or values of the dict.
3862 If you need more detailed validation,
3863 you may use a custom validator method.
3865 .. versionchanged:: 5.0
3866 Added key_trait for validating dict keys.
3868 .. versionchanged:: 5.0
3869 Deprecated ambiguous ``trait``, ``traits`` args in favor of ``value_trait``, ``per_key_traits``.
3870 """
3872 _value_trait = None
3873 _key_trait = None
3875 def __init__(
3876 self,
3877 value_trait: TraitType[t.Any, t.Any] | dict[K, V] | Sentinel | None = None,
3878 per_key_traits: t.Any = None,
3879 key_trait: TraitType[t.Any, t.Any] | None = None,
3880 default_value: dict[K, V] | Sentinel | None = Undefined,
3881 **kwargs: t.Any,
3882 ) -> None:
3883 """Create a dict trait type from a Python dict.
3885 The default value is created by doing ``dict(default_value)``,
3886 which creates a copy of the ``default_value``.
3888 Parameters
3889 ----------
3890 value_trait : TraitType [ optional ]
3891 The specified trait type to check and use to restrict the values of
3892 the dict. If unspecified, values are not checked.
3893 per_key_traits : Dictionary of {keys:trait types} [ optional, keyword-only ]
3894 A Python dictionary containing the types that are valid for
3895 restricting the values of the dict on a per-key basis.
3896 Each value in this dict should be a Trait for validating
3897 key_trait : TraitType [ optional, keyword-only ]
3898 The type for restricting the keys of the dict. If
3899 unspecified, the types of the keys are not checked.
3900 default_value : SequenceType [ optional, keyword-only ]
3901 The default value for the Dict. Must be dict, tuple, or None, and
3902 will be cast to a dict if not None. If any key or value traits are specified,
3903 the `default_value` must conform to the constraints.
3905 Examples
3906 --------
3907 a dict whose values must be text
3908 >>> d = Dict(Unicode())
3910 d2['n'] must be an integer
3911 d2['s'] must be text
3912 >>> d2 = Dict(per_key_traits={"n": Integer(), "s": Unicode()})
3914 d3's keys must be text
3915 d3's values must be integers
3916 >>> d3 = Dict(value_trait=Integer(), key_trait=Unicode())
3918 """
3920 # handle deprecated keywords
3921 trait = kwargs.pop("trait", None)
3922 if trait is not None:
3923 if value_trait is not None:
3924 raise TypeError(
3925 "Found a value for both `value_trait` and its deprecated alias `trait`."
3926 )
3927 value_trait = trait
3928 warn(
3929 "Keyword `trait` is deprecated in traitlets 5.0, use `value_trait` instead",
3930 DeprecationWarning,
3931 stacklevel=2,
3932 )
3933 traits = kwargs.pop("traits", None)
3934 if traits is not None:
3935 if per_key_traits is not None:
3936 raise TypeError(
3937 "Found a value for both `per_key_traits` and its deprecated alias `traits`."
3938 )
3939 per_key_traits = traits
3940 warn(
3941 "Keyword `traits` is deprecated in traitlets 5.0, use `per_key_traits` instead",
3942 DeprecationWarning,
3943 stacklevel=2,
3944 )
3946 # Handling positional arguments
3947 if default_value is Undefined and value_trait is not None:
3948 if not is_trait(value_trait):
3949 assert not isinstance(value_trait, TraitType)
3950 default_value = value_trait
3951 value_trait = None
3953 if key_trait is None and per_key_traits is not None:
3954 if is_trait(per_key_traits):
3955 key_trait = per_key_traits
3956 per_key_traits = None
3958 # Handling default value
3959 if default_value is Undefined:
3960 default_value = {}
3961 if default_value is None:
3962 args: t.Any = None
3963 elif isinstance(default_value, dict):
3964 args = (default_value,)
3965 elif isinstance(default_value, SequenceTypes):
3966 args = (default_value,)
3967 else:
3968 raise TypeError("default value of Dict was %s" % default_value)
3970 # Case where a type of TraitType is provided rather than an instance
3971 if is_trait(value_trait):
3972 if isinstance(value_trait, type):
3973 warn( # type:ignore[unreachable]
3974 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3975 " Passing types is deprecated in traitlets 4.1.",
3976 DeprecationWarning,
3977 stacklevel=2,
3978 )
3979 value_trait = value_trait()
3980 self._value_trait = value_trait
3981 elif value_trait is not None:
3982 raise TypeError(
3983 "`value_trait` must be a Trait or None, got %s" % repr_type(value_trait)
3984 )
3986 if is_trait(key_trait):
3987 if isinstance(key_trait, type):
3988 warn( # type:ignore[unreachable]
3989 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3990 " Passing types is deprecated in traitlets 4.1.",
3991 DeprecationWarning,
3992 stacklevel=2,
3993 )
3994 key_trait = key_trait()
3995 self._key_trait = key_trait
3996 elif key_trait is not None:
3997 raise TypeError("`key_trait` must be a Trait or None, got %s" % repr_type(key_trait))
3999 self._per_key_traits = per_key_traits
4001 super().__init__(klass=dict, args=args, **kwargs)
4003 def element_error(
4004 self, obj: t.Any, element: t.Any, validator: t.Any, side: str = "Values"
4005 ) -> None:
4006 e = (
4007 side
4008 + f" of the '{self.name}' trait of {class_of(obj)} instance must be {validator.info()}, but a value of {repr_type(element)} was specified."
4009 )
4010 raise TraitError(e)
4012 def validate(self, obj: t.Any, value: t.Any) -> dict[K, V] | None:
4013 value = super().validate(obj, value)
4014 if value is None:
4015 return value
4016 return self.validate_elements(obj, value)
4018 def validate_elements(self, obj: t.Any, value: dict[t.Any, t.Any]) -> dict[K, V] | None:
4019 per_key_override = self._per_key_traits or {}
4020 key_trait = self._key_trait
4021 value_trait = self._value_trait
4022 if not (key_trait or value_trait or per_key_override):
4023 return value
4025 validated = {}
4026 for key in value:
4027 v = value[key]
4028 if key_trait:
4029 try:
4030 key = key_trait._validate(obj, key)
4031 except TraitError:
4032 self.element_error(obj, key, key_trait, "Keys")
4033 active_value_trait = per_key_override.get(key, value_trait)
4034 if active_value_trait:
4035 try:
4036 v = active_value_trait._validate(obj, v)
4037 except TraitError:
4038 self.element_error(obj, v, active_value_trait, "Values")
4039 validated[key] = v
4041 return self.klass(validated) # type:ignore[misc,operator]
4043 def class_init(self, cls: type[t.Any], name: str | None) -> None:
4044 if isinstance(self._value_trait, TraitType):
4045 self._value_trait.class_init(cls, None)
4046 if isinstance(self._key_trait, TraitType):
4047 self._key_trait.class_init(cls, None)
4048 if self._per_key_traits is not None:
4049 for trait in self._per_key_traits.values():
4050 trait.class_init(cls, None)
4051 super().class_init(cls, name)
4053 def subclass_init(self, cls: type[t.Any]) -> None:
4054 if isinstance(self._value_trait, TraitType):
4055 self._value_trait.subclass_init(cls)
4056 if isinstance(self._key_trait, TraitType):
4057 self._key_trait.subclass_init(cls)
4058 if self._per_key_traits is not None:
4059 for trait in self._per_key_traits.values():
4060 trait.subclass_init(cls)
4061 # explicitly not calling super().subclass_init(cls)
4062 # to opt out of instance_init
4064 def from_string(self, s: str) -> dict[K, V] | None:
4065 """Load value from a single string"""
4066 if not isinstance(s, str):
4067 raise TypeError(f"from_string expects a string, got {s!r} of type {type(s)}")
4068 try:
4069 return self.from_string_list([s]) # type:ignore[no-any-return]
4070 except Exception:
4071 test = _safe_literal_eval(s)
4072 if isinstance(test, dict):
4073 return test
4074 raise
4076 def from_string_list(self, s_list: list[str]) -> t.Any:
4077 """Return a dict from a list of config strings.
4079 This is where we parse CLI configuration.
4081 Each item should have the form ``"key=value"``.
4083 item parsing is done in :meth:`.item_from_string`.
4084 """
4085 if len(s_list) == 1 and s_list[0] == "None" and self.allow_none:
4086 return None
4087 if len(s_list) == 1 and s_list[0].startswith("{") and s_list[0].endswith("}"):
4088 warn(
4089 f"--{self.name}={s_list[0]} for dict-traits is deprecated in traitlets 5.0. "
4090 f"You can pass --{self.name} <key=value> ... multiple times to add items to a dict.",
4091 DeprecationWarning,
4092 stacklevel=2,
4093 )
4095 return literal_eval(s_list[0])
4097 combined = {}
4098 for d in [self.item_from_string(s) for s in s_list]:
4099 combined.update(d)
4100 return combined
4102 def item_from_string(self, s: str) -> dict[K, V]:
4103 """Cast a single-key dict from a string.
4105 Evaluated when parsing CLI configuration from a string.
4107 Dicts expect strings of the form key=value.
4109 Returns a one-key dictionary,
4110 which will be merged in :meth:`.from_string_list`.
4111 """
4113 if "=" not in s:
4114 raise TraitError(
4115 f"'{self.__class__.__name__}' options must have the form 'key=value', got {s!r}"
4116 )
4117 key, value = s.split("=", 1)
4119 # cast key with key trait, if defined
4120 if self._key_trait:
4121 key = self._key_trait.from_string(key)
4123 # cast value with value trait, if defined (per-key or global)
4124 value_trait = (self._per_key_traits or {}).get(key, self._value_trait)
4125 if value_trait:
4126 value = value_trait.from_string(value)
4127 return {key: value} # type:ignore[dict-item]
4130class TCPAddress(TraitType[G, S]):
4131 """A trait for an (ip, port) tuple.
4133 This allows for both IPv4 IP addresses as well as hostnames.
4134 """
4136 default_value = ("127.0.0.1", 0)
4137 info_text = "an (ip, port) tuple"
4139 if t.TYPE_CHECKING:
4141 @t.overload
4142 def __init__(
4143 self: TCPAddress[tuple[str, int], tuple[str, int]],
4144 default_value: bool | Sentinel = ...,
4145 allow_none: Literal[False] = ...,
4146 read_only: bool | None = ...,
4147 help: str | None = ...,
4148 config: t.Any = ...,
4149 **kwargs: t.Any,
4150 ) -> None:
4151 ...
4153 @t.overload
4154 def __init__(
4155 self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None],
4156 default_value: bool | None | Sentinel = ...,
4157 allow_none: Literal[True] = ...,
4158 read_only: bool | None = ...,
4159 help: str | None = ...,
4160 config: t.Any = ...,
4161 **kwargs: t.Any,
4162 ) -> None:
4163 ...
4165 def __init__(
4166 self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None]
4167 | TCPAddress[tuple[str, int], tuple[str, int]],
4168 default_value: bool | None | Sentinel = Undefined,
4169 allow_none: Literal[True, False] = False,
4170 read_only: bool | None = None,
4171 help: str | None = None,
4172 config: t.Any = None,
4173 **kwargs: t.Any,
4174 ) -> None:
4175 ...
4177 def validate(self, obj: t.Any, value: t.Any) -> G:
4178 if isinstance(value, tuple):
4179 if len(value) == 2:
4180 if isinstance(value[0], str) and isinstance(value[1], int):
4181 port = value[1]
4182 if port >= 0 and port <= 65535:
4183 return value # type:ignore[return-value]
4184 self.error(obj, value)
4186 def from_string(self, s: str) -> G:
4187 if self.allow_none and s == "None":
4188 return None # type:ignore[return-value]
4189 if ":" not in s:
4190 raise ValueError("Require `ip:port`, got %r" % s)
4191 ip, port_str = s.split(":", 1)
4192 port = int(port_str)
4193 return (ip, port) # type:ignore[return-value]
4196class CRegExp(TraitType["re.Pattern[t.Any]", t.Union["re.Pattern[t.Any]", str]]):
4197 """A casting compiled regular expression trait.
4199 Accepts both strings and compiled regular expressions. The resulting
4200 attribute will be a compiled regular expression."""
4202 info_text = "a regular expression"
4204 def validate(self, obj: t.Any, value: t.Any) -> re.Pattern[t.Any] | None:
4205 try:
4206 return re.compile(value)
4207 except Exception:
4208 self.error(obj, value)
4211class UseEnum(TraitType[t.Any, t.Any]):
4212 """Use a Enum class as model for the data type description.
4213 Note that if no default-value is provided, the first enum-value is used
4214 as default-value.
4216 .. sourcecode:: python
4218 # -- SINCE: Python 3.4 (or install backport: pip install enum34)
4219 import enum
4220 from traitlets import HasTraits, UseEnum
4223 class Color(enum.Enum):
4224 red = 1 # -- IMPLICIT: default_value
4225 blue = 2
4226 green = 3
4229 class MyEntity(HasTraits):
4230 color = UseEnum(Color, default_value=Color.blue)
4233 entity = MyEntity(color=Color.red)
4234 entity.color = Color.green # USE: Enum-value (preferred)
4235 entity.color = "green" # USE: name (as string)
4236 entity.color = "Color.green" # USE: scoped-name (as string)
4237 entity.color = 3 # USE: number (as int)
4238 assert entity.color is Color.green
4239 """
4241 default_value: enum.Enum | None = None
4242 info_text = "Trait type adapter to a Enum class"
4244 def __init__(
4245 self, enum_class: type[t.Any], default_value: t.Any = None, **kwargs: t.Any
4246 ) -> None:
4247 assert issubclass(enum_class, enum.Enum), "REQUIRE: enum.Enum, but was: %r" % enum_class
4248 allow_none = kwargs.get("allow_none", False)
4249 if default_value is None and not allow_none:
4250 default_value = next(iter(enum_class.__members__.values()))
4251 super().__init__(default_value=default_value, **kwargs)
4252 self.enum_class = enum_class
4253 self.name_prefix = enum_class.__name__ + "."
4255 def select_by_number(self, value: int, default: t.Any = Undefined) -> t.Any:
4256 """Selects enum-value by using its number-constant."""
4257 assert isinstance(value, int)
4258 enum_members = self.enum_class.__members__
4259 for enum_item in enum_members.values():
4260 if enum_item.value == value:
4261 return enum_item
4262 # -- NOT FOUND:
4263 return default
4265 def select_by_name(self, value: str, default: t.Any = Undefined) -> t.Any:
4266 """Selects enum-value by using its name or scoped-name."""
4267 assert isinstance(value, str)
4268 if value.startswith(self.name_prefix):
4269 # -- SUPPORT SCOPED-NAMES, like: "Color.red" => "red"
4270 value = value.replace(self.name_prefix, "", 1)
4271 return self.enum_class.__members__.get(value, default)
4273 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
4274 if isinstance(value, self.enum_class):
4275 return value
4276 elif isinstance(value, int):
4277 # -- CONVERT: number => enum_value (item)
4278 value2 = self.select_by_number(value)
4279 if value2 is not Undefined:
4280 return value2
4281 elif isinstance(value, str):
4282 # -- CONVERT: name or scoped_name (as string) => enum_value (item)
4283 value2 = self.select_by_name(value)
4284 if value2 is not Undefined:
4285 return value2
4286 elif value is None:
4287 if self.allow_none:
4288 return None
4289 else:
4290 return self.default_value
4291 self.error(obj, value)
4293 def _choices_str(self, as_rst: bool = False) -> str:
4294 """Returns a description of the trait choices (not none)."""
4295 choices = self.enum_class.__members__.keys()
4296 if as_rst:
4297 return "|".join("``%r``" % x for x in choices)
4298 else:
4299 return repr(list(choices)) # Listify because py3.4- prints odict-class
4301 def _info(self, as_rst: bool = False) -> str:
4302 """Returns a description of the trait."""
4303 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
4304 return f"any of {self._choices_str(as_rst)}{none}"
4306 def info(self) -> str:
4307 return self._info(as_rst=False)
4309 def info_rst(self) -> str:
4310 return self._info(as_rst=True)
4313class Callable(TraitType[t.Callable[..., t.Any], t.Callable[..., t.Any]]):
4314 """A trait which is callable.
4316 Notes
4317 -----
4318 Classes are callable, as are instances
4319 with a __call__() method."""
4321 info_text = "a callable"
4323 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
4324 if callable(value):
4325 return value
4326 else:
4327 self.error(obj, value)