You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			601 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			601 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
"""A base class for objects that are configurable."""
 | 
						|
 | 
						|
# Copyright (c) IPython Development Team.
 | 
						|
# Distributed under the terms of the Modified BSD License.
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
import logging
 | 
						|
import typing as t
 | 
						|
from copy import deepcopy
 | 
						|
from textwrap import dedent
 | 
						|
 | 
						|
from traitlets.traitlets import (
 | 
						|
    Any,
 | 
						|
    Container,
 | 
						|
    Dict,
 | 
						|
    HasTraits,
 | 
						|
    Instance,
 | 
						|
    TraitType,
 | 
						|
    default,
 | 
						|
    observe,
 | 
						|
    observe_compat,
 | 
						|
    validate,
 | 
						|
)
 | 
						|
from traitlets.utils import warnings
 | 
						|
from traitlets.utils.bunch import Bunch
 | 
						|
from traitlets.utils.text import indent, wrap_paragraphs
 | 
						|
 | 
						|
from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
# Helper classes for Configurables
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
 | 
						|
if t.TYPE_CHECKING:
 | 
						|
    LoggerType = t.Union[logging.Logger, logging.LoggerAdapter[t.Any]]
 | 
						|
else:
 | 
						|
    LoggerType = t.Any
 | 
						|
 | 
						|
 | 
						|
