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 pathlib
50import re
51import sys
52import types
53import typing as t
54from ast import literal_eval
56from .utils.bunch import Bunch
57from .utils.descriptions import add_article, class_of, describe, repr_type
58from .utils.getargspec import getargspec
59from .utils.importstring import import_item
60from .utils.sentinel import Sentinel
61from .utils.warnings import deprecated_method, should_warn, warn
63SequenceTypes = (list, tuple, set, frozenset)
65if t.TYPE_CHECKING:
66 from typing_extensions import TypeVar
67else:
68 from typing import TypeVar
70# exports:
72__all__ = [
73 "All",
74 "Any",
75 "BaseDescriptor",
76 "Bool",
77 "Bytes",
78 "CBool",
79 "CBytes",
80 "CComplex",
81 "CFloat",
82 "CInt",
83 "CLong",
84 "CRegExp",
85 "CUnicode",
86 "Callable",
87 "CaselessStrEnum",
88 "ClassBasedTraitType",
89 "Complex",
90 "Container",
91 "DefaultHandler",
92 "Dict",
93 "DottedObjectName",
94 "Enum",
95 "EventHandler",
96 "Float",
97 "ForwardDeclaredInstance",
98 "ForwardDeclaredMixin",
99 "ForwardDeclaredType",
100 "FuzzyEnum",
101 "HasDescriptors",
102 "HasTraits",
103 "Instance",
104 "Int",
105 "Integer",
106 "List",
107 "Long",
108 "MetaHasDescriptors",
109 "MetaHasTraits",
110 "ObjectName",
111 "ObserveHandler",
112 "Path",
113 "Set",
114 "TCPAddress",
115 "This",
116 "TraitError",
117 "TraitType",
118 "Tuple",
119 "Type",
120 "Undefined",
121 "Unicode",
122 "Union",
123 "UseEnum",
124 "ValidateHandler",
125 "default",
126 "directional_link",
127 "dlink",
128 "link",
129 "observe",
130 "observe_compat",
131 "parse_notifier_name",
132 "validate",
133]
135# any TraitType subclass (that doesn't start with _) will be added automatically
137# -----------------------------------------------------------------------------
138# Basic classes
139# -----------------------------------------------------------------------------
142Undefined = Sentinel(
143 "Undefined",
144 "traitlets",
145 """
146Used in Traitlets to specify that no defaults are set in kwargs
147""",
148)
150All = Sentinel(
151 "All",
152 "traitlets",
153 """
154Used in Traitlets to listen to all types of notification or to notifications
155from all trait attributes.
156""",
157)
159# Deprecated alias
160NoDefaultSpecified = Undefined
163class TraitError(Exception):
164 pass
167# -----------------------------------------------------------------------------
168# Utilities
169# -----------------------------------------------------------------------------
172def isidentifier(s: str) -> bool:
173 warn(
174 "traitlets.traitlets.isidentifier(s) is deprecated since traitlets 5.14.4 Use `s.isidentifier()`.",
175 DeprecationWarning,
176 stacklevel=2,
177 )
178 return s.isidentifier()
181def _safe_literal_eval(s: str) -> t.Any:
182 """Safely evaluate an expression
184 Returns original string if eval fails.
186 Use only where types are ambiguous.
187 """
188 try:
189 return literal_eval(s)
190 except (NameError, SyntaxError, ValueError):
191 return s
194def is_trait(t: t.Any) -> bool:
195 """Returns whether the given value is an instance or subclass of TraitType."""
196 return isinstance(t, TraitType) or (isinstance(t, type) and issubclass(t, TraitType))
199def parse_notifier_name(names: Sentinel | str | t.Collection[Sentinel | str]) -> t.Iterable[t.Any]:
200 """Convert the name argument to a list of names.
202 Examples
203 --------
204 >>> parse_notifier_name([])
205 [traitlets.All]
206 >>> parse_notifier_name("a")
207 ['a']
208 >>> parse_notifier_name(["a", "b"])
209 ['a', 'b']
210 >>> parse_notifier_name(All)
211 [traitlets.All]
212 """
213 if names is All or isinstance(names, str):
214 return [names]
215 elif isinstance(names, Sentinel):
216 raise TypeError("`names` must be either `All`, a str, or a list of strs.")
217 else:
218 if not names or All in names:
219 return [All]
220 for n in names:
221 if not isinstance(n, str):
222 raise TypeError(f"names must be strings, not {type(n).__name__}({n!r})")
223 return names
226class _SimpleTest:
227 def __init__(self, value: t.Any) -> None:
228 self.value = value
230 def __call__(self, test: t.Any) -> bool:
231 return bool(test == self.value)
233 def __repr__(self) -> str:
234 return f"<SimpleTest({self.value!r})"
236 def __str__(self) -> str:
237 return self.__repr__()
240def getmembers(object: t.Any, predicate: t.Any = None) -> list[tuple[str, t.Any]]:
241 """A safe version of inspect.getmembers that handles missing attributes.
243 This is useful when there are descriptor based attributes that for
244 some reason raise AttributeError even though they exist. This happens
245 in zope.interface with the __provides__ attribute.
246 """
247 results = []
248 for key in dir(object):
249 try:
250 value = getattr(object, key)
251 except AttributeError:
252 pass
253 else:
254 if not predicate or predicate(value):
255 results.append((key, value))
256 results.sort()
257 return results
260def _validate_link(*tuples: t.Any) -> None:
261 """Validate arguments for traitlet link functions"""
262 for tup in tuples:
263 if not len(tup) == 2:
264 raise TypeError(
265 f"Each linked traitlet must be specified as (HasTraits, 'trait_name'), not {t!r}"
266 )
267 obj, trait_name = tup
268 if not isinstance(obj, HasTraits):
269 raise TypeError(f"Each object must be HasTraits, not {type(obj)!r}")
270 if trait_name not in obj.traits():
271 raise TypeError(f"{obj!r} has no trait {trait_name!r}")
274class link:
275 """Link traits from different objects together so they remain in sync.
277 Parameters
278 ----------
279 source : (object / attribute name) pair
280 target : (object / attribute name) pair
281 transform: iterable with two callables (optional)
282 Data transformation between source and target and target and source.
284 Examples
285 --------
286 >>> class X(HasTraits):
287 ... value = Int()
289 >>> src = X(value=1)
290 >>> tgt = X(value=42)
291 >>> c = link((src, "value"), (tgt, "value"))
293 Setting source updates target objects:
294 >>> src.value = 5
295 >>> tgt.value
296 5
297 """
299 updating = False
301 def __init__(
302 self, source: t.Any, target: t.Any, transform: t.Iterable[FuncT] | None = None
303 ) -> None:
304 _validate_link(source, target)
305 self.source, self.target = source, target
306 if transform:
307 self._transform, self._transform_inv = transform # type:ignore[method-assign]
308 self.link()
310 def _transform(self, x: T) -> T:
311 """default transform: no-op"""
312 return x
314 _transform_inv = _transform
316 def link(self) -> None:
317 try:
318 setattr(
319 self.target[0],
320 self.target[1],
321 self._transform(getattr(self.source[0], self.source[1])),
322 )
324 finally:
325 self.source[0].observe(self._update_target, names=self.source[1])
326 self.target[0].observe(self._update_source, names=self.target[1])
328 @contextlib.contextmanager
329 def _busy_updating(self) -> t.Any:
330 self.updating = True
331 try:
332 yield
333 finally:
334 self.updating = False
336 def _update_target(self, change: t.Any) -> None:
337 if self.updating:
338 return
339 with self._busy_updating():
340 setattr(self.target[0], self.target[1], self._transform(change.new))
341 if getattr(self.source[0], self.source[1]) != change.new:
342 raise TraitError(
343 f"Broken link {self}: the source value changed while updating the target."
344 )
346 def _update_source(self, change: t.Any) -> None:
347 if self.updating:
348 return
349 with self._busy_updating():
350 setattr(self.source[0], self.source[1], self._transform_inv(change.new))
351 if getattr(self.target[0], self.target[1]) != change.new:
352 raise TraitError(
353 f"Broken link {self}: the target value changed while updating the source."
354 )
356 def unlink(self) -> None:
357 self.source[0].unobserve(self._update_target, names=self.source[1])
358 self.target[0].unobserve(self._update_source, names=self.target[1])
361class directional_link:
362 """Link the trait of a source object with traits of target objects.
364 Parameters
365 ----------
366 source : (object, attribute name) pair
367 target : (object, attribute name) pair
368 transform: callable (optional)
369 Data transformation between source and target.
371 Examples
372 --------
373 >>> class X(HasTraits):
374 ... value = Int()
376 >>> src = X(value=1)
377 >>> tgt = X(value=42)
378 >>> c = directional_link((src, "value"), (tgt, "value"))
380 Setting source updates target objects:
381 >>> src.value = 5
382 >>> tgt.value
383 5
385 Setting target does not update source object:
386 >>> tgt.value = 6
387 >>> src.value
388 5
390 """
392 updating = False
394 def __init__(self, source: t.Any, target: t.Any, transform: t.Any = None) -> None:
395 self._transform = transform if transform else lambda x: x
396 _validate_link(source, target)
397 self.source, self.target = source, target
398 self.link()
400 def link(self) -> None:
401 try:
402 setattr(
403 self.target[0],
404 self.target[1],
405 self._transform(getattr(self.source[0], self.source[1])),
406 )
407 finally:
408 self.source[0].observe(self._update, names=self.source[1])
410 @contextlib.contextmanager
411 def _busy_updating(self) -> t.Any:
412 self.updating = True
413 try:
414 yield
415 finally:
416 self.updating = False
418 def _update(self, change: t.Any) -> None:
419 if self.updating:
420 return
421 with self._busy_updating():
422 setattr(self.target[0], self.target[1], self._transform(change.new))
424 def unlink(self) -> None:
425 self.source[0].unobserve(self._update, names=self.source[1])
428dlink = directional_link
431# -----------------------------------------------------------------------------
432# Base Descriptor Class
433# -----------------------------------------------------------------------------
436class BaseDescriptor:
437 """Base descriptor class
439 Notes
440 -----
441 This implements Python's descriptor protocol.
443 This class is the base class for all such descriptors. The
444 only magic we use is a custom metaclass for the main :class:`HasTraits`
445 class that does the following:
447 1. Sets the :attr:`name` attribute of every :class:`BaseDescriptor`
448 instance in the class dict to the name of the attribute.
449 2. Sets the :attr:`this_class` attribute of every :class:`BaseDescriptor`
450 instance in the class dict to the *class* that declared the trait.
451 This is used by the :class:`This` trait to allow subclasses to
452 accept superclasses for :class:`This` values.
453 """
455 name: str | None = None
456 this_class: type[HasTraits] | None = None
458 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
459 """Part of the initialization which may depend on the underlying
460 HasDescriptors class.
462 It is typically overloaded for specific types.
464 This method is called by :meth:`MetaHasDescriptors.__init__`
465 passing the class (`cls`) and `name` under which the descriptor
466 has been assigned.
467 """
468 self.this_class = cls
469 self.name = name
471 def subclass_init(self, cls: type[HasTraits]) -> None:
472 # Instead of HasDescriptors.setup_instance calling
473 # every instance_init, we opt in by default.
474 # This gives descriptors a change to opt out for
475 # performance reasons.
476 # Because most traits do not need instance_init,
477 # and it will otherwise be called for every HasTrait instance
478 # being created, this otherwise gives a significant performance
479 # pentalty. Most TypeTraits in traitlets opt out.
480 cls._instance_inits.append(self.instance_init)
482 def instance_init(self, obj: t.Any) -> None:
483 """Part of the initialization which may depend on the underlying
484 HasDescriptors instance.
486 It is typically overloaded for specific types.
488 This method is called by :meth:`HasTraits.__new__` and in the
489 :meth:`BaseDescriptor.instance_init` method of descriptors holding
490 other descriptors.
491 """
494G = TypeVar("G")
495S = TypeVar("S")
496T = TypeVar("T")
499# Self from typing extension doesn't work well with mypy https://github.com/python/mypy/pull/14041
500# see https://peps.python.org/pep-0673/#use-in-generic-classes
501# Self = t.TypeVar("Self", bound="TraitType[Any, Any]")
502if t.TYPE_CHECKING:
503 from typing import Literal
505 from typing_extensions import Self
507 K = TypeVar("K", default=str)
508 V = TypeVar("V", default=t.Any)
509else:
510 # This is required to avoid warnings about unresolved references when generating
511 # the documentation of downstream projects.
512 K = TypeVar("K")
513 V = TypeVar("V")
516# We use a type for the getter (G) and setter (G) because we allow
517# for traits to cast (for instance CInt will use G=int, S=t.Any)
518class TraitType(BaseDescriptor, t.Generic[G, S]):
519 """A base class for all trait types."""
521 metadata: dict[str, t.Any] = {}
522 allow_none: bool = False
523 read_only: bool = False
524 info_text: str = "any value"
525 default_value: t.Any = Undefined
527 def __init__(
528 self: TraitType[G, S],
529 default_value: t.Any = Undefined,
530 allow_none: bool = False,
531 read_only: bool | None = None,
532 help: str | None = None,
533 config: t.Any = None,
534 **kwargs: t.Any,
535 ) -> None:
536 """Declare a traitlet.
538 If *allow_none* is True, None is a valid value in addition to any
539 values that are normally valid. The default is up to the subclass.
540 For most trait types, the default value for ``allow_none`` is False.
542 If *read_only* is True, attempts to directly modify a trait attribute raises a TraitError.
544 If *help* is a string, it documents the attribute's purpose.
546 Extra metadata can be associated with the traitlet using the .tag() convenience method
547 or by using the traitlet instance's .metadata dictionary.
548 """
549 if default_value is not Undefined:
550 self.default_value = default_value
551 if allow_none:
552 self.allow_none = allow_none
553 if read_only is not None:
554 self.read_only = read_only
555 self.help = help if help is not None else ""
556 if self.help:
557 # define __doc__ so that inspectors like autodoc find traits
558 self.__doc__ = self.help
560 if len(kwargs) > 0:
561 stacklevel = 1
562 f = inspect.currentframe()
563 # count supers to determine stacklevel for warning
564 assert f is not None
565 while f.f_code.co_name == "__init__":
566 stacklevel += 1
567 f = f.f_back
568 assert f is not None
569 mod = f.f_globals.get("__name__") or ""
570 pkg = mod.split(".", 1)[0]
571 key = ("metadata-tag", pkg, *sorted(kwargs))
572 if should_warn(key):
573 warn(
574 f"metadata {kwargs} was set from the constructor. "
575 "With traitlets 4.1, metadata should be set using the .tag() method, "
576 "e.g., Int().tag(key1='value1', key2='value2')",
577 DeprecationWarning,
578 stacklevel=stacklevel,
579 )
580 if len(self.metadata) > 0:
581 self.metadata = self.metadata.copy()
582 self.metadata.update(kwargs)
583 else:
584 self.metadata = kwargs
585 else:
586 self.metadata = self.metadata.copy()
587 if config is not None:
588 self.metadata["config"] = config
590 # We add help to the metadata during a deprecation period so that
591 # code that looks for the help string there can find it.
592 if help is not None:
593 self.metadata["help"] = help
595 def from_string(self, s: str) -> G | None:
596 """Get a value from a config string
598 such as an environment variable or CLI arguments.
600 Traits can override this method to define their own
601 parsing of config strings.
603 .. seealso:: item_from_string
605 .. versionadded:: 5.0
606 """
607 if self.allow_none and s == "None":
608 return None
609 return s # type:ignore[return-value]
611 def default(self, obj: t.Any = None) -> G | None:
612 """The default generator for this trait
614 Notes
615 -----
616 This method is registered to HasTraits classes during ``class_init``
617 in the same way that dynamic defaults defined by ``@default`` are.
618 """
619 if self.default_value is not Undefined:
620 return self.default_value # type:ignore[no-any-return]
621 elif hasattr(self, "make_dynamic_default"):
622 return self.make_dynamic_default() # type:ignore[no-any-return]
623 else:
624 # Undefined will raise in TraitType.get
625 return self.default_value # type:ignore[return-value]
627 def get_default_value(self) -> G | None:
628 """DEPRECATED: Retrieve the static default value for this trait.
629 Use self.default_value instead
630 """
631 warn(
632 "get_default_value is deprecated in traitlets 4.0: use the .default_value attribute",
633 DeprecationWarning,
634 stacklevel=2,
635 )
636 return self.default_value # type:ignore[no-any-return]
638 def init_default_value(self, obj: t.Any) -> G | None:
639 """DEPRECATED: Set the static default value for the trait type."""
640 warn(
641 "init_default_value is deprecated in traitlets 4.0, and may be removed in the future",
642 DeprecationWarning,
643 stacklevel=2,
644 )
645 value = self._validate(obj, self.default_value)
646 obj._trait_values[self.name] = value
647 return value
649 def get(self, obj: HasTraits, cls: type[t.Any] | None = None) -> G | None:
650 assert self.name is not None
651 try:
652 value = obj._trait_values[self.name]
653 except KeyError:
654 # Check for a dynamic initializer.
655 default = obj.trait_defaults(self.name)
656 if default is Undefined:
657 warn(
658 "Explicit using of Undefined as the default value "
659 "is deprecated in traitlets 5.0, and may cause "
660 "exceptions in the future.",
661 DeprecationWarning,
662 stacklevel=2,
663 )
664 # Using a context manager has a large runtime overhead, so we
665 # write out the obj.cross_validation_lock call here.
666 _cross_validation_lock = obj._cross_validation_lock
667 try:
668 obj._cross_validation_lock = True
669 value = self._validate(obj, default)
670 finally:
671 obj._cross_validation_lock = _cross_validation_lock
672 obj._trait_values[self.name] = value
673 obj._notify_observers(
674 Bunch(
675 name=self.name,
676 value=value,
677 owner=obj,
678 type="default",
679 )
680 )
681 return value
682 except Exception as e:
683 # This should never be reached.
684 raise TraitError("Unexpected error in TraitType: default value not set properly") from e
685 else:
686 return value # type:ignore[no-any-return]
688 @t.overload
689 def __get__(self, obj: None, cls: type[t.Any]) -> Self: ...
691 @t.overload
692 def __get__(self, obj: t.Any, cls: type[t.Any]) -> G: ...
694 def __get__(self, obj: HasTraits | None, cls: type[t.Any]) -> Self | G:
695 """Get the value of the trait by self.name for the instance.
697 Default values are instantiated when :meth:`HasTraits.__new__`
698 is called. Thus by the time this method gets called either the
699 default value or a user defined value (they called :meth:`__set__`)
700 is in the :class:`HasTraits` instance.
701 """
702 if obj is None:
703 return self
704 else:
705 return self.get(obj, cls) # type:ignore[return-value]
707 def set(self, obj: HasTraits, value: S) -> None:
708 new_value = self._validate(obj, value)
709 assert self.name is not None
710 try:
711 old_value = obj._trait_values[self.name]
712 except KeyError:
713 old_value = self.default_value
715 obj._trait_values[self.name] = new_value
716 try:
717 silent = bool(old_value == new_value)
718 except Exception:
719 # if there is an error in comparing, default to notify
720 silent = False
721 if silent is not True:
722 # we explicitly compare silent to True just in case the equality
723 # comparison above returns something other than True/False
724 obj._notify_trait(self.name, old_value, new_value)
726 def __set__(self, obj: HasTraits, value: S) -> None:
727 """Set the value of the trait by self.name for the instance.
729 Values pass through a validation stage where errors are raised when
730 impropper types, or types that cannot be coerced, are encountered.
731 """
732 if self.read_only:
733 raise TraitError(f'The "{self.name}" trait is read-only.')
734 self.set(obj, value)
736 def _validate(self, obj: t.Any, value: t.Any) -> G | None:
737 if value is None and self.allow_none:
738 return value
739 if hasattr(self, "validate"):
740 value = self.validate(obj, value)
741 if obj._cross_validation_lock is False:
742 value = self._cross_validate(obj, value)
743 return value # type:ignore[no-any-return]
745 def _cross_validate(self, obj: t.Any, value: t.Any) -> G | None:
746 if self.name in obj._trait_validators:
747 proposal = Bunch({"trait": self, "value": value, "owner": obj})
748 value = obj._trait_validators[self.name](obj, proposal)
749 elif hasattr(obj, f"_{self.name}_validate"):
750 meth_name = f"_{self.name}_validate"
751 cross_validate = getattr(obj, meth_name)
752 deprecated_method(
753 cross_validate,
754 obj.__class__,
755 meth_name,
756 "use @validate decorator instead.",
757 )
758 value = cross_validate(value, self)
759 return value # type:ignore[no-any-return]
761 def __or__(self, other: TraitType[t.Any, t.Any]) -> Union:
762 if isinstance(other, Union):
763 return Union([self, *other.trait_types])
764 else:
765 return Union([self, other])
767 def info(self) -> str:
768 return self.info_text
770 def error(
771 self,
772 obj: HasTraits | None,
773 value: t.Any,
774 error: Exception | None = None,
775 info: str | None = None,
776 ) -> t.NoReturn:
777 """Raise a TraitError
779 Parameters
780 ----------
781 obj : HasTraits or None
782 The instance which owns the trait. If not
783 object is given, then an object agnostic
784 error will be raised.
785 value : any
786 The value that caused the error.
787 error : Exception (default: None)
788 An error that was raised by a child trait.
789 The arguments of this exception should be
790 of the form ``(value, info, *traits)``.
791 Where the ``value`` and ``info`` are the
792 problem value, and string describing the
793 expected value. The ``traits`` are a series
794 of :class:`TraitType` instances that are
795 "children" of this one (the first being
796 the deepest).
797 info : str (default: None)
798 A description of the expected value. By
799 default this is inferred from this trait's
800 ``info`` method.
801 """
802 if error is not None:
803 # handle nested error
804 error.args += (self,)
805 if self.name is not None:
806 # this is the root trait that must format the final message
807 chain = " of ".join(describe("a", t) for t in error.args[2:])
808 if obj is not None:
809 error.args = (
810 "The '{}' trait of {} instance contains {} which "
811 "expected {}, not {}.".format(
812 self.name,
813 describe("an", obj),
814 chain,
815 error.args[1],
816 describe("the", error.args[0]),
817 ),
818 )
819 else:
820 error.args = (
821 "The '{}' trait contains {} which expected {}, not {}.".format(
822 self.name,
823 chain,
824 error.args[1],
825 describe("the", error.args[0]),
826 ),
827 )
828 raise error
830 # this trait caused an error
831 if self.name is None:
832 # this is not the root trait
833 raise TraitError(value, info or self.info(), self)
835 # this is the root trait
836 if obj is not None:
837 e = "The '{}' trait of {} instance expected {}, not {}.".format(
838 self.name,
839 class_of(obj),
840 info or self.info(),
841 describe("the", value),
842 )
843 else:
844 e = "The '{}' trait expected {}, not {}.".format(
845 self.name,
846 info or self.info(),
847 describe("the", value),
848 )
849 raise TraitError(e)
851 def get_metadata(self, key: str, default: t.Any = None) -> t.Any:
852 """DEPRECATED: Get a metadata value.
854 Use .metadata[key] or .metadata.get(key, default) instead.
855 """
856 if key == "help":
857 msg = "use the instance .help string directly, like x.help"
858 else:
859 msg = "use the instance .metadata dictionary directly, like x.metadata[key] or x.metadata.get(key, default)"
860 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
861 return self.metadata.get(key, default)
863 def set_metadata(self, key: str, value: t.Any) -> None:
864 """DEPRECATED: Set a metadata key/value.
866 Use .metadata[key] = value instead.
867 """
868 if key == "help":
869 msg = "use the instance .help string directly, like x.help = value"
870 else:
871 msg = "use the instance .metadata dictionary directly, like x.metadata[key] = value"
872 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
873 self.metadata[key] = value
875 def tag(self, **metadata: t.Any) -> Self:
876 """Sets metadata and returns self.
878 This allows convenient metadata tagging when initializing the trait, such as:
880 Examples
881 --------
882 >>> Int(0).tag(config=True, sync=True)
883 <traitlets.traitlets.Int object at ...>
885 """
886 maybe_constructor_keywords = set(metadata.keys()).intersection(
887 {"help", "allow_none", "read_only", "default_value"}
888 )
889 if maybe_constructor_keywords:
890 warn(
891 f"The following attributes are set in using `tag`, but seem to be constructor keywords arguments: {maybe_constructor_keywords} ",
892 UserWarning,
893 stacklevel=2,
894 )
896 self.metadata.update(metadata)
897 return self
899 def default_value_repr(self) -> str:
900 return repr(self.default_value)
903# -----------------------------------------------------------------------------
904# The HasTraits implementation
905# -----------------------------------------------------------------------------
908class _CallbackWrapper:
909 """An object adapting a on_trait_change callback into an observe callback.
911 The comparison operator __eq__ is implemented to enable removal of wrapped
912 callbacks.
914 __hash__ is deliberately left unset: instances compare equal to the callback
915 they wrap, so no hash could stay consistent with __eq__.
916 """
918 def __init__(self, cb: t.Any) -> None:
919 self.cb = cb
920 # Bound methods have an additional 'self' argument.
921 offset = -1 if isinstance(self.cb, types.MethodType) else 0
922 self.nargs = len(getargspec(cb)[0]) + offset
923 if self.nargs > 4:
924 raise TraitError("a trait changed callback must have 0-4 arguments.")
926 def __eq__(self, other: object) -> bool:
927 # The wrapper is equal to the wrapped element
928 if isinstance(other, _CallbackWrapper):
929 return bool(self.cb == other.cb)
930 else:
931 return bool(self.cb == other)
933 def __hash__(self) -> int:
934 # Keep the hash consistent with __eq__ (a wrapper hashes like its callback).
935 return hash(self.cb)
937 def __call__(self, change: Bunch) -> None:
938 # The wrapper is callable
939 if self.nargs == 0:
940 self.cb()
941 elif self.nargs == 1:
942 self.cb(change.name)
943 elif self.nargs == 2:
944 self.cb(change.name, change.new)
945 elif self.nargs == 3:
946 self.cb(change.name, change.old, change.new)
947 elif self.nargs == 4:
948 self.cb(change.name, change.old, change.new, change.owner)
951def _callback_wrapper(cb: t.Any) -> _CallbackWrapper:
952 if isinstance(cb, _CallbackWrapper):
953 return cb
954 else:
955 return _CallbackWrapper(cb)
958class MetaHasDescriptors(type):
959 """A metaclass for HasDescriptors.
961 This metaclass makes sure that any TraitType class attributes are
962 instantiated and sets their name attribute.
963 """
965 def __new__(
966 mcls: type[MetaHasDescriptors],
967 name: str,
968 bases: tuple[type, ...],
969 classdict: dict[str, t.Any],
970 **kwds: t.Any,
971 ) -> MetaHasDescriptors:
972 """Create the HasDescriptors class."""
973 for k, v in classdict.items():
974 # ----------------------------------------------------------------
975 # Support of deprecated behavior allowing for TraitType types
976 # to be used instead of TraitType instances.
977 if inspect.isclass(v) and issubclass(v, TraitType):
978 warn(
979 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
980 " Passing types is deprecated in traitlets 4.1.",
981 DeprecationWarning,
982 stacklevel=2,
983 )
984 classdict[k] = v()
985 # ----------------------------------------------------------------
987 return super().__new__(mcls, name, bases, classdict, **kwds)
989 def __init__(
990 cls, name: str, bases: tuple[type, ...], classdict: dict[str, t.Any], **kwds: t.Any
991 ) -> None:
992 """Finish initializing the HasDescriptors class."""
993 super().__init__(name, bases, classdict, **kwds)
994 cls.setup_class(classdict)
996 def setup_class(cls: MetaHasDescriptors, classdict: dict[str, t.Any]) -> None:
997 """Setup descriptor instance on the class
999 This sets the :attr:`this_class` and :attr:`name` attributes of each
1000 BaseDescriptor in the class dict of the newly created ``cls`` before
1001 calling their :attr:`class_init` method.
1002 """
1003 cls._descriptors = []
1004 cls._instance_inits: list[t.Any] = []
1005 for k, v in classdict.items():
1006 if isinstance(v, BaseDescriptor):
1007 v.class_init(cls, k) # type:ignore[arg-type]
1009 for _, v in getmembers(cls):
1010 if isinstance(v, BaseDescriptor):
1011 v.subclass_init(cls) # type:ignore[arg-type]
1012 cls._descriptors.append(v)
1015class MetaHasTraits(MetaHasDescriptors):
1016 """A metaclass for HasTraits."""
1018 def setup_class(cls: MetaHasTraits, classdict: dict[str, t.Any]) -> None:
1019 # for only the current class
1020 cls._trait_default_generators: dict[str, t.Any] = {}
1021 # also looking at base classes
1022 cls._all_trait_default_generators = {}
1023 cls._traits = {}
1024 cls._static_immutable_initial_values = {}
1026 super().setup_class(classdict)
1028 mro = cls.mro()
1030 for name in dir(cls):
1031 # Some descriptors raise AttributeError like zope.interface's
1032 # __provides__ attributes even though they exist. This causes
1033 # AttributeErrors even though they are listed in dir(cls).
1034 try:
1035 value = getattr(cls, name)
1036 except AttributeError:
1037 continue
1038 if isinstance(value, TraitType):
1039 cls._traits[name] = value
1040 trait = value
1041 default_method_name = f"_{name}_default"
1042 mro_trait = mro
1043 try:
1044 mro_trait = mro[: mro.index(trait.this_class) + 1] # type:ignore[arg-type]
1045 except ValueError:
1046 # this_class not in mro
1047 pass
1048 for c in mro_trait:
1049 if default_method_name in c.__dict__:
1050 cls._all_trait_default_generators[name] = c.__dict__[default_method_name]
1051 break
1052 if name in c.__dict__.get("_trait_default_generators", {}):
1053 cls._all_trait_default_generators[name] = c._trait_default_generators[name] # type: ignore[attr-defined]
1054 break
1055 else:
1056 # We don't have a dynamic default generator using @default etc.
1057 # Now if the default value is not dynamic and immutable (string, number)
1058 # and does not require any validation, we keep them in a dict
1059 # of initial values to speed up instance creation.
1060 # This is a very specific optimization, but a very common scenario in
1061 # for instance ipywidgets.
1062 none_ok = trait.default_value is None and trait.allow_none
1063 if (
1064 type(trait) in [CInt, Int, Long, CLong]
1065 and trait.min is None # type: ignore[attr-defined]
1066 and trait.max is None # type: ignore[attr-defined]
1067 and (isinstance(trait.default_value, int) or none_ok)
1068 ):
1069 cls._static_immutable_initial_values[name] = trait.default_value
1070 elif (
1071 type(trait) in [CFloat, Float]
1072 and trait.min is None # type: ignore[attr-defined]
1073 and trait.max is None # type: ignore[attr-defined]
1074 and (isinstance(trait.default_value, float) or none_ok)
1075 ):
1076 cls._static_immutable_initial_values[name] = trait.default_value
1077 elif type(trait) in [CBool, Bool] and (
1078 isinstance(trait.default_value, bool) or none_ok
1079 ):
1080 cls._static_immutable_initial_values[name] = trait.default_value
1081 elif type(trait) in [CUnicode, Unicode] and (
1082 isinstance(trait.default_value, str) or none_ok
1083 ):
1084 cls._static_immutable_initial_values[name] = trait.default_value
1085 elif type(trait) is Any and (
1086 isinstance(trait.default_value, (str, int, float, bool)) or none_ok
1087 ):
1088 cls._static_immutable_initial_values[name] = trait.default_value
1089 elif type(trait) is Union and trait.default_value is None:
1090 cls._static_immutable_initial_values[name] = None
1091 elif (
1092 isinstance(trait, Instance)
1093 and trait.default_args is None
1094 and trait.default_kwargs is None
1095 and trait.allow_none
1096 ):
1097 cls._static_immutable_initial_values[name] = None
1099 # we always add it, because a class may change when we call add_trait
1100 # and then the instance may not have all the _static_immutable_initial_values
1101 cls._all_trait_default_generators[name] = trait.default
1104def observe(*names: Sentinel | str, type: str = "change") -> ObserveHandler:
1105 """A decorator which can be used to observe Traits on a class.
1107 The handler passed to the decorator will be called with one ``change``
1108 dict argument. The change dictionary at least holds a 'type' key and a
1109 'name' key, corresponding respectively to the type of notification and the
1110 name of the attribute that triggered the notification.
1112 Other keys may be passed depending on the value of 'type'. In the case
1113 where type is 'change', we also have the following keys:
1114 * ``owner`` : the HasTraits instance
1115 * ``old`` : the old value of the modified trait attribute
1116 * ``new`` : the new value of the modified trait attribute
1117 * ``name`` : the name of the modified trait attribute.
1119 Parameters
1120 ----------
1121 *names
1122 The str names of the Traits to observe on the object.
1123 type : str, kwarg-only
1124 The type of event to observe (e.g. 'change')
1125 """
1126 if not names:
1127 raise TypeError("Please specify at least one trait name to observe.")
1128 for name in names:
1129 if name is not All and not isinstance(name, str):
1130 raise TypeError(f"trait names to observe must be strings or All, not {name!r}")
1131 return ObserveHandler(names, type=type)
1134def observe_compat(func: FuncT) -> FuncT:
1135 """Backward-compatibility shim decorator for observers
1137 Use with:
1139 @observe('name')
1140 @observe_compat
1141 def _foo_changed(self, change):
1142 ...
1144 With this, `super()._foo_changed(self, name, old, new)` in subclasses will still work.
1145 Allows adoption of new observer API without breaking subclasses that override and super.
1146 """
1148 def compatible_observer(
1149 self: t.Any, change_or_name: str, old: t.Any = Undefined, new: t.Any = Undefined
1150 ) -> t.Any:
1151 if isinstance(change_or_name, dict): # type:ignore[unreachable]
1152 change = Bunch(change_or_name) # type:ignore[unreachable]
1153 else:
1154 clsname = self.__class__.__name__
1155 warn(
1156 f"A parent of {clsname}._{change_or_name}_changed has adopted the new (traitlets 4.1) @observe(change) API",
1157 DeprecationWarning,
1158 stacklevel=2,
1159 )
1160 change = Bunch(
1161 type="change",
1162 old=old,
1163 new=new,
1164 name=change_or_name,
1165 owner=self,
1166 )
1167 return func(self, change)
1169 return compatible_observer # type:ignore[return-value]
1172def validate(*names: Sentinel | str) -> ValidateHandler:
1173 """A decorator to register cross validator of HasTraits object's state
1174 when a Trait is set.
1176 The handler passed to the decorator must have one ``proposal`` dict argument.
1177 The proposal dictionary must hold the following keys:
1179 * ``owner`` : the HasTraits instance
1180 * ``value`` : the proposed value for the modified trait attribute
1181 * ``trait`` : the TraitType instance associated with the attribute
1183 Parameters
1184 ----------
1185 *names
1186 The str names of the Traits to validate.
1188 Notes
1189 -----
1190 Since the owner has access to the ``HasTraits`` instance via the 'owner' key,
1191 the registered cross validator could potentially make changes to attributes
1192 of the ``HasTraits`` instance. However, we recommend not to do so. The reason
1193 is that the cross-validation of attributes may run in arbitrary order when
1194 exiting the ``hold_trait_notifications`` context, and such changes may not
1195 commute.
1196 """
1197 if not names:
1198 raise TypeError("Please specify at least one trait name to validate.")
1199 for name in names:
1200 if name is not All and not isinstance(name, str):
1201 raise TypeError(f"trait names to validate must be strings or All, not {name!r}")
1202 return ValidateHandler(names)
1205def default(name: str) -> DefaultHandler:
1206 """A decorator which assigns a dynamic default for a Trait on a HasTraits object.
1208 Parameters
1209 ----------
1210 name
1211 The str name of the Trait on the object whose default should be generated.
1213 Notes
1214 -----
1215 Unlike observers and validators which are properties of the HasTraits
1216 instance, default value generators are class-level properties.
1218 Besides, default generators are only invoked if they are registered in
1219 subclasses of `this_type`.
1221 ::
1223 class A(HasTraits):
1224 bar = Int()
1226 @default('bar')
1227 def get_bar_default(self):
1228 return 11
1230 class B(A):
1231 bar = Float() # This trait ignores the default generator defined in
1232 # the base class A
1234 class C(B):
1236 @default('bar')
1237 def some_other_default(self): # This default generator should not be
1238 return 3.0 # ignored since it is defined in a
1239 # class derived from B.a.this_class.
1240 """
1241 if not isinstance(name, str):
1242 raise TypeError(f"Trait name must be a string or All, not {name!r}")
1243 return DefaultHandler(name)
1246FuncT = t.TypeVar("FuncT", bound=t.Callable[..., t.Any])
1249class EventHandler(BaseDescriptor):
1250 def _init_call(self, func: FuncT) -> EventHandler:
1251 self.func = func
1252 return self
1254 @t.overload
1255 def __call__(self, func: FuncT, *args: t.Any, **kwargs: t.Any) -> FuncT: ...
1257 @t.overload
1258 def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: ...
1260 def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
1261 """Pass `*args` and `**kwargs` to the handler's function if it exists."""
1262 if hasattr(self, "func"):
1263 return self.func(*args, **kwargs)
1264 else:
1265 return self._init_call(*args, **kwargs)
1267 def __get__(self, inst: t.Any, cls: t.Any = None) -> types.MethodType | EventHandler:
1268 if inst is None:
1269 return self
1270 return types.MethodType(self.func, inst)
1273class ObserveHandler(EventHandler):
1274 def __init__(self, names: tuple[Sentinel | str, ...], type: str = "") -> None:
1275 self.trait_names = names
1276 self.type = type
1278 def instance_init(self, inst: HasTraits) -> None:
1279 inst.observe(self, self.trait_names, type=self.type)
1282class ValidateHandler(EventHandler):
1283 def __init__(self, names: tuple[Sentinel | str, ...]) -> None:
1284 self.trait_names = names
1286 def instance_init(self, inst: HasTraits) -> None:
1287 inst._register_validator(self, self.trait_names)
1290class DefaultHandler(EventHandler):
1291 def __init__(self, name: str) -> None:
1292 self.trait_name = name
1294 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
1295 super().class_init(cls, name)
1296 cls._trait_default_generators[self.trait_name] = self
1299class HasDescriptors(metaclass=MetaHasDescriptors):
1300 """The base class for all classes that have descriptors."""
1302 def __new__(cls, /, *args: t.Any, **kwargs: t.Any) -> Self:
1303 # This is needed because object.__new__ only accepts
1304 # the cls argument.
1305 new_meth = super(HasDescriptors, cls).__new__
1306 if new_meth is object.__new__:
1307 inst = new_meth(cls)
1308 else:
1309 inst = new_meth(cls, *args, **kwargs)
1310 inst.setup_instance(*args, **kwargs)
1311 return inst
1313 def setup_instance(self, /, *args: t.Any, **kwargs: t.Any) -> None:
1314 """
1315 This is called **before** self.__init__ is called.
1316 """
1318 self._cross_validation_lock = False
1319 cls = self.__class__
1320 # Let descriptors performance initialization when a HasDescriptor
1321 # instance is created. This allows registration of observers and
1322 # default creations or other bookkeepings.
1323 # Note that descriptors can opt-out of this behavior by overriding
1324 # subclass_init.
1325 for init in cls._instance_inits:
1326 init(self)
1329class HasTraits(HasDescriptors, metaclass=MetaHasTraits):
1330 _trait_values: dict[str, t.Any]
1331 _static_immutable_initial_values: dict[str, t.Any]
1332 _trait_notifiers: dict[str | Sentinel, t.Any]
1333 _trait_validators: dict[str | Sentinel, t.Any]
1334 _cross_validation_lock: bool
1335 _traits: dict[str, t.Any]
1336 _all_trait_default_generators: dict[str, t.Any]
1338 def setup_instance(self, /, *args: t.Any, **kwargs: t.Any) -> None:
1339 # although we'd prefer to set only the initial values not present
1340 # in kwargs, we will overwrite them in `__init__`, and simply making
1341 # a copy of a dict is faster than checking for each key.
1342 self._trait_values = self._static_immutable_initial_values.copy()
1343 self._trait_notifiers = {}
1344 self._trait_validators = {}
1345 self._cross_validation_lock = False
1346 super(HasTraits, self).setup_instance(*args, **kwargs)
1348 def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
1349 # Allow trait values to be set using keyword arguments.
1350 # We need to use setattr for this to trigger validation and
1351 # notifications.
1352 super_args = args
1353 super_kwargs = {}
1355 if kwargs:
1356 # this is a simplified (and faster) version of
1357 # the hold_trait_notifications(self) context manager
1358 def ignore(change: Bunch) -> None:
1359 pass
1361 self.notify_change = ignore # type:ignore[method-assign]
1362 self._cross_validation_lock = True
1363 changes = {}
1364 for key, value in kwargs.items():
1365 if self.has_trait(key):
1366 setattr(self, key, value)
1367 changes[key] = Bunch(
1368 name=key,
1369 old=None,
1370 new=value,
1371 owner=self,
1372 type="change",
1373 )
1374 else:
1375 # passthrough args that don't set traits to super
1376 super_kwargs[key] = value
1377 # notify and cross validate all trait changes that were set in kwargs
1378 changed = set(kwargs) & set(self._traits)
1379 for key in changed:
1380 value = self._traits[key]._cross_validate(self, getattr(self, key))
1381 self.set_trait(key, value)
1382 changes[key]["new"] = value
1383 self._cross_validation_lock = False
1384 # Restore method retrieval from class
1385 del self.notify_change
1386 for key in changed:
1387 self.notify_change(changes[key])
1389 try:
1390 super().__init__(*super_args, **super_kwargs)
1391 except TypeError as e:
1392 arg_s_list = [repr(arg) for arg in super_args]
1393 for k, v in super_kwargs.items():
1394 arg_s_list.append(f"{k}={v!r}")
1395 arg_s = ", ".join(arg_s_list)
1396 warn(
1397 f"Passing unrecognized arguments to super({self.__class__.__name__}).__init__({arg_s}).\n"
1398 f"{e}\n"
1399 "This is deprecated in traitlets 4.2."
1400 "This error will be raised in a future release of traitlets.",
1401 DeprecationWarning,
1402 stacklevel=2,
1403 )
1405 def __getstate__(self) -> dict[str, t.Any]:
1406 d = self.__dict__.copy()
1407 # event handlers stored on an instance are
1408 # expected to be reinstantiated during a
1409 # recall of instance_init during __setstate__
1410 d["_trait_notifiers"] = {}
1411 d["_trait_validators"] = {}
1412 d["_trait_values"] = self._trait_values.copy()
1413 d["_cross_validation_lock"] = False # FIXME: raise if cloning locked!
1415 return d
1417 def __setstate__(self, state: dict[str, t.Any]) -> None:
1418 self.__dict__ = state.copy()
1420 # event handlers are reassigned to self
1421 cls = self.__class__
1422 for key in dir(cls):
1423 # Some descriptors raise AttributeError like zope.interface's
1424 # __provides__ attributes even though they exist. This causes
1425 # AttributeErrors even though they are listed in dir(cls).
1426 try:
1427 value = getattr(cls, key)
1428 except AttributeError:
1429 pass
1430 else:
1431 if isinstance(value, EventHandler):
1432 value.instance_init(self)
1434 @property
1435 @contextlib.contextmanager
1436 def cross_validation_lock(self) -> t.Any:
1437 """
1438 A contextmanager for running a block with our cross validation lock set
1439 to True.
1441 At the end of the block, the lock's value is restored to its value
1442 prior to entering the block.
1443 """
1444 if self._cross_validation_lock:
1445 yield
1446 return
1447 else:
1448 try:
1449 self._cross_validation_lock = True
1450 yield
1451 finally:
1452 self._cross_validation_lock = False
1454 @contextlib.contextmanager
1455 def hold_trait_notifications(self) -> t.Any:
1456 """Context manager for bundling trait change notifications and cross
1457 validation.
1459 Use this when doing multiple trait assignments (init, config), to avoid
1460 race conditions in trait notifiers requesting other trait values.
1461 All trait notifications will fire after all values have been assigned.
1462 """
1463 if self._cross_validation_lock:
1464 yield
1465 return
1466 else:
1467 cache: dict[str, list[Bunch]] = {}
1469 def compress(past_changes: list[Bunch] | None, change: Bunch) -> list[Bunch]:
1470 """Merges the provided change with the last if possible."""
1471 if past_changes is None:
1472 return [change]
1473 else:
1474 if past_changes[-1]["type"] == "change" and change.type == "change":
1475 past_changes[-1]["new"] = change.new
1476 else:
1477 # In case of changes other than 'change', append the notification.
1478 past_changes.append(change)
1479 return past_changes
1481 def hold(change: Bunch) -> None:
1482 name = change.name
1483 cache[name] = compress(cache.get(name), change)
1485 try:
1486 # Replace notify_change with `hold`, caching and compressing
1487 # notifications, disable cross validation and yield.
1488 self.notify_change = hold # type:ignore[method-assign]
1489 self._cross_validation_lock = True
1490 yield
1491 # Cross validate final values when context is released.
1492 for name in list(cache.keys()):
1493 trait = getattr(self.__class__, name)
1494 value = trait._cross_validate(self, getattr(self, name))
1495 self.set_trait(name, value)
1496 except TraitError as e:
1497 # Roll back in case of TraitError during final cross validation.
1498 self.notify_change = lambda x: None # type:ignore[method-assign, assignment] # noqa: ARG005
1499 for name, changes in cache.items():
1500 for change in changes[::-1]:
1501 # TODO: Separate in a rollback function per notification type.
1502 if change.type == "change":
1503 if change.old is not Undefined:
1504 self.set_trait(name, change.old)
1505 else:
1506 self._trait_values.pop(name)
1507 cache = {}
1508 raise e
1509 finally:
1510 self._cross_validation_lock = False
1511 # Restore method retrieval from class
1512 del self.notify_change
1514 # trigger delayed notifications
1515 for changes in cache.values():
1516 for change in changes:
1517 self.notify_change(change)
1519 def _notify_trait(self, name: str, old_value: t.Any, new_value: t.Any) -> None:
1520 self.notify_change(
1521 Bunch(
1522 name=name,
1523 old=old_value,
1524 new=new_value,
1525 owner=self,
1526 type="change",
1527 )
1528 )
1530 def notify_change(self, change: Bunch) -> None:
1531 """Notify observers of a change event"""
1532 return self._notify_observers(change)
1534 def _notify_observers(self, event: Bunch) -> None:
1535 """Notify observers of any event"""
1536 if not isinstance(event, Bunch):
1537 # cast to bunch if given a dict
1538 event = Bunch(event) # type:ignore[unreachable]
1539 name, type = event["name"], event["type"]
1541 callables = []
1542 if name in self._trait_notifiers:
1543 callables.extend(self._trait_notifiers.get(name, {}).get(type, []))
1544 callables.extend(self._trait_notifiers.get(name, {}).get(All, []))
1545 if All in self._trait_notifiers:
1546 callables.extend(self._trait_notifiers.get(All, {}).get(type, []))
1547 callables.extend(self._trait_notifiers.get(All, {}).get(All, []))
1549 # Now static ones
1550 magic_name = f"_{name}_changed"
1551 if event["type"] == "change" and hasattr(self, magic_name):
1552 class_value = getattr(self.__class__, magic_name)
1553 if not isinstance(class_value, ObserveHandler):
1554 deprecated_method(
1555 class_value,
1556 self.__class__,
1557 magic_name,
1558 "use @observe and @unobserve instead.",
1559 )
1560 cb = getattr(self, magic_name)
1561 # Only append the magic method if it was not manually registered
1562 if cb not in callables:
1563 callables.append(_callback_wrapper(cb))
1565 # Call them all now
1566 # Traits catches and logs errors here. I allow them to raise
1567 for c in callables:
1568 # Bound methods have an additional 'self' argument.
1570 if isinstance(c, _CallbackWrapper):
1571 c = c.__call__
1572 elif isinstance(c, EventHandler) and c.name is not None:
1573 c = getattr(self, c.name)
1575 c(event)
1577 def _add_notifiers(
1578 self, handler: t.Callable[..., t.Any], name: Sentinel | str, type: str | Sentinel
1579 ) -> None:
1580 if name not in self._trait_notifiers:
1581 nlist: list[t.Any] = []
1582 self._trait_notifiers[name] = {type: nlist}
1583 else:
1584 if type not in self._trait_notifiers[name]:
1585 nlist = []
1586 self._trait_notifiers[name][type] = nlist
1587 else:
1588 nlist = self._trait_notifiers[name][type]
1589 if handler not in nlist:
1590 nlist.append(handler)
1592 def _remove_notifiers(
1593 self, handler: t.Callable[..., t.Any] | None, name: Sentinel | str, type: str | Sentinel
1594 ) -> None:
1595 try:
1596 if handler is None:
1597 del self._trait_notifiers[name][type]
1598 else:
1599 self._trait_notifiers[name][type].remove(handler)
1600 except KeyError:
1601 pass
1603 def on_trait_change(
1604 self,
1605 handler: EventHandler | None = None,
1606 name: Sentinel | str | None = None,
1607 remove: bool = False,
1608 ) -> None:
1609 """DEPRECATED: Setup a handler to be called when a trait changes.
1611 This is used to setup dynamic notifications of trait changes.
1613 Static handlers can be created by creating methods on a HasTraits
1614 subclass with the naming convention '_[traitname]_changed'. Thus,
1615 to create static handler for the trait 'a', create the method
1616 _a_changed(self, name, old, new) (fewer arguments can be used, see
1617 below).
1619 If `remove` is True and `handler` is not specified, all change
1620 handlers for the specified name are uninstalled.
1622 Parameters
1623 ----------
1624 handler : callable, None
1625 A callable that is called when a trait changes. Its
1626 signature can be handler(), handler(name), handler(name, new),
1627 handler(name, old, new), or handler(name, old, new, self).
1628 name : list, str, None
1629 If None, the handler will apply to all traits. If a list
1630 of str, handler will apply to all names in the list. If a
1631 str, the handler will apply just to that name.
1632 remove : bool
1633 If False (the default), then install the handler. If True
1634 then unintall it.
1635 """
1636 warn(
1637 "on_trait_change is deprecated in traitlets 4.1: use observe instead",
1638 DeprecationWarning,
1639 stacklevel=2,
1640 )
1641 if name is None:
1642 name = All
1643 if remove:
1644 self.unobserve(_callback_wrapper(handler), names=name)
1645 else:
1646 self.observe(_callback_wrapper(handler), names=name)
1648 def observe(
1649 self,
1650 handler: t.Callable[..., t.Any],
1651 names: Sentinel | str | t.Collection[Sentinel | str] = All,
1652 type: Sentinel | str = "change",
1653 ) -> None:
1654 """Setup a handler to be called when a trait changes.
1656 This is used to setup dynamic notifications of trait changes.
1658 Parameters
1659 ----------
1660 handler : callable
1661 A callable that is called when a trait changes. Its
1662 signature should be ``handler(change)``, where ``change`` is a
1663 dictionary. The change dictionary at least holds a 'type' key.
1664 * ``type``: the type of notification.
1665 Other keys may be passed depending on the value of 'type'. In the
1666 case where type is 'change', we also have the following keys:
1667 * ``owner`` : the HasTraits instance
1668 * ``old`` : the old value of the modified trait attribute
1669 * ``new`` : the new value of the modified trait attribute
1670 * ``name`` : the name of the modified trait attribute.
1671 names : list, str, All
1672 If names is All, the handler will apply to all traits. If a list
1673 of str, handler will apply to all names in the list. If a
1674 str, the handler will apply just to that name.
1675 type : str, All (default: 'change')
1676 The type of notification to filter by. If equal to All, then all
1677 notifications are passed to the observe handler.
1678 """
1679 for name in parse_notifier_name(names):
1680 self._add_notifiers(handler, name, type)
1682 def unobserve(
1683 self,
1684 handler: t.Callable[..., t.Any],
1685 names: Sentinel | str | t.Collection[Sentinel | str] = All,
1686 type: Sentinel | str = "change",
1687 ) -> None:
1688 """Remove a trait change handler.
1690 This is used to unregister handlers to trait change notifications.
1692 Parameters
1693 ----------
1694 handler : callable
1695 The callable called when a trait attribute changes.
1696 names : list, str, All (default: All)
1697 The names of the traits for which the specified handler should be
1698 uninstalled. If names is All, the specified handler is uninstalled
1699 from the list of notifiers corresponding to all changes.
1700 type : str or All (default: 'change')
1701 The type of notification to filter by. If All, the specified handler
1702 is uninstalled from the list of notifiers corresponding to all types.
1703 """
1704 for name in parse_notifier_name(names):
1705 self._remove_notifiers(handler, name, type)
1707 def unobserve_all(self, name: str | t.Any = All) -> None:
1708 """Remove trait change handlers of any type for the specified name.
1709 If name is not specified, removes all trait notifiers."""
1710 if name is All:
1711 self._trait_notifiers = {}
1712 else:
1713 try:
1714 del self._trait_notifiers[name]
1715 except KeyError:
1716 pass
1718 def _register_validator(
1719 self, handler: t.Callable[..., None], names: tuple[str | Sentinel, ...]
1720 ) -> None:
1721 """Setup a handler to be called when a trait should be cross validated.
1723 This is used to setup dynamic notifications for cross-validation.
1725 If a validator is already registered for any of the provided names, a
1726 TraitError is raised and no new validator is registered.
1728 Parameters
1729 ----------
1730 handler : callable
1731 A callable that is called when the given trait is cross-validated.
1732 Its signature is handler(proposal), where proposal is a Bunch (dictionary with attribute access)
1733 with the following attributes/keys:
1734 * ``owner`` : the HasTraits instance
1735 * ``value`` : the proposed value for the modified trait attribute
1736 * ``trait`` : the TraitType instance associated with the attribute
1737 names : List of strings
1738 The names of the traits that should be cross-validated
1739 """
1740 for name in names:
1741 magic_name = f"_{name}_validate"
1742 if hasattr(self, magic_name):
1743 class_value = getattr(self.__class__, magic_name)
1744 if not isinstance(class_value, ValidateHandler):
1745 deprecated_method(
1746 class_value,
1747 self.__class__,
1748 magic_name,
1749 "use @validate decorator instead.",
1750 )
1751 for name in names:
1752 self._trait_validators[name] = handler
1754 def add_traits(self, **traits: t.Any) -> None:
1755 """Dynamically add trait attributes to the HasTraits instance."""
1756 cls = self.__class__
1757 attrs = {"__module__": cls.__module__}
1758 if hasattr(cls, "__qualname__"):
1759 # __qualname__ introduced in Python 3.3 (see PEP 3155)
1760 attrs["__qualname__"] = cls.__qualname__
1761 attrs.update(traits)
1762 self.__class__ = type(cls.__name__, (cls,), attrs)
1763 for trait in traits.values():
1764 trait.instance_init(self)
1766 def set_trait(self, name: str, value: t.Any) -> None:
1767 """Forcibly sets trait attribute, including read-only attributes."""
1768 cls = self.__class__
1769 if not self.has_trait(name):
1770 raise TraitError(f"Class {cls.__name__} does not have a trait named {name}")
1771 getattr(cls, name).set(self, value)
1773 @classmethod
1774 def class_trait_names(cls: type[HasTraits], **metadata: t.Any) -> list[str]:
1775 """Get a list of all the names of this class' traits.
1777 This method is just like the :meth:`trait_names` method,
1778 but is unbound.
1779 """
1780 return list(cls.class_traits(**metadata))
1782 @classmethod
1783 def class_traits(cls: type[HasTraits], **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]:
1784 """Get a ``dict`` of all the traits of this class. The dictionary
1785 is keyed on the name and the values are the TraitType objects.
1787 This method is just like the :meth:`traits` method, but is unbound.
1789 The TraitTypes returned don't know anything about the values
1790 that the various HasTrait's instances are holding.
1792 The metadata kwargs allow functions to be passed in which
1793 filter traits based on metadata values. The functions should
1794 take a single value as an argument and return a boolean. If
1795 any function returns False, then the trait is not included in
1796 the output. If a metadata key doesn't exist, None will be passed
1797 to the function.
1798 """
1799 traits = cls._traits.copy()
1801 if len(metadata) == 0:
1802 return traits
1804 result = {}
1805 for name, trait in traits.items():
1806 for meta_name, meta_eval in metadata.items():
1807 if not callable(meta_eval):
1808 meta_eval = _SimpleTest(meta_eval)
1809 if not meta_eval(trait.metadata.get(meta_name, None)):
1810 break
1811 else:
1812 result[name] = trait
1814 return result
1816 @classmethod
1817 def class_own_traits(
1818 cls: type[HasTraits], **metadata: t.Any
1819 ) -> dict[str, TraitType[t.Any, t.Any]]:
1820 """Get a dict of all the traitlets defined on this class, not a parent.
1822 Works like `class_traits`, except for excluding traits from parents.
1823 """
1824 sup = super(cls, cls)
1825 return {
1826 n: t
1827 for (n, t) in cls.class_traits(**metadata).items()
1828 if getattr(sup, n, None) is not t
1829 }
1831 def has_trait(self, name: str) -> bool:
1832 """Returns True if the object has a trait with the specified name."""
1833 return name in self._traits
1835 def trait_has_value(self, name: str) -> bool:
1836 """Returns True if the specified trait has a value.
1838 This will return false even if ``getattr`` would return a
1839 dynamically generated default value. These default values
1840 will be recognized as existing only after they have been
1841 generated.
1843 Example
1845 .. code-block:: python
1847 class MyClass(HasTraits):
1848 i = Int()
1851 mc = MyClass()
1852 assert not mc.trait_has_value("i")
1853 mc.i # generates a default value
1854 assert mc.trait_has_value("i")
1855 """
1856 return name in self._trait_values
1858 def trait_values(self, **metadata: t.Any) -> dict[str, t.Any]:
1859 """A ``dict`` of trait names and their values.
1861 The metadata kwargs allow functions to be passed in which
1862 filter traits based on metadata values. The functions should
1863 take a single value as an argument and return a boolean. If
1864 any function returns False, then the trait is not included in
1865 the output. If a metadata key doesn't exist, None will be passed
1866 to the function.
1868 Returns
1869 -------
1870 A ``dict`` of trait names and their values.
1872 Notes
1873 -----
1874 Trait values are retrieved via ``getattr``, any exceptions raised
1875 by traits or the operations they may trigger will result in the
1876 absence of a trait value in the result ``dict``.
1877 """
1878 return {name: getattr(self, name) for name in self.trait_names(**metadata)}
1880 def _get_trait_default_generator(self, name: str) -> t.Any:
1881 """Return default generator for a given trait
1883 Walk the MRO to resolve the correct default generator according to inheritance.
1884 """
1885 method_name = f"_{name}_default"
1886 if method_name in self.__dict__:
1887 return getattr(self, method_name)
1888 if method_name in self.__class__.__dict__:
1889 return getattr(self.__class__, method_name)
1890 return self._all_trait_default_generators[name]
1892 def trait_defaults(self, *names: str, **metadata: t.Any) -> dict[str, t.Any] | Sentinel:
1893 """Return a trait's default value or a dictionary of them
1895 Notes
1896 -----
1897 Dynamically generated default values may
1898 depend on the current state of the object."""
1899 for n in names:
1900 if not self.has_trait(n):
1901 raise TraitError(f"'{n}' is not a trait of '{type(self).__name__}' instances")
1903 if len(names) == 1 and len(metadata) == 0:
1904 return self._get_trait_default_generator(names[0])(self) # type:ignore[no-any-return]
1906 trait_names = self.trait_names(**metadata)
1907 trait_names.extend(names)
1909 defaults = {}
1910 for n in trait_names:
1911 defaults[n] = self._get_trait_default_generator(n)(self)
1912 return defaults
1914 def trait_names(self, **metadata: t.Any) -> list[str]:
1915 """Get a list of all the names of this class' traits."""
1916 return list(self.traits(**metadata))
1918 def traits(self, **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]:
1919 """Get a ``dict`` of all the traits of this class. The dictionary
1920 is keyed on the name and the values are the TraitType objects.
1922 The TraitTypes returned don't know anything about the values
1923 that the various HasTrait's instances are holding.
1925 The metadata kwargs allow functions to be passed in which
1926 filter traits based on metadata values. The functions should
1927 take a single value as an argument and return a boolean. If
1928 any function returns False, then the trait is not included in
1929 the output. If a metadata key doesn't exist, None will be passed
1930 to the function.
1931 """
1932 traits = self._traits.copy()
1934 if len(metadata) == 0:
1935 return traits
1937 result = {}
1938 for name, trait in traits.items():
1939 for meta_name, meta_eval in metadata.items():
1940 if not callable(meta_eval):
1941 meta_eval = _SimpleTest(meta_eval)
1942 if not meta_eval(trait.metadata.get(meta_name, None)):
1943 break
1944 else:
1945 result[name] = trait
1947 return result
1949 def trait_metadata(self, traitname: str, key: str, default: t.Any = None) -> t.Any:
1950 """Get metadata values for trait by key."""
1951 try:
1952 trait = getattr(self.__class__, traitname)
1953 except AttributeError as e:
1954 raise TraitError(
1955 f"Class {self.__class__.__name__} does not have a trait named {traitname}"
1956 ) from e
1957 metadata_name = "_" + traitname + "_metadata"
1958 if hasattr(self, metadata_name) and key in getattr(self, metadata_name):
1959 return getattr(self, metadata_name).get(key, default)
1960 else:
1961 return trait.metadata.get(key, default)
1963 @classmethod
1964 def class_own_trait_events(cls: type[HasTraits], name: str) -> dict[str, EventHandler]:
1965 """Get a dict of all event handlers defined on this class, not a parent.
1967 Works like ``trait_events``, except for excluding traits from parents.
1968 """
1969 sup = super(cls, cls)
1970 return {n: e for (n, e) in cls.trait_events(name).items() if getattr(sup, n, None) is not e}
1972 @classmethod
1973 def trait_events(cls: type[HasTraits], name: str | None = None) -> dict[str, EventHandler]:
1974 """Get a ``dict`` of all the event handlers of this class.
1976 Parameters
1977 ----------
1978 name : str (default: None)
1979 The name of a trait of this class. If name is ``None`` then all
1980 the event handlers of this class will be returned instead.
1982 Returns
1983 -------
1984 The event handlers associated with a trait name, or all event handlers.
1985 """
1986 events = {}
1987 for k, v in getmembers(cls):
1988 if isinstance(v, EventHandler):
1989 if name is None:
1990 events[k] = v
1991 elif name in v.trait_names: # type:ignore[attr-defined]
1992 events[k] = v
1993 return events
1996# -----------------------------------------------------------------------------
1997# Actual TraitTypes implementations/subclasses
1998# -----------------------------------------------------------------------------
2000# -----------------------------------------------------------------------------
2001# TraitTypes subclasses for handling classes and instances of classes
2002# -----------------------------------------------------------------------------
2005class ClassBasedTraitType(TraitType[G, S]):
2006 """
2007 A trait with error reporting and string -> type resolution for Type,
2008 Instance and This.
2009 """
2011 def _resolve_string(self, string: str) -> t.Any:
2012 """
2013 Resolve a string supplied for a type into an actual object.
2014 """
2015 return import_item(string)
2018class Type(ClassBasedTraitType[G, S]):
2019 """A trait whose value must be a subclass of a specified class."""
2021 if t.TYPE_CHECKING:
2023 @t.overload
2024 def __init__(
2025 self: Type[type, type],
2026 default_value: Sentinel | None | str = ...,
2027 klass: None | str = ...,
2028 allow_none: Literal[False] = ...,
2029 read_only: bool | None = ...,
2030 help: str | None = ...,
2031 config: t.Any | None = ...,
2032 **kwargs: t.Any,
2033 ) -> None: ...
2035 @t.overload
2036 def __init__(
2037 self: Type[type | None, type | None],
2038 default_value: Sentinel | None | str = ...,
2039 klass: None | str = ...,
2040 allow_none: Literal[True] = ...,
2041 read_only: bool | None = ...,
2042 help: str | None = ...,
2043 config: t.Any | None = ...,
2044 **kwargs: t.Any,
2045 ) -> None: ...
2047 @t.overload
2048 def __init__(
2049 self: Type[S, S],
2050 default_value: S = ...,
2051 klass: S = ...,
2052 allow_none: Literal[False] = ...,
2053 read_only: bool | None = ...,
2054 help: str | None = ...,
2055 config: t.Any | None = ...,
2056 **kwargs: t.Any,
2057 ) -> None: ...
2059 @t.overload
2060 def __init__(
2061 self: Type[S | None, S | None],
2062 default_value: S | None = ...,
2063 klass: S = ...,
2064 allow_none: Literal[True] = ...,
2065 read_only: bool | None = ...,
2066 help: str | None = ...,
2067 config: t.Any | None = ...,
2068 **kwargs: t.Any,
2069 ) -> None: ...
2071 def __init__(
2072 self,
2073 default_value: t.Any = Undefined,
2074 klass: t.Any = None,
2075 allow_none: bool = False,
2076 read_only: bool | None = None,
2077 help: str | None = None,
2078 config: t.Any | None = None,
2079 **kwargs: t.Any,
2080 ) -> None:
2081 """Construct a Type trait
2083 A Type trait specifies that its values must be subclasses of
2084 a particular class.
2086 If only ``default_value`` is given, it is used for the ``klass`` as
2087 well. If neither are given, both default to ``object``.
2089 Parameters
2090 ----------
2091 default_value : class, str or None
2092 The default value must be a subclass of klass. If an str,
2093 the str must be a fully specified class name, like 'foo.bar.Bah'.
2094 The string is resolved into real class, when the parent
2095 :class:`HasTraits` class is instantiated.
2096 klass : class, str [ default object ]
2097 Values of this trait must be a subclass of klass. The klass
2098 may be specified in a string like: 'foo.bar.MyClass'.
2099 The string is resolved into real class, when the parent
2100 :class:`HasTraits` class is instantiated.
2101 allow_none : bool [ default False ]
2102 Indicates whether None is allowed as an assignable value.
2103 **kwargs
2104 extra kwargs passed to `ClassBasedTraitType`
2105 """
2106 if default_value is Undefined:
2107 new_default_value = object if (klass is None) else klass
2108 else:
2109 new_default_value = default_value
2111 if klass is None:
2112 if (default_value is None) or (default_value is Undefined):
2113 klass = object
2114 else:
2115 klass = default_value
2117 if not (inspect.isclass(klass) or isinstance(klass, str)):
2118 raise TraitError("A Type trait must specify a class.")
2120 self.klass = klass
2122 super().__init__(
2123 new_default_value,
2124 allow_none=allow_none,
2125 read_only=read_only,
2126 help=help,
2127 config=config,
2128 **kwargs,
2129 )
2131 def validate(self, obj: t.Any, value: t.Any) -> G:
2132 """Validates that the value is a valid object instance."""
2133 if isinstance(value, str):
2134 try:
2135 value = self._resolve_string(value)
2136 except ImportError as e:
2137 raise TraitError(
2138 f"The '{self.name}' trait of {obj} instance must be a type, but "
2139 f"{value!r} could not be imported"
2140 ) from e
2141 try:
2142 if issubclass(value, self.klass): # type:ignore[arg-type]
2143 return value # type:ignore[no-any-return]
2144 except Exception:
2145 pass
2147 self.error(obj, value)
2149 def info(self) -> str:
2150 """Returns a description of the trait."""
2151 if isinstance(self.klass, str):
2152 klass = self.klass
2153 else:
2154 klass = self.klass.__module__ + "." + self.klass.__name__
2155 result = f"a subclass of '{klass}'"
2156 if self.allow_none:
2157 return result + " or None"
2158 return result
2160 def instance_init(self, obj: t.Any) -> None:
2161 # we can't do this in subclass_init because that
2162 # might be called before all imports are done.
2163 self._resolve_classes()
2165 def _resolve_classes(self) -> None:
2166 if isinstance(self.klass, str):
2167 self.klass = self._resolve_string(self.klass)
2168 if isinstance(self.default_value, str):
2169 self.default_value = self._resolve_string(self.default_value)
2171 def default_value_repr(self) -> str:
2172 value = self.default_value
2173 assert value is not None
2174 if isinstance(value, str):
2175 return repr(value)
2176 else:
2177 return repr(f"{value.__module__}.{value.__name__}")
2180class Instance(ClassBasedTraitType[T, T]):
2181 """A trait whose value must be an instance of a specified class.
2183 The value can also be an instance of a subclass of the specified class.
2185 Subclasses can declare default classes by overriding the klass attribute
2186 """
2188 klass: str | type[T] | None = None
2190 if t.TYPE_CHECKING:
2192 @t.overload
2193 def __init__(
2194 self: Instance[T],
2195 klass: type[T] = ...,
2196 args: tuple[t.Any, ...] | None = ...,
2197 kw: dict[str, t.Any] | None = ...,
2198 allow_none: Literal[False] = ...,
2199 read_only: bool | None = ...,
2200 help: str | None = ...,
2201 **kwargs: t.Any,
2202 ) -> None: ...
2204 @t.overload
2205 def __init__(
2206 self: Instance[T | None],
2207 klass: type[T] = ...,
2208 args: tuple[t.Any, ...] | None = ...,
2209 kw: dict[str, t.Any] | None = ...,
2210 allow_none: Literal[True] = ...,
2211 read_only: bool | None = ...,
2212 help: str | None = ...,
2213 **kwargs: t.Any,
2214 ) -> None: ...
2216 @t.overload
2217 def __init__(
2218 self: Instance[t.Any],
2219 klass: str | None = ...,
2220 args: tuple[t.Any, ...] | None = ...,
2221 kw: dict[str, t.Any] | None = ...,
2222 allow_none: Literal[False] = ...,
2223 read_only: bool | None = ...,
2224 help: str | None = ...,
2225 **kwargs: t.Any,
2226 ) -> None: ...
2228 @t.overload
2229 def __init__(
2230 self: Instance[t.Any | None],
2231 klass: str | None = ...,
2232 args: tuple[t.Any, ...] | None = ...,
2233 kw: dict[str, t.Any] | None = ...,
2234 allow_none: Literal[True] = ...,
2235 read_only: bool | None = ...,
2236 help: str | None = ...,
2237 **kwargs: t.Any,
2238 ) -> None: ...
2240 def __init__(
2241 self,
2242 klass: str | type[T] | None = None,
2243 args: tuple[t.Any, ...] | None = None,
2244 kw: dict[str, t.Any] | None = None,
2245 allow_none: bool = False,
2246 read_only: bool | None = None,
2247 help: str | None = None,
2248 **kwargs: t.Any,
2249 ) -> None:
2250 """Construct an Instance trait.
2252 This trait allows values that are instances of a particular
2253 class or its subclasses. Our implementation is quite different
2254 from that of enthough.traits as we don't allow instances to be used
2255 for klass and we handle the ``args`` and ``kw`` arguments differently.
2257 Parameters
2258 ----------
2259 klass : class, str
2260 The class that forms the basis for the trait. Class names
2261 can also be specified as strings, like 'foo.bar.Bar'.
2262 args : tuple
2263 Positional arguments for generating the default value.
2264 kw : dict
2265 Keyword arguments for generating the default value.
2266 allow_none : bool [ default False ]
2267 Indicates whether None is allowed as a value.
2268 **kwargs
2269 Extra kwargs passed to `ClassBasedTraitType`
2271 Notes
2272 -----
2273 If both ``args`` and ``kw`` are None, then the default value is None.
2274 If ``args`` is a tuple and ``kw`` is a dict, then the default is
2275 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
2276 None, the None is replaced by ``()`` or ``{}``, respectively.
2277 """
2278 if klass is None:
2279 klass = self.klass
2281 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, str)):
2282 self.klass = klass
2283 else:
2284 raise TraitError(f"The klass attribute must be a class not: {klass!r}")
2286 if (kw is not None) and not isinstance(kw, dict):
2287 raise TraitError("The 'kw' argument must be a dict or None.")
2288 if (args is not None) and not isinstance(args, tuple):
2289 raise TraitError("The 'args' argument must be a tuple or None.")
2291 self.default_args = args
2292 self.default_kwargs = kw
2294 super().__init__(allow_none=allow_none, read_only=read_only, help=help, **kwargs)
2296 def validate(self, obj: t.Any, value: t.Any) -> T | None:
2297 assert self.klass is not None
2298 if self.allow_none and value is None:
2299 return value
2300 if isinstance(value, self.klass): # type:ignore[arg-type]
2301 return value
2302 else:
2303 self.error(obj, value)
2305 def info(self) -> str:
2306 if isinstance(self.klass, str):
2307 result = add_article(self.klass)
2308 else:
2309 result = describe("a", self.klass)
2310 if self.allow_none:
2311 result += " or None"
2312 return result
2314 def instance_init(self, obj: t.Any) -> None:
2315 # we can't do this in subclass_init because that
2316 # might be called before all imports are done.
2317 self._resolve_classes()
2319 def _resolve_classes(self) -> None:
2320 if isinstance(self.klass, str):
2321 self.klass = self._resolve_string(self.klass)
2323 def make_dynamic_default(self) -> T | None:
2324 if (self.default_args is None) and (self.default_kwargs is None):
2325 return None
2326 assert self.klass is not None
2327 return self.klass(*(self.default_args or ()), **(self.default_kwargs or {})) # type:ignore[operator]
2329 def default_value_repr(self) -> str:
2330 return repr(self.make_dynamic_default())
2332 def from_string(self, s: str) -> T | None:
2333 return _safe_literal_eval(s) # type:ignore[no-any-return]
2336class ForwardDeclaredMixin:
2337 """
2338 Mixin for forward-declared versions of Instance and Type.
2339 """
2341 def _resolve_string(self, string: str) -> t.Any:
2342 """
2343 Find the specified class name by looking for it in the module in which
2344 our this_class attribute was defined.
2345 """
2346 modname = self.this_class.__module__ # type:ignore[attr-defined]
2347 return import_item(".".join([modname, string]))
2350class ForwardDeclaredType(ForwardDeclaredMixin, Type[G, S]):
2351 """
2352 Forward-declared version of Type.
2353 """
2356class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance[T]):
2357 """
2358 Forward-declared version of Instance.
2359 """
2362class This(ClassBasedTraitType[T | None, T | None]):
2363 """A trait for instances of the class containing this trait.
2365 Because how how and when class bodies are executed, the ``This``
2366 trait can only have a default value of None. This, and because we
2367 always validate default values, ``allow_none`` is *always* true.
2368 """
2370 info_text = "an instance of the same type as the receiver or None"
2372 def __init__(self, **kwargs: t.Any) -> None:
2373 super().__init__(None, **kwargs)
2375 def validate(self, obj: t.Any, value: t.Any) -> HasTraits | None:
2376 # What if value is a superclass of obj.__class__? This is
2377 # complicated if it was the superclass that defined the This
2378 # trait.
2379 assert self.this_class is not None
2380 if isinstance(value, self.this_class) or (value is None):
2381 return value
2382 else:
2383 self.error(obj, value)
2386class Union(TraitType[t.Any, t.Any]):
2387 """A trait type representing a Union type."""
2389 def __init__(self, trait_types: t.Any, **kwargs: t.Any) -> None:
2390 """Construct a Union trait.
2392 This trait allows values that are allowed by at least one of the
2393 specified trait types. A Union traitlet cannot have metadata on
2394 its own, besides the metadata of the listed types.
2396 Parameters
2397 ----------
2398 trait_types : sequence
2399 The list of trait types of length at least 1.
2400 **kwargs
2401 Extra kwargs passed to `TraitType`
2403 Notes
2404 -----
2405 Union([Float(), Bool(), Int()]) attempts to validate the provided values
2406 with the validation function of Float, then Bool, and finally Int.
2408 Parsing from string is ambiguous for container types which accept other
2409 collection-like literals (e.g. List accepting both `[]` and `()`
2410 precludes Union from ever parsing ``Union([List(), Tuple()])`` as a tuple;
2411 you can modify behaviour of too permissive container traits by overriding
2412 ``_literal_from_string_pairs`` in subclasses.
2413 Similarly, parsing unions of numeric types is only unambiguous if
2414 types are provided in order of increasing permissiveness, e.g.
2415 ``Union([Int(), Float()])`` (since floats accept integer-looking values).
2416 """
2417 self.trait_types = list(trait_types)
2418 self.info_text = " or ".join([tt.info() for tt in self.trait_types])
2419 super().__init__(**kwargs)
2421 def default(self, obj: t.Any = None) -> t.Any:
2422 default = super().default(obj)
2423 for trait in self.trait_types:
2424 if default is Undefined:
2425 default = trait.default(obj)
2426 else:
2427 break
2428 return default
2430 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
2431 for trait_type in reversed(self.trait_types):
2432 trait_type.class_init(cls, None)
2433 super().class_init(cls, name)
2435 def subclass_init(self, cls: type[t.Any]) -> None:
2436 for trait_type in reversed(self.trait_types):
2437 trait_type.subclass_init(cls)
2438 # explicitly not calling super().subclass_init(cls)
2439 # to opt out of instance_init
2441 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
2442 with obj.cross_validation_lock:
2443 for trait_type in self.trait_types:
2444 try:
2445 v = trait_type._validate(obj, value)
2446 # In the case of an element trait, the name is None
2447 if self.name is not None:
2448 setattr(obj, "_" + self.name + "_metadata", trait_type.metadata)
2449 return v
2450 except TraitError:
2451 continue
2452 self.error(obj, value)
2454 def __or__(self, other: t.Any) -> Union:
2455 if isinstance(other, Union):
2456 return Union(self.trait_types + other.trait_types)
2457 else:
2458 return Union([*self.trait_types, other])
2460 def from_string(self, s: str) -> t.Any:
2461 for trait_type in self.trait_types:
2462 try:
2463 v = trait_type.from_string(s)
2464 return trait_type.validate(None, v)
2465 except (TraitError, ValueError):
2466 continue
2467 return super().from_string(s)
2470# -----------------------------------------------------------------------------
2471# Basic TraitTypes implementations/subclasses
2472# -----------------------------------------------------------------------------
2475class Any(TraitType[t.Any | None, t.Any | None]):
2476 """A trait which allows any value."""
2478 if t.TYPE_CHECKING:
2480 @t.overload
2481 def __init__(
2482 self: Any,
2483 default_value: t.Any = ...,
2484 *,
2485 allow_none: Literal[False],
2486 read_only: bool | None = ...,
2487 help: str | None = ...,
2488 config: t.Any | None = ...,
2489 **kwargs: t.Any,
2490 ) -> None: ...
2492 @t.overload
2493 def __init__(
2494 self: Any,
2495 default_value: t.Any = ...,
2496 *,
2497 allow_none: Literal[True],
2498 read_only: bool | None = ...,
2499 help: str | None = ...,
2500 config: t.Any | None = ...,
2501 **kwargs: t.Any,
2502 ) -> None: ...
2504 @t.overload
2505 def __init__(
2506 self: Any,
2507 default_value: t.Any = ...,
2508 *,
2509 allow_none: Literal[True, False] = ...,
2510 help: str | None = ...,
2511 read_only: bool | None = False,
2512 config: t.Any = None,
2513 **kwargs: t.Any,
2514 ) -> None: ...
2516 def __init__(
2517 self: Any,
2518 default_value: t.Any = ...,
2519 *,
2520 allow_none: bool = False,
2521 help: str | None = "",
2522 read_only: bool | None = False,
2523 config: t.Any = None,
2524 **kwargs: t.Any,
2525 ) -> None: ...
2527 @t.overload
2528 def __get__(self, obj: None, cls: type[t.Any]) -> Any: ...
2530 @t.overload
2531 def __get__(self, obj: t.Any, cls: type[t.Any]) -> t.Any: ...
2533 def __get__(self, obj: t.Any | None, cls: type[t.Any]) -> t.Any | Any: ...
2535 default_value: t.Any | None = None
2536 allow_none = True
2537 info_text = "any value"
2539 def subclass_init(self, cls: type[t.Any]) -> None:
2540 pass # fully opt out of instance_init
2543def _validate_bounds(
2544 trait: Int[t.Any, t.Any] | Float[t.Any, t.Any], obj: t.Any, value: t.Any
2545) -> t.Any:
2546 """
2547 Validate that a number to be applied to a trait is between bounds.
2549 If value is not between min_bound and max_bound, this raises a
2550 TraitError with an error message appropriate for this trait.
2551 """
2552 if trait.min is not None and value < trait.min:
2553 raise TraitError(
2554 f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
2555 f"not be less than {trait.min}, but a value of {value} was "
2556 "specified"
2557 )
2558 if trait.max is not None and value > trait.max:
2559 raise TraitError(
2560 f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
2561 f"not be greater than {trait.max}, but a value of {value} was "
2562 "specified"
2563 )
2564 return value
2567# I = t.TypeVar('I', int | None, int)
2570class Int(TraitType[G, S]):
2571 """An integer trait."""
2573 default_value = 0
2574 info_text = "an int"
2576 @t.overload
2577 def __init__(
2578 self: Int[int, int],
2579 default_value: int | Sentinel = ...,
2580 allow_none: Literal[False] = ...,
2581 read_only: bool | None = ...,
2582 help: str | None = ...,
2583 config: t.Any | None = ...,
2584 **kwargs: t.Any,
2585 ) -> None: ...
2587 @t.overload
2588 def __init__(
2589 self: Int[int | None, int | None],
2590 default_value: int | Sentinel | None = ...,
2591 allow_none: Literal[True] = ...,
2592 read_only: bool | None = ...,
2593 help: str | None = ...,
2594 config: t.Any | None = ...,
2595 **kwargs: t.Any,
2596 ) -> None: ...
2598 def __init__(
2599 self,
2600 default_value: t.Any = Undefined,
2601 allow_none: bool = False,
2602 read_only: bool | None = None,
2603 help: str | None = None,
2604 config: t.Any | None = None,
2605 **kwargs: t.Any,
2606 ) -> None:
2607 self.min = kwargs.pop("min", None)
2608 self.max = kwargs.pop("max", None)
2609 super().__init__(
2610 default_value=default_value,
2611 allow_none=allow_none,
2612 read_only=read_only,
2613 help=help,
2614 config=config,
2615 **kwargs,
2616 )
2618 def validate(self, obj: t.Any, value: t.Any) -> G:
2619 if not isinstance(value, int) and isinstance(value, numbers.Number):
2620 # allow casting integer-valued numbers to int
2621 # allows for more concise assignment like `4e9` which is a float
2622 try:
2623 int_value = int(value) # type:ignore[call-overload]
2624 if int_value == value:
2625 value = int_value
2626 except Exception:
2627 pass
2628 if not isinstance(value, int):
2629 self.error(obj, value)
2630 return _validate_bounds(self, obj, value) # type:ignore[no-any-return]
2632 def from_string(self, s: str) -> G:
2633 if self.allow_none and s == "None":
2634 return None # type:ignore[return-value]
2635 return int(s) # type:ignore[return-value]
2637 def subclass_init(self, cls: type[t.Any]) -> None:
2638 pass # fully opt out of instance_init
2641class CInt(Int[G, S]):
2642 """A casting version of the int trait."""
2644 if t.TYPE_CHECKING:
2646 @t.overload
2647 def __init__(
2648 self: CInt[int, t.Any],
2649 default_value: t.Any | Sentinel = ...,
2650 allow_none: Literal[False] = ...,
2651 read_only: bool | None = ...,
2652 help: str | None = ...,
2653 config: t.Any | None = ...,
2654 **kwargs: t.Any,
2655 ) -> None: ...
2657 @t.overload
2658 def __init__(
2659 self: CInt[int | None, t.Any],
2660 default_value: t.Any | Sentinel | None = ...,
2661 allow_none: Literal[True] = ...,
2662 read_only: bool | None = ...,
2663 help: str | None = ...,
2664 config: t.Any | None = ...,
2665 **kwargs: t.Any,
2666 ) -> None: ...
2668 def __init__(
2669 self: CInt[int | None, t.Any],
2670 default_value: t.Any | Sentinel | None = ...,
2671 allow_none: bool = ...,
2672 read_only: bool | None = ...,
2673 help: str | None = ...,
2674 config: t.Any | None = ...,
2675 **kwargs: t.Any,
2676 ) -> None: ...
2678 def validate(self, obj: t.Any, value: t.Any) -> G:
2679 try:
2680 value = int(value)
2681 except Exception:
2682 self.error(obj, value)
2683 return _validate_bounds(self, obj, value) # type:ignore[no-any-return]
2686Integer = Int
2689class Long(Int[G, S]):
2690 """A deprecated alias for :class:`Integer`.
2692 .. deprecated:: 5.16
2693 Use :class:`Integer` instead. ``Int`` and ``Long`` used to be distinct
2694 traits back when Python 2 had separate ``int`` and ``long`` types; they
2695 are now both just integers.
2696 """
2698 def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
2699 warn(
2700 "The `Long` trait is deprecated since traitlets 5.16, use `Integer` instead.",
2701 DeprecationWarning,
2702 stacklevel=2,
2703 )
2704 super().__init__(*args, **kwargs) # type:ignore[misc]
2707class CLong(CInt[G, S]):
2708 """A deprecated alias for :class:`CInt`.
2710 .. deprecated:: 5.16
2711 Use :class:`CInt` instead.
2712 """
2714 def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
2715 warn(
2716 "The `CLong` trait is deprecated since traitlets 5.16, use `CInt` instead.",
2717 DeprecationWarning,
2718 stacklevel=2,
2719 )
2720 super().__init__(*args, **kwargs) # type:ignore[misc]
2723class Float(TraitType[G, S]):
2724 """A float trait."""
2726 default_value = 0.0
2727 info_text = "a float"
2729 @t.overload
2730 def __init__(
2731 self: Float[float, int | float],
2732 default_value: float | Sentinel = ...,
2733 allow_none: Literal[False] = ...,
2734 read_only: bool | None = ...,
2735 help: str | None = ...,
2736 config: t.Any | None = ...,
2737 **kwargs: t.Any,
2738 ) -> None: ...
2740 @t.overload
2741 def __init__(
2742 self: Float[int | None, int | float | None],
2743 default_value: float | Sentinel | None = ...,
2744 allow_none: Literal[True] = ...,
2745 read_only: bool | None = ...,
2746 help: str | None = ...,
2747 config: t.Any | None = ...,
2748 **kwargs: t.Any,
2749 ) -> None: ...
2751 def __init__(
2752 self: Float[int | None, int | float | None],
2753 default_value: float | Sentinel | None = Undefined,
2754 allow_none: bool = False,
2755 read_only: bool | None = False,
2756 help: str | None = None,
2757 config: t.Any | None = None,
2758 **kwargs: t.Any,
2759 ) -> None:
2760 self.min = kwargs.pop("min", -float("inf"))
2761 self.max = kwargs.pop("max", float("inf"))
2762 super().__init__(
2763 default_value=default_value,
2764 allow_none=allow_none,
2765 read_only=read_only,
2766 help=help,
2767 config=config,
2768 **kwargs,
2769 )
2771 def validate(self, obj: t.Any, value: t.Any) -> G:
2772 if isinstance(value, int):
2773 value = float(value)
2774 if not isinstance(value, float):
2775 self.error(obj, value)
2776 return _validate_bounds(self, obj, value) # type:ignore[no-any-return]
2778 def from_string(self, s: str) -> G:
2779 if self.allow_none and s == "None":
2780 return None # type:ignore[return-value]
2781 return float(s) # type:ignore[return-value]
2783 def subclass_init(self, cls: type[t.Any]) -> None:
2784 pass # fully opt out of instance_init
2787class CFloat(Float[G, S]):
2788 """A casting version of the float trait."""
2790 if t.TYPE_CHECKING:
2792 @t.overload
2793 def __init__(
2794 self: CFloat[float, t.Any],
2795 default_value: t.Any = ...,
2796 allow_none: Literal[False] = ...,
2797 read_only: bool | None = ...,
2798 help: str | None = ...,
2799 config: t.Any | None = ...,
2800 **kwargs: t.Any,
2801 ) -> None: ...
2803 @t.overload
2804 def __init__(
2805 self: CFloat[float | None, t.Any],
2806 default_value: t.Any = ...,
2807 allow_none: Literal[True] = ...,
2808 read_only: bool | None = ...,
2809 help: str | None = ...,
2810 config: t.Any | None = ...,
2811 **kwargs: t.Any,
2812 ) -> None: ...
2814 def __init__(
2815 self: CFloat[float | None, t.Any],
2816 default_value: t.Any = ...,
2817 allow_none: bool = ...,
2818 read_only: bool | None = ...,
2819 help: str | None = ...,
2820 config: t.Any | None = ...,
2821 **kwargs: t.Any,
2822 ) -> None: ...
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, 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: ...
2929 @t.overload
2930 def __init__(
2931 self: Unicode[str | None, str | bytes | None],
2932 default_value: str | Sentinel | None = ...,
2933 allow_none: Literal[True] = ...,
2934 read_only: bool | None = ...,
2935 help: str | None = ...,
2936 config: t.Any = ...,
2937 **kwargs: t.Any,
2938 ) -> None: ...
2940 def __init__(
2941 self: Unicode[str | None, str | bytes | None],
2942 default_value: str | Sentinel | None = ...,
2943 allow_none: bool = ...,
2944 read_only: bool | None = ...,
2945 help: str | None = ...,
2946 config: t.Any = ...,
2947 **kwargs: t.Any,
2948 ) -> None: ...
2950 def validate(self, obj: t.Any, value: t.Any) -> G:
2951 if isinstance(value, str):
2952 return value # type:ignore[return-value]
2953 if isinstance(value, bytes):
2954 try:
2955 return value.decode("ascii", "strict") # type:ignore[return-value]
2956 except UnicodeDecodeError as e:
2957 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
2958 raise TraitError(msg.format(value, self.name, class_of(obj))) from e
2959 self.error(obj, value)
2961 def from_string(self, s: str) -> G:
2962 if self.allow_none and s == "None":
2963 return None # type:ignore[return-value]
2964 s = os.path.expanduser(s)
2965 if len(s) >= 2:
2966 # handle deprecated "1"
2967 for c in ('"', "'"):
2968 if s[0] == s[-1] == c:
2969 old_s = s
2970 s = s[1:-1]
2971 warn(
2972 "Supporting extra quotes around strings is deprecated in traitlets 5.0. "
2973 f"You can use {s!r} instead of {old_s!r} if you require traitlets >=5.",
2974 DeprecationWarning,
2975 stacklevel=2,
2976 )
2977 return s # type:ignore[return-value]
2979 def subclass_init(self, cls: type[t.Any]) -> None:
2980 pass # fully opt out of instance_init
2983class CUnicode(Unicode[G, S], TraitType[str, t.Any]):
2984 """A casting version of the unicode trait."""
2986 if t.TYPE_CHECKING:
2988 @t.overload
2989 def __init__(
2990 self: CUnicode[str, t.Any],
2991 default_value: str | Sentinel = ...,
2992 allow_none: Literal[False] = ...,
2993 read_only: bool | None = ...,
2994 help: str | None = ...,
2995 config: t.Any = ...,
2996 **kwargs: t.Any,
2997 ) -> None: ...
2999 @t.overload
3000 def __init__(
3001 self: CUnicode[str | None, t.Any],
3002 default_value: str | Sentinel | None = ...,
3003 allow_none: Literal[True] = ...,
3004 read_only: bool | None = ...,
3005 help: str | None = ...,
3006 config: t.Any = ...,
3007 **kwargs: t.Any,
3008 ) -> None: ...
3010 def __init__(
3011 self: CUnicode[str | None, t.Any],
3012 default_value: str | Sentinel | None = ...,
3013 allow_none: bool = ...,
3014 read_only: bool | None = ...,
3015 help: str | None = ...,
3016 config: t.Any = ...,
3017 **kwargs: t.Any,
3018 ) -> None: ...
3020 def validate(self, obj: t.Any, value: t.Any) -> G:
3021 try:
3022 return str(value) # type:ignore[return-value]
3023 except Exception:
3024 self.error(obj, value)
3027class ObjectName(TraitType[str, str]):
3028 """A string holding a valid object name in this version of Python.
3030 This does not check that the name exists in any scope."""
3032 info_text = "a valid object identifier in Python"
3034 coerce_str = staticmethod(lambda _, s: s)
3036 def validate(self, obj: t.Any, value: t.Any) -> str:
3037 value = self.coerce_str(obj, value)
3039 if isinstance(value, str) and value.isidentifier():
3040 return value
3041 self.error(obj, value)
3043 def from_string(self, s: str) -> str | None:
3044 if self.allow_none and s == "None":
3045 return None
3046 return s
3049class DottedObjectName(ObjectName):
3050 """A string holding a valid dotted object name in Python, such as A.b3._c"""
3052 def validate(self, obj: t.Any, value: t.Any) -> str:
3053 value = self.coerce_str(obj, value)
3055 if isinstance(value, str) and all(a.isidentifier() for a in value.split(".")):
3056 return value
3057 self.error(obj, value)
3060class Bool(TraitType[G, S]):
3061 """A boolean (True, False) trait."""
3063 default_value = False
3064 info_text = "a boolean"
3066 if t.TYPE_CHECKING:
3068 @t.overload
3069 def __init__(
3070 self: Bool[bool, bool | int],
3071 default_value: bool | Sentinel = ...,
3072 allow_none: Literal[False] = ...,
3073 read_only: bool | None = ...,
3074 help: str | None = ...,
3075 config: t.Any = ...,
3076 **kwargs: t.Any,
3077 ) -> None: ...
3079 @t.overload
3080 def __init__(
3081 self: Bool[bool | None, bool | int | None],
3082 default_value: bool | Sentinel | None = ...,
3083 allow_none: Literal[True] = ...,
3084 read_only: bool | None = ...,
3085 help: str | None = ...,
3086 config: t.Any = ...,
3087 **kwargs: t.Any,
3088 ) -> None: ...
3090 def __init__(
3091 self: Bool[bool | None, bool | int | None],
3092 default_value: bool | Sentinel | None = ...,
3093 allow_none: bool = ...,
3094 read_only: bool | None = ...,
3095 help: str | None = ...,
3096 config: t.Any = ...,
3097 **kwargs: t.Any,
3098 ) -> None: ...
3100 def validate(self, obj: t.Any, value: t.Any) -> G:
3101 if isinstance(value, bool):
3102 return value # type:ignore[return-value]
3103 elif isinstance(value, int):
3104 if value == 1:
3105 return True # type:ignore[return-value]
3106 elif value == 0:
3107 return False # type:ignore[return-value]
3108 self.error(obj, value)
3110 def from_string(self, s: str) -> G:
3111 if self.allow_none and s == "None":
3112 return None # type:ignore[return-value]
3113 s = s.lower()
3114 if s in {"true", "1"}:
3115 return True # type:ignore[return-value]
3116 elif s in {"false", "0"}:
3117 return False # type:ignore[return-value]
3118 else:
3119 raise ValueError("%r is not 1, 0, true, or false")
3121 def subclass_init(self, cls: type[t.Any]) -> None:
3122 pass # fully opt out of instance_init
3124 def argcompleter(self, **kwargs: t.Any) -> list[str]:
3125 """Completion hints for argcomplete"""
3126 completions = ["true", "1", "false", "0"]
3127 if self.allow_none:
3128 completions.append("None")
3129 return completions
3132class CBool(Bool[G, S]):
3133 """A casting version of the boolean trait."""
3135 if t.TYPE_CHECKING:
3137 @t.overload
3138 def __init__(
3139 self: CBool[bool, t.Any],
3140 default_value: bool | Sentinel = ...,
3141 allow_none: Literal[False] = ...,
3142 read_only: bool | None = ...,
3143 help: str | None = ...,
3144 config: t.Any = ...,
3145 **kwargs: t.Any,
3146 ) -> None: ...
3148 @t.overload
3149 def __init__(
3150 self: CBool[bool | None, t.Any],
3151 default_value: bool | Sentinel | None = ...,
3152 allow_none: Literal[True] = ...,
3153 read_only: bool | None = ...,
3154 help: str | None = ...,
3155 config: t.Any = ...,
3156 **kwargs: t.Any,
3157 ) -> None: ...
3159 def __init__(
3160 self: CBool[bool | None, t.Any],
3161 default_value: bool | Sentinel | None = ...,
3162 allow_none: bool = ...,
3163 read_only: bool | None = ...,
3164 help: str | None = ...,
3165 config: t.Any = ...,
3166 **kwargs: t.Any,
3167 ) -> None: ...
3169 def validate(self, obj: t.Any, value: t.Any) -> G:
3170 try:
3171 return bool(value) # type:ignore[return-value]
3172 except Exception:
3173 self.error(obj, value)
3176class Enum(TraitType[G, G]):
3177 """An enum whose value must be in a given sequence."""
3179 if t.TYPE_CHECKING:
3181 @t.overload
3182 def __init__(
3183 self: Enum[G],
3184 values: t.Sequence[G],
3185 default_value: G | Sentinel = ...,
3186 allow_none: Literal[False] = ...,
3187 read_only: bool | None = ...,
3188 help: str | None = ...,
3189 config: t.Any = ...,
3190 **kwargs: t.Any,
3191 ) -> None: ...
3193 @t.overload
3194 def __init__(
3195 self: Enum[G | None],
3196 values: t.Sequence[G] | None,
3197 default_value: G | Sentinel | None = ...,
3198 allow_none: Literal[True] = ...,
3199 read_only: bool | None = ...,
3200 help: str | None = ...,
3201 config: t.Any = ...,
3202 **kwargs: t.Any,
3203 ) -> None: ...
3205 def __init__(
3206 self: Enum[G],
3207 values: t.Sequence[G] | None,
3208 default_value: G | Sentinel | None = Undefined,
3209 allow_none: bool = False,
3210 read_only: bool | None = None,
3211 help: str | None = None,
3212 config: t.Any = None,
3213 **kwargs: t.Any,
3214 ) -> None:
3215 self.values = values
3216 if allow_none is True and default_value is Undefined:
3217 default_value = None
3218 kwargs["allow_none"] = allow_none
3219 kwargs["read_only"] = read_only
3220 kwargs["help"] = help
3221 kwargs["config"] = config
3222 super().__init__(default_value, **kwargs)
3224 def validate(self, obj: t.Any, value: t.Any) -> G:
3225 if self.values and value in self.values:
3226 return value # type:ignore[no-any-return]
3227 self.error(obj, value)
3229 def _choices_str(self, as_rst: bool = False) -> str:
3230 """Returns a description of the trait choices (not none)."""
3231 choices = self.values or []
3232 if as_rst:
3233 choice_str = "|".join(f"``{x!r}``" for x in choices)
3234 else:
3235 choice_str = repr(list(choices))
3236 return choice_str
3238 def _info(self, as_rst: bool = False) -> str:
3239 """Returns a description of the trait."""
3240 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3241 return f"any of {self._choices_str(as_rst)}{none}"
3243 def info(self) -> str:
3244 return self._info(as_rst=False)
3246 def info_rst(self) -> str:
3247 return self._info(as_rst=True)
3249 def from_string(self, s: str) -> G:
3250 try:
3251 return self.validate(None, s)
3252 except TraitError:
3253 return _safe_literal_eval(s) # type:ignore[no-any-return]
3255 def subclass_init(self, cls: type[t.Any]) -> None:
3256 pass # fully opt out of instance_init
3258 def argcompleter(self, **kwargs: t.Any) -> list[str]:
3259 """Completion hints for argcomplete"""
3260 return [str(v) for v in self.values or []]
3263class CaselessStrEnum(Enum[G]):
3264 """An enum of strings where the case should be ignored."""
3266 def __init__(
3267 self: CaselessStrEnum[t.Any],
3268 values: t.Any,
3269 default_value: t.Any = Undefined,
3270 **kwargs: t.Any,
3271 ) -> None:
3272 super().__init__(values, default_value=default_value, **kwargs)
3274 def validate(self, obj: t.Any, value: t.Any) -> G:
3275 if not isinstance(value, str):
3276 self.error(obj, value)
3278 for v in self.values or []:
3279 assert isinstance(v, str)
3280 if v.lower() == value.lower():
3281 return v
3282 self.error(obj, value)
3284 def _info(self, as_rst: bool = False) -> str:
3285 """Returns a description of the trait."""
3286 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3287 return f"any of {self._choices_str(as_rst)} (case-insensitive){none}"
3289 def info(self) -> str:
3290 return self._info(as_rst=False)
3292 def info_rst(self) -> str:
3293 return self._info(as_rst=True)
3296class FuzzyEnum(Enum[G]):
3297 """An case-ignoring enum matching choices by unique prefixes/substrings."""
3299 case_sensitive = False
3300 #: If True, choices match anywhere in the string, otherwise match prefixes.
3301 substring_matching = False
3303 def __init__(
3304 self: FuzzyEnum[t.Any],
3305 values: t.Any,
3306 default_value: t.Any = Undefined,
3307 case_sensitive: bool = False,
3308 substring_matching: bool = False,
3309 **kwargs: t.Any,
3310 ) -> None:
3311 self.case_sensitive = case_sensitive
3312 self.substring_matching = substring_matching
3313 super().__init__(values, default_value=default_value, **kwargs)
3315 def validate(self, obj: t.Any, value: t.Any) -> G:
3316 if not isinstance(value, str):
3317 self.error(obj, value)
3319 conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower()
3320 substring_matching = self.substring_matching
3321 match_func = (lambda v, c: v in c) if substring_matching else (lambda v, c: c.startswith(v))
3322 value = conv_func(value) # type:ignore[no-untyped-call]
3323 choices = self.values or []
3324 matches = [match_func(value, conv_func(c)) for c in choices] # type:ignore[no-untyped-call]
3325 if sum(matches) == 1:
3326 for v, m in zip(choices, matches, strict=True):
3327 if m:
3328 return v
3330 self.error(obj, value)
3332 def _info(self, as_rst: bool = False) -> str:
3333 """Returns a description of the trait."""
3334 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3335 case = "sensitive" if self.case_sensitive else "insensitive"
3336 substr = "substring" if self.substring_matching else "prefix"
3337 return f"any case-{case} {substr} of {self._choices_str(as_rst)}{none}"
3339 def info(self) -> str:
3340 return self._info(as_rst=False)
3342 def info_rst(self) -> str:
3343 return self._info(as_rst=True)
3346class Container(Instance[T]):
3347 """An instance of a container (list, set, etc.)
3349 To be subclassed by overriding klass.
3350 """
3352 klass: type[T] | None = None
3353 _cast_types: t.Any = ()
3354 _valid_defaults = SequenceTypes
3355 _trait: t.Any = None
3356 _literal_from_string_pairs: t.Any = ("[]", "()")
3358 @t.overload
3359 def __init__(
3360 self: Container[T],
3361 *,
3362 allow_none: Literal[False],
3363 read_only: bool | None = ...,
3364 help: str | None = ...,
3365 config: t.Any | None = ...,
3366 **kwargs: t.Any,
3367 ) -> None: ...
3369 @t.overload
3370 def __init__(
3371 self: Container[T | None],
3372 *,
3373 allow_none: Literal[True],
3374 read_only: bool | None = ...,
3375 help: str | None = ...,
3376 config: t.Any | None = ...,
3377 **kwargs: t.Any,
3378 ) -> None: ...
3380 @t.overload
3381 def __init__(
3382 self: Container[T],
3383 *,
3384 trait: t.Any = ...,
3385 default_value: t.Any = ...,
3386 help: str = ...,
3387 read_only: bool = ...,
3388 config: t.Any = ...,
3389 **kwargs: t.Any,
3390 ) -> None: ...
3392 def __init__(
3393 self,
3394 trait: t.Any | None = None,
3395 default_value: t.Any = Undefined,
3396 help: str | None = None,
3397 read_only: bool | None = None,
3398 config: t.Any | None = None,
3399 **kwargs: t.Any,
3400 ) -> None:
3401 """Create a container trait type from a list, set, or tuple.
3403 The default value is created by doing ``List(default_value)``,
3404 which creates a copy of the ``default_value``.
3406 ``trait`` can be specified, which restricts the type of elements
3407 in the container to that TraitType.
3409 If only one arg is given and it is not a Trait, it is taken as
3410 ``default_value``:
3412 ``c = List([1, 2, 3])``
3414 Parameters
3415 ----------
3416 trait : TraitType [ optional ]
3417 the type for restricting the contents of the Container. If unspecified,
3418 types are not checked.
3419 default_value : SequenceType [ optional ]
3420 The default value for the Trait. Must be list/tuple/set, and
3421 will be cast to the container type.
3422 allow_none : bool [ default False ]
3423 Whether to allow the value to be None
3424 **kwargs : any
3425 further keys for extensions to the Trait (e.g. config)
3427 """
3429 # allow List([values]):
3430 if trait is not None and default_value is Undefined and not is_trait(trait):
3431 default_value = trait
3432 trait = None
3434 if default_value is None and not kwargs.get("allow_none", False):
3435 # improve backward-compatibility for possible subclasses
3436 # specifying default_value=None as default,
3437 # keeping 'unspecified' behavior (i.e. empty container)
3438 warn(
3439 f"Specifying {self.__class__.__name__}(default_value=None)"
3440 " for no default is deprecated in traitlets 5.0.5."
3441 " Use default_value=Undefined",
3442 DeprecationWarning,
3443 stacklevel=2,
3444 )
3445 default_value = Undefined
3447 if default_value is Undefined:
3448 args: t.Any = ()
3449 elif default_value is None:
3450 # default_value back on kwargs for super() to handle
3451 args = ()
3452 kwargs["default_value"] = None
3453 elif isinstance(default_value, self._valid_defaults):
3454 args = (default_value,)
3455 else:
3456 raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
3458 if is_trait(trait):
3459 if isinstance(trait, type):
3460 warn(
3461 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
3462 " Passing types is deprecated in traitlets 4.1.",
3463 DeprecationWarning,
3464 stacklevel=3,
3465 )
3466 self._trait = trait() if isinstance(trait, type) else trait
3467 elif trait is not None:
3468 raise TypeError(f"`trait` must be a Trait or None, got {repr_type(trait)}")
3470 super().__init__(
3471 klass=self.klass, args=args, help=help, read_only=read_only, config=config, **kwargs
3472 )
3474 def validate(self, obj: t.Any, value: t.Any) -> T | None:
3475 if isinstance(value, self._cast_types):
3476 assert self.klass is not None
3477 value = self.klass(value) # type:ignore[call-arg]
3478 value = super().validate(obj, value)
3479 if value is None:
3480 return value
3482 return self.validate_elements(obj, value)
3484 def validate_elements(self, obj: t.Any, value: t.Any) -> T | None:
3485 validated = []
3486 if self._trait is None or isinstance(self._trait, Any):
3487 return value # type:ignore[no-any-return]
3488 for v in value:
3489 try:
3490 v = self._trait._validate(obj, v)
3491 except TraitError as error:
3492 self.error(obj, v, error)
3493 else:
3494 validated.append(v)
3495 assert self.klass is not None
3496 return self.klass(validated) # type:ignore[call-arg]
3498 def class_init(self, cls: type[t.Any], name: str | None) -> None:
3499 if isinstance(self._trait, TraitType):
3500 self._trait.class_init(cls, None)
3501 super().class_init(cls, name)
3503 def subclass_init(self, cls: type[t.Any]) -> None:
3504 if isinstance(self._trait, TraitType):
3505 self._trait.subclass_init(cls)
3506 # explicitly not calling super().subclass_init(cls)
3507 # to opt out of instance_init
3509 def from_string(self, s: str) -> T | None:
3510 """Load value from a single string"""
3511 if not isinstance(s, str):
3512 raise TraitError(f"Expected string, got {s!r}")
3513 try:
3514 test = literal_eval(s)
3515 except Exception:
3516 test = None
3517 return self.validate(None, test)
3519 def from_string_list(self, s_list: list[str]) -> T | None:
3520 """Return the value from a list of config strings
3522 This is where we parse CLI configuration
3523 """
3524 assert self.klass is not None
3525 if len(s_list) == 1:
3526 # check for deprecated --Class.trait="['a', 'b', 'c']"
3527 r = s_list[0]
3528 if r == "None" and self.allow_none:
3529 return None
3530 if len(r) >= 2 and any(
3531 r.startswith(start) and r.endswith(end)
3532 for start, end in self._literal_from_string_pairs
3533 ):
3534 if self.this_class:
3535 clsname = self.this_class.__name__ + "."
3536 else:
3537 clsname = ""
3538 assert self.name is not None
3539 warn(
3540 f"--{clsname + self.name}={r} for containers is deprecated in traitlets 5.0. "
3541 f"You can pass `--{clsname + self.name} item` ... multiple times to add items to a list.",
3542 DeprecationWarning,
3543 stacklevel=2,
3544 )
3545 return self.klass(literal_eval(r)) # type:ignore[call-arg]
3546 sig = inspect.signature(self.item_from_string)
3547 if "index" in sig.parameters:
3548 item_from_string = self.item_from_string
3549 else:
3550 # backward-compat: allow item_from_string to ignore index arg
3551 def item_from_string(s: str, index: int | None = None) -> T | str:
3552 return self.item_from_string(s)
3554 return self.klass( # type:ignore[call-arg]
3555 [item_from_string(s, index=idx) for idx, s in enumerate(s_list)]
3556 )
3558 def item_from_string(self, s: str, index: int | None = None) -> T | str:
3559 """Cast a single item from a string
3561 Evaluated when parsing CLI configuration from a string
3562 """
3563 if self._trait:
3564 return self._trait.from_string(s) # type:ignore[no-any-return]
3565 else:
3566 return s
3569class List(Container[list[T]]):
3570 """An instance of a Python list."""
3572 klass = list # type:ignore[assignment]
3573 _cast_types: t.Any = (tuple,)
3575 def __init__(
3576 self,
3577 trait: list[T] | tuple[T] | set[T] | Sentinel | TraitType[T, t.Any] | None = None,
3578 default_value: list[T] | tuple[T] | set[T] | Sentinel | None = Undefined,
3579 minlen: int = 0,
3580 maxlen: int = sys.maxsize,
3581 **kwargs: t.Any,
3582 ) -> None:
3583 """Create a List trait type from a list, set, or tuple.
3585 The default value is created by doing ``list(default_value)``,
3586 which creates a copy of the ``default_value``.
3588 ``trait`` can be specified, which restricts the type of elements
3589 in the container to that TraitType.
3591 If only one arg is given and it is not a Trait, it is taken as
3592 ``default_value``:
3594 ``c = List([1, 2, 3])``
3596 Parameters
3597 ----------
3598 trait : TraitType [ optional ]
3599 the type for restricting the contents of the Container.
3600 If unspecified, types are not checked.
3601 default_value : SequenceType [ optional ]
3602 The default value for the Trait. Must be list/tuple/set, and
3603 will be cast to the container type.
3604 minlen : Int [ default 0 ]
3605 The minimum length of the input list
3606 maxlen : Int [ default sys.maxsize ]
3607 The maximum length of the input list
3608 """
3609 self._maxlen = maxlen
3610 self._minlen = minlen
3611 super().__init__(trait=trait, default_value=default_value, **kwargs)
3613 def length_error(self, obj: t.Any, value: t.Any) -> None:
3614 e = (
3615 f"The '{self.name}' trait of {class_of(obj)} instance must be of length"
3616 f" {self._minlen:d} <= L <= {self._maxlen:d}, but a value of {value} was specified."
3617 )
3618 raise TraitError(e)
3620 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3621 length = len(value)
3622 if length < self._minlen or length > self._maxlen:
3623 self.length_error(obj, value)
3625 return super().validate_elements(obj, value)
3627 def set(self, obj: t.Any, value: t.Any) -> None:
3628 if isinstance(value, str):
3629 return super().set(obj, [value]) # type:ignore[list-item]
3630 else:
3631 return super().set(obj, value)
3634class Set(Container[set[t.Any]]):
3635 """An instance of a Python set."""
3637 klass = set
3638 _cast_types = (tuple, list)
3640 _literal_from_string_pairs = ("[]", "()", "{}")
3642 # Redefine __init__ just to make the docstring more accurate.
3643 def __init__(
3644 self,
3645 trait: t.Any = None,
3646 default_value: t.Any = Undefined,
3647 minlen: int = 0,
3648 maxlen: int = sys.maxsize,
3649 **kwargs: t.Any,
3650 ) -> None:
3651 """Create a Set trait type from a list, set, or tuple.
3653 The default value is created by doing ``set(default_value)``,
3654 which creates a copy of the ``default_value``.
3656 ``trait`` can be specified, which restricts the type of elements
3657 in the container to that TraitType.
3659 If only one arg is given and it is not a Trait, it is taken as
3660 ``default_value``:
3662 ``c = Set({1, 2, 3})``
3664 Parameters
3665 ----------
3666 trait : TraitType [ optional ]
3667 the type for restricting the contents of the Container.
3668 If unspecified, types are not checked.
3669 default_value : SequenceType [ optional ]
3670 The default value for the Trait. Must be list/tuple/set, and
3671 will be cast to the container type.
3672 minlen : Int [ default 0 ]
3673 The minimum length of the input list
3674 maxlen : Int [ default sys.maxsize ]
3675 The maximum length of the input list
3676 """
3677 self._maxlen = maxlen
3678 self._minlen = minlen
3679 super().__init__(trait=trait, default_value=default_value, **kwargs)
3681 def length_error(self, obj: t.Any, value: t.Any) -> None:
3682 e = (
3683 f"The '{self.name}' trait of {class_of(obj)} instance must be of length"
3684 f" {self._minlen:d} <= L <= {self._maxlen:d}, but a value of {value} was specified."
3685 )
3686 raise TraitError(e)
3688 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3689 length = len(value)
3690 if length < self._minlen or length > self._maxlen:
3691 self.length_error(obj, value)
3693 return super().validate_elements(obj, value)
3695 def set(self, obj: t.Any, value: t.Any) -> None:
3696 if isinstance(value, str):
3697 return super().set(obj, {value})
3698 else:
3699 return super().set(obj, value)
3701 def default_value_repr(self) -> str:
3702 # Ensure default value is sorted for a reproducible build
3703 list_repr = repr(sorted(self.make_dynamic_default() or []))
3704 if list_repr == "[]":
3705 return "set()"
3706 return "{" + list_repr[1:-1] + "}"
3709class Tuple(Container[tuple[t.Any, ...]]):
3710 """An instance of a Python tuple."""
3712 klass = tuple
3713 _cast_types = (list,)
3715 def __init__(self, *traits: t.Any, **kwargs: t.Any) -> None:
3716 """Create a tuple from a list, set, or tuple.
3718 Create a fixed-type tuple with Traits:
3720 ``t = Tuple(Int(), Str(), CStr())``
3722 would be length 3, with Int,Str,CStr for each element.
3724 If only one arg is given and it is not a Trait, it is taken as
3725 default_value:
3727 ``t = Tuple((1, 2, 3))``
3729 Otherwise, ``default_value`` *must* be specified by keyword.
3731 Parameters
3732 ----------
3733 *traits : TraitTypes [ optional ]
3734 the types for restricting the contents of the Tuple. If unspecified,
3735 types are not checked. If specified, then each positional argument
3736 corresponds to an element of the tuple. Tuples defined with traits
3737 are of fixed length.
3738 default_value : SequenceType [ optional ]
3739 The default value for the Tuple. Must be list/tuple/set, and
3740 will be cast to a tuple. If ``traits`` are specified,
3741 ``default_value`` must conform to the shape and type they specify.
3742 **kwargs
3743 Other kwargs passed to `Container`
3744 """
3745 default_value = kwargs.pop("default_value", Undefined)
3746 # allow Tuple((values,)):
3747 if len(traits) == 1 and default_value is Undefined and not is_trait(traits[0]):
3748 default_value = traits[0]
3749 traits = ()
3751 if default_value is None and not kwargs.get("allow_none", False):
3752 # improve backward-compatibility for possible subclasses
3753 # specifying default_value=None as default,
3754 # keeping 'unspecified' behavior (i.e. empty container)
3755 warn(
3756 f"Specifying {self.__class__.__name__}(default_value=None)"
3757 " for no default is deprecated in traitlets 5.0.5."
3758 " Use default_value=Undefined",
3759 DeprecationWarning,
3760 stacklevel=2,
3761 )
3762 default_value = Undefined
3764 if default_value is Undefined:
3765 args: t.Any = ()
3766 elif default_value is None:
3767 # default_value back on kwargs for super() to handle
3768 args = ()
3769 kwargs["default_value"] = None
3770 elif isinstance(default_value, self._valid_defaults):
3771 args = (default_value,)
3772 else:
3773 raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
3775 self._traits = []
3776 for trait in traits:
3777 if isinstance(trait, type):
3778 warn(
3779 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3780 " Passing types is deprecated in traitlets 4.1.",
3781 DeprecationWarning,
3782 stacklevel=2,
3783 )
3784 trait = trait()
3785 self._traits.append(trait)
3787 if self._traits and (default_value is None or default_value is Undefined):
3788 # don't allow default to be an empty container if length is specified
3789 args = None
3790 super(Container, self).__init__(klass=self.klass, args=args, **kwargs)
3792 def item_from_string(self, s: str, index: int) -> t.Any: # type:ignore[override]
3793 """Cast a single item from a string
3795 Evaluated when parsing CLI configuration from a string
3796 """
3797 if not self._traits or index >= len(self._traits):
3798 # return s instead of raising index error
3799 # length errors will be raised later on validation
3800 return s
3801 return self._traits[index].from_string(s)
3803 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3804 if not self._traits:
3805 # nothing to validate
3806 return value
3807 if len(value) != len(self._traits):
3808 e = (
3809 f"The '{self.name}' trait of {class_of(obj)} instance requires"
3810 f" {len(self._traits):d} elements, but a value of {repr_type(value)} was specified."
3811 )
3812 raise TraitError(e)
3814 validated = []
3815 for trait, v in zip(self._traits, value, strict=True):
3816 try:
3817 v = trait._validate(obj, v)
3818 except TraitError as error:
3819 self.error(obj, v, error)
3820 else:
3821 validated.append(v)
3822 return tuple(validated)
3824 def class_init(self, cls: type[t.Any], name: str | None) -> None:
3825 for trait in self._traits:
3826 if isinstance(trait, TraitType):
3827 trait.class_init(cls, None)
3828 super(Container, self).class_init(cls, name)
3830 def subclass_init(self, cls: type[t.Any]) -> None:
3831 for trait in self._traits:
3832 if isinstance(trait, TraitType):
3833 trait.subclass_init(cls)
3834 # explicitly not calling super().subclass_init(cls)
3835 # to opt out of instance_init
3838class Dict(Instance["dict[K, V]"]):
3839 """An instance of a Python dict.
3841 One or more traits can be passed to the constructor
3842 to validate the keys and/or values of the dict.
3843 If you need more detailed validation,
3844 you may use a custom validator method.
3846 .. versionchanged:: 5.0
3847 Added key_trait for validating dict keys.
3849 .. versionchanged:: 5.0
3850 Deprecated ambiguous ``trait``, ``traits`` args in favor of ``value_trait``, ``per_key_traits``.
3851 """
3853 _value_trait = None
3854 _key_trait = None
3856 def __init__(
3857 self,
3858 value_trait: TraitType[t.Any, t.Any] | dict[K, V] | Sentinel | None = None,
3859 per_key_traits: t.Any = None,
3860 key_trait: TraitType[t.Any, t.Any] | None = None,
3861 default_value: dict[K, V] | Sentinel | None = Undefined,
3862 **kwargs: t.Any,
3863 ) -> None:
3864 """Create a dict trait type from a Python dict.
3866 The default value is created by doing ``dict(default_value)``,
3867 which creates a copy of the ``default_value``.
3869 Parameters
3870 ----------
3871 value_trait : TraitType [ optional ]
3872 The specified trait type to check and use to restrict the values of
3873 the dict. If unspecified, values are not checked.
3874 per_key_traits : Dictionary of {keys:trait types} [ optional, keyword-only ]
3875 A Python dictionary containing the types that are valid for
3876 restricting the values of the dict on a per-key basis.
3877 Each value in this dict should be a Trait for validating
3878 key_trait : TraitType [ optional, keyword-only ]
3879 The type for restricting the keys of the dict. If
3880 unspecified, the types of the keys are not checked.
3881 default_value : SequenceType [ optional, keyword-only ]
3882 The default value for the Dict. Must be dict, tuple, or None, and
3883 will be cast to a dict if not None. If any key or value traits are specified,
3884 the `default_value` must conform to the constraints.
3886 Examples
3887 --------
3888 a dict whose values must be text
3889 >>> d = Dict(Unicode())
3891 d2['n'] must be an integer
3892 d2['s'] must be text
3893 >>> d2 = Dict(per_key_traits={"n": Int(), "s": Unicode()})
3895 d3's keys must be text
3896 d3's values must be integers
3897 >>> d3 = Dict(value_trait=Int(), key_trait=Unicode())
3899 """
3901 # handle deprecated keywords
3902 trait = kwargs.pop("trait", None)
3903 if trait is not None:
3904 if value_trait is not None:
3905 raise TypeError(
3906 "Found a value for both `value_trait` and its deprecated alias `trait`."
3907 )
3908 value_trait = trait
3909 warn(
3910 "Keyword `trait` is deprecated in traitlets 5.0, use `value_trait` instead",
3911 DeprecationWarning,
3912 stacklevel=2,
3913 )
3914 traits = kwargs.pop("traits", None)
3915 if traits is not None:
3916 if per_key_traits is not None:
3917 raise TypeError(
3918 "Found a value for both `per_key_traits` and its deprecated alias `traits`."
3919 )
3920 per_key_traits = traits
3921 warn(
3922 "Keyword `traits` is deprecated in traitlets 5.0, use `per_key_traits` instead",
3923 DeprecationWarning,
3924 stacklevel=2,
3925 )
3927 # Handling positional arguments
3928 if default_value is Undefined and value_trait is not None:
3929 if not is_trait(value_trait):
3930 assert not isinstance(value_trait, TraitType)
3931 default_value = value_trait
3932 value_trait = None
3934 if key_trait is None and per_key_traits is not None:
3935 if is_trait(per_key_traits):
3936 key_trait = per_key_traits
3937 per_key_traits = None
3939 # Handling default value
3940 if default_value is Undefined:
3941 default_value = {}
3942 if default_value is None:
3943 args: t.Any = None
3944 elif isinstance(default_value, dict):
3945 args = (default_value,)
3946 elif isinstance(default_value, SequenceTypes):
3947 args = (default_value,)
3948 else:
3949 raise TypeError(f"default value of Dict was {default_value}")
3951 # Case where a type of TraitType is provided rather than an instance
3952 if is_trait(value_trait):
3953 if isinstance(value_trait, type):
3954 warn( # type:ignore[unreachable]
3955 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3956 " Passing types is deprecated in traitlets 4.1.",
3957 DeprecationWarning,
3958 stacklevel=2,
3959 )
3960 value_trait = value_trait()
3961 self._value_trait = value_trait
3962 elif value_trait is not None:
3963 raise TypeError(f"`value_trait` must be a Trait or None, got {repr_type(value_trait)}")
3965 if is_trait(key_trait):
3966 if isinstance(key_trait, type):
3967 warn( # type:ignore[unreachable]
3968 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3969 " Passing types is deprecated in traitlets 4.1.",
3970 DeprecationWarning,
3971 stacklevel=2,
3972 )
3973 key_trait = key_trait()
3974 self._key_trait = key_trait
3975 elif key_trait is not None:
3976 raise TypeError(f"`key_trait` must be a Trait or None, got {repr_type(key_trait)}")
3978 self._per_key_traits = per_key_traits
3980 super().__init__(klass=dict, args=args, **kwargs)
3982 def element_error(
3983 self, obj: t.Any, element: t.Any, validator: t.Any, side: str = "Values"
3984 ) -> None:
3985 e = (
3986 side
3987 + f" of the '{self.name}' trait of {class_of(obj)} instance must be {validator.info()}, but a value of {repr_type(element)} was specified."
3988 )
3989 raise TraitError(e)
3991 def validate(self, obj: t.Any, value: t.Any) -> dict[K, V] | None:
3992 value = super().validate(obj, value)
3993 if value is None:
3994 return value
3995 return self.validate_elements(obj, value)
3997 def validate_elements(self, obj: t.Any, value: dict[t.Any, t.Any]) -> dict[K, V] | None:
3998 per_key_override = self._per_key_traits or {}
3999 key_trait = self._key_trait
4000 value_trait = self._value_trait
4001 if not (key_trait or value_trait or per_key_override):
4002 return value
4004 validated = {}
4005 for key, v in value.items():
4006 if key_trait:
4007 try:
4008 key = key_trait._validate(obj, key)
4009 except TraitError:
4010 self.element_error(obj, key, key_trait, "Keys")
4011 active_value_trait = per_key_override.get(key, value_trait)
4012 if active_value_trait:
4013 try:
4014 v = active_value_trait._validate(obj, v)
4015 except TraitError:
4016 self.element_error(obj, v, active_value_trait, "Values")
4017 validated[key] = v
4019 return self.klass(validated) # type:ignore[misc,operator]
4021 def class_init(self, cls: type[t.Any], name: str | None) -> None:
4022 if isinstance(self._value_trait, TraitType):
4023 self._value_trait.class_init(cls, None)
4024 if isinstance(self._key_trait, TraitType):
4025 self._key_trait.class_init(cls, None)
4026 if self._per_key_traits is not None:
4027 for trait in self._per_key_traits.values():
4028 trait.class_init(cls, None)
4029 super().class_init(cls, name)
4031 def subclass_init(self, cls: type[t.Any]) -> None:
4032 if isinstance(self._value_trait, TraitType):
4033 self._value_trait.subclass_init(cls)
4034 if isinstance(self._key_trait, TraitType):
4035 self._key_trait.subclass_init(cls)
4036 if self._per_key_traits is not None:
4037 for trait in self._per_key_traits.values():
4038 trait.subclass_init(cls)
4039 # explicitly not calling super().subclass_init(cls)
4040 # to opt out of instance_init
4042 def from_string(self, s: str) -> dict[K, V] | None:
4043 """Load value from a single string"""
4044 if not isinstance(s, str):
4045 raise TypeError(f"from_string expects a string, got {s!r} of type {type(s)}")
4046 try:
4047 return self.from_string_list([s]) # type:ignore[no-any-return]
4048 except Exception:
4049 test = _safe_literal_eval(s)
4050 if isinstance(test, dict):
4051 return test
4052 raise
4054 def from_string_list(self, s_list: list[str]) -> t.Any:
4055 """Return a dict from a list of config strings.
4057 This is where we parse CLI configuration.
4059 Each item should have the form ``"key=value"``.
4061 item parsing is done in :meth:`.item_from_string`.
4062 """
4063 if len(s_list) == 1 and s_list[0] == "None" and self.allow_none:
4064 return None
4065 if len(s_list) == 1 and s_list[0].startswith("{") and s_list[0].endswith("}"):
4066 warn(
4067 f"--{self.name}={s_list[0]} for dict-traits is deprecated in traitlets 5.0. "
4068 f"You can pass --{self.name} <key=value> ... multiple times to add items to a dict.",
4069 DeprecationWarning,
4070 stacklevel=2,
4071 )
4073 return literal_eval(s_list[0])
4075 combined = {}
4076 for d in [self.item_from_string(s) for s in s_list]:
4077 combined.update(d)
4078 return combined
4080 def item_from_string(self, s: str) -> dict[K, V]:
4081 """Cast a single-key dict from a string.
4083 Evaluated when parsing CLI configuration from a string.
4085 Dicts expect strings of the form key=value.
4087 Returns a one-key dictionary,
4088 which will be merged in :meth:`.from_string_list`.
4089 """
4091 if "=" not in s:
4092 raise TraitError(
4093 f"'{self.__class__.__name__}' options must have the form 'key=value', got {s!r}"
4094 )
4095 key, value = s.split("=", 1)
4097 # cast key with key trait, if defined
4098 if self._key_trait:
4099 key = self._key_trait.from_string(key)
4101 # cast value with value trait, if defined (per-key or global)
4102 value_trait = (self._per_key_traits or {}).get(key, self._value_trait)
4103 if value_trait:
4104 value = value_trait.from_string(value)
4105 return {key: value} # type:ignore[dict-item]
4108class TCPAddress(TraitType[G, S]):
4109 """A trait for an (ip, port) tuple.
4111 This allows for both IPv4 IP addresses as well as hostnames.
4112 """
4114 default_value = ("127.0.0.1", 0)
4115 info_text = "an (ip, port) tuple"
4117 if t.TYPE_CHECKING:
4119 @t.overload
4120 def __init__(
4121 self: TCPAddress[tuple[str, int], tuple[str, int]],
4122 default_value: bool | Sentinel = ...,
4123 allow_none: Literal[False] = ...,
4124 read_only: bool | None = ...,
4125 help: str | None = ...,
4126 config: t.Any = ...,
4127 **kwargs: t.Any,
4128 ) -> None: ...
4130 @t.overload
4131 def __init__(
4132 self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None],
4133 default_value: bool | None | Sentinel = ...,
4134 allow_none: Literal[True] = ...,
4135 read_only: bool | None = ...,
4136 help: str | None = ...,
4137 config: t.Any = ...,
4138 **kwargs: t.Any,
4139 ) -> None: ...
4141 def __init__(
4142 self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None]
4143 | TCPAddress[tuple[str, int], tuple[str, int]],
4144 default_value: bool | None | Sentinel = Undefined,
4145 allow_none: Literal[True, False] = False,
4146 read_only: bool | None = None,
4147 help: str | None = None,
4148 config: t.Any = None,
4149 **kwargs: t.Any,
4150 ) -> None: ...
4152 def validate(self, obj: t.Any, value: t.Any) -> G:
4153 if isinstance(value, tuple):
4154 if len(value) == 2:
4155 if isinstance(value[0], str) and isinstance(value[1], int):
4156 port = value[1]
4157 if port >= 0 and port <= 65535:
4158 return value # type:ignore[return-value]
4159 self.error(obj, value)
4161 def from_string(self, s: str) -> G:
4162 if self.allow_none and s == "None":
4163 return None # type:ignore[return-value]
4164 if ":" not in s:
4165 raise ValueError(f"Require `ip:port`, got {s!r}")
4166 ip, port_str = s.split(":", 1)
4167 port = int(port_str)
4168 return (ip, port) # type:ignore[return-value]
4171class CRegExp(TraitType[re.Pattern[t.Any], re.Pattern[t.Any] | str]):
4172 """A casting compiled regular expression trait.
4174 Accepts both strings and compiled regular expressions. The resulting
4175 attribute will be a compiled regular expression."""
4177 info_text = "a regular expression"
4179 def validate(self, obj: t.Any, value: t.Any) -> re.Pattern[t.Any] | None:
4180 try:
4181 return re.compile(value)
4182 except Exception:
4183 self.error(obj, value)
4186class Path(TraitType["pathlib.Path", t.Union["pathlib.Path", str, "os.PathLike[str]"]]):
4187 """A trait for filesystem paths.
4189 Accepts strings and :class:`os.PathLike` objects. The resulting
4190 attribute will be a :class:`pathlib.Path` instance."""
4192 info_text = "a filesystem path"
4194 def validate(self, obj: t.Any, value: t.Any) -> pathlib.Path | None:
4195 if isinstance(value, (str, os.PathLike)):
4196 return pathlib.Path(value)
4197 self.error(obj, value)
4199 def from_string(self, s: str) -> pathlib.Path | None:
4200 if self.allow_none and s == "None":
4201 return None
4202 return pathlib.Path(s)
4205class UseEnum(TraitType[t.Any, t.Any]):
4206 """Use a Enum class as model for the data type description.
4207 Note that if no default-value is provided, the first enum-value is used
4208 as default-value.
4210 .. sourcecode:: python
4212 import enum
4213 from traitlets import HasTraits, UseEnum
4216 class Color(enum.Enum):
4217 red = 1 # -- IMPLICIT: default_value
4218 blue = 2
4219 green = 3
4222 class MyEntity(HasTraits):
4223 color = UseEnum(Color, default_value=Color.blue)
4226 entity = MyEntity(color=Color.red)
4227 entity.color = Color.green # USE: Enum-value (preferred)
4228 entity.color = "green" # USE: name (as string)
4229 entity.color = "Color.green" # USE: scoped-name (as string)
4230 entity.color = 3 # USE: number (as int)
4231 assert entity.color is Color.green
4232 """
4234 default_value: enum.Enum | None = None
4235 info_text = "Trait type adapter to a Enum class"
4237 def __init__(
4238 self, enum_class: type[t.Any], default_value: t.Any = None, **kwargs: t.Any
4239 ) -> None:
4240 assert issubclass(enum_class, enum.Enum), f"REQUIRE: enum.Enum, but was: {enum_class!r}"
4241 allow_none = kwargs.get("allow_none", False)
4242 if default_value is None and not allow_none:
4243 default_value = next(iter(enum_class.__members__.values()))
4244 super().__init__(default_value=default_value, **kwargs)
4245 self.enum_class = enum_class
4246 self.name_prefix = enum_class.__name__ + "."
4248 def select_by_number(self, value: int, default: t.Any = Undefined) -> t.Any:
4249 """Selects enum-value by using its number-constant."""
4250 assert isinstance(value, int)
4251 enum_members = self.enum_class.__members__
4252 for enum_item in enum_members.values():
4253 if enum_item.value == value:
4254 return enum_item
4255 # -- NOT FOUND:
4256 return default
4258 def select_by_name(self, value: str, default: t.Any = Undefined) -> t.Any:
4259 """Selects enum-value by using its name or scoped-name."""
4260 assert isinstance(value, str)
4261 if value.startswith(self.name_prefix):
4262 # -- SUPPORT SCOPED-NAMES, like: "Color.red" => "red"
4263 value = value.replace(self.name_prefix, "", 1)
4264 return self.enum_class.__members__.get(value, default)
4266 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
4267 if isinstance(value, self.enum_class):
4268 return value
4269 elif isinstance(value, int):
4270 # -- CONVERT: number => enum_value (item)
4271 value2 = self.select_by_number(value)
4272 if value2 is not Undefined:
4273 return value2
4274 elif isinstance(value, str):
4275 # -- CONVERT: name or scoped_name (as string) => enum_value (item)
4276 value2 = self.select_by_name(value)
4277 if value2 is not Undefined:
4278 return value2
4279 elif value is None:
4280 if self.allow_none:
4281 return None
4282 else:
4283 return self.default_value
4284 self.error(obj, value)
4286 def _choices_str(self, as_rst: bool = False) -> str:
4287 """Returns a description of the trait choices (not none)."""
4288 choices = self.enum_class.__members__.keys()
4289 if as_rst:
4290 return "|".join(f"``{x!r}``" for x in choices)
4291 else:
4292 return repr(list(choices)) # Listify because py3.4- prints odict-class
4294 def _info(self, as_rst: bool = False) -> str:
4295 """Returns a description of the trait."""
4296 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
4297 return f"any of {self._choices_str(as_rst)}{none}"
4299 def info(self) -> str:
4300 return self._info(as_rst=False)
4302 def info_rst(self) -> str:
4303 return self._info(as_rst=True)
4306class Callable(TraitType[t.Callable[..., t.Any], t.Callable[..., t.Any]]):
4307 """A trait which is callable.
4309 Notes
4310 -----
4311 Classes are callable, as are instances
4312 with a __call__() method."""
4314 info_text = "a callable"
4316 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
4317 if callable(value):
4318 return value
4319 else:
4320 self.error(obj, value)