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