class ConfigurableError(Exception):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
class MultipleInstanceError(ConfigurableError):
 | 
						|
    pass
 | 
						|
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
# Configurable implementation
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
class Configurable(HasTraits):
 | 
						|
    config = Instance(Config, (), {})
 | 
						|
    parent = Instance("traitlets.config.configurable.Configurable", allow_none=True)
 | 
						|
 | 
						|
    def __init__(self, **kwargs: t.Any) -> None:
 | 
						|
        """Create a configurable given a config config.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        config : Config
 | 
						|
            If this is empty, default values are used. If config is a
 | 
						|
            :class:`Config` instance, it will be used to configure the
 | 
						|
            instance.
 | 
						|
        parent : Configurable instance, optional
 | 
						|
            The parent Configurable instance of this object.
 | 
						|
 | 
						|
        Notes
 | 
						|
        -----
 | 
						|
        Subclasses of Configurable must call the :meth:`__init__` method of
 | 
						|
        :class:`Configurable` *before* doing anything else and using
 | 
						|
        :func:`super`::
 | 
						|
 | 
						|
            class MyConfigurable(Configurable):
 | 
						|
                def __init__(self, config=None):
 | 
						|
                    super(MyConfigurable, self).__init__(config=config)
 | 
						|
                    # Then any other code you need to finish initialization.
 | 
						|
 | 
						|
        This ensures that instances will be configured properly.
 | 
						|
        """
 | 
						|
        parent = kwargs.pop("parent", None)
 | 
						|
        if parent is not None:
 | 
						|
            # config is implied from parent
 | 
						|
            if kwargs.get("config", None) is None:
 | 
						|
                kwargs["config"] = parent.config
 | 
						|
            self.parent = parent
 | 
						|
 | 
						|
        config = kwargs.pop("config", None)
 | 
						|
 | 
						|
        # load kwarg traits, other than config
 | 
						|
        super().__init__(**kwargs)
 | 
						|
 | 
						|
        # record traits set by config
 | 
						|
        config_override_names = set()
 | 
						|
 | 
						|
        def notice_config_override(change: Bunch) -> None:
 | 
						|
            """Record traits set by both config and kwargs.
 | 
						|
 | 
						|
            They will need to be overridden again after loading config.
 | 
						|
            """
 | 
						|
            if change.name in kwargs:
 | 
						|
                config_override_names.add(change.name)
 | 
						|
 | 
						|
        self.observe(notice_config_override)
 | 
						|
 | 
						|
        # load config
 | 
						|
        if config is not None:
 | 
						|
            # We used to deepcopy, but for now we are trying to just save
 | 
						|
            # by reference.  This *could* have side effects as all components
 | 
						|
            # will share config. In fact, I did find such a side effect in
 | 
						|
            # _config_changed below. If a config attribute value was a mutable type
 | 
						|
            # all instances of a component were getting the same copy, effectively
 | 
						|
            # making that a class attribute.
 | 
						|
            # self.config = deepcopy(config)
 | 
						|
            self.config = config
 | 
						|
        else:
 | 
						|
            # allow _config_default to return something
 | 
						|
            self._load_config(self.config)
 | 
						|
        self.unobserve(notice_config_override)
 | 
						|
 | 
						|
        for name in config_override_names:
 | 
						|
            setattr(self, name, kwargs[name])
 | 
						|
 | 
						|
    # -------------------------------------------------------------------------
 | 
						|
    # Static trait notifications
 | 
						|
    # -------------------------------------------------------------------------
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def section_names(cls) -> list[str]:
 | 
						|
        """return section names as a list"""
 | 
						|
        return [
 | 
						|
            c.__name__
 | 
						|
            for c in reversed(cls.__mro__)
 | 
						|
            if issubclass(c, Configurable) and issubclass(cls, c)
 | 
						|
        ]
 | 
						|
 | 
						|
    def _find_my_config(self, cfg: Config) -> t.Any:
 | 
						|
        """extract my config from a global Config object
 | 
						|
 | 
						|
        will construct a Config object of only the config values that apply to me
 | 
						|
        based on my mro(), as well as those of my parent(s) if they exist.
 | 
						|
 | 
						|
        If I am Bar and my parent is Foo, and their parent is Tim,
 | 
						|
        this will return merge following config sections, in this order::
 | 
						|
 | 
						|
            [Bar, Foo.Bar, Tim.Foo.Bar]
 | 
						|
 | 
						|
        With the last item being the highest priority.
 | 
						|
        """
 | 
						|
        cfgs = [cfg]
 | 
						|
        if self.parent:
 | 
						|
            cfgs.append(self.parent._find_my_config(cfg))
 | 
						|
        my_config = Config()
 | 
						|
        for c in cfgs:
 | 
						|
            for sname in self.section_names():
 | 
						|
                # Don't do a blind getattr as that would cause the config to
 | 
						|
                # dynamically create the section with name Class.__name__.
 | 
						|
                if c._has_section(sname):
 | 
						|
                    my_config.merge(c[sname])
 | 
						|
        return my_config
 | 
						|
 | 
						|
    def _load_config(
 | 
						|
        self,
 | 
						|
        cfg: Config,
 | 
						|
        section_names: list[str] | None = None,
 | 
						|
        traits: dict[str, TraitType[t.Any, t.Any]] | None = None,
 | 
						|
    ) -> None:
 | 
						|
        """load traits from a Config object"""
 | 
						|
 | 
						|
        if traits is None:
 | 
						|
            traits = self.traits(config=True)
 | 
						|
        if section_names is None:
 | 
						|
            section_names = self.section_names()
 | 
						|
 | 
						|
        my_config = self._find_my_config(cfg)
 | 
						|
 | 
						|
        # hold trait notifications until after all config has been loaded
 | 
						|
        with self.hold_trait_notifications():
 | 
						|
            for name, config_value in my_config.items():
 | 
						|
                if name in traits:
 | 
						|
                    if isinstance(config_value, LazyConfigValue):
 | 
						|
                        # ConfigValue is a wrapper for using append / update on containers
 | 
						|
                        # without having to copy the initial value
 | 
						|
                        initial = getattr(self, name)
 | 
						|
                        config_value = config_value.get_value(initial)
 | 
						|
                    elif isinstance(config_value, DeferredConfig):
 | 
						|
                        # DeferredConfig tends to come from CLI/environment variables
 | 
						|
                        config_value = config_value.get_value(traits[name])
 | 
						|
                    # We have to do a deepcopy here if we don't deepcopy the entire
 | 
						|
                    # config object. If we don't, a mutable config_value will be
 | 
						|
                    # shared by all instances, effectively making it a class attribute.
 | 
						|
                    setattr(self, name, deepcopy(config_value))
 | 
						|
                elif not _is_section_key(name) and not isinstance(config_value, Config):
 | 
						|
                    from difflib import get_close_matches
 | 
						|
 | 
						|
                    if isinstance(self, LoggingConfigurable):
 | 
						|
                        assert self.log is not None
 | 
						|
                        warn = self.log.warning
 | 
						|
                    else:
 | 
						|
 | 
						|
                        def warn(msg: t.Any) -> None:
 | 
						|
                            return warnings.warn(msg, UserWarning, stacklevel=9)
 | 
						|
 | 
						|
                    matches = get_close_matches(name, traits)
 | 
						|
                    msg = f"Config option `{name}` not recognized by `{self.__class__.__name__}`."
 | 
						|
 | 
						|
                    if len(matches) == 1:
 | 
						|
                        msg += f"  Did you mean `{matches[0]}`?"
 | 
						|
                    elif len(matches) >= 1:
 | 
						|
                        msg += "  Did you mean one of: `{matches}`?".format(
 | 
						|
                            matches=", ".join(sorted(matches))
 | 
						|
                        )
 | 
						|
                    warn(msg)
 | 
						|
 | 
						|
    @observe("config")
 | 
						|
    @observe_compat
 | 
						|
    def _config_changed(self, change: Bunch) -> None:
 | 
						|
        """Update all the class traits having ``config=True`` in metadata.
 | 
						|
 | 
						|
        For any class trait with a ``config`` metadata attribute that is
 | 
						|
        ``True``, we update the trait with the value of the corresponding
 | 
						|
        config entry.
 | 
						|
        """
 | 
						|
        # Get all traits with a config metadata entry that is True
 | 
						|
        traits = self.traits(config=True)
 | 
						|
 | 
						|
        # We auto-load config section for this class as well as any parent
 | 
						|
        # classes that are Configurable subclasses.  This starts with Configurable
 | 
						|
        # and works down the mro loading the config for each section.
 | 
						|
        section_names = self.section_names()
 | 
						|
        self._load_config(change.new, traits=traits, section_names=section_names)
 | 
						|
 | 
						|
    def update_config(self, config: Config) -> None:
 | 
						|
        """Update config and load the new values"""
 | 
						|
        # traitlets prior to 4.2 created a copy of self.config in order to trigger change events.
 | 
						|
        # Some projects (IPython < 5) relied upon one side effect of this,
 | 
						|
        # that self.config prior to update_config was not modified in-place.
 | 
						|
        # For backward-compatibility, we must ensure that self.config
 | 
						|
        # is a new object and not modified in-place,
 | 
						|
        # but config consumers should not rely on this behavior.
 | 
						|
        self.config = deepcopy(self.config)
 | 
						|
        # load config
 | 
						|
        self._load_config(config)
 | 
						|
        # merge it into self.config
 | 
						|
        self.config.merge(config)
 | 
						|
        # TODO: trigger change event if/when dict-update change events take place
 | 
						|
        # DO NOT trigger full trait-change
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def class_get_help(cls, inst: HasTraits | None = None) -> str:
 | 
						|
        """Get the help string for this class in ReST format.
 | 
						|
 | 
						|
        If `inst` is given, its current trait values will be used in place of
 | 
						|
        class defaults.
 | 
						|
        """
 | 
						|
        assert inst is None or isinstance(inst, cls)
 | 
						|
        final_help = []
 | 
						|
        base_classes = ", ".join(p.__name__ for p in cls.__bases__)
 | 
						|
        final_help.append(f"{cls.__name__}({base_classes}) options")
 | 
						|
        final_help.append(len(final_help[0]) * "-")
 | 
						|
        for _, v in sorted(cls.class_traits(config=True).items()):
 | 
						|
            help = cls.class_get_trait_help(v, inst)
 | 
						|
            final_help.append(help)
 | 
						|
        return "\n".join(final_help)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def class_get_trait_help(
 | 
						|
        cls,
 | 
						|
        trait: TraitType[t.Any, t.Any],
 | 
						|
        inst: HasTraits | None = None,
 | 
						|
        helptext: str | None = None,
 | 
						|
    ) -> str:
 | 
						|
        """Get the helptext string for a single trait.
 | 
						|
 | 
						|
        :param inst:
 | 
						|
            If given, its current trait values will be used in place of
 | 
						|
            the class default.
 | 
						|
        :param helptext:
 | 
						|
            If not given, uses the `help` attribute of the current trait.
 | 
						|
        """
 | 
						|
        assert inst is None or isinstance(inst, cls)
 | 
						|
        lines = []
 | 
						|
        header = f"--{cls.__name__}.{trait.name}"
 | 
						|
        if isinstance(trait, (Container, Dict)):
 | 
						|
            multiplicity = trait.metadata.get("multiplicity", "append")
 | 
						|
            if isinstance(trait, Dict):
 | 
						|
                sample_value = "<key-1>=<value-1>"
 | 
						|
            else:
 | 
						|
                sample_value = "<%s-item-1>" % trait.__class__.__name__.lower()
 | 
						|
            if multiplicity == "append":
 | 
						|
                header = f"{header}={sample_value}..."
 | 
						|
            else:
 | 
						|
                header = f"{header} {sample_value}..."
 | 
						|
        else:
 | 
						|
            header = f"{header}=<{trait.__class__.__name__}>"
 | 
						|
        # header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
 | 
						|
        lines.append(header)
 | 
						|
 | 
						|
        if helptext is None:
 | 
						|
            helptext = trait.help
 | 
						|
        if helptext != "":
 | 
						|
            helptext = "\n".join(wrap_paragraphs(helptext, 76))
 | 
						|
            lines.append(indent(helptext))
 | 
						|
 | 
						|
        if "Enum" in trait.__class__.__name__:
 | 
						|
            # include Enum choices
 | 
						|
            lines.append(indent("Choices: %s" % trait.info()))
 | 
						|
 | 
						|
        if inst is not None:
 | 
						|
            lines.append(indent(f"Current: {getattr(inst, trait.name or '')!r}"))
 | 
						|
        else:
 | 
						|
            try:
 | 
						|
                dvr = trait.default_value_repr()
 | 
						|
            except Exception:
 | 
						|
                dvr = None  # ignore defaults we can't construct
 | 
						|
            if dvr is not None:
 | 
						|
                if len(dvr) > 64:
 | 
						|
                    dvr = dvr[:61] + "..."
 | 
						|
                lines.append(indent("Default: %s" % dvr))
 | 
						|
 | 
						|
        return "\n".join(lines)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def class_print_help(cls, inst: HasTraits | None = None) -> None:
 | 
						|
        """Get the help string for a single trait and print it."""
 | 
						|
        print(cls.class_get_help(inst))  # noqa: T201
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _defining_class(
 | 
						|
        cls, trait: TraitType[t.Any, t.Any], classes: t.Sequence[type[HasTraits]]
 | 
						|
    ) -> type[Configurable]:
 | 
						|
        """Get the class that defines a trait
 | 
						|
 | 
						|
        For reducing redundant help output in config files.
 | 
						|
        Returns the current class if:
 | 
						|
        - the trait is defined on this class, or
 | 
						|
        - the class where it is defined would not be in the config file
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        trait : Trait
 | 
						|
            The trait to look for
 | 
						|
        classes : list
 | 
						|
            The list of other classes to consider for redundancy.
 | 
						|
            Will return `cls` even if it is not defined on `cls`
 | 
						|
            if the defining class is not in `classes`.
 | 
						|
        """
 | 
						|
        defining_cls = cls
 | 
						|
        assert trait.name is not None
 | 
						|
        for parent in cls.mro():
 | 
						|
            if (
 | 
						|
                issubclass(parent, Configurable)
 | 
						|
                and parent in classes
 | 
						|
                and parent.class_own_traits(config=True).get(trait.name, None) is trait
 | 
						|
            ):
 | 
						|
                defining_cls = parent
 | 
						|
        return defining_cls
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def class_config_section(cls, classes: t.Sequence[type[HasTraits]] | None = None) -> str:
 | 
						|
        """Get the config section for this class.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        classes : list, optional
 | 
						|
            The list of other classes in the config file.
 | 
						|
            Used to reduce redundant information.
 | 
						|
        """
 | 
						|
 | 
						|
        def c(s: str) -> str:
 | 
						|
            """return a commented, wrapped block."""
 | 
						|
            s = "\n\n".join(wrap_paragraphs(s, 78))
 | 
						|
 | 
						|
            return "## " + s.replace("\n", "\n#  ")
 | 
						|
 | 
						|
        # section header
 | 
						|
        breaker = "#" + "-" * 78
 | 
						|
        parent_classes = ", ".join(p.__name__ for p in cls.__bases__ if issubclass(p, Configurable))
 | 
						|
 | 
						|
        s = f"# {cls.__name__}({parent_classes}) configuration"
 | 
						|
        lines = [breaker, s, breaker]
 | 
						|
        # get the description trait
 | 
						|
        desc = cls.class_traits().get("description")
 | 
						|
        if desc:
 | 
						|
            desc = desc.default_value
 | 
						|
        if not desc:
 | 
						|
            # no description from trait, use __doc__
 | 
						|
            desc = getattr(cls, "__doc__", "")  # type:ignore[arg-type]
 | 
						|
        if desc:
 | 
						|
            lines.append(c(desc))  # type:ignore[arg-type]
 | 
						|
            lines.append("")
 | 
						|
 | 
						|
        for name, trait in sorted(cls.class_traits(config=True).items()):
 | 
						|
            default_repr = trait.default_value_repr()
 | 
						|
 | 
						|
            if classes:
 | 
						|
                defining_class = cls._defining_class(trait, classes)
 | 
						|
            else:
 | 
						|
                defining_class = cls
 | 
						|
            if defining_class is cls:
 | 
						|
                # cls owns the trait, show full help
 | 
						|
                if trait.help:
 | 
						|
                    lines.append(c(trait.help))
 | 
						|
                if "Enum" in type(trait).__name__:
 | 
						|
                    # include Enum choices
 | 
						|
                    lines.append("#  Choices: %s" % trait.info())
 | 
						|
                lines.append("#  Default: %s" % default_repr)
 | 
						|
            else:
 | 
						|
                # Trait appears multiple times and isn't defined here.
 | 
						|
                # Truncate help to first line + "See also Original.trait"
 | 
						|
                if trait.help:
 | 
						|
                    lines.append(c(trait.help.split("\n", 1)[0]))
 | 
						|
                lines.append(f"#  See also: {defining_class.__name__}.{name}")
 | 
						|
 | 
						|
            lines.append(f"# c.{cls.__name__}.{name} = {default_repr}")
 | 
						|
            lines.append("")
 | 
						|
        return "\n".join(lines)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def class_config_rst_doc(cls) -> str:
 | 
						|
        """Generate rST documentation for this class' config options.
 | 
						|
 | 
						|
        Excludes traits defined on parent classes.
 | 
						|
        """
 | 
						|
        lines = []
 | 
						|
        classname = cls.__name__
 | 
						|
        for _, trait in sorted(cls.class_traits(config=True).items()):
 | 
						|
            ttype = trait.__class__.__name__
 | 
						|
 | 
						|
            if not trait.name:
 | 
						|
                continue
 | 
						|
            termline = classname + "." + trait.name
 | 
						|
 | 
						|
            # Choices or type
 | 
						|
            if "Enum" in ttype:
 | 
						|
                # include Enum choices
 | 
						|
                termline += " : " + trait.info_rst()  # type:ignore[attr-defined]
 | 
						|
            else:
 | 
						|
                termline += " : " + ttype
 | 
						|
            lines.append(termline)
 | 
						|
 | 
						|
            # Default value
 | 
						|
            try:
 | 
						|
                dvr = trait.default_value_repr()
 | 
						|
            except Exception:
 | 
						|
                dvr = None  # ignore defaults we can't construct
 | 
						|
            if dvr is not None:
 | 
						|
                if len(dvr) > 64:
 | 
						|
                    dvr = dvr[:61] + "..."
 | 
						|
                # Double up backslashes, so they get to the rendered docs
 | 
						|
                dvr = dvr.replace("\\n", "\\\\n")
 | 
						|
                lines.append(indent("Default: ``%s``" % dvr))
 | 
						|
                lines.append("")
 | 
						|
 | 
						|
            help = trait.help or "No description"
 | 
						|
            lines.append(indent(dedent(help)))
 | 
						|
 | 
						|
            # Blank line
 | 
						|
            lines.append("")
 | 
						|
 | 
						|
        return "\n".join(lines)
 | 
						|
 | 
						|
 | 
						|
class LoggingConfigurable(Configurable):
 | 
						|
    """A parent class for Configurables that log.
 | 
						|
 | 
						|
    Subclasses have a log trait, and the default behavior
 | 
						|
    is to get the logger from the currently running Application.
 | 
						|
    """
 | 
						|
 | 
						|
    log = Any(help="Logger or LoggerAdapter instance", allow_none=False)
 | 
						|
 | 
						|
    @validate("log")
 | 
						|
    def _validate_log(self, proposal: Bunch) -> LoggerType:
 | 
						|
        if not isinstance(proposal.value, (logging.Logger, logging.LoggerAdapter)):
 | 
						|
            # warn about unsupported type, but be lenient to allow for duck typing
 | 
						|
            warnings.warn(
 | 
						|
                f"{self.__class__.__name__}.log should be a Logger or LoggerAdapter,"
 | 
						|
                f" got {proposal.value}.",
 | 
						|
                UserWarning,
 | 
						|
                stacklevel=2,
 | 
						|
            )
 | 
						|
        return t.cast(LoggerType, proposal.value)
 | 
						|
 | 
						|
    @default("log")
 | 
						|
    def _log_default(self) -> LoggerType:
 | 
						|
        if isinstance(self.parent, LoggingConfigurable):
 | 
						|
            assert self.parent is not None
 | 
						|
            return t.cast(logging.Logger, self.parent.log)
 | 
						|
        from traitlets import log
 | 
						|
 | 
						|
        return log.get_logger()
 | 
						|
 | 
						|
    def _get_log_handler(self) -> logging.Handler | None:
 | 
						|
        """Return the default Handler
 | 
						|
 | 
						|
        Returns None if none can be found
 | 
						|
 | 
						|
        Deprecated, this now returns the first log handler which may or may
 | 
						|
        not be the default one.
 | 
						|
        """
 | 
						|
        if not self.log:
 | 
						|
            return None
 | 
						|
        logger: logging.Logger = (
 | 
						|
            self.log if isinstance(self.log, logging.Logger) else self.log.logger
 | 
						|
        )
 | 
						|
        if not getattr(logger, "handlers", None):
 | 
						|
            # no handlers attribute or empty handlers list
 | 
						|
            return None
 | 
						|
        return logger.handlers[0]
 | 
						|
 | 
						|
 | 
						|
CT = t.TypeVar("CT", bound="SingletonConfigurable")
 | 
						|
 | 
						|
 | 
						|
class SingletonConfigurable(LoggingConfigurable):
 | 
						|
    """A configurable that only allows one instance.
 | 
						|
 | 
						|
    This class is for classes that should only have one instance of itself
 | 
						|
    or *any* subclass. To create and retrieve such a class use the
 | 
						|
    :meth:`SingletonConfigurable.instance` method.
 | 
						|
    """
 | 
						|
 | 
						|
    _instance = None
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def _walk_mro(cls) -> t.Generator[type[SingletonConfigurable], None, None]:
 | 
						|
        """Walk the cls.mro() for parent classes that are also singletons
 | 
						|
 | 
						|
        For use in instance()
 | 
						|
        """
 | 
						|
 | 
						|
        for subclass in cls.mro():
 | 
						|
            if (
 | 
						|
                issubclass(cls, subclass)
 | 
						|
                and issubclass(subclass, SingletonConfigurable)
 | 
						|
                and subclass != SingletonConfigurable
 | 
						|
            ):
 | 
						|
                yield subclass
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def clear_instance(cls) -> None:
 | 
						|
        """unset _instance for this class and singleton parents."""
 | 
						|
        if not cls.initialized():
 | 
						|
            return
 | 
						|
        for subclass in cls._walk_mro():
 | 
						|
            if isinstance(subclass._instance, cls):
 | 
						|
                # only clear instances that are instances
 | 
						|
                # of the calling class
 | 
						|
                subclass._instance = None  # type:ignore[unreachable]
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def instance(cls: type[CT], *args: t.Any, **kwargs: t.Any) -> CT:
 | 
						|
        """Returns a global instance of this class.
 | 
						|
 | 
						|
        This method create a new instance if none have previously been created
 | 
						|
        and returns a previously created instance is one already exists.
 | 
						|
 | 
						|
        The arguments and keyword arguments passed to this method are passed
 | 
						|
        on to the :meth:`__init__` method of the class upon instantiation.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        Create a singleton class using instance, and retrieve it::
 | 
						|
 | 
						|
            >>> from traitlets.config.configurable import SingletonConfigurable
 | 
						|
            >>> class Foo(SingletonConfigurable): pass
 | 
						|
            >>> foo = Foo.instance()
 | 
						|
            >>> foo == Foo.instance()
 | 
						|
            True
 | 
						|
 | 
						|
        Create a subclass that is retrieved using the base class instance::
 | 
						|
 | 
						|
            >>> class Bar(SingletonConfigurable): pass
 | 
						|
            >>> class Bam(Bar): pass
 | 
						|
            >>> bam = Bam.instance()
 | 
						|
            >>> bam == Bar.instance()
 | 
						|
            True
 | 
						|
        """
 | 
						|
        # Create and save the instance
 | 
						|
        if cls._instance is None:
 | 
						|
            inst = cls(*args, **kwargs)
 | 
						|
            # Now make sure that the instance will also be returned by
 | 
						|
            # parent classes' _instance attribute.
 | 
						|
            for subclass in cls._walk_mro():
 | 
						|
                subclass._instance = inst
 | 
						|
 | 
						|
        if isinstance(cls._instance, cls):
 | 
						|
            return cls._instance
 | 
						|
        else:
 | 
						|
            raise MultipleInstanceError(
 | 
						|
                f"An incompatible sibling of '{cls.__name__}' is already instantiated"
 | 
						|
                f" as singleton: {type(cls._instance).__name__}"
 | 
						|
            )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def initialized(cls) -> bool:
 | 
						|
        """Has an instance been created?"""
 | 
						|
        return hasattr(cls, "_instance") and cls._instance is not None
 |