Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/traitlets/traitlets.py: 21%
1739 statements
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-15 06:13 +0000
« prev ^ index » next coverage.py v7.3.3, created at 2023-12-15 06:13 +0000
1"""
2A lightweight Traits like module.
4This is designed to provide a lightweight, simple, pure Python version of
5many of the capabilities of enthought.traits. This includes:
7* Validation
8* Type specification with defaults
9* Static and dynamic notification
10* Basic predefined types
11* An API that is similar to enthought.traits
13We don't support:
15* Delegation
16* Automatic GUI generation
17* A full set of trait types. Most importantly, we don't provide container
18 traits (list, dict, tuple) that can trigger notifications if their
19 contents change.
20* API compatibility with enthought.traits
22There are also some important difference in our design:
24* enthought.traits does not validate default values. We do.
26We choose to create this module because we need these capabilities, but
27we need them to be pure Python so they work in all Python implementations,
28including Jython and IronPython.
30Inheritance diagram:
32.. inheritance-diagram:: traitlets.traitlets
33 :parts: 3
34"""
36# Copyright (c) IPython Development Team.
37# Distributed under the terms of the Modified BSD License.
38#
39# Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
40# also under the terms of the Modified BSD License.
42from __future__ import annotations
44import contextlib
45import enum
46import inspect
47import os
48import re
49import sys
50import types
51import typing as t
52from ast import literal_eval
54from .utils.bunch import Bunch
55from .utils.descriptions import add_article, class_of, describe, repr_type
56from .utils.getargspec import getargspec
57from .utils.importstring import import_item
58from .utils.sentinel import Sentinel
59from .utils.warnings import deprecated_method, should_warn, warn
61SequenceTypes = (list, tuple, set, frozenset)
63# backward compatibility, use to differ between Python 2 and 3.
64ClassTypes = (type,)
66if t.TYPE_CHECKING:
67 from typing_extensions import TypeVar
68else:
69 from typing import TypeVar
71# exports:
73__all__ = [
74 "All",
75 "Any",
76 "BaseDescriptor",
77 "Bool",
78 "Bytes",
79 "CBool",
80 "CBytes",
81 "CComplex",
82 "CFloat",
83 "CInt",
84 "CLong",
85 "CRegExp",
86 "CUnicode",
87 "Callable",
88 "CaselessStrEnum",
89 "ClassBasedTraitType",
90 "Complex",
91 "Container",
92 "DefaultHandler",
93 "Dict",
94 "DottedObjectName",
95 "Enum",
96 "EventHandler",
97 "Float",
98 "ForwardDeclaredInstance",
99 "ForwardDeclaredMixin",
100 "ForwardDeclaredType",
101 "FuzzyEnum",
102 "HasDescriptors",
103 "HasTraits",
104 "Instance",
105 "Int",
106 "Integer",
107 "List",
108 "Long",
109 "MetaHasDescriptors",
110 "MetaHasTraits",
111 "ObjectName",
112 "ObserveHandler",
113 "Set",
114 "TCPAddress",
115 "This",
116 "TraitError",
117 "TraitType",
118 "Tuple",
119 "Type",
120 "Unicode",
121 "Undefined",
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: t.Any) -> bool:
173 return t.cast(bool, s.isidentifier())
176def _safe_literal_eval(s: str) -> t.Any:
177 """Safely evaluate an expression
179 Returns original string if eval fails.
181 Use only where types are ambiguous.
182 """
183 try:
184 return literal_eval(s)
185 except (NameError, SyntaxError, ValueError):
186 return s
189def is_trait(t: t.Any) -> bool:
190 """Returns whether the given value is an instance or subclass of TraitType."""
191 return isinstance(t, TraitType) or (isinstance(t, type) and issubclass(t, TraitType))
194def parse_notifier_name(names: Sentinel | str | t.Iterable[Sentinel | str]) -> t.Iterable[t.Any]:
195 """Convert the name argument to a list of names.
197 Examples
198 --------
199 >>> parse_notifier_name([])
200 [traitlets.All]
201 >>> parse_notifier_name("a")
202 ['a']
203 >>> parse_notifier_name(["a", "b"])
204 ['a', 'b']
205 >>> parse_notifier_name(All)
206 [traitlets.All]
207 """
208 if names is All or isinstance(names, str):
209 return [names]
210 elif isinstance(names, Sentinel):
211 raise TypeError("`names` must be either `All`, a str, or a list of strs.")
212 else:
213 if not names or All in names:
214 return [All]
215 for n in names:
216 if not isinstance(n, str):
217 raise TypeError(f"names must be strings, not {type(n).__name__}({n!r})")
218 return names
221class _SimpleTest:
222 def __init__(self, value: t.Any) -> None:
223 self.value = value
225 def __call__(self, test: t.Any) -> bool:
226 return bool(test == self.value)
228 def __repr__(self) -> str:
229 return "<SimpleTest(%r)" % self.value
231 def __str__(self) -> str:
232 return self.__repr__()
235def getmembers(object: t.Any, predicate: t.Any = None) -> list[tuple[str, t.Any]]:
236 """A safe version of inspect.getmembers that handles missing attributes.
238 This is useful when there are descriptor based attributes that for
239 some reason raise AttributeError even though they exist. This happens
240 in zope.interface with the __provides__ attribute.
241 """
242 results = []
243 for key in dir(object):
244 try:
245 value = getattr(object, key)
246 except AttributeError:
247 pass
248 else:
249 if not predicate or predicate(value):
250 results.append((key, value))
251 results.sort()
252 return results
255def _validate_link(*tuples: t.Any) -> None:
256 """Validate arguments for traitlet link functions"""
257 for tup in tuples:
258 if not len(tup) == 2:
259 raise TypeError(
260 "Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t
261 )
262 obj, trait_name = tup
263 if not isinstance(obj, HasTraits):
264 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
265 if trait_name not in obj.traits():
266 raise TypeError(f"{obj!r} has no trait {trait_name!r}")
269class link:
270 """Link traits from different objects together so they remain in sync.
272 Parameters
273 ----------
274 source : (object / attribute name) pair
275 target : (object / attribute name) pair
276 transform: iterable with two callables (optional)
277 Data transformation between source and target and target and source.
279 Examples
280 --------
281 >>> class X(HasTraits):
282 ... value = Int()
284 >>> src = X(value=1)
285 >>> tgt = X(value=42)
286 >>> c = link((src, "value"), (tgt, "value"))
288 Setting source updates target objects:
289 >>> src.value = 5
290 >>> tgt.value
291 5
292 """
294 updating = False
296 def __init__(self, source: t.Any, target: t.Any, transform: t.Any = None) -> None:
297 _validate_link(source, target)
298 self.source, self.target = source, target
299 self._transform, self._transform_inv = transform if transform else (lambda x: x,) * 2
301 self.link()
303 def link(self) -> None:
304 try:
305 setattr(
306 self.target[0],
307 self.target[1],
308 self._transform(getattr(self.source[0], self.source[1])),
309 )
311 finally:
312 self.source[0].observe(self._update_target, names=self.source[1])
313 self.target[0].observe(self._update_source, names=self.target[1])
315 @contextlib.contextmanager
316 def _busy_updating(self) -> t.Any:
317 self.updating = True
318 try:
319 yield
320 finally:
321 self.updating = False
323 def _update_target(self, change: t.Any) -> None:
324 if self.updating:
325 return
326 with self._busy_updating():
327 setattr(self.target[0], self.target[1], self._transform(change.new))
328 if getattr(self.source[0], self.source[1]) != change.new:
329 raise TraitError(
330 f"Broken link {self}: the source value changed while updating " "the target."
331 )
333 def _update_source(self, change: t.Any) -> None:
334 if self.updating:
335 return
336 with self._busy_updating():
337 setattr(self.source[0], self.source[1], self._transform_inv(change.new))
338 if getattr(self.target[0], self.target[1]) != change.new:
339 raise TraitError(
340 f"Broken link {self}: the target value changed while updating " "the source."
341 )
343 def unlink(self) -> None:
344 self.source[0].unobserve(self._update_target, names=self.source[1])
345 self.target[0].unobserve(self._update_source, names=self.target[1])
348class directional_link:
349 """Link the trait of a source object with traits of target objects.
351 Parameters
352 ----------
353 source : (object, attribute name) pair
354 target : (object, attribute name) pair
355 transform: callable (optional)
356 Data transformation between source and target.
358 Examples
359 --------
360 >>> class X(HasTraits):
361 ... value = Int()
363 >>> src = X(value=1)
364 >>> tgt = X(value=42)
365 >>> c = directional_link((src, "value"), (tgt, "value"))
367 Setting source updates target objects:
368 >>> src.value = 5
369 >>> tgt.value
370 5
372 Setting target does not update source object:
373 >>> tgt.value = 6
374 >>> src.value
375 5
377 """
379 updating = False
381 def __init__(self, source: t.Any, target: t.Any, transform: t.Any = None) -> None:
382 self._transform = transform if transform else lambda x: x
383 _validate_link(source, target)
384 self.source, self.target = source, target
385 self.link()
387 def link(self) -> None:
388 try:
389 setattr(
390 self.target[0],
391 self.target[1],
392 self._transform(getattr(self.source[0], self.source[1])),
393 )
394 finally:
395 self.source[0].observe(self._update, names=self.source[1])
397 @contextlib.contextmanager
398 def _busy_updating(self) -> t.Any:
399 self.updating = True
400 try:
401 yield
402 finally:
403 self.updating = False
405 def _update(self, change: t.Any) -> None:
406 if self.updating:
407 return
408 with self._busy_updating():
409 setattr(self.target[0], self.target[1], self._transform(change.new))
411 def unlink(self) -> None:
412 self.source[0].unobserve(self._update, names=self.source[1])
415dlink = directional_link
418# -----------------------------------------------------------------------------
419# Base Descriptor Class
420# -----------------------------------------------------------------------------
423class BaseDescriptor:
424 """Base descriptor class
426 Notes
427 -----
428 This implements Python's descriptor protocol.
430 This class is the base class for all such descriptors. The
431 only magic we use is a custom metaclass for the main :class:`HasTraits`
432 class that does the following:
434 1. Sets the :attr:`name` attribute of every :class:`BaseDescriptor`
435 instance in the class dict to the name of the attribute.
436 2. Sets the :attr:`this_class` attribute of every :class:`BaseDescriptor`
437 instance in the class dict to the *class* that declared the trait.
438 This is used by the :class:`This` trait to allow subclasses to
439 accept superclasses for :class:`This` values.
440 """
442 name: str | None = None
443 this_class: type[HasTraits] | None = None
445 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
446 """Part of the initialization which may depend on the underlying
447 HasDescriptors class.
449 It is typically overloaded for specific types.
451 This method is called by :meth:`MetaHasDescriptors.__init__`
452 passing the class (`cls`) and `name` under which the descriptor
453 has been assigned.
454 """
455 self.this_class = cls
456 self.name = name
458 def subclass_init(self, cls: type[HasTraits]) -> None:
459 # Instead of HasDescriptors.setup_instance calling
460 # every instance_init, we opt in by default.
461 # This gives descriptors a change to opt out for
462 # performance reasons.
463 # Because most traits do not need instance_init,
464 # and it will otherwise be called for every HasTrait instance
465 # being created, this otherwise gives a significant performance
466 # pentalty. Most TypeTraits in traitlets opt out.
467 cls._instance_inits.append(self.instance_init)
469 def instance_init(self, obj: t.Any) -> None:
470 """Part of the initialization which may depend on the underlying
471 HasDescriptors instance.
473 It is typically overloaded for specific types.
475 This method is called by :meth:`HasTraits.__new__` and in the
476 :meth:`BaseDescriptor.instance_init` method of descriptors holding
477 other descriptors.
478 """
479 pass
482G = TypeVar("G")
483S = TypeVar("S")
484T = TypeVar("T")
487# Self from typing extension doesn't work well with mypy https://github.com/python/mypy/pull/14041
488# see https://peps.python.org/pep-0673/#use-in-generic-classes
489# Self = t.TypeVar("Self", bound="TraitType[Any, Any]")
490if t.TYPE_CHECKING:
491 from typing_extensions import Literal, Self
493 K = TypeVar("K", default=str)
494 V = TypeVar("V", default=t.Any)
497# We use a type for the getter (G) and setter (G) because we allow
498# for traits to cast (for instance CInt will use G=int, S=t.Any)
499class TraitType(BaseDescriptor, t.Generic[G, S]):
500 """A base class for all trait types."""
502 metadata: dict[str, t.Any] = {}
503 allow_none: bool = False
504 read_only: bool = False
505 info_text: str = "any value"
506 default_value: t.Any = Undefined
508 def __init__(
509 self: TraitType[G, S],
510 default_value: t.Any = Undefined,
511 allow_none: bool = False,
512 read_only: bool | None = None,
513 help: str | None = None,
514 config: t.Any = None,
515 **kwargs: t.Any,
516 ) -> None:
517 """Declare a traitlet.
519 If *allow_none* is True, None is a valid value in addition to any
520 values that are normally valid. The default is up to the subclass.
521 For most trait types, the default value for ``allow_none`` is False.
523 If *read_only* is True, attempts to directly modify a trait attribute raises a TraitError.
525 If *help* is a string, it documents the attribute's purpose.
527 Extra metadata can be associated with the traitlet using the .tag() convenience method
528 or by using the traitlet instance's .metadata dictionary.
529 """
530 if default_value is not Undefined:
531 self.default_value = default_value
532 if allow_none:
533 self.allow_none = allow_none
534 if read_only is not None:
535 self.read_only = read_only
536 self.help = help if help is not None else ""
537 if self.help:
538 # define __doc__ so that inspectors like autodoc find traits
539 self.__doc__ = self.help
541 if len(kwargs) > 0:
542 stacklevel = 1
543 f = inspect.currentframe()
544 # count supers to determine stacklevel for warning
545 assert f is not None
546 while f.f_code.co_name == "__init__":
547 stacklevel += 1
548 f = f.f_back
549 assert f is not None
550 mod = f.f_globals.get("__name__") or ""
551 pkg = mod.split(".", 1)[0]
552 key = ("metadata-tag", pkg, *sorted(kwargs))
553 if should_warn(key):
554 warn(
555 f"metadata {kwargs} was set from the constructor. "
556 "With traitlets 4.1, metadata should be set using the .tag() method, "
557 "e.g., Int().tag(key1='value1', key2='value2')",
558 DeprecationWarning,
559 stacklevel=stacklevel,
560 )
561 if len(self.metadata) > 0:
562 self.metadata = self.metadata.copy()
563 self.metadata.update(kwargs)
564 else:
565 self.metadata = kwargs
566 else:
567 self.metadata = self.metadata.copy()
568 if config is not None:
569 self.metadata["config"] = config
571 # We add help to the metadata during a deprecation period so that
572 # code that looks for the help string there can find it.
573 if help is not None:
574 self.metadata["help"] = help
576 def from_string(self, s: str) -> G | None:
577 """Get a value from a config string
579 such as an environment variable or CLI arguments.
581 Traits can override this method to define their own
582 parsing of config strings.
584 .. seealso:: item_from_string
586 .. versionadded:: 5.0
587 """
588 if self.allow_none and s == "None":
589 return None
590 return s # type:ignore[return-value]
592 def default(self, obj: t.Any = None) -> G | None:
593 """The default generator for this trait
595 Notes
596 -----
597 This method is registered to HasTraits classes during ``class_init``
598 in the same way that dynamic defaults defined by ``@default`` are.
599 """
600 if self.default_value is not Undefined:
601 return t.cast(G, self.default_value)
602 elif hasattr(self, "make_dynamic_default"):
603 return t.cast(G, self.make_dynamic_default())
604 else:
605 # Undefined will raise in TraitType.get
606 return t.cast(G, self.default_value)
608 def get_default_value(self) -> G | None:
609 """DEPRECATED: Retrieve the static default value for this trait.
610 Use self.default_value instead
611 """
612 warn(
613 "get_default_value is deprecated in traitlets 4.0: use the .default_value attribute",
614 DeprecationWarning,
615 stacklevel=2,
616 )
617 return t.cast(G, self.default_value)
619 def init_default_value(self, obj: t.Any) -> G | None:
620 """DEPRECATED: Set the static default value for the trait type."""
621 warn(
622 "init_default_value is deprecated in traitlets 4.0, and may be removed in the future",
623 DeprecationWarning,
624 stacklevel=2,
625 )
626 value = self._validate(obj, self.default_value)
627 obj._trait_values[self.name] = value
628 return value
630 def get(self, obj: HasTraits, cls: type[t.Any] | None = None) -> G | None:
631 assert self.name is not None
632 try:
633 value = obj._trait_values[self.name]
634 except KeyError:
635 # Check for a dynamic initializer.
636 default = obj.trait_defaults(self.name)
637 if default is Undefined:
638 warn(
639 "Explicit using of Undefined as the default value "
640 "is deprecated in traitlets 5.0, and may cause "
641 "exceptions in the future.",
642 DeprecationWarning,
643 stacklevel=2,
644 )
645 # Using a context manager has a large runtime overhead, so we
646 # write out the obj.cross_validation_lock call here.
647 _cross_validation_lock = obj._cross_validation_lock
648 try:
649 obj._cross_validation_lock = True
650 value = self._validate(obj, default)
651 finally:
652 obj._cross_validation_lock = _cross_validation_lock
653 obj._trait_values[self.name] = value
654 obj._notify_observers(
655 Bunch(
656 name=self.name,
657 value=value,
658 owner=obj,
659 type="default",
660 )
661 )
662 return t.cast(G, value)
663 except Exception as e:
664 # This should never be reached.
665 raise TraitError("Unexpected error in TraitType: default value not set properly") from e
666 else:
667 return t.cast(G, value)
669 @t.overload
670 def __get__(self, obj: None, cls: type[t.Any]) -> Self:
671 ...
673 @t.overload
674 def __get__(self, obj: t.Any, cls: type[t.Any]) -> G:
675 ...
677 def __get__(self, obj: HasTraits | None, cls: type[t.Any]) -> Self | G:
678 """Get the value of the trait by self.name for the instance.
680 Default values are instantiated when :meth:`HasTraits.__new__`
681 is called. Thus by the time this method gets called either the
682 default value or a user defined value (they called :meth:`__set__`)
683 is in the :class:`HasTraits` instance.
684 """
685 if obj is None:
686 return self
687 else:
688 return t.cast(G, self.get(obj, cls)) # the G should encode the Optional
690 def set(self, obj: HasTraits, value: S) -> None:
691 new_value = self._validate(obj, value)
692 assert self.name is not None
693 try:
694 old_value = obj._trait_values[self.name]
695 except KeyError:
696 old_value = self.default_value
698 obj._trait_values[self.name] = new_value
699 try:
700 silent = bool(old_value == new_value)
701 except Exception:
702 # if there is an error in comparing, default to notify
703 silent = False
704 if silent is not True:
705 # we explicitly compare silent to True just in case the equality
706 # comparison above returns something other than True/False
707 obj._notify_trait(self.name, old_value, new_value)
709 def __set__(self, obj: HasTraits, value: S) -> None:
710 """Set the value of the trait by self.name for the instance.
712 Values pass through a validation stage where errors are raised when
713 impropper types, or types that cannot be coerced, are encountered.
714 """
715 if self.read_only:
716 raise TraitError('The "%s" trait is read-only.' % self.name)
717 else:
718 self.set(obj, value)
720 def _validate(self, obj: t.Any, value: t.Any) -> G | None:
721 if value is None and self.allow_none:
722 return value
723 if hasattr(self, "validate"):
724 value = self.validate(obj, value)
725 if obj._cross_validation_lock is False:
726 value = self._cross_validate(obj, value)
727 return t.cast(G, value)
729 def _cross_validate(self, obj: t.Any, value: t.Any) -> G | None:
730 if self.name in obj._trait_validators:
731 proposal = Bunch({"trait": self, "value": value, "owner": obj})
732 value = obj._trait_validators[self.name](obj, proposal)
733 elif hasattr(obj, "_%s_validate" % self.name):
734 meth_name = "_%s_validate" % self.name
735 cross_validate = getattr(obj, meth_name)
736 deprecated_method(
737 cross_validate,
738 obj.__class__,
739 meth_name,
740 "use @validate decorator instead.",
741 )
742 value = cross_validate(value, self)
743 return t.cast(G, value)
745 def __or__(self, other: TraitType[t.Any, t.Any]) -> Union:
746 if isinstance(other, Union):
747 return Union([self, *other.trait_types])
748 else:
749 return Union([self, other])
751 def info(self) -> str:
752 return self.info_text
754 def error(
755 self,
756 obj: HasTraits | None,
757 value: t.Any,
758 error: Exception | None = None,
759 info: str | None = None,
760 ) -> t.NoReturn:
761 """Raise a TraitError
763 Parameters
764 ----------
765 obj : HasTraits or None
766 The instance which owns the trait. If not
767 object is given, then an object agnostic
768 error will be raised.
769 value : any
770 The value that caused the error.
771 error : Exception (default: None)
772 An error that was raised by a child trait.
773 The arguments of this exception should be
774 of the form ``(value, info, *traits)``.
775 Where the ``value`` and ``info`` are the
776 problem value, and string describing the
777 expected value. The ``traits`` are a series
778 of :class:`TraitType` instances that are
779 "children" of this one (the first being
780 the deepest).
781 info : str (default: None)
782 A description of the expected value. By
783 default this is inferred from this trait's
784 ``info`` method.
785 """
786 if error is not None:
787 # handle nested error
788 error.args += (self,)
789 if self.name is not None:
790 # this is the root trait that must format the final message
791 chain = " of ".join(describe("a", t) for t in error.args[2:])
792 if obj is not None:
793 error.args = (
794 "The '{}' trait of {} instance contains {} which "
795 "expected {}, not {}.".format(
796 self.name,
797 describe("an", obj),
798 chain,
799 error.args[1],
800 describe("the", error.args[0]),
801 ),
802 )
803 else:
804 error.args = (
805 "The '{}' trait contains {} which " "expected {}, not {}.".format(
806 self.name,
807 chain,
808 error.args[1],
809 describe("the", error.args[0]),
810 ),
811 )
812 raise error
813 else:
814 # this trait caused an error
815 if self.name is None:
816 # this is not the root trait
817 raise TraitError(value, info or self.info(), self)
818 else:
819 # this is the root trait
820 if obj is not None:
821 e = "The '{}' trait of {} instance expected {}, not {}.".format(
822 self.name,
823 class_of(obj),
824 info or self.info(),
825 describe("the", value),
826 )
827 else:
828 e = "The '{}' trait expected {}, not {}.".format(
829 self.name,
830 info or self.info(),
831 describe("the", value),
832 )
833 raise TraitError(e)
835 def get_metadata(self, key: str, default: t.Any = None) -> t.Any:
836 """DEPRECATED: Get a metadata value.
838 Use .metadata[key] or .metadata.get(key, default) instead.
839 """
840 if key == "help":
841 msg = "use the instance .help string directly, like x.help"
842 else:
843 msg = "use the instance .metadata dictionary directly, like x.metadata[key] or x.metadata.get(key, default)"
844 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
845 return self.metadata.get(key, default)
847 def set_metadata(self, key: str, value: t.Any) -> None:
848 """DEPRECATED: Set a metadata key/value.
850 Use .metadata[key] = value instead.
851 """
852 if key == "help":
853 msg = "use the instance .help string directly, like x.help = value"
854 else:
855 msg = "use the instance .metadata dictionary directly, like x.metadata[key] = value"
856 warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2)
857 self.metadata[key] = value
859 def tag(self, **metadata: t.Any) -> Self:
860 """Sets metadata and returns self.
862 This allows convenient metadata tagging when initializing the trait, such as:
864 Examples
865 --------
866 >>> Int(0).tag(config=True, sync=True)
867 <traitlets.traitlets.Int object at ...>
869 """
870 maybe_constructor_keywords = set(metadata.keys()).intersection(
871 {"help", "allow_none", "read_only", "default_value"}
872 )
873 if maybe_constructor_keywords:
874 warn(
875 "The following attributes are set in using `tag`, but seem to be constructor keywords arguments: %s "
876 % maybe_constructor_keywords,
877 UserWarning,
878 stacklevel=2,
879 )
881 self.metadata.update(metadata)
882 return self
884 def default_value_repr(self) -> str:
885 return repr(self.default_value)
888# -----------------------------------------------------------------------------
889# The HasTraits implementation
890# -----------------------------------------------------------------------------
893class _CallbackWrapper:
894 """An object adapting a on_trait_change callback into an observe callback.
896 The comparison operator __eq__ is implemented to enable removal of wrapped
897 callbacks.
898 """
900 def __init__(self, cb: t.Any) -> None:
901 self.cb = cb
902 # Bound methods have an additional 'self' argument.
903 offset = -1 if isinstance(self.cb, types.MethodType) else 0
904 self.nargs = len(getargspec(cb)[0]) + offset
905 if self.nargs > 4:
906 raise TraitError("a trait changed callback must have 0-4 arguments.")
908 def __eq__(self, other: t.Any) -> bool:
909 # The wrapper is equal to the wrapped element
910 if isinstance(other, _CallbackWrapper):
911 return bool(self.cb == other.cb)
912 else:
913 return bool(self.cb == other)
915 def __call__(self, change: Bunch) -> None:
916 # The wrapper is callable
917 if self.nargs == 0:
918 self.cb()
919 elif self.nargs == 1:
920 self.cb(change.name)
921 elif self.nargs == 2:
922 self.cb(change.name, change.new)
923 elif self.nargs == 3:
924 self.cb(change.name, change.old, change.new)
925 elif self.nargs == 4:
926 self.cb(change.name, change.old, change.new, change.owner)
929def _callback_wrapper(cb: t.Any) -> _CallbackWrapper:
930 if isinstance(cb, _CallbackWrapper):
931 return cb
932 else:
933 return _CallbackWrapper(cb)
936class MetaHasDescriptors(type):
937 """A metaclass for HasDescriptors.
939 This metaclass makes sure that any TraitType class attributes are
940 instantiated and sets their name attribute.
941 """
943 def __new__(
944 mcls: type[MetaHasDescriptors], # noqa: N804
945 name: str,
946 bases: tuple[type, ...],
947 classdict: dict[str, t.Any],
948 **kwds: t.Any,
949 ) -> MetaHasDescriptors:
950 """Create the HasDescriptors class."""
951 for k, v in classdict.items():
952 # ----------------------------------------------------------------
953 # Support of deprecated behavior allowing for TraitType types
954 # to be used instead of TraitType instances.
955 if inspect.isclass(v) and issubclass(v, TraitType):
956 warn(
957 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
958 " Passing types is deprecated in traitlets 4.1.",
959 DeprecationWarning,
960 stacklevel=2,
961 )
962 classdict[k] = v()
963 # ----------------------------------------------------------------
965 return super().__new__(mcls, name, bases, classdict, **kwds)
967 def __init__(
968 cls, name: str, bases: tuple[type, ...], classdict: dict[str, t.Any], **kwds: t.Any
969 ) -> None:
970 """Finish initializing the HasDescriptors class."""
971 super().__init__(name, bases, classdict, **kwds)
972 cls.setup_class(classdict)
974 def setup_class(cls: MetaHasDescriptors, classdict: dict[str, t.Any]) -> None:
975 """Setup descriptor instance on the class
977 This sets the :attr:`this_class` and :attr:`name` attributes of each
978 BaseDescriptor in the class dict of the newly created ``cls`` before
979 calling their :attr:`class_init` method.
980 """
981 cls._descriptors = []
982 cls._instance_inits: list[t.Any] = []
983 for k, v in classdict.items():
984 if isinstance(v, BaseDescriptor):
985 v.class_init(cls, k) # type:ignore[arg-type]
987 for _, v in getmembers(cls):
988 if isinstance(v, BaseDescriptor):
989 v.subclass_init(cls) # type:ignore[arg-type]
990 cls._descriptors.append(v)
993class MetaHasTraits(MetaHasDescriptors):
994 """A metaclass for HasTraits."""
996 def setup_class(cls: MetaHasTraits, classdict: dict[str, t.Any]) -> None: # noqa
997 # for only the current class
998 cls._trait_default_generators: dict[str, t.Any] = {}
999 # also looking at base classes
1000 cls._all_trait_default_generators = {}
1001 cls._traits = {}
1002 cls._static_immutable_initial_values = {}
1004 super().setup_class(classdict)
1006 mro = cls.mro()
1008 for name in dir(cls):
1009 # Some descriptors raise AttributeError like zope.interface's
1010 # __provides__ attributes even though they exist. This causes
1011 # AttributeErrors even though they are listed in dir(cls).
1012 try:
1013 value = getattr(cls, name)
1014 except AttributeError:
1015 continue
1016 if isinstance(value, TraitType):
1017 cls._traits[name] = value
1018 trait = value
1019 default_method_name = "_%s_default" % name
1020 mro_trait = mro
1021 try:
1022 mro_trait = mro[: mro.index(trait.this_class) + 1] # type:ignore[arg-type]
1023 except ValueError:
1024 # this_class not in mro
1025 pass
1026 for c in mro_trait:
1027 if default_method_name in c.__dict__:
1028 cls._all_trait_default_generators[name] = c.__dict__[default_method_name]
1029 break
1030 if name in c.__dict__.get("_trait_default_generators", {}):
1031 cls._all_trait_default_generators[name] = c._trait_default_generators[name] # type: ignore[attr-defined]
1032 break
1033 else:
1034 # We don't have a dynamic default generator using @default etc.
1035 # Now if the default value is not dynamic and immutable (string, number)
1036 # and does not require any validation, we keep them in a dict
1037 # of initial values to speed up instance creation.
1038 # This is a very specific optimization, but a very common scenario in
1039 # for instance ipywidgets.
1040 none_ok = trait.default_value is None and trait.allow_none
1041 if (
1042 type(trait) in [CInt, Int]
1043 and trait.min is None # type: ignore[attr-defined]
1044 and trait.max is None # type: ignore[attr-defined]
1045 and (isinstance(trait.default_value, int) or none_ok)
1046 ):
1047 cls._static_immutable_initial_values[name] = trait.default_value
1048 elif (
1049 type(trait) in [CFloat, Float]
1050 and trait.min is None # type: ignore[attr-defined]
1051 and trait.max is None # type: ignore[attr-defined]
1052 and (isinstance(trait.default_value, float) or none_ok)
1053 ):
1054 cls._static_immutable_initial_values[name] = trait.default_value
1055 elif type(trait) in [CBool, Bool] and (
1056 isinstance(trait.default_value, bool) or none_ok
1057 ):
1058 cls._static_immutable_initial_values[name] = trait.default_value
1059 elif type(trait) in [CUnicode, Unicode] and (
1060 isinstance(trait.default_value, str) or none_ok
1061 ):
1062 cls._static_immutable_initial_values[name] = trait.default_value
1063 elif type(trait) == Any and (
1064 isinstance(trait.default_value, (str, int, float, bool)) or none_ok
1065 ):
1066 cls._static_immutable_initial_values[name] = trait.default_value
1067 elif type(trait) == Union and trait.default_value is None:
1068 cls._static_immutable_initial_values[name] = None
1069 elif (
1070 isinstance(trait, Instance)
1071 and trait.default_args is None
1072 and trait.default_kwargs is None
1073 and trait.allow_none
1074 ):
1075 cls._static_immutable_initial_values[name] = None
1077 # we always add it, because a class may change when we call add_trait
1078 # and then the instance may not have all the _static_immutable_initial_values
1079 cls._all_trait_default_generators[name] = trait.default
1082def observe(*names: Sentinel | str, type: str = "change") -> ObserveHandler:
1083 """A decorator which can be used to observe Traits on a class.
1085 The handler passed to the decorator will be called with one ``change``
1086 dict argument. The change dictionary at least holds a 'type' key and a
1087 'name' key, corresponding respectively to the type of notification and the
1088 name of the attribute that triggered the notification.
1090 Other keys may be passed depending on the value of 'type'. In the case
1091 where type is 'change', we also have the following keys:
1092 * ``owner`` : the HasTraits instance
1093 * ``old`` : the old value of the modified trait attribute
1094 * ``new`` : the new value of the modified trait attribute
1095 * ``name`` : the name of the modified trait attribute.
1097 Parameters
1098 ----------
1099 *names
1100 The str names of the Traits to observe on the object.
1101 type : str, kwarg-only
1102 The type of event to observe (e.g. 'change')
1103 """
1104 if not names:
1105 raise TypeError("Please specify at least one trait name to observe.")
1106 for name in names:
1107 if name is not All and not isinstance(name, str):
1108 raise TypeError("trait names to observe must be strings or All, not %r" % name)
1109 return ObserveHandler(names, type=type)
1112def observe_compat(func: FuncT) -> FuncT:
1113 """Backward-compatibility shim decorator for observers
1115 Use with:
1117 @observe('name')
1118 @observe_compat
1119 def _foo_changed(self, change):
1120 ...
1122 With this, `super()._foo_changed(self, name, old, new)` in subclasses will still work.
1123 Allows adoption of new observer API without breaking subclasses that override and super.
1124 """
1126 def compatible_observer(
1127 self: t.Any, change_or_name: str, old: t.Any = Undefined, new: t.Any = Undefined
1128 ) -> t.Any:
1129 if isinstance(change_or_name, dict): # type:ignore[unreachable]
1130 change = Bunch(change_or_name) # type:ignore[unreachable]
1131 else:
1132 clsname = self.__class__.__name__
1133 warn(
1134 f"A parent of {clsname}._{change_or_name}_changed has adopted the new (traitlets 4.1) @observe(change) API",
1135 DeprecationWarning,
1136 stacklevel=2,
1137 )
1138 change = Bunch(
1139 type="change",
1140 old=old,
1141 new=new,
1142 name=change_or_name,
1143 owner=self,
1144 )
1145 return func(self, change)
1147 return t.cast(FuncT, compatible_observer)
1150def validate(*names: Sentinel | str) -> ValidateHandler:
1151 """A decorator to register cross validator of HasTraits object's state
1152 when a Trait is set.
1154 The handler passed to the decorator must have one ``proposal`` dict argument.
1155 The proposal dictionary must hold the following keys:
1157 * ``owner`` : the HasTraits instance
1158 * ``value`` : the proposed value for the modified trait attribute
1159 * ``trait`` : the TraitType instance associated with the attribute
1161 Parameters
1162 ----------
1163 *names
1164 The str names of the Traits to validate.
1166 Notes
1167 -----
1168 Since the owner has access to the ``HasTraits`` instance via the 'owner' key,
1169 the registered cross validator could potentially make changes to attributes
1170 of the ``HasTraits`` instance. However, we recommend not to do so. The reason
1171 is that the cross-validation of attributes may run in arbitrary order when
1172 exiting the ``hold_trait_notifications`` context, and such changes may not
1173 commute.
1174 """
1175 if not names:
1176 raise TypeError("Please specify at least one trait name to validate.")
1177 for name in names:
1178 if name is not All and not isinstance(name, str):
1179 raise TypeError("trait names to validate must be strings or All, not %r" % name)
1180 return ValidateHandler(names)
1183def default(name: str) -> DefaultHandler:
1184 """A decorator which assigns a dynamic default for a Trait on a HasTraits object.
1186 Parameters
1187 ----------
1188 name
1189 The str name of the Trait on the object whose default should be generated.
1191 Notes
1192 -----
1193 Unlike observers and validators which are properties of the HasTraits
1194 instance, default value generators are class-level properties.
1196 Besides, default generators are only invoked if they are registered in
1197 subclasses of `this_type`.
1199 ::
1201 class A(HasTraits):
1202 bar = Int()
1204 @default('bar')
1205 def get_bar_default(self):
1206 return 11
1208 class B(A):
1209 bar = Float() # This trait ignores the default generator defined in
1210 # the base class A
1212 class C(B):
1214 @default('bar')
1215 def some_other_default(self): # This default generator should not be
1216 return 3.0 # ignored since it is defined in a
1217 # class derived from B.a.this_class.
1218 """
1219 if not isinstance(name, str):
1220 raise TypeError("Trait name must be a string or All, not %r" % name)
1221 return DefaultHandler(name)
1224FuncT = t.TypeVar("FuncT", bound=t.Callable[..., t.Any])
1227class EventHandler(BaseDescriptor):
1228 def _init_call(self, func: FuncT) -> EventHandler:
1229 self.func = func
1230 return self
1232 @t.overload
1233 def __call__(self, func: FuncT, *args: t.Any, **kwargs: t.Any) -> FuncT:
1234 ...
1236 @t.overload
1237 def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
1238 ...
1240 def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
1241 """Pass `*args` and `**kwargs` to the handler's function if it exists."""
1242 if hasattr(self, "func"):
1243 return self.func(*args, **kwargs)
1244 else:
1245 return self._init_call(*args, **kwargs)
1247 def __get__(self, inst: t.Any, cls: t.Any = None) -> types.MethodType | EventHandler:
1248 if inst is None:
1249 return self
1250 return types.MethodType(self.func, inst)
1253class ObserveHandler(EventHandler):
1254 def __init__(self, names: tuple[Sentinel | str, ...], type: str = "") -> None:
1255 self.trait_names = names
1256 self.type = type
1258 def instance_init(self, inst: HasTraits) -> None:
1259 inst.observe(self, self.trait_names, type=self.type)
1262class ValidateHandler(EventHandler):
1263 def __init__(self, names: tuple[Sentinel | str, ...]) -> None:
1264 self.trait_names = names
1266 def instance_init(self, inst: HasTraits) -> None:
1267 inst._register_validator(self, self.trait_names)
1270class DefaultHandler(EventHandler):
1271 def __init__(self, name: str) -> None:
1272 self.trait_name = name
1274 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
1275 super().class_init(cls, name)
1276 cls._trait_default_generators[self.trait_name] = self
1279class HasDescriptors(metaclass=MetaHasDescriptors):
1280 """The base class for all classes that have descriptors."""
1282 def __new__(*args: t.Any, **kwargs: t.Any) -> t.Any:
1283 # Pass cls as args[0] to allow "cls" as keyword argument
1284 cls = args[0]
1285 args = args[1:]
1287 # This is needed because object.__new__ only accepts
1288 # the cls argument.
1289 new_meth = super(HasDescriptors, cls).__new__
1290 if new_meth is object.__new__:
1291 inst = new_meth(cls)
1292 else:
1293 inst = new_meth(cls, *args, **kwargs)
1294 inst.setup_instance(*args, **kwargs)
1295 return inst
1297 def setup_instance(*args: t.Any, **kwargs: t.Any) -> None:
1298 """
1299 This is called **before** self.__init__ is called.
1300 """
1301 # Pass self as args[0] to allow "self" as keyword argument
1302 self = args[0]
1303 args = args[1:]
1305 self._cross_validation_lock = False
1306 cls = self.__class__
1307 # Let descriptors performance initialization when a HasDescriptor
1308 # instance is created. This allows registration of observers and
1309 # default creations or other bookkeepings.
1310 # Note that descriptors can opt-out of this behavior by overriding
1311 # subclass_init.
1312 for init in cls._instance_inits:
1313 init(self)
1316class HasTraits(HasDescriptors, metaclass=MetaHasTraits):
1317 _trait_values: dict[str, t.Any]
1318 _static_immutable_initial_values: dict[str, t.Any]
1319 _trait_notifiers: dict[str | Sentinel, t.Any]
1320 _trait_validators: dict[str | Sentinel, t.Any]
1321 _cross_validation_lock: bool
1322 _traits: dict[str, t.Any]
1323 _all_trait_default_generators: dict[str, t.Any]
1325 def setup_instance(*args: t.Any, **kwargs: t.Any) -> None:
1326 # Pass self as args[0] to allow "self" as keyword argument
1327 self = args[0]
1328 args = args[1:]
1330 # although we'd prefer to set only the initial values not present
1331 # in kwargs, we will overwrite them in `__init__`, and simply making
1332 # a copy of a dict is faster than checking for each key.
1333 self._trait_values = self._static_immutable_initial_values.copy()
1334 self._trait_notifiers = {}
1335 self._trait_validators = {}
1336 self._cross_validation_lock = False
1337 super(HasTraits, self).setup_instance(*args, **kwargs)
1339 def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
1340 # Allow trait values to be set using keyword arguments.
1341 # We need to use setattr for this to trigger validation and
1342 # notifications.
1343 super_args = args
1344 super_kwargs = {}
1346 if kwargs:
1347 # this is a simplified (and faster) version of
1348 # the hold_trait_notifications(self) context manager
1349 def ignore(change: Bunch) -> None:
1350 pass
1352 self.notify_change = ignore # type:ignore[method-assign]
1353 self._cross_validation_lock = True
1354 changes = {}
1355 for key, value in kwargs.items():
1356 if self.has_trait(key):
1357 setattr(self, key, value)
1358 changes[key] = Bunch(
1359 name=key,
1360 old=None,
1361 new=value,
1362 owner=self,
1363 type="change",
1364 )
1365 else:
1366 # passthrough args that don't set traits to super
1367 super_kwargs[key] = value
1368 # notify and cross validate all trait changes that were set in kwargs
1369 changed = set(kwargs) & set(self._traits)
1370 for key in changed:
1371 value = self._traits[key]._cross_validate(self, getattr(self, key))
1372 self.set_trait(key, value)
1373 changes[key]["new"] = value
1374 self._cross_validation_lock = False
1375 # Restore method retrieval from class
1376 del self.notify_change
1377 for key in changed:
1378 self.notify_change(changes[key])
1380 try:
1381 super().__init__(*super_args, **super_kwargs)
1382 except TypeError as e:
1383 arg_s_list = [repr(arg) for arg in super_args]
1384 for k, v in super_kwargs.items():
1385 arg_s_list.append(f"{k}={v!r}")
1386 arg_s = ", ".join(arg_s_list)
1387 warn(
1388 "Passing unrecognized arguments to super({classname}).__init__({arg_s}).\n"
1389 "{error}\n"
1390 "This is deprecated in traitlets 4.2."
1391 "This error will be raised in a future release of traitlets.".format(
1392 arg_s=arg_s,
1393 classname=self.__class__.__name__,
1394 error=e,
1395 ),
1396 DeprecationWarning,
1397 stacklevel=2,
1398 )
1400 def __getstate__(self) -> dict[str, t.Any]:
1401 d = self.__dict__.copy()
1402 # event handlers stored on an instance are
1403 # expected to be reinstantiated during a
1404 # recall of instance_init during __setstate__
1405 d["_trait_notifiers"] = {}
1406 d["_trait_validators"] = {}
1407 d["_trait_values"] = self._trait_values.copy()
1408 d["_cross_validation_lock"] = False # FIXME: raise if cloning locked!
1410 return d
1412 def __setstate__(self, state: dict[str, t.Any]) -> None:
1413 self.__dict__ = state.copy()
1415 # event handlers are reassigned to self
1416 cls = self.__class__
1417 for key in dir(cls):
1418 # Some descriptors raise AttributeError like zope.interface's
1419 # __provides__ attributes even though they exist. This causes
1420 # AttributeErrors even though they are listed in dir(cls).
1421 try:
1422 value = getattr(cls, key)
1423 except AttributeError:
1424 pass
1425 else:
1426 if isinstance(value, EventHandler):
1427 value.instance_init(self)
1429 @property
1430 @contextlib.contextmanager
1431 def cross_validation_lock(self) -> t.Any:
1432 """
1433 A contextmanager for running a block with our cross validation lock set
1434 to True.
1436 At the end of the block, the lock's value is restored to its value
1437 prior to entering the block.
1438 """
1439 if self._cross_validation_lock:
1440 yield
1441 return
1442 else:
1443 try:
1444 self._cross_validation_lock = True
1445 yield
1446 finally:
1447 self._cross_validation_lock = False
1449 @contextlib.contextmanager
1450 def hold_trait_notifications(self) -> t.Any:
1451 """Context manager for bundling trait change notifications and cross
1452 validation.
1454 Use this when doing multiple trait assignments (init, config), to avoid
1455 race conditions in trait notifiers requesting other trait values.
1456 All trait notifications will fire after all values have been assigned.
1457 """
1458 if self._cross_validation_lock:
1459 yield
1460 return
1461 else:
1462 cache: dict[str, list[Bunch]] = {}
1464 def compress(past_changes: list[Bunch] | None, change: Bunch) -> list[Bunch]:
1465 """Merges the provided change with the last if possible."""
1466 if past_changes is None:
1467 return [change]
1468 else:
1469 if past_changes[-1]["type"] == "change" and change.type == "change":
1470 past_changes[-1]["new"] = change.new
1471 else:
1472 # In case of changes other than 'change', append the notification.
1473 past_changes.append(change)
1474 return past_changes
1476 def hold(change: Bunch) -> None:
1477 name = change.name
1478 cache[name] = compress(cache.get(name), change)
1480 try:
1481 # Replace notify_change with `hold`, caching and compressing
1482 # notifications, disable cross validation and yield.
1483 self.notify_change = hold # type:ignore[method-assign]
1484 self._cross_validation_lock = True
1485 yield
1486 # Cross validate final values when context is released.
1487 for name in list(cache.keys()):
1488 trait = getattr(self.__class__, name)
1489 value = trait._cross_validate(self, getattr(self, name))
1490 self.set_trait(name, value)
1491 except TraitError as e:
1492 # Roll back in case of TraitError during final cross validation.
1493 self.notify_change = lambda x: None # type:ignore[method-assign, assignment]
1494 for name, changes in cache.items():
1495 for change in changes[::-1]:
1496 # TODO: Separate in a rollback function per notification type.
1497 if change.type == "change":
1498 if change.old is not Undefined:
1499 self.set_trait(name, change.old)
1500 else:
1501 self._trait_values.pop(name)
1502 cache = {}
1503 raise e
1504 finally:
1505 self._cross_validation_lock = False
1506 # Restore method retrieval from class
1507 del self.notify_change
1509 # trigger delayed notifications
1510 for changes in cache.values():
1511 for change in changes:
1512 self.notify_change(change)
1514 def _notify_trait(self, name: str, old_value: t.Any, new_value: t.Any) -> None:
1515 self.notify_change(
1516 Bunch(
1517 name=name,
1518 old=old_value,
1519 new=new_value,
1520 owner=self,
1521 type="change",
1522 )
1523 )
1525 def notify_change(self, change: Bunch) -> None:
1526 """Notify observers of a change event"""
1527 return self._notify_observers(change)
1529 def _notify_observers(self, event: Bunch) -> None:
1530 """Notify observers of any event"""
1531 if not isinstance(event, Bunch):
1532 # cast to bunch if given a dict
1533 event = Bunch(event) # type:ignore[unreachable]
1534 name, type = event["name"], event["type"]
1536 callables = []
1537 if name in self._trait_notifiers:
1538 callables.extend(self._trait_notifiers.get(name, {}).get(type, []))
1539 callables.extend(self._trait_notifiers.get(name, {}).get(All, []))
1540 if All in self._trait_notifiers:
1541 callables.extend(self._trait_notifiers.get(All, {}).get(type, []))
1542 callables.extend(self._trait_notifiers.get(All, {}).get(All, []))
1544 # Now static ones
1545 magic_name = "_%s_changed" % name
1546 if event["type"] == "change" and hasattr(self, magic_name):
1547 class_value = getattr(self.__class__, magic_name)
1548 if not isinstance(class_value, ObserveHandler):
1549 deprecated_method(
1550 class_value,
1551 self.__class__,
1552 magic_name,
1553 "use @observe and @unobserve instead.",
1554 )
1555 cb = getattr(self, magic_name)
1556 # Only append the magic method if it was not manually registered
1557 if cb not in callables:
1558 callables.append(_callback_wrapper(cb))
1560 # Call them all now
1561 # Traits catches and logs errors here. I allow them to raise
1562 for c in callables:
1563 # Bound methods have an additional 'self' argument.
1565 if isinstance(c, _CallbackWrapper):
1566 c = c.__call__
1567 elif isinstance(c, EventHandler) and c.name is not None:
1568 c = getattr(self, c.name)
1570 c(event)
1572 def _add_notifiers(
1573 self, handler: t.Callable[..., t.Any], name: Sentinel | str, type: str | Sentinel
1574 ) -> None:
1575 if name not in self._trait_notifiers:
1576 nlist: list[t.Any] = []
1577 self._trait_notifiers[name] = {type: nlist}
1578 else:
1579 if type not in self._trait_notifiers[name]:
1580 nlist = []
1581 self._trait_notifiers[name][type] = nlist
1582 else:
1583 nlist = self._trait_notifiers[name][type]
1584 if handler not in nlist:
1585 nlist.append(handler)
1587 def _remove_notifiers(
1588 self, handler: t.Callable[..., t.Any] | None, name: Sentinel | str, type: str | Sentinel
1589 ) -> None:
1590 try:
1591 if handler is None:
1592 del self._trait_notifiers[name][type]
1593 else:
1594 self._trait_notifiers[name][type].remove(handler)
1595 except KeyError:
1596 pass
1598 def on_trait_change(
1599 self,
1600 handler: EventHandler | None = None,
1601 name: Sentinel | str | None = None,
1602 remove: bool = False,
1603 ) -> None:
1604 """DEPRECATED: Setup a handler to be called when a trait changes.
1606 This is used to setup dynamic notifications of trait changes.
1608 Static handlers can be created by creating methods on a HasTraits
1609 subclass with the naming convention '_[traitname]_changed'. Thus,
1610 to create static handler for the trait 'a', create the method
1611 _a_changed(self, name, old, new) (fewer arguments can be used, see
1612 below).
1614 If `remove` is True and `handler` is not specified, all change
1615 handlers for the specified name are uninstalled.
1617 Parameters
1618 ----------
1619 handler : callable, None
1620 A callable that is called when a trait changes. Its
1621 signature can be handler(), handler(name), handler(name, new),
1622 handler(name, old, new), or handler(name, old, new, self).
1623 name : list, str, None
1624 If None, the handler will apply to all traits. If a list
1625 of str, handler will apply to all names in the list. If a
1626 str, the handler will apply just to that name.
1627 remove : bool
1628 If False (the default), then install the handler. If True
1629 then unintall it.
1630 """
1631 warn(
1632 "on_trait_change is deprecated in traitlets 4.1: use observe instead",
1633 DeprecationWarning,
1634 stacklevel=2,
1635 )
1636 if name is None:
1637 name = All
1638 if remove:
1639 self.unobserve(_callback_wrapper(handler), names=name)
1640 else:
1641 self.observe(_callback_wrapper(handler), names=name)
1643 def observe(
1644 self,
1645 handler: t.Callable[..., t.Any],
1646 names: Sentinel | str | t.Iterable[Sentinel | str] = All,
1647 type: Sentinel | str = "change",
1648 ) -> None:
1649 """Setup a handler to be called when a trait changes.
1651 This is used to setup dynamic notifications of trait changes.
1653 Parameters
1654 ----------
1655 handler : callable
1656 A callable that is called when a trait changes. Its
1657 signature should be ``handler(change)``, where ``change`` is a
1658 dictionary. The change dictionary at least holds a 'type' key.
1659 * ``type``: the type of notification.
1660 Other keys may be passed depending on the value of 'type'. In the
1661 case where type is 'change', we also have the following keys:
1662 * ``owner`` : the HasTraits instance
1663 * ``old`` : the old value of the modified trait attribute
1664 * ``new`` : the new value of the modified trait attribute
1665 * ``name`` : the name of the modified trait attribute.
1666 names : list, str, All
1667 If names is All, the handler will apply to all traits. If a list
1668 of str, handler will apply to all names in the list. If a
1669 str, the handler will apply just to that name.
1670 type : str, All (default: 'change')
1671 The type of notification to filter by. If equal to All, then all
1672 notifications are passed to the observe handler.
1673 """
1674 for name in parse_notifier_name(names):
1675 self._add_notifiers(handler, name, type)
1677 def unobserve(
1678 self,
1679 handler: t.Callable[..., t.Any],
1680 names: Sentinel | str | t.Iterable[Sentinel | str] = All,
1681 type: Sentinel | str = "change",
1682 ) -> None:
1683 """Remove a trait change handler.
1685 This is used to unregister handlers to trait change notifications.
1687 Parameters
1688 ----------
1689 handler : callable
1690 The callable called when a trait attribute changes.
1691 names : list, str, All (default: All)
1692 The names of the traits for which the specified handler should be
1693 uninstalled. If names is All, the specified handler is uninstalled
1694 from the list of notifiers corresponding to all changes.
1695 type : str or All (default: 'change')
1696 The type of notification to filter by. If All, the specified handler
1697 is uninstalled from the list of notifiers corresponding to all types.
1698 """
1699 for name in parse_notifier_name(names):
1700 self._remove_notifiers(handler, name, type)
1702 def unobserve_all(self, name: str | t.Any = All) -> None:
1703 """Remove trait change handlers of any type for the specified name.
1704 If name is not specified, removes all trait notifiers."""
1705 if name is All:
1706 self._trait_notifiers = {}
1707 else:
1708 try:
1709 del self._trait_notifiers[name]
1710 except KeyError:
1711 pass
1713 def _register_validator(
1714 self, handler: t.Callable[..., None], names: tuple[str | Sentinel, ...]
1715 ) -> None:
1716 """Setup a handler to be called when a trait should be cross validated.
1718 This is used to setup dynamic notifications for cross-validation.
1720 If a validator is already registered for any of the provided names, a
1721 TraitError is raised and no new validator is registered.
1723 Parameters
1724 ----------
1725 handler : callable
1726 A callable that is called when the given trait is cross-validated.
1727 Its signature is handler(proposal), where proposal is a Bunch (dictionary with attribute access)
1728 with the following attributes/keys:
1729 * ``owner`` : the HasTraits instance
1730 * ``value`` : the proposed value for the modified trait attribute
1731 * ``trait`` : the TraitType instance associated with the attribute
1732 names : List of strings
1733 The names of the traits that should be cross-validated
1734 """
1735 for name in names:
1736 magic_name = "_%s_validate" % name
1737 if hasattr(self, magic_name):
1738 class_value = getattr(self.__class__, magic_name)
1739 if not isinstance(class_value, ValidateHandler):
1740 deprecated_method(
1741 class_value,
1742 self.__class__,
1743 magic_name,
1744 "use @validate decorator instead.",
1745 )
1746 for name in names:
1747 self._trait_validators[name] = handler
1749 def add_traits(self, **traits: t.Any) -> None:
1750 """Dynamically add trait attributes to the HasTraits instance."""
1751 cls = self.__class__
1752 attrs = {"__module__": cls.__module__}
1753 if hasattr(cls, "__qualname__"):
1754 # __qualname__ introduced in Python 3.3 (see PEP 3155)
1755 attrs["__qualname__"] = cls.__qualname__
1756 attrs.update(traits)
1757 self.__class__ = type(cls.__name__, (cls,), attrs)
1758 for trait in traits.values():
1759 trait.instance_init(self)
1761 def set_trait(self, name: str, value: t.Any) -> None:
1762 """Forcibly sets trait attribute, including read-only attributes."""
1763 cls = self.__class__
1764 if not self.has_trait(name):
1765 raise TraitError(f"Class {cls.__name__} does not have a trait named {name}")
1766 else:
1767 getattr(cls, name).set(self, value)
1769 @classmethod
1770 def class_trait_names(cls: type[HasTraits], **metadata: t.Any) -> list[str]:
1771 """Get a list of all the names of this class' traits.
1773 This method is just like the :meth:`trait_names` method,
1774 but is unbound.
1775 """
1776 return list(cls.class_traits(**metadata))
1778 @classmethod
1779 def class_traits(cls: type[HasTraits], **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]:
1780 """Get a ``dict`` of all the traits of this class. The dictionary
1781 is keyed on the name and the values are the TraitType objects.
1783 This method is just like the :meth:`traits` method, but is unbound.
1785 The TraitTypes returned don't know anything about the values
1786 that the various HasTrait's instances are holding.
1788 The metadata kwargs allow functions to be passed in which
1789 filter traits based on metadata values. The functions should
1790 take a single value as an argument and return a boolean. If
1791 any function returns False, then the trait is not included in
1792 the output. If a metadata key doesn't exist, None will be passed
1793 to the function.
1794 """
1795 traits = cls._traits.copy()
1797 if len(metadata) == 0:
1798 return traits
1800 result = {}
1801 for name, trait in traits.items():
1802 for meta_name, meta_eval in metadata.items():
1803 if not callable(meta_eval):
1804 meta_eval = _SimpleTest(meta_eval)
1805 if not meta_eval(trait.metadata.get(meta_name, None)):
1806 break
1807 else:
1808 result[name] = trait
1810 return result
1812 @classmethod
1813 def class_own_traits(
1814 cls: type[HasTraits], **metadata: t.Any
1815 ) -> dict[str, TraitType[t.Any, t.Any]]:
1816 """Get a dict of all the traitlets defined on this class, not a parent.
1818 Works like `class_traits`, except for excluding traits from parents.
1819 """
1820 sup = super(cls, cls)
1821 return {
1822 n: t
1823 for (n, t) in cls.class_traits(**metadata).items()
1824 if getattr(sup, n, None) is not t
1825 }
1827 def has_trait(self, name: str) -> bool:
1828 """Returns True if the object has a trait with the specified name."""
1829 return name in self._traits
1831 def trait_has_value(self, name: str) -> bool:
1832 """Returns True if the specified trait has a value.
1834 This will return false even if ``getattr`` would return a
1835 dynamically generated default value. These default values
1836 will be recognized as existing only after they have been
1837 generated.
1839 Example
1841 .. code-block:: python
1843 class MyClass(HasTraits):
1844 i = Int()
1847 mc = MyClass()
1848 assert not mc.trait_has_value("i")
1849 mc.i # generates a default value
1850 assert mc.trait_has_value("i")
1851 """
1852 return name in self._trait_values
1854 def trait_values(self, **metadata: t.Any) -> dict[str, t.Any]:
1855 """A ``dict`` of trait names and their values.
1857 The metadata kwargs allow functions to be passed in which
1858 filter traits based on metadata values. The functions should
1859 take a single value as an argument and return a boolean. If
1860 any function returns False, then the trait is not included in
1861 the output. If a metadata key doesn't exist, None will be passed
1862 to the function.
1864 Returns
1865 -------
1866 A ``dict`` of trait names and their values.
1868 Notes
1869 -----
1870 Trait values are retrieved via ``getattr``, any exceptions raised
1871 by traits or the operations they may trigger will result in the
1872 absence of a trait value in the result ``dict``.
1873 """
1874 return {name: getattr(self, name) for name in self.trait_names(**metadata)}
1876 def _get_trait_default_generator(self, name: str) -> t.Any:
1877 """Return default generator for a given trait
1879 Walk the MRO to resolve the correct default generator according to inheritance.
1880 """
1881 method_name = "_%s_default" % name
1882 if method_name in self.__dict__:
1883 return getattr(self, method_name)
1884 if method_name in self.__class__.__dict__:
1885 return getattr(self.__class__, method_name)
1886 return self._all_trait_default_generators[name]
1888 def trait_defaults(self, *names: str, **metadata: t.Any) -> dict[str, t.Any] | Sentinel:
1889 """Return a trait's default value or a dictionary of them
1891 Notes
1892 -----
1893 Dynamically generated default values may
1894 depend on the current state of the object."""
1895 for n in names:
1896 if not self.has_trait(n):
1897 raise TraitError(f"'{n}' is not a trait of '{type(self).__name__}' instances")
1899 if len(names) == 1 and len(metadata) == 0:
1900 return t.cast(Sentinel, self._get_trait_default_generator(names[0])(self))
1902 trait_names = self.trait_names(**metadata)
1903 trait_names.extend(names)
1905 defaults = {}
1906 for n in trait_names:
1907 defaults[n] = self._get_trait_default_generator(n)(self)
1908 return defaults
1910 def trait_names(self, **metadata: t.Any) -> list[str]:
1911 """Get a list of all the names of this class' traits."""
1912 return list(self.traits(**metadata))
1914 def traits(self, **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]:
1915 """Get a ``dict`` of all the traits of this class. The dictionary
1916 is keyed on the name and the values are the TraitType objects.
1918 The TraitTypes returned don't know anything about the values
1919 that the various HasTrait's instances are holding.
1921 The metadata kwargs allow functions to be passed in which
1922 filter traits based on metadata values. The functions should
1923 take a single value as an argument and return a boolean. If
1924 any function returns False, then the trait is not included in
1925 the output. If a metadata key doesn't exist, None will be passed
1926 to the function.
1927 """
1928 traits = self._traits.copy()
1930 if len(metadata) == 0:
1931 return traits
1933 result = {}
1934 for name, trait in traits.items():
1935 for meta_name, meta_eval in metadata.items():
1936 if not callable(meta_eval):
1937 meta_eval = _SimpleTest(meta_eval)
1938 if not meta_eval(trait.metadata.get(meta_name, None)):
1939 break
1940 else:
1941 result[name] = trait
1943 return result
1945 def trait_metadata(self, traitname: str, key: str, default: t.Any = None) -> t.Any:
1946 """Get metadata values for trait by key."""
1947 try:
1948 trait = getattr(self.__class__, traitname)
1949 except AttributeError as e:
1950 raise TraitError(
1951 f"Class {self.__class__.__name__} does not have a trait named {traitname}"
1952 ) from e
1953 metadata_name = "_" + traitname + "_metadata"
1954 if hasattr(self, metadata_name) and key in getattr(self, metadata_name):
1955 return getattr(self, metadata_name).get(key, default)
1956 else:
1957 return trait.metadata.get(key, default)
1959 @classmethod
1960 def class_own_trait_events(cls: type[HasTraits], name: str) -> dict[str, EventHandler]:
1961 """Get a dict of all event handlers defined on this class, not a parent.
1963 Works like ``event_handlers``, except for excluding traits from parents.
1964 """
1965 sup = super(cls, cls)
1966 return {
1967 n: e
1968 for (n, e) in cls.events(name).items() # type:ignore[attr-defined]
1969 if getattr(sup, n, None) is not e
1970 }
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 elif hasattr(v, "tags"):
1994 if cls.trait_names(**v.tags):
1995 events[k] = v
1996 return events
1999# -----------------------------------------------------------------------------
2000# Actual TraitTypes implementations/subclasses
2001# -----------------------------------------------------------------------------
2003# -----------------------------------------------------------------------------
2004# TraitTypes subclasses for handling classes and instances of classes
2005# -----------------------------------------------------------------------------
2008class ClassBasedTraitType(TraitType[G, S]):
2009 """
2010 A trait with error reporting and string -> type resolution for Type,
2011 Instance and This.
2012 """
2014 def _resolve_string(self, string: str) -> t.Any:
2015 """
2016 Resolve a string supplied for a type into an actual object.
2017 """
2018 return import_item(string)
2021class Type(ClassBasedTraitType[G, S]):
2022 """A trait whose value must be a subclass of a specified class."""
2024 if t.TYPE_CHECKING:
2026 @t.overload
2027 def __init__(
2028 self: Type[type, type],
2029 default_value: Sentinel | None | str = ...,
2030 klass: None | str = ...,
2031 allow_none: Literal[False] = ...,
2032 read_only: bool | None = ...,
2033 help: str | None = ...,
2034 config: t.Any | None = ...,
2035 **kwargs: t.Any,
2036 ) -> None:
2037 ...
2039 @t.overload
2040 def __init__(
2041 self: Type[type | None, type | None],
2042 default_value: Sentinel | None | str = ...,
2043 klass: None | str = ...,
2044 allow_none: Literal[True] = ...,
2045 read_only: bool | None = ...,
2046 help: str | None = ...,
2047 config: t.Any | None = ...,
2048 **kwargs: t.Any,
2049 ) -> None:
2050 ...
2052 @t.overload
2053 def __init__(
2054 self: Type[S, S],
2055 default_value: S = ...,
2056 klass: S = ...,
2057 allow_none: Literal[False] = ...,
2058 read_only: bool | None = ...,
2059 help: str | None = ...,
2060 config: t.Any | None = ...,
2061 **kwargs: t.Any,
2062 ) -> None:
2063 ...
2065 @t.overload
2066 def __init__(
2067 self: Type[S | None, S | None],
2068 default_value: S | None = ...,
2069 klass: S = ...,
2070 allow_none: Literal[True] = ...,
2071 read_only: bool | None = ...,
2072 help: str | None = ...,
2073 config: t.Any | None = ...,
2074 **kwargs: t.Any,
2075 ) -> None:
2076 ...
2078 def __init__(
2079 self,
2080 default_value: t.Any = Undefined,
2081 klass: t.Any = None,
2082 allow_none: bool = False,
2083 read_only: bool | None = None,
2084 help: str | None = None,
2085 config: t.Any | None = None,
2086 **kwargs: t.Any,
2087 ) -> None:
2088 """Construct a Type trait
2090 A Type trait specifies that its values must be subclasses of
2091 a particular class.
2093 If only ``default_value`` is given, it is used for the ``klass`` as
2094 well. If neither are given, both default to ``object``.
2096 Parameters
2097 ----------
2098 default_value : class, str or None
2099 The default value must be a subclass of klass. If an str,
2100 the str must be a fully specified class name, like 'foo.bar.Bah'.
2101 The string is resolved into real class, when the parent
2102 :class:`HasTraits` class is instantiated.
2103 klass : class, str [ default object ]
2104 Values of this trait must be a subclass of klass. The klass
2105 may be specified in a string like: 'foo.bar.MyClass'.
2106 The string is resolved into real class, when the parent
2107 :class:`HasTraits` class is instantiated.
2108 allow_none : bool [ default False ]
2109 Indicates whether None is allowed as an assignable value.
2110 **kwargs
2111 extra kwargs passed to `ClassBasedTraitType`
2112 """
2113 if default_value is Undefined:
2114 new_default_value = object if (klass is None) else klass
2115 else:
2116 new_default_value = default_value
2118 if klass is None:
2119 if (default_value is None) or (default_value is Undefined):
2120 klass = object
2121 else:
2122 klass = default_value
2124 if not (inspect.isclass(klass) or isinstance(klass, str)):
2125 raise TraitError("A Type trait must specify a class.")
2127 self.klass = klass
2129 super().__init__(
2130 new_default_value,
2131 allow_none=allow_none,
2132 read_only=read_only,
2133 help=help,
2134 config=config,
2135 **kwargs,
2136 )
2138 def validate(self, obj: t.Any, value: t.Any) -> G:
2139 """Validates that the value is a valid object instance."""
2140 if isinstance(value, str):
2141 try:
2142 value = self._resolve_string(value)
2143 except ImportError as e:
2144 raise TraitError(
2145 f"The '{self.name}' trait of {obj} instance must be a type, but "
2146 f"{value!r} could not be imported"
2147 ) from e
2148 try:
2149 if issubclass(value, self.klass): # type:ignore[arg-type]
2150 return t.cast(G, value)
2151 except Exception:
2152 pass
2154 self.error(obj, value)
2156 def info(self) -> str:
2157 """Returns a description of the trait."""
2158 if isinstance(self.klass, str):
2159 klass = self.klass
2160 else:
2161 klass = self.klass.__module__ + "." + self.klass.__name__
2162 result = "a subclass of '%s'" % klass
2163 if self.allow_none:
2164 return result + " or None"
2165 return result
2167 def instance_init(self, obj: t.Any) -> None:
2168 # we can't do this in subclass_init because that
2169 # might be called before all imports are done.
2170 self._resolve_classes()
2172 def _resolve_classes(self) -> None:
2173 if isinstance(self.klass, str):
2174 self.klass = self._resolve_string(self.klass)
2175 if isinstance(self.default_value, str):
2176 self.default_value = self._resolve_string(self.default_value)
2178 def default_value_repr(self) -> str:
2179 value = self.default_value
2180 assert value is not None
2181 if isinstance(value, str):
2182 return repr(value)
2183 else:
2184 return repr(f"{value.__module__}.{value.__name__}")
2187class Instance(ClassBasedTraitType[T, T]):
2188 """A trait whose value must be an instance of a specified class.
2190 The value can also be an instance of a subclass of the specified class.
2192 Subclasses can declare default classes by overriding the klass attribute
2193 """
2195 klass: str | type[T] | None = None
2197 if t.TYPE_CHECKING:
2199 @t.overload
2200 def __init__(
2201 self: Instance[T],
2202 klass: type[T] = ...,
2203 args: tuple[t.Any, ...] | None = ...,
2204 kw: dict[str, t.Any] | None = ...,
2205 allow_none: Literal[False] = ...,
2206 read_only: bool | None = ...,
2207 help: str | None = ...,
2208 **kwargs: t.Any,
2209 ) -> None:
2210 ...
2212 @t.overload
2213 def __init__(
2214 self: Instance[T | None],
2215 klass: type[T] = ...,
2216 args: tuple[t.Any, ...] | None = ...,
2217 kw: dict[str, t.Any] | None = ...,
2218 allow_none: Literal[True] = ...,
2219 read_only: bool | None = ...,
2220 help: str | None = ...,
2221 **kwargs: t.Any,
2222 ) -> None:
2223 ...
2225 @t.overload
2226 def __init__(
2227 self: Instance[t.Any],
2228 klass: str | None = ...,
2229 args: tuple[t.Any, ...] | None = ...,
2230 kw: dict[str, t.Any] | None = ...,
2231 allow_none: Literal[False] = ...,
2232 read_only: bool | None = ...,
2233 help: str | None = ...,
2234 **kwargs: t.Any,
2235 ) -> None:
2236 ...
2238 @t.overload
2239 def __init__(
2240 self: Instance[t.Any | None],
2241 klass: str | None = ...,
2242 args: tuple[t.Any, ...] | None = ...,
2243 kw: dict[str, t.Any] | None = ...,
2244 allow_none: Literal[True] = ...,
2245 read_only: bool | None = ...,
2246 help: str | None = ...,
2247 **kwargs: t.Any,
2248 ) -> None:
2249 ...
2251 def __init__(
2252 self,
2253 klass: str | type[T] | None = None,
2254 args: tuple[t.Any, ...] | None = None,
2255 kw: dict[str, t.Any] | None = None,
2256 allow_none: bool = False,
2257 read_only: bool | None = None,
2258 help: str | None = None,
2259 **kwargs: t.Any,
2260 ) -> None:
2261 """Construct an Instance trait.
2263 This trait allows values that are instances of a particular
2264 class or its subclasses. Our implementation is quite different
2265 from that of enthough.traits as we don't allow instances to be used
2266 for klass and we handle the ``args`` and ``kw`` arguments differently.
2268 Parameters
2269 ----------
2270 klass : class, str
2271 The class that forms the basis for the trait. Class names
2272 can also be specified as strings, like 'foo.bar.Bar'.
2273 args : tuple
2274 Positional arguments for generating the default value.
2275 kw : dict
2276 Keyword arguments for generating the default value.
2277 allow_none : bool [ default False ]
2278 Indicates whether None is allowed as a value.
2279 **kwargs
2280 Extra kwargs passed to `ClassBasedTraitType`
2282 Notes
2283 -----
2284 If both ``args`` and ``kw`` are None, then the default value is None.
2285 If ``args`` is a tuple and ``kw`` is a dict, then the default is
2286 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
2287 None, the None is replaced by ``()`` or ``{}``, respectively.
2288 """
2289 if klass is None:
2290 klass = self.klass
2292 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, str)):
2293 self.klass = klass
2294 else:
2295 raise TraitError("The klass attribute must be a class not: %r" % klass)
2297 if (kw is not None) and not isinstance(kw, dict):
2298 raise TraitError("The 'kw' argument must be a dict or None.")
2299 if (args is not None) and not isinstance(args, tuple):
2300 raise TraitError("The 'args' argument must be a tuple or None.")
2302 self.default_args = args
2303 self.default_kwargs = kw
2305 super().__init__(allow_none=allow_none, read_only=read_only, help=help, **kwargs)
2307 def validate(self, obj: t.Any, value: t.Any) -> T | None:
2308 assert self.klass is not None
2309 if self.allow_none and value is None:
2310 return value
2311 if isinstance(value, self.klass): # type:ignore[arg-type]
2312 return t.cast(T, value)
2313 else:
2314 self.error(obj, value)
2316 def info(self) -> str:
2317 if isinstance(self.klass, str):
2318 result = add_article(self.klass)
2319 else:
2320 result = describe("a", self.klass)
2321 if self.allow_none:
2322 result += " or None"
2323 return result
2325 def instance_init(self, obj: t.Any) -> None:
2326 # we can't do this in subclass_init because that
2327 # might be called before all imports are done.
2328 self._resolve_classes()
2330 def _resolve_classes(self) -> None:
2331 if isinstance(self.klass, str):
2332 self.klass = self._resolve_string(self.klass)
2334 def make_dynamic_default(self) -> T | None:
2335 if (self.default_args is None) and (self.default_kwargs is None):
2336 return None
2337 assert self.klass is not None
2338 return self.klass(*(self.default_args or ()), **(self.default_kwargs or {})) # type:ignore[operator]
2340 def default_value_repr(self) -> str:
2341 return repr(self.make_dynamic_default())
2343 def from_string(self, s: str) -> T | None:
2344 return t.cast(T, _safe_literal_eval(s))
2347class ForwardDeclaredMixin:
2348 """
2349 Mixin for forward-declared versions of Instance and Type.
2350 """
2352 def _resolve_string(self, string: str) -> t.Any:
2353 """
2354 Find the specified class name by looking for it in the module in which
2355 our this_class attribute was defined.
2356 """
2357 modname = self.this_class.__module__ # type:ignore[attr-defined]
2358 return import_item(".".join([modname, string]))
2361class ForwardDeclaredType(ForwardDeclaredMixin, Type[G, S]):
2362 """
2363 Forward-declared version of Type.
2364 """
2366 pass
2369class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance[T]):
2370 """
2371 Forward-declared version of Instance.
2372 """
2374 pass
2377class This(ClassBasedTraitType[t.Optional[T], t.Optional[T]]):
2378 """A trait for instances of the class containing this trait.
2380 Because how how and when class bodies are executed, the ``This``
2381 trait can only have a default value of None. This, and because we
2382 always validate default values, ``allow_none`` is *always* true.
2383 """
2385 info_text = "an instance of the same type as the receiver or None"
2387 def __init__(self, **kwargs: t.Any) -> None:
2388 super().__init__(None, **kwargs)
2390 def validate(self, obj: t.Any, value: t.Any) -> HasTraits | None:
2391 # What if value is a superclass of obj.__class__? This is
2392 # complicated if it was the superclass that defined the This
2393 # trait.
2394 assert self.this_class is not None
2395 if isinstance(value, self.this_class) or (value is None):
2396 return value
2397 else:
2398 self.error(obj, value)
2401class Union(TraitType[t.Any, t.Any]):
2402 """A trait type representing a Union type."""
2404 def __init__(self, trait_types: t.Any, **kwargs: t.Any) -> None:
2405 """Construct a Union trait.
2407 This trait allows values that are allowed by at least one of the
2408 specified trait types. A Union traitlet cannot have metadata on
2409 its own, besides the metadata of the listed types.
2411 Parameters
2412 ----------
2413 trait_types : sequence
2414 The list of trait types of length at least 1.
2415 **kwargs
2416 Extra kwargs passed to `TraitType`
2418 Notes
2419 -----
2420 Union([Float(), Bool(), Int()]) attempts to validate the provided values
2421 with the validation function of Float, then Bool, and finally Int.
2423 Parsing from string is ambiguous for container types which accept other
2424 collection-like literals (e.g. List accepting both `[]` and `()`
2425 precludes Union from ever parsing ``Union([List(), Tuple()])`` as a tuple;
2426 you can modify behaviour of too permissive container traits by overriding
2427 ``_literal_from_string_pairs`` in subclasses.
2428 Similarly, parsing unions of numeric types is only unambiguous if
2429 types are provided in order of increasing permissiveness, e.g.
2430 ``Union([Int(), Float()])`` (since floats accept integer-looking values).
2431 """
2432 self.trait_types = list(trait_types)
2433 self.info_text = " or ".join([tt.info() for tt in self.trait_types])
2434 super().__init__(**kwargs)
2436 def default(self, obj: t.Any = None) -> t.Any:
2437 default = super().default(obj)
2438 for trait in self.trait_types:
2439 if default is Undefined:
2440 default = trait.default(obj)
2441 else:
2442 break
2443 return default
2445 def class_init(self, cls: type[HasTraits], name: str | None) -> None:
2446 for trait_type in reversed(self.trait_types):
2447 trait_type.class_init(cls, None)
2448 super().class_init(cls, name)
2450 def subclass_init(self, cls: type[t.Any]) -> None:
2451 for trait_type in reversed(self.trait_types):
2452 trait_type.subclass_init(cls)
2453 # explicitly not calling super().subclass_init(cls)
2454 # to opt out of instance_init
2456 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
2457 with obj.cross_validation_lock:
2458 for trait_type in self.trait_types:
2459 try:
2460 v = trait_type._validate(obj, value)
2461 # In the case of an element trait, the name is None
2462 if self.name is not None:
2463 setattr(obj, "_" + self.name + "_metadata", trait_type.metadata)
2464 return v
2465 except TraitError:
2466 continue
2467 self.error(obj, value)
2469 def __or__(self, other: t.Any) -> Union:
2470 if isinstance(other, Union):
2471 return Union(self.trait_types + other.trait_types)
2472 else:
2473 return Union([*self.trait_types, other])
2475 def from_string(self, s: str) -> t.Any:
2476 for trait_type in self.trait_types:
2477 try:
2478 v = trait_type.from_string(s)
2479 return trait_type.validate(None, v)
2480 except (TraitError, ValueError):
2481 continue
2482 return super().from_string(s)
2485# -----------------------------------------------------------------------------
2486# Basic TraitTypes implementations/subclasses
2487# -----------------------------------------------------------------------------
2490class Any(TraitType[t.Optional[t.Any], t.Optional[t.Any]]):
2491 """A trait which allows any value."""
2493 if t.TYPE_CHECKING:
2495 @t.overload
2496 def __init__(
2497 self: Any,
2498 default_value: t.Any = ...,
2499 *,
2500 allow_none: Literal[False],
2501 read_only: bool | None = ...,
2502 help: str | None = ...,
2503 config: t.Any | None = ...,
2504 **kwargs: t.Any,
2505 ) -> None:
2506 ...
2508 @t.overload
2509 def __init__(
2510 self: Any,
2511 default_value: t.Any = ...,
2512 *,
2513 allow_none: Literal[True],
2514 read_only: bool | None = ...,
2515 help: str | None = ...,
2516 config: t.Any | None = ...,
2517 **kwargs: t.Any,
2518 ) -> None:
2519 ...
2521 @t.overload
2522 def __init__(
2523 self: Any,
2524 default_value: t.Any = ...,
2525 *,
2526 allow_none: Literal[True, False] = ...,
2527 help: str | None = ...,
2528 read_only: bool | None = False,
2529 config: t.Any = None,
2530 **kwargs: t.Any,
2531 ) -> None:
2532 ...
2534 def __init__(
2535 self: Any,
2536 default_value: t.Any = ...,
2537 *,
2538 allow_none: bool = False,
2539 help: str | None = "",
2540 read_only: bool | None = False,
2541 config: t.Any = None,
2542 **kwargs: t.Any,
2543 ) -> None:
2544 ...
2546 @t.overload
2547 def __get__(self, obj: None, cls: type[t.Any]) -> Any:
2548 ...
2550 @t.overload
2551 def __get__(self, obj: t.Any, cls: type[t.Any]) -> t.Any:
2552 ...
2554 def __get__(self, obj: t.Any | None, cls: type[t.Any]) -> t.Any | Any:
2555 ...
2557 default_value: t.Any | None = None
2558 allow_none = True
2559 info_text = "any value"
2561 def subclass_init(self, cls: type[t.Any]) -> None:
2562 pass # fully opt out of instance_init
2565def _validate_bounds(
2566 trait: Int[t.Any, t.Any] | Float[t.Any, t.Any], obj: t.Any, value: t.Any
2567) -> t.Any:
2568 """
2569 Validate that a number to be applied to a trait is between bounds.
2571 If value is not between min_bound and max_bound, this raises a
2572 TraitError with an error message appropriate for this trait.
2573 """
2574 if trait.min is not None and value < trait.min:
2575 raise TraitError(
2576 f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
2577 f"not be less than {trait.min}, but a value of {value} was "
2578 "specified"
2579 )
2580 if trait.max is not None and value > trait.max:
2581 raise TraitError(
2582 f"The value of the '{trait.name}' trait of {class_of(obj)} instance should "
2583 f"not be greater than {trait.max}, but a value of {value} was "
2584 "specified"
2585 )
2586 return value
2589# I = t.TypeVar('I', t.Optional[int], int)
2592class Int(TraitType[G, S]):
2593 """An int trait."""
2595 default_value = 0
2596 info_text = "an int"
2598 @t.overload
2599 def __init__(
2600 self: Int[int, int],
2601 default_value: int | Sentinel = ...,
2602 allow_none: Literal[False] = ...,
2603 read_only: bool | None = ...,
2604 help: str | None = ...,
2605 config: t.Any | None = ...,
2606 **kwargs: t.Any,
2607 ) -> None:
2608 ...
2610 @t.overload
2611 def __init__(
2612 self: Int[int | None, int | None],
2613 default_value: int | Sentinel | None = ...,
2614 allow_none: Literal[True] = ...,
2615 read_only: bool | None = ...,
2616 help: str | None = ...,
2617 config: t.Any | None = ...,
2618 **kwargs: t.Any,
2619 ) -> None:
2620 ...
2622 def __init__(
2623 self,
2624 default_value: t.Any = Undefined,
2625 allow_none: bool = False,
2626 read_only: bool | None = None,
2627 help: str | None = None,
2628 config: t.Any | None = None,
2629 **kwargs: t.Any,
2630 ) -> None:
2631 self.min = kwargs.pop("min", None)
2632 self.max = kwargs.pop("max", None)
2633 super().__init__(
2634 default_value=default_value,
2635 allow_none=allow_none,
2636 read_only=read_only,
2637 help=help,
2638 config=config,
2639 **kwargs,
2640 )
2642 def validate(self, obj: t.Any, value: t.Any) -> G:
2643 if not isinstance(value, int):
2644 self.error(obj, value)
2645 return t.cast(G, _validate_bounds(self, obj, value))
2647 def from_string(self, s: str) -> G:
2648 if self.allow_none and s == "None":
2649 return t.cast(G, None)
2650 return t.cast(G, int(s))
2652 def subclass_init(self, cls: type[t.Any]) -> None:
2653 pass # fully opt out of instance_init
2656class CInt(Int[G, S]):
2657 """A casting version of the int trait."""
2659 if t.TYPE_CHECKING:
2661 @t.overload
2662 def __init__(
2663 self: CInt[int, t.Any],
2664 default_value: t.Any | Sentinel = ...,
2665 allow_none: Literal[False] = ...,
2666 read_only: bool | None = ...,
2667 help: str | None = ...,
2668 config: t.Any | None = ...,
2669 **kwargs: t.Any,
2670 ) -> None:
2671 ...
2673 @t.overload
2674 def __init__(
2675 self: CInt[int | None, t.Any],
2676 default_value: t.Any | Sentinel | None = ...,
2677 allow_none: Literal[True] = ...,
2678 read_only: bool | None = ...,
2679 help: str | None = ...,
2680 config: t.Any | None = ...,
2681 **kwargs: t.Any,
2682 ) -> None:
2683 ...
2685 def __init__(
2686 self: CInt[int | None, t.Any],
2687 default_value: t.Any | Sentinel | None = ...,
2688 allow_none: bool = ...,
2689 read_only: bool | None = ...,
2690 help: str | None = ...,
2691 config: t.Any | None = ...,
2692 **kwargs: t.Any,
2693 ) -> None:
2694 ...
2696 def validate(self, obj: t.Any, value: t.Any) -> G:
2697 try:
2698 value = int(value)
2699 except Exception:
2700 self.error(obj, value)
2701 return t.cast(G, _validate_bounds(self, obj, value))
2704Long, CLong = Int, CInt
2705Integer = Int
2708class Float(TraitType[G, S]):
2709 """A float trait."""
2711 default_value = 0.0
2712 info_text = "a float"
2714 @t.overload
2715 def __init__(
2716 self: Float[float, int | float],
2717 default_value: float | Sentinel = ...,
2718 allow_none: Literal[False] = ...,
2719 read_only: bool | None = ...,
2720 help: str | None = ...,
2721 config: t.Any | None = ...,
2722 **kwargs: t.Any,
2723 ) -> None:
2724 ...
2726 @t.overload
2727 def __init__(
2728 self: Float[int | None, int | float | None],
2729 default_value: float | Sentinel | None = ...,
2730 allow_none: Literal[True] = ...,
2731 read_only: bool | None = ...,
2732 help: str | None = ...,
2733 config: t.Any | None = ...,
2734 **kwargs: t.Any,
2735 ) -> None:
2736 ...
2738 def __init__(
2739 self: Float[int | None, int | float | None],
2740 default_value: float | Sentinel | None = Undefined,
2741 allow_none: bool = False,
2742 read_only: bool | None = False,
2743 help: str | None = None,
2744 config: t.Any | None = None,
2745 **kwargs: t.Any,
2746 ) -> None:
2747 self.min = kwargs.pop("min", -float("inf"))
2748 self.max = kwargs.pop("max", float("inf"))
2749 super().__init__(
2750 default_value=default_value,
2751 allow_none=allow_none,
2752 read_only=read_only,
2753 help=help,
2754 config=config,
2755 **kwargs,
2756 )
2758 def validate(self, obj: t.Any, value: t.Any) -> G:
2759 if isinstance(value, int):
2760 value = float(value)
2761 if not isinstance(value, float):
2762 self.error(obj, value)
2763 return t.cast(G, _validate_bounds(self, obj, value))
2765 def from_string(self, s: str) -> G:
2766 if self.allow_none and s == "None":
2767 return t.cast(G, None)
2768 return t.cast(G, float(s))
2770 def subclass_init(self, cls: type[t.Any]) -> None:
2771 pass # fully opt out of instance_init
2774class CFloat(Float[G, S]):
2775 """A casting version of the float trait."""
2777 if t.TYPE_CHECKING:
2779 @t.overload
2780 def __init__(
2781 self: CFloat[float, t.Any],
2782 default_value: t.Any = ...,
2783 allow_none: Literal[False] = ...,
2784 read_only: bool | None = ...,
2785 help: str | None = ...,
2786 config: t.Any | None = ...,
2787 **kwargs: t.Any,
2788 ) -> None:
2789 ...
2791 @t.overload
2792 def __init__(
2793 self: CFloat[float | None, t.Any],
2794 default_value: t.Any = ...,
2795 allow_none: Literal[True] = ...,
2796 read_only: bool | None = ...,
2797 help: str | None = ...,
2798 config: t.Any | None = ...,
2799 **kwargs: t.Any,
2800 ) -> None:
2801 ...
2803 def __init__(
2804 self: CFloat[float | None, t.Any],
2805 default_value: t.Any = ...,
2806 allow_none: bool = ...,
2807 read_only: bool | None = ...,
2808 help: str | None = ...,
2809 config: t.Any | None = ...,
2810 **kwargs: t.Any,
2811 ) -> None:
2812 ...
2814 def validate(self, obj: t.Any, value: t.Any) -> G:
2815 try:
2816 value = float(value)
2817 except Exception:
2818 self.error(obj, value)
2819 return t.cast(G, _validate_bounds(self, obj, value))
2822class Complex(TraitType[complex, t.Union[complex, float, int]]):
2823 """A trait for complex numbers."""
2825 default_value = 0.0 + 0.0j
2826 info_text = "a complex number"
2828 def validate(self, obj: t.Any, value: t.Any) -> complex | None:
2829 if isinstance(value, complex):
2830 return value
2831 if isinstance(value, (float, int)):
2832 return complex(value)
2833 self.error(obj, value)
2835 def from_string(self, s: str) -> complex | None:
2836 if self.allow_none and s == "None":
2837 return None
2838 return complex(s)
2840 def subclass_init(self, cls: type[t.Any]) -> None:
2841 pass # fully opt out of instance_init
2844class CComplex(Complex, TraitType[complex, t.Any]):
2845 """A casting version of the complex number trait."""
2847 def validate(self, obj: t.Any, value: t.Any) -> complex | None:
2848 try:
2849 return complex(value)
2850 except Exception:
2851 self.error(obj, value)
2854# We should always be explicit about whether we're using bytes or unicode, both
2855# for Python 3 conversion and for reliable unicode behaviour on Python 2. So
2856# we don't have a Str type.
2857class Bytes(TraitType[bytes, bytes]):
2858 """A trait for byte strings."""
2860 default_value = b""
2861 info_text = "a bytes object"
2863 def validate(self, obj: t.Any, value: t.Any) -> bytes | None:
2864 if isinstance(value, bytes):
2865 return value
2866 self.error(obj, value)
2868 def from_string(self, s: str) -> bytes | None:
2869 if self.allow_none and s == "None":
2870 return None
2871 if len(s) >= 3:
2872 # handle deprecated b"string"
2873 for quote in ('"', "'"):
2874 if s[:2] == f"b{quote}" and s[-1] == quote:
2875 old_s = s
2876 s = s[2:-1]
2877 warn(
2878 "Supporting extra quotes around Bytes is deprecated in traitlets 5.0. "
2879 f"Use {s!r} instead of {old_s!r}.",
2880 DeprecationWarning,
2881 stacklevel=2,
2882 )
2883 break
2884 return s.encode("utf8")
2886 def subclass_init(self, cls: type[t.Any]) -> None:
2887 pass # fully opt out of instance_init
2890class CBytes(Bytes, TraitType[bytes, t.Any]):
2891 """A casting version of the byte string trait."""
2893 def validate(self, obj: t.Any, value: t.Any) -> bytes | None:
2894 try:
2895 return bytes(value)
2896 except Exception:
2897 self.error(obj, value)
2900class Unicode(TraitType[G, S]):
2901 """A trait for unicode strings."""
2903 default_value = ""
2904 info_text = "a unicode string"
2906 if t.TYPE_CHECKING:
2908 @t.overload
2909 def __init__(
2910 self: Unicode[str, str | bytes],
2911 default_value: str | Sentinel = ...,
2912 allow_none: Literal[False] = ...,
2913 read_only: bool | None = ...,
2914 help: str | None = ...,
2915 config: t.Any = ...,
2916 **kwargs: t.Any,
2917 ) -> None:
2918 ...
2920 @t.overload
2921 def __init__(
2922 self: Unicode[str | None, str | bytes | None],
2923 default_value: str | Sentinel | None = ...,
2924 allow_none: Literal[True] = ...,
2925 read_only: bool | None = ...,
2926 help: str | None = ...,
2927 config: t.Any = ...,
2928 **kwargs: t.Any,
2929 ) -> None:
2930 ...
2932 def __init__(
2933 self: Unicode[str | None, str | bytes | None],
2934 default_value: str | Sentinel | None = ...,
2935 allow_none: bool = ...,
2936 read_only: bool | None = ...,
2937 help: str | None = ...,
2938 config: t.Any = ...,
2939 **kwargs: t.Any,
2940 ) -> None:
2941 ...
2943 def validate(self, obj: t.Any, value: t.Any) -> G:
2944 if isinstance(value, str):
2945 return t.cast(G, value)
2946 if isinstance(value, bytes):
2947 try:
2948 return t.cast(G, value.decode("ascii", "strict"))
2949 except UnicodeDecodeError as e:
2950 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
2951 raise TraitError(msg.format(value, self.name, class_of(obj))) from e
2952 self.error(obj, value)
2954 def from_string(self, s: str) -> G:
2955 if self.allow_none and s == "None":
2956 return t.cast(G, None)
2957 s = os.path.expanduser(s)
2958 if len(s) >= 2:
2959 # handle deprecated "1"
2960 for c in ('"', "'"):
2961 if s[0] == s[-1] == c:
2962 old_s = s
2963 s = s[1:-1]
2964 warn(
2965 "Supporting extra quotes around strings is deprecated in traitlets 5.0. "
2966 f"You can use {s!r} instead of {old_s!r} if you require traitlets >=5.",
2967 DeprecationWarning,
2968 stacklevel=2,
2969 )
2970 return t.cast(G, s)
2972 def subclass_init(self, cls: type[t.Any]) -> None:
2973 pass # fully opt out of instance_init
2976class CUnicode(Unicode[G, S], TraitType[str, t.Any]):
2977 """A casting version of the unicode trait."""
2979 if t.TYPE_CHECKING:
2981 @t.overload
2982 def __init__(
2983 self: CUnicode[str, t.Any],
2984 default_value: str | Sentinel = ...,
2985 allow_none: Literal[False] = ...,
2986 read_only: bool | None = ...,
2987 help: str | None = ...,
2988 config: t.Any = ...,
2989 **kwargs: t.Any,
2990 ) -> None:
2991 ...
2993 @t.overload
2994 def __init__(
2995 self: CUnicode[str | None, t.Any],
2996 default_value: str | Sentinel | None = ...,
2997 allow_none: Literal[True] = ...,
2998 read_only: bool | None = ...,
2999 help: str | None = ...,
3000 config: t.Any = ...,
3001 **kwargs: t.Any,
3002 ) -> None:
3003 ...
3005 def __init__(
3006 self: CUnicode[str | None, t.Any],
3007 default_value: str | Sentinel | None = ...,
3008 allow_none: bool = ...,
3009 read_only: bool | None = ...,
3010 help: str | None = ...,
3011 config: t.Any = ...,
3012 **kwargs: t.Any,
3013 ) -> None:
3014 ...
3016 def validate(self, obj: t.Any, value: t.Any) -> G:
3017 try:
3018 return t.cast(G, str(value))
3019 except Exception:
3020 self.error(obj, value)
3023class ObjectName(TraitType[str, str]):
3024 """A string holding a valid object name in this version of Python.
3026 This does not check that the name exists in any scope."""
3028 info_text = "a valid object identifier in Python"
3030 coerce_str = staticmethod(lambda _, s: s)
3032 def validate(self, obj: t.Any, value: t.Any) -> str:
3033 value = self.coerce_str(obj, value)
3035 if isinstance(value, str) and isidentifier(value):
3036 return value
3037 self.error(obj, value)
3039 def from_string(self, s: str) -> str | None:
3040 if self.allow_none and s == "None":
3041 return None
3042 return s
3045class DottedObjectName(ObjectName):
3046 """A string holding a valid dotted object name in Python, such as A.b3._c"""
3048 def validate(self, obj: t.Any, value: t.Any) -> str:
3049 value = self.coerce_str(obj, value)
3051 if isinstance(value, str) and all(isidentifier(a) for a in value.split(".")):
3052 return value
3053 self.error(obj, value)
3056class Bool(TraitType[G, S]):
3057 """A boolean (True, False) trait."""
3059 default_value = False
3060 info_text = "a boolean"
3062 if t.TYPE_CHECKING:
3064 @t.overload
3065 def __init__(
3066 self: Bool[bool, bool | int],
3067 default_value: bool | Sentinel = ...,
3068 allow_none: Literal[False] = ...,
3069 read_only: bool | None = ...,
3070 help: str | None = ...,
3071 config: t.Any = ...,
3072 **kwargs: t.Any,
3073 ) -> None:
3074 ...
3076 @t.overload
3077 def __init__(
3078 self: Bool[bool | None, bool | int | None],
3079 default_value: bool | Sentinel | None = ...,
3080 allow_none: Literal[True] = ...,
3081 read_only: bool | None = ...,
3082 help: str | None = ...,
3083 config: t.Any = ...,
3084 **kwargs: t.Any,
3085 ) -> None:
3086 ...
3088 def __init__(
3089 self: Bool[bool | None, bool | int | None],
3090 default_value: bool | Sentinel | None = ...,
3091 allow_none: bool = ...,
3092 read_only: bool | None = ...,
3093 help: str | None = ...,
3094 config: t.Any = ...,
3095 **kwargs: t.Any,
3096 ) -> None:
3097 ...
3099 def validate(self, obj: t.Any, value: t.Any) -> G:
3100 if isinstance(value, bool):
3101 return t.cast(G, value)
3102 elif isinstance(value, int):
3103 if value == 1:
3104 return t.cast(G, True)
3105 elif value == 0:
3106 return t.cast(G, False)
3107 self.error(obj, value)
3109 def from_string(self, s: str) -> G:
3110 if self.allow_none and s == "None":
3111 return t.cast(G, None)
3112 s = s.lower()
3113 if s in {"true", "1"}:
3114 return t.cast(G, True)
3115 elif s in {"false", "0"}:
3116 return t.cast(G, False)
3117 else:
3118 raise ValueError("%r is not 1, 0, true, or false")
3120 def subclass_init(self, cls: type[t.Any]) -> None:
3121 pass # fully opt out of instance_init
3123 def argcompleter(self, **kwargs: t.Any) -> list[str]:
3124 """Completion hints for argcomplete"""
3125 completions = ["true", "1", "false", "0"]
3126 if self.allow_none:
3127 completions.append("None")
3128 return completions
3131class CBool(Bool[G, S]):
3132 """A casting version of the boolean trait."""
3134 if t.TYPE_CHECKING:
3136 @t.overload
3137 def __init__(
3138 self: CBool[bool, t.Any],
3139 default_value: bool | Sentinel = ...,
3140 allow_none: Literal[False] = ...,
3141 read_only: bool | None = ...,
3142 help: str | None = ...,
3143 config: t.Any = ...,
3144 **kwargs: t.Any,
3145 ) -> None:
3146 ...
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:
3158 ...
3160 def __init__(
3161 self: CBool[bool | None, t.Any],
3162 default_value: bool | Sentinel | None = ...,
3163 allow_none: bool = ...,
3164 read_only: bool | None = ...,
3165 help: str | None = ...,
3166 config: t.Any = ...,
3167 **kwargs: t.Any,
3168 ) -> None:
3169 ...
3171 def validate(self, obj: t.Any, value: t.Any) -> G:
3172 try:
3173 return t.cast(G, bool(value))
3174 except Exception:
3175 self.error(obj, value)
3178class Enum(TraitType[G, G]):
3179 """An enum whose value must be in a given sequence."""
3181 if t.TYPE_CHECKING:
3183 @t.overload
3184 def __init__(
3185 self: Enum[G],
3186 values: t.Sequence[G],
3187 default_value: G | Sentinel = ...,
3188 allow_none: Literal[False] = ...,
3189 read_only: bool | None = ...,
3190 help: str | None = ...,
3191 config: t.Any = ...,
3192 **kwargs: t.Any,
3193 ) -> None:
3194 ...
3196 @t.overload
3197 def __init__(
3198 self: Enum[G | None],
3199 values: t.Sequence[G] | None,
3200 default_value: G | Sentinel | None = ...,
3201 allow_none: Literal[True] = ...,
3202 read_only: bool | None = ...,
3203 help: str | None = ...,
3204 config: t.Any = ...,
3205 **kwargs: t.Any,
3206 ) -> None:
3207 ...
3209 def __init__(
3210 self: Enum[G],
3211 values: t.Sequence[G] | None,
3212 default_value: G | Sentinel | None = Undefined,
3213 allow_none: bool = False,
3214 read_only: bool | None = None,
3215 help: str | None = None,
3216 config: t.Any = None,
3217 **kwargs: t.Any,
3218 ) -> None:
3219 self.values = values
3220 if allow_none is True and default_value is Undefined:
3221 default_value = None
3222 kwargs["allow_none"] = allow_none
3223 kwargs["read_only"] = read_only
3224 kwargs["help"] = help
3225 kwargs["config"] = config
3226 super().__init__(default_value, **kwargs)
3228 def validate(self, obj: t.Any, value: t.Any) -> G:
3229 if self.values and value in self.values:
3230 return t.cast(G, value)
3231 self.error(obj, value)
3233 def _choices_str(self, as_rst: bool = False) -> str:
3234 """Returns a description of the trait choices (not none)."""
3235 choices = self.values or []
3236 if as_rst:
3237 choice_str = "|".join("``%r``" % x for x in choices)
3238 else:
3239 choice_str = repr(list(choices))
3240 return choice_str
3242 def _info(self, as_rst: bool = False) -> str:
3243 """Returns a description of the trait."""
3244 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3245 return f"any of {self._choices_str(as_rst)}{none}"
3247 def info(self) -> str:
3248 return self._info(as_rst=False)
3250 def info_rst(self) -> str:
3251 return self._info(as_rst=True)
3253 def from_string(self, s: str) -> G:
3254 try:
3255 return self.validate(None, s)
3256 except TraitError:
3257 return t.cast(G, _safe_literal_eval(s))
3259 def subclass_init(self, cls: type[t.Any]) -> None:
3260 pass # fully opt out of instance_init
3262 def argcompleter(self, **kwargs: t.Any) -> list[str]:
3263 """Completion hints for argcomplete"""
3264 return [str(v) for v in self.values or []]
3267class CaselessStrEnum(Enum[G]):
3268 """An enum of strings where the case should be ignored."""
3270 def __init__(
3271 self: CaselessStrEnum[t.Any],
3272 values: t.Any,
3273 default_value: t.Any = Undefined,
3274 **kwargs: t.Any,
3275 ) -> None:
3276 super().__init__(values, default_value=default_value, **kwargs)
3278 def validate(self, obj: t.Any, value: t.Any) -> G:
3279 if not isinstance(value, str):
3280 self.error(obj, value)
3282 for v in self.values or []:
3283 assert isinstance(v, str)
3284 if v.lower() == value.lower():
3285 return t.cast(G, v)
3286 self.error(obj, value)
3288 def _info(self, as_rst: bool = False) -> str:
3289 """Returns a description of the trait."""
3290 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3291 return f"any of {self._choices_str(as_rst)} (case-insensitive){none}"
3293 def info(self) -> str:
3294 return self._info(as_rst=False)
3296 def info_rst(self) -> str:
3297 return self._info(as_rst=True)
3300class FuzzyEnum(Enum[G]):
3301 """An case-ignoring enum matching choices by unique prefixes/substrings."""
3303 case_sensitive = False
3304 #: If True, choices match anywhere in the string, otherwise match prefixes.
3305 substring_matching = False
3307 def __init__(
3308 self: FuzzyEnum[t.Any],
3309 values: t.Any,
3310 default_value: t.Any = Undefined,
3311 case_sensitive: bool = False,
3312 substring_matching: bool = False,
3313 **kwargs: t.Any,
3314 ) -> None:
3315 self.case_sensitive = case_sensitive
3316 self.substring_matching = substring_matching
3317 super().__init__(values, default_value=default_value, **kwargs)
3319 def validate(self, obj: t.Any, value: t.Any) -> G:
3320 if not isinstance(value, str):
3321 self.error(obj, value)
3323 conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower()
3324 substring_matching = self.substring_matching
3325 match_func = (lambda v, c: v in c) if substring_matching else (lambda v, c: c.startswith(v))
3326 value = conv_func(value) # type:ignore[no-untyped-call]
3327 choices = self.values or []
3328 matches = [match_func(value, conv_func(c)) for c in choices] # type:ignore[no-untyped-call]
3329 if sum(matches) == 1:
3330 for v, m in zip(choices, matches):
3331 if m:
3332 return v
3334 self.error(obj, value)
3336 def _info(self, as_rst: bool = False) -> str:
3337 """Returns a description of the trait."""
3338 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
3339 case = "sensitive" if self.case_sensitive else "insensitive"
3340 substr = "substring" if self.substring_matching else "prefix"
3341 return f"any case-{case} {substr} of {self._choices_str(as_rst)}{none}"
3343 def info(self) -> str:
3344 return self._info(as_rst=False)
3346 def info_rst(self) -> str:
3347 return self._info(as_rst=True)
3350class Container(Instance[T]):
3351 """An instance of a container (list, set, etc.)
3353 To be subclassed by overriding klass.
3354 """
3356 klass: type[T] | None = None
3357 _cast_types: t.Any = ()
3358 _valid_defaults = SequenceTypes
3359 _trait: t.Any = None
3360 _literal_from_string_pairs: t.Any = ("[]", "()")
3362 @t.overload
3363 def __init__(
3364 self: Container[T],
3365 *,
3366 allow_none: Literal[False],
3367 read_only: bool | None = ...,
3368 help: str | None = ...,
3369 config: t.Any | None = ...,
3370 **kwargs: t.Any,
3371 ) -> None:
3372 ...
3374 @t.overload
3375 def __init__(
3376 self: Container[T | None],
3377 *,
3378 allow_none: Literal[True],
3379 read_only: bool | None = ...,
3380 help: str | None = ...,
3381 config: t.Any | None = ...,
3382 **kwargs: t.Any,
3383 ) -> None:
3384 ...
3386 @t.overload
3387 def __init__(
3388 self: Container[T],
3389 *,
3390 trait: t.Any = ...,
3391 default_value: t.Any = ...,
3392 help: str = ...,
3393 read_only: bool = ...,
3394 config: t.Any = ...,
3395 **kwargs: t.Any,
3396 ) -> None:
3397 ...
3399 def __init__(
3400 self,
3401 trait: t.Any | None = None,
3402 default_value: t.Any = Undefined,
3403 help: str | None = None,
3404 read_only: bool | None = None,
3405 config: t.Any | None = None,
3406 **kwargs: t.Any,
3407 ) -> None:
3408 """Create a container trait type from a list, set, or tuple.
3410 The default value is created by doing ``List(default_value)``,
3411 which creates a copy of the ``default_value``.
3413 ``trait`` can be specified, which restricts the type of elements
3414 in the container to that TraitType.
3416 If only one arg is given and it is not a Trait, it is taken as
3417 ``default_value``:
3419 ``c = List([1, 2, 3])``
3421 Parameters
3422 ----------
3423 trait : TraitType [ optional ]
3424 the type for restricting the contents of the Container. If unspecified,
3425 types are not checked.
3426 default_value : SequenceType [ optional ]
3427 The default value for the Trait. Must be list/tuple/set, and
3428 will be cast to the container type.
3429 allow_none : bool [ default False ]
3430 Whether to allow the value to be None
3431 **kwargs : any
3432 further keys for extensions to the Trait (e.g. config)
3434 """
3436 # allow List([values]):
3437 if trait is not None and default_value is Undefined and not is_trait(trait):
3438 default_value = trait
3439 trait = None
3441 if default_value is None and not kwargs.get("allow_none", False):
3442 # improve backward-compatibility for possible subclasses
3443 # specifying default_value=None as default,
3444 # keeping 'unspecified' behavior (i.e. empty container)
3445 warn(
3446 f"Specifying {self.__class__.__name__}(default_value=None)"
3447 " for no default is deprecated in traitlets 5.0.5."
3448 " Use default_value=Undefined",
3449 DeprecationWarning,
3450 stacklevel=2,
3451 )
3452 default_value = Undefined
3454 if default_value is Undefined:
3455 args: t.Any = ()
3456 elif default_value is None:
3457 # default_value back on kwargs for super() to handle
3458 args = ()
3459 kwargs["default_value"] = None
3460 elif isinstance(default_value, self._valid_defaults):
3461 args = (default_value,)
3462 else:
3463 raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
3465 if is_trait(trait):
3466 if isinstance(trait, type):
3467 warn(
3468 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
3469 " Passing types is deprecated in traitlets 4.1.",
3470 DeprecationWarning,
3471 stacklevel=3,
3472 )
3473 self._trait = trait() if isinstance(trait, type) else trait
3474 elif trait is not None:
3475 raise TypeError("`trait` must be a Trait or None, got %s" % repr_type(trait))
3477 super().__init__(
3478 klass=self.klass, args=args, help=help, read_only=read_only, config=config, **kwargs
3479 )
3481 def validate(self, obj: t.Any, value: t.Any) -> T | None:
3482 if isinstance(value, self._cast_types):
3483 assert self.klass is not None
3484 value = self.klass(value) # type:ignore[call-arg]
3485 value = super().validate(obj, value)
3486 if value is None:
3487 return value
3489 value = self.validate_elements(obj, value)
3491 return t.cast(T, value)
3493 def validate_elements(self, obj: t.Any, value: t.Any) -> T | None:
3494 validated = []
3495 if self._trait is None or isinstance(self._trait, Any):
3496 return t.cast(T, value)
3497 for v in value:
3498 try:
3499 v = self._trait._validate(obj, v)
3500 except TraitError as error:
3501 self.error(obj, v, error)
3502 else:
3503 validated.append(v)
3504 assert self.klass is not None
3505 return self.klass(validated) # type:ignore[call-arg]
3507 def class_init(self, cls: type[t.Any], name: str | None) -> None:
3508 if isinstance(self._trait, TraitType):
3509 self._trait.class_init(cls, None)
3510 super().class_init(cls, name)
3512 def subclass_init(self, cls: type[t.Any]) -> None:
3513 if isinstance(self._trait, TraitType):
3514 self._trait.subclass_init(cls)
3515 # explicitly not calling super().subclass_init(cls)
3516 # to opt out of instance_init
3518 def from_string(self, s: str) -> T | None:
3519 """Load value from a single string"""
3520 if not isinstance(s, str):
3521 raise TraitError(f"Expected string, got {s!r}")
3522 try:
3523 test = literal_eval(s)
3524 except Exception:
3525 test = None
3526 return self.validate(None, test)
3528 def from_string_list(self, s_list: list[str]) -> T | None:
3529 """Return the value from a list of config strings
3531 This is where we parse CLI configuration
3532 """
3533 assert self.klass is not None
3534 if len(s_list) == 1:
3535 # check for deprecated --Class.trait="['a', 'b', 'c']"
3536 r = s_list[0]
3537 if r == "None" and self.allow_none:
3538 return None
3539 if len(r) >= 2 and any(
3540 r.startswith(start) and r.endswith(end)
3541 for start, end in self._literal_from_string_pairs
3542 ):
3543 if self.this_class:
3544 clsname = self.this_class.__name__ + "."
3545 else:
3546 clsname = ""
3547 assert self.name is not None
3548 warn(
3549 "--{0}={1} for containers is deprecated in traitlets 5.0. "
3550 "You can pass `--{0} item` ... multiple times to add items to a list.".format(
3551 clsname + self.name, r
3552 ),
3553 DeprecationWarning,
3554 stacklevel=2,
3555 )
3556 return self.klass(literal_eval(r)) # type:ignore[call-arg]
3557 sig = inspect.signature(self.item_from_string)
3558 if "index" in sig.parameters:
3559 item_from_string = self.item_from_string
3560 else:
3561 # backward-compat: allow item_from_string to ignore index arg
3562 def item_from_string(s: str, index: int | None = None) -> T | str:
3563 return t.cast(T, self.item_from_string(s))
3565 return self.klass( # type:ignore[call-arg]
3566 [item_from_string(s, index=idx) for idx, s in enumerate(s_list)]
3567 )
3569 def item_from_string(self, s: str, index: int | None = None) -> T | str:
3570 """Cast a single item from a string
3572 Evaluated when parsing CLI configuration from a string
3573 """
3574 if self._trait:
3575 return t.cast(T, self._trait.from_string(s))
3576 else:
3577 return s
3580class List(Container[t.List[T]]):
3581 """An instance of a Python list."""
3583 klass = list # type:ignore[assignment]
3584 _cast_types: t.Any = (tuple,)
3586 def __init__(
3587 self,
3588 trait: t.List[T] | t.Tuple[T] | t.Set[T] | Sentinel | TraitType[T, t.Any] | None = None,
3589 default_value: t.List[T] | t.Tuple[T] | t.Set[T] | Sentinel | None = Undefined,
3590 minlen: int = 0,
3591 maxlen: int = sys.maxsize,
3592 **kwargs: t.Any,
3593 ) -> None:
3594 """Create a List trait type from a list, set, or tuple.
3596 The default value is created by doing ``list(default_value)``,
3597 which creates a copy of the ``default_value``.
3599 ``trait`` can be specified, which restricts the type of elements
3600 in the container to that TraitType.
3602 If only one arg is given and it is not a Trait, it is taken as
3603 ``default_value``:
3605 ``c = List([1, 2, 3])``
3607 Parameters
3608 ----------
3609 trait : TraitType [ optional ]
3610 the type for restricting the contents of the Container.
3611 If unspecified, types are not checked.
3612 default_value : SequenceType [ optional ]
3613 The default value for the Trait. Must be list/tuple/set, and
3614 will be cast to the container type.
3615 minlen : Int [ default 0 ]
3616 The minimum length of the input list
3617 maxlen : Int [ default sys.maxsize ]
3618 The maximum length of the input list
3619 """
3620 self._maxlen = maxlen
3621 self._minlen = minlen
3622 super().__init__(trait=trait, default_value=default_value, **kwargs)
3624 def length_error(self, obj: t.Any, value: t.Any) -> None:
3625 e = (
3626 "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified."
3627 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
3628 )
3629 raise TraitError(e)
3631 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3632 length = len(value)
3633 if length < self._minlen or length > self._maxlen:
3634 self.length_error(obj, value)
3636 return super().validate_elements(obj, value)
3638 def set(self, obj: t.Any, value: t.Any) -> None:
3639 if isinstance(value, str):
3640 return super().set(obj, [value]) # type:ignore[list-item]
3641 else:
3642 return super().set(obj, value)
3645class Set(Container[t.Set[t.Any]]):
3646 """An instance of a Python set."""
3648 klass = set
3649 _cast_types = (tuple, list)
3651 _literal_from_string_pairs = ("[]", "()", "{}")
3653 # Redefine __init__ just to make the docstring more accurate.
3654 def __init__(
3655 self,
3656 trait: t.Any = None,
3657 default_value: t.Any = Undefined,
3658 minlen: int = 0,
3659 maxlen: int = sys.maxsize,
3660 **kwargs: t.Any,
3661 ) -> None:
3662 """Create a Set trait type from a list, set, or tuple.
3664 The default value is created by doing ``set(default_value)``,
3665 which creates a copy of the ``default_value``.
3667 ``trait`` can be specified, which restricts the type of elements
3668 in the container to that TraitType.
3670 If only one arg is given and it is not a Trait, it is taken as
3671 ``default_value``:
3673 ``c = Set({1, 2, 3})``
3675 Parameters
3676 ----------
3677 trait : TraitType [ optional ]
3678 the type for restricting the contents of the Container.
3679 If unspecified, types are not checked.
3680 default_value : SequenceType [ optional ]
3681 The default value for the Trait. Must be list/tuple/set, and
3682 will be cast to the container type.
3683 minlen : Int [ default 0 ]
3684 The minimum length of the input list
3685 maxlen : Int [ default sys.maxsize ]
3686 The maximum length of the input list
3687 """
3688 self._maxlen = maxlen
3689 self._minlen = minlen
3690 super().__init__(trait=trait, default_value=default_value, **kwargs)
3692 def length_error(self, obj: t.Any, value: t.Any) -> None:
3693 e = (
3694 "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified."
3695 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
3696 )
3697 raise TraitError(e)
3699 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3700 length = len(value)
3701 if length < self._minlen or length > self._maxlen:
3702 self.length_error(obj, value)
3704 return super().validate_elements(obj, value)
3706 def set(self, obj: t.Any, value: t.Any) -> None:
3707 if isinstance(value, str):
3708 return super().set(
3709 obj,
3710 set(
3711 value,
3712 ),
3713 )
3714 else:
3715 return super().set(obj, value)
3717 def default_value_repr(self) -> str:
3718 # Ensure default value is sorted for a reproducible build
3719 list_repr = repr(sorted(self.make_dynamic_default() or []))
3720 if list_repr == "[]":
3721 return "set()"
3722 return "{" + list_repr[1:-1] + "}"
3725class Tuple(Container[t.Tuple[t.Any, ...]]):
3726 """An instance of a Python tuple."""
3728 klass = tuple
3729 _cast_types = (list,)
3731 def __init__(self, *traits: t.Any, **kwargs: t.Any) -> None:
3732 """Create a tuple from a list, set, or tuple.
3734 Create a fixed-type tuple with Traits:
3736 ``t = Tuple(Int(), Str(), CStr())``
3738 would be length 3, with Int,Str,CStr for each element.
3740 If only one arg is given and it is not a Trait, it is taken as
3741 default_value:
3743 ``t = Tuple((1, 2, 3))``
3745 Otherwise, ``default_value`` *must* be specified by keyword.
3747 Parameters
3748 ----------
3749 *traits : TraitTypes [ optional ]
3750 the types for restricting the contents of the Tuple. If unspecified,
3751 types are not checked. If specified, then each positional argument
3752 corresponds to an element of the tuple. Tuples defined with traits
3753 are of fixed length.
3754 default_value : SequenceType [ optional ]
3755 The default value for the Tuple. Must be list/tuple/set, and
3756 will be cast to a tuple. If ``traits`` are specified,
3757 ``default_value`` must conform to the shape and type they specify.
3758 **kwargs
3759 Other kwargs passed to `Container`
3760 """
3761 default_value = kwargs.pop("default_value", Undefined)
3762 # allow Tuple((values,)):
3763 if len(traits) == 1 and default_value is Undefined and not is_trait(traits[0]):
3764 default_value = traits[0]
3765 traits = ()
3767 if default_value is None and not kwargs.get("allow_none", False):
3768 # improve backward-compatibility for possible subclasses
3769 # specifying default_value=None as default,
3770 # keeping 'unspecified' behavior (i.e. empty container)
3771 warn(
3772 f"Specifying {self.__class__.__name__}(default_value=None)"
3773 " for no default is deprecated in traitlets 5.0.5."
3774 " Use default_value=Undefined",
3775 DeprecationWarning,
3776 stacklevel=2,
3777 )
3778 default_value = Undefined
3780 if default_value is Undefined:
3781 args: t.Any = ()
3782 elif default_value is None:
3783 # default_value back on kwargs for super() to handle
3784 args = ()
3785 kwargs["default_value"] = None
3786 elif isinstance(default_value, self._valid_defaults):
3787 args = (default_value,)
3788 else:
3789 raise TypeError(f"default value of {self.__class__.__name__} was {default_value}")
3791 self._traits = []
3792 for trait in traits:
3793 if isinstance(trait, type):
3794 warn(
3795 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3796 " Passing types is deprecated in traitlets 4.1.",
3797 DeprecationWarning,
3798 stacklevel=2,
3799 )
3800 trait = trait()
3801 self._traits.append(trait)
3803 if self._traits and (default_value is None or default_value is Undefined):
3804 # don't allow default to be an empty container if length is specified
3805 args = None
3806 super(Container, self).__init__(klass=self.klass, args=args, **kwargs)
3808 def item_from_string(self, s: str, index: int) -> t.Any: # type:ignore[override]
3809 """Cast a single item from a string
3811 Evaluated when parsing CLI configuration from a string
3812 """
3813 if not self._traits or index >= len(self._traits):
3814 # return s instead of raising index error
3815 # length errors will be raised later on validation
3816 return s
3817 return self._traits[index].from_string(s)
3819 def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any:
3820 if not self._traits:
3821 # nothing to validate
3822 return value
3823 if len(value) != len(self._traits):
3824 e = (
3825 "The '%s' trait of %s instance requires %i elements, but a value of %s was specified."
3826 % (self.name, class_of(obj), len(self._traits), repr_type(value))
3827 )
3828 raise TraitError(e)
3830 validated = []
3831 for trait, v in zip(self._traits, value):
3832 try:
3833 v = trait._validate(obj, v)
3834 except TraitError as error:
3835 self.error(obj, v, error)
3836 else:
3837 validated.append(v)
3838 return tuple(validated)
3840 def class_init(self, cls: type[t.Any], name: str | None) -> None:
3841 for trait in self._traits:
3842 if isinstance(trait, TraitType):
3843 trait.class_init(cls, None)
3844 super(Container, self).class_init(cls, name)
3846 def subclass_init(self, cls: type[t.Any]) -> None:
3847 for trait in self._traits:
3848 if isinstance(trait, TraitType):
3849 trait.subclass_init(cls)
3850 # explicitly not calling super().subclass_init(cls)
3851 # to opt out of instance_init
3854class Dict(Instance["dict[K, V]"]):
3855 """An instance of a Python dict.
3857 One or more traits can be passed to the constructor
3858 to validate the keys and/or values of the dict.
3859 If you need more detailed validation,
3860 you may use a custom validator method.
3862 .. versionchanged:: 5.0
3863 Added key_trait for validating dict keys.
3865 .. versionchanged:: 5.0
3866 Deprecated ambiguous ``trait``, ``traits`` args in favor of ``value_trait``, ``per_key_traits``.
3867 """
3869 _value_trait = None
3870 _key_trait = None
3872 def __init__(
3873 self,
3874 value_trait: TraitType[t.Any, t.Any] | dict[K, V] | Sentinel | None = None,
3875 per_key_traits: t.Any = None,
3876 key_trait: TraitType[t.Any, t.Any] | None = None,
3877 default_value: dict[K, V] | Sentinel | None = Undefined,
3878 **kwargs: t.Any,
3879 ) -> None:
3880 """Create a dict trait type from a Python dict.
3882 The default value is created by doing ``dict(default_value)``,
3883 which creates a copy of the ``default_value``.
3885 Parameters
3886 ----------
3887 value_trait : TraitType [ optional ]
3888 The specified trait type to check and use to restrict the values of
3889 the dict. If unspecified, values are not checked.
3890 per_key_traits : Dictionary of {keys:trait types} [ optional, keyword-only ]
3891 A Python dictionary containing the types that are valid for
3892 restricting the values of the dict on a per-key basis.
3893 Each value in this dict should be a Trait for validating
3894 key_trait : TraitType [ optional, keyword-only ]
3895 The type for restricting the keys of the dict. If
3896 unspecified, the types of the keys are not checked.
3897 default_value : SequenceType [ optional, keyword-only ]
3898 The default value for the Dict. Must be dict, tuple, or None, and
3899 will be cast to a dict if not None. If any key or value traits are specified,
3900 the `default_value` must conform to the constraints.
3902 Examples
3903 --------
3904 a dict whose values must be text
3905 >>> d = Dict(Unicode())
3907 d2['n'] must be an integer
3908 d2['s'] must be text
3909 >>> d2 = Dict(per_key_traits={"n": Integer(), "s": Unicode()})
3911 d3's keys must be text
3912 d3's values must be integers
3913 >>> d3 = Dict(value_trait=Integer(), key_trait=Unicode())
3915 """
3917 # handle deprecated keywords
3918 trait = kwargs.pop("trait", None)
3919 if trait is not None:
3920 if value_trait is not None:
3921 raise TypeError(
3922 "Found a value for both `value_trait` and its deprecated alias `trait`."
3923 )
3924 value_trait = trait
3925 warn(
3926 "Keyword `trait` is deprecated in traitlets 5.0, use `value_trait` instead",
3927 DeprecationWarning,
3928 stacklevel=2,
3929 )
3930 traits = kwargs.pop("traits", None)
3931 if traits is not None:
3932 if per_key_traits is not None:
3933 raise TypeError(
3934 "Found a value for both `per_key_traits` and its deprecated alias `traits`."
3935 )
3936 per_key_traits = traits
3937 warn(
3938 "Keyword `traits` is deprecated in traitlets 5.0, use `per_key_traits` instead",
3939 DeprecationWarning,
3940 stacklevel=2,
3941 )
3943 # Handling positional arguments
3944 if default_value is Undefined and value_trait is not None:
3945 if not is_trait(value_trait):
3946 assert not isinstance(value_trait, TraitType)
3947 default_value = value_trait
3948 value_trait = None
3950 if key_trait is None and per_key_traits is not None:
3951 if is_trait(per_key_traits):
3952 key_trait = per_key_traits
3953 per_key_traits = None
3955 # Handling default value
3956 if default_value is Undefined:
3957 default_value = {}
3958 if default_value is None:
3959 args: t.Any = None
3960 elif isinstance(default_value, dict):
3961 args = (default_value,)
3962 elif isinstance(default_value, SequenceTypes):
3963 args = (default_value,)
3964 else:
3965 raise TypeError("default value of Dict was %s" % default_value)
3967 # Case where a type of TraitType is provided rather than an instance
3968 if is_trait(value_trait):
3969 if isinstance(value_trait, type):
3970 warn( # type:ignore[unreachable]
3971 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3972 " Passing types is deprecated in traitlets 4.1.",
3973 DeprecationWarning,
3974 stacklevel=2,
3975 )
3976 value_trait = value_trait()
3977 self._value_trait = value_trait
3978 elif value_trait is not None:
3979 raise TypeError(
3980 "`value_trait` must be a Trait or None, got %s" % repr_type(value_trait)
3981 )
3983 if is_trait(key_trait):
3984 if isinstance(key_trait, type):
3985 warn( # type:ignore[unreachable]
3986 "Traits should be given as instances, not types (for example, `Int()`, not `Int`)"
3987 " Passing types is deprecated in traitlets 4.1.",
3988 DeprecationWarning,
3989 stacklevel=2,
3990 )
3991 key_trait = key_trait()
3992 self._key_trait = key_trait
3993 elif key_trait is not None:
3994 raise TypeError("`key_trait` must be a Trait or None, got %s" % repr_type(key_trait))
3996 self._per_key_traits = per_key_traits
3998 super().__init__(klass=dict, args=args, **kwargs)
4000 def element_error(
4001 self, obj: t.Any, element: t.Any, validator: t.Any, side: str = "Values"
4002 ) -> None:
4003 e = (
4004 side
4005 + f" of the '{self.name}' trait of {class_of(obj)} instance must be {validator.info()}, but a value of {repr_type(element)} was specified."
4006 )
4007 raise TraitError(e)
4009 def validate(self, obj: t.Any, value: t.Any) -> dict[K, V] | None:
4010 value = super().validate(obj, value)
4011 if value is None:
4012 return value
4013 value_dict = self.validate_elements(obj, value)
4014 return value_dict
4016 def validate_elements(self, obj: t.Any, value: dict[t.Any, t.Any]) -> dict[K, V] | None:
4017 per_key_override = self._per_key_traits or {}
4018 key_trait = self._key_trait
4019 value_trait = self._value_trait
4020 if not (key_trait or value_trait or per_key_override):
4021 return value
4023 validated = {}
4024 for key in value:
4025 v = value[key]
4026 if key_trait:
4027 try:
4028 key = key_trait._validate(obj, key)
4029 except TraitError:
4030 self.element_error(obj, key, key_trait, "Keys")
4031 active_value_trait = per_key_override.get(key, value_trait)
4032 if active_value_trait:
4033 try:
4034 v = active_value_trait._validate(obj, v)
4035 except TraitError:
4036 self.element_error(obj, v, active_value_trait, "Values")
4037 validated[key] = v
4039 return self.klass(validated) # type:ignore[misc,operator]
4041 def class_init(self, cls: type[t.Any], name: str | None) -> None:
4042 if isinstance(self._value_trait, TraitType):
4043 self._value_trait.class_init(cls, None)
4044 if isinstance(self._key_trait, TraitType):
4045 self._key_trait.class_init(cls, None)
4046 if self._per_key_traits is not None:
4047 for trait in self._per_key_traits.values():
4048 trait.class_init(cls, None)
4049 super().class_init(cls, name)
4051 def subclass_init(self, cls: type[t.Any]) -> None:
4052 if isinstance(self._value_trait, TraitType):
4053 self._value_trait.subclass_init(cls)
4054 if isinstance(self._key_trait, TraitType):
4055 self._key_trait.subclass_init(cls)
4056 if self._per_key_traits is not None:
4057 for trait in self._per_key_traits.values():
4058 trait.subclass_init(cls)
4059 # explicitly not calling super().subclass_init(cls)
4060 # to opt out of instance_init
4062 def from_string(self, s: str) -> dict[K, V] | None:
4063 """Load value from a single string"""
4064 if not isinstance(s, str):
4065 raise TypeError(f"from_string expects a string, got {s!r} of type {type(s)}")
4066 try:
4067 return t.cast("dict[K, V]", self.from_string_list([s]))
4068 except Exception:
4069 test = _safe_literal_eval(s)
4070 if isinstance(test, dict):
4071 return test
4072 raise
4074 def from_string_list(self, s_list: list[str]) -> t.Any:
4075 """Return a dict from a list of config strings.
4077 This is where we parse CLI configuration.
4079 Each item should have the form ``"key=value"``.
4081 item parsing is done in :meth:`.item_from_string`.
4082 """
4083 if len(s_list) == 1 and s_list[0] == "None" and self.allow_none:
4084 return None
4085 if len(s_list) == 1 and s_list[0].startswith("{") and s_list[0].endswith("}"):
4086 warn(
4087 f"--{self.name}={s_list[0]} for dict-traits is deprecated in traitlets 5.0. "
4088 f"You can pass --{self.name} <key=value> ... multiple times to add items to a dict.",
4089 DeprecationWarning,
4090 stacklevel=2,
4091 )
4093 return literal_eval(s_list[0])
4095 combined = {}
4096 for d in [self.item_from_string(s) for s in s_list]:
4097 combined.update(d)
4098 return combined
4100 def item_from_string(self, s: str) -> dict[K, V]:
4101 """Cast a single-key dict from a string.
4103 Evaluated when parsing CLI configuration from a string.
4105 Dicts expect strings of the form key=value.
4107 Returns a one-key dictionary,
4108 which will be merged in :meth:`.from_string_list`.
4109 """
4111 if "=" not in s:
4112 raise TraitError(
4113 f"'{self.__class__.__name__}' options must have the form 'key=value', got {s!r}"
4114 )
4115 key, value = s.split("=", 1)
4117 # cast key with key trait, if defined
4118 if self._key_trait:
4119 key = self._key_trait.from_string(key)
4121 # cast value with value trait, if defined (per-key or global)
4122 value_trait = (self._per_key_traits or {}).get(key, self._value_trait)
4123 if value_trait:
4124 value = value_trait.from_string(value)
4125 return t.cast("dict[K, V]", {key: value})
4128class TCPAddress(TraitType[G, S]):
4129 """A trait for an (ip, port) tuple.
4131 This allows for both IPv4 IP addresses as well as hostnames.
4132 """
4134 default_value = ("127.0.0.1", 0)
4135 info_text = "an (ip, port) tuple"
4137 if t.TYPE_CHECKING:
4139 @t.overload
4140 def __init__(
4141 self: TCPAddress[tuple[str, int], tuple[str, int]],
4142 default_value: bool | Sentinel = ...,
4143 allow_none: Literal[False] = ...,
4144 read_only: bool | None = ...,
4145 help: str | None = ...,
4146 config: t.Any = ...,
4147 **kwargs: t.Any,
4148 ) -> None:
4149 ...
4151 @t.overload
4152 def __init__(
4153 self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None],
4154 default_value: bool | None | Sentinel = ...,
4155 allow_none: Literal[True] = ...,
4156 read_only: bool | None = ...,
4157 help: str | None = ...,
4158 config: t.Any = ...,
4159 **kwargs: t.Any,
4160 ) -> None:
4161 ...
4163 def __init__(
4164 self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None]
4165 | TCPAddress[tuple[str, int], tuple[str, int]],
4166 default_value: bool | None | Sentinel = Undefined,
4167 allow_none: Literal[True, False] = False,
4168 read_only: bool | None = None,
4169 help: str | None = None,
4170 config: t.Any = None,
4171 **kwargs: t.Any,
4172 ) -> None:
4173 ...
4175 def validate(self, obj: t.Any, value: t.Any) -> G:
4176 if isinstance(value, tuple):
4177 if len(value) == 2:
4178 if isinstance(value[0], str) and isinstance(value[1], int):
4179 port = value[1]
4180 if port >= 0 and port <= 65535:
4181 return t.cast(G, value)
4182 self.error(obj, value)
4184 def from_string(self, s: str) -> G:
4185 if self.allow_none and s == "None":
4186 return t.cast(G, None)
4187 if ":" not in s:
4188 raise ValueError("Require `ip:port`, got %r" % s)
4189 ip, port_str = s.split(":", 1)
4190 port = int(port_str)
4191 return t.cast(G, (ip, port))
4194class CRegExp(TraitType["re.Pattern[t.Any]", t.Union["re.Pattern[t.Any]", str]]):
4195 """A casting compiled regular expression trait.
4197 Accepts both strings and compiled regular expressions. The resulting
4198 attribute will be a compiled regular expression."""
4200 info_text = "a regular expression"
4202 def validate(self, obj: t.Any, value: t.Any) -> re.Pattern[t.Any] | None:
4203 try:
4204 return re.compile(value)
4205 except Exception:
4206 self.error(obj, value)
4209class UseEnum(TraitType[t.Any, t.Any]):
4210 """Use a Enum class as model for the data type description.
4211 Note that if no default-value is provided, the first enum-value is used
4212 as default-value.
4214 .. sourcecode:: python
4216 # -- SINCE: Python 3.4 (or install backport: pip install enum34)
4217 import enum
4218 from traitlets import HasTraits, UseEnum
4221 class Color(enum.Enum):
4222 red = 1 # -- IMPLICIT: default_value
4223 blue = 2
4224 green = 3
4227 class MyEntity(HasTraits):
4228 color = UseEnum(Color, default_value=Color.blue)
4231 entity = MyEntity(color=Color.red)
4232 entity.color = Color.green # USE: Enum-value (preferred)
4233 entity.color = "green" # USE: name (as string)
4234 entity.color = "Color.green" # USE: scoped-name (as string)
4235 entity.color = 3 # USE: number (as int)
4236 assert entity.color is Color.green
4237 """
4239 default_value: enum.Enum | None = None
4240 info_text = "Trait type adapter to a Enum class"
4242 def __init__(
4243 self, enum_class: type[t.Any], default_value: t.Any = None, **kwargs: t.Any
4244 ) -> None:
4245 assert issubclass(enum_class, enum.Enum), "REQUIRE: enum.Enum, but was: %r" % enum_class
4246 allow_none = kwargs.get("allow_none", False)
4247 if default_value is None and not allow_none:
4248 default_value = next(iter(enum_class.__members__.values()))
4249 super().__init__(default_value=default_value, **kwargs)
4250 self.enum_class = enum_class
4251 self.name_prefix = enum_class.__name__ + "."
4253 def select_by_number(self, value: int, default: t.Any = Undefined) -> t.Any:
4254 """Selects enum-value by using its number-constant."""
4255 assert isinstance(value, int)
4256 enum_members = self.enum_class.__members__
4257 for enum_item in enum_members.values():
4258 if enum_item.value == value:
4259 return enum_item
4260 # -- NOT FOUND:
4261 return default
4263 def select_by_name(self, value: str, default: t.Any = Undefined) -> t.Any:
4264 """Selects enum-value by using its name or scoped-name."""
4265 assert isinstance(value, str)
4266 if value.startswith(self.name_prefix):
4267 # -- SUPPORT SCOPED-NAMES, like: "Color.red" => "red"
4268 value = value.replace(self.name_prefix, "", 1)
4269 return self.enum_class.__members__.get(value, default)
4271 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
4272 if isinstance(value, self.enum_class):
4273 return value
4274 elif isinstance(value, int):
4275 # -- CONVERT: number => enum_value (item)
4276 value2 = self.select_by_number(value)
4277 if value2 is not Undefined:
4278 return value2
4279 elif isinstance(value, str):
4280 # -- CONVERT: name or scoped_name (as string) => enum_value (item)
4281 value2 = self.select_by_name(value)
4282 if value2 is not Undefined:
4283 return value2
4284 elif value is None:
4285 if self.allow_none:
4286 return None
4287 else:
4288 return self.default_value
4289 self.error(obj, value)
4291 def _choices_str(self, as_rst: bool = False) -> str:
4292 """Returns a description of the trait choices (not none)."""
4293 choices = self.enum_class.__members__.keys()
4294 if as_rst:
4295 return "|".join("``%r``" % x for x in choices)
4296 else:
4297 return repr(list(choices)) # Listify because py3.4- prints odict-class
4299 def _info(self, as_rst: bool = False) -> str:
4300 """Returns a description of the trait."""
4301 none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else ""
4302 return f"any of {self._choices_str(as_rst)}{none}"
4304 def info(self) -> str:
4305 return self._info(as_rst=False)
4307 def info_rst(self) -> str:
4308 return self._info(as_rst=True)
4311class Callable(TraitType[t.Callable[..., t.Any], t.Callable[..., t.Any]]):
4312 """A trait which is callable.
4314 Notes
4315 -----
4316 Classes are callable, as are instances
4317 with a __call__() method."""
4319 info_text = "a callable"
4321 def validate(self, obj: t.Any, value: t.Any) -> t.Any:
4322 if callable(value):
4323 return value
4324 else:
4325 self.error(obj, value)