482 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			482 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import abc
							 | 
						||
| 
								 | 
							
								import collections
							 | 
						||
| 
								 | 
							
								import inspect
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import typing
							 | 
						||
| 
								 | 
							
								import uuid
							 | 
						||
| 
								 | 
							
								import random
							 | 
						||
| 
								 | 
							
								import warnings
							 | 
						||
| 
								 | 
							
								import textwrap
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .._utils import patch_collections_abc, stringify_id, OrderedSet
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								MutableSequence = patch_collections_abc("MutableSequence")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								rd = random.Random(0)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								_deprecated_components = {
							 | 
						||
| 
								 | 
							
								    "dash_core_components": {
							 | 
						||
| 
								 | 
							
								        "LogoutButton": textwrap.dedent(
							 | 
						||
| 
								 | 
							
								            """
							 | 
						||
| 
								 | 
							
								        The Logout Button is no longer used with Dash Enterprise and can be replaced with a html.Button or html.A.
							 | 
						||
| 
								 | 
							
								        eg: html.A(href=os.getenv('DASH_LOGOUT_URL'))
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# pylint: disable=no-init,too-few-public-methods
							 | 
						||
| 
								 | 
							
								class ComponentRegistry:
							 | 
						||
| 
								 | 
							
								    """Holds a registry of the namespaces used by components."""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    registry = OrderedSet()
							 | 
						||
| 
								 | 
							
								    children_props = collections.defaultdict(dict)
							 | 
						||
| 
								 | 
							
								    namespace_to_package = {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def get_resources(cls, resource_name, includes=None):
							 | 
						||
| 
								 | 
							
								        resources = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for module_name in cls.registry:
							 | 
						||
| 
								 | 
							
								            if includes is not None and module_name not in includes:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								            module = sys.modules[module_name]
							 | 
						||
| 
								 | 
							
								            resources.extend(getattr(module, resource_name, []))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return resources
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class ComponentMeta(abc.ABCMeta):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # pylint: disable=arguments-differ
							 | 
						||
| 
								 | 
							
								    def __new__(mcs, name, bases, attributes):
							 | 
						||
| 
								 | 
							
								        module = attributes["__module__"].split(".")[0]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if attributes.get("_explicitize_dash_init", False):
							 | 
						||
| 
								 | 
							
								            # We only want to patch the new generated component without
							 | 
						||
| 
								 | 
							
								            # the `@_explicitize_args` decorator for mypy support
							 | 
						||
| 
								 | 
							
								            # See issue: https://github.com/plotly/dash/issues/3226
							 | 
						||
| 
								 | 
							
								            # Only for component that were generated by 3.0.3
							 | 
						||
| 
								 | 
							
								            # Better to setattr on the component afterwards to ensure
							 | 
						||
| 
								 | 
							
								            # backward compatibility.
							 | 
						||
| 
								 | 
							
								            attributes["__init__"] = _explicitize_args(attributes["__init__"])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        _component = abc.ABCMeta.__new__(mcs, name, bases, attributes)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if name == "Component" or module == "builtins":
							 | 
						||
| 
								 | 
							
								            # Don't add to the registry the base component
							 | 
						||
| 
								 | 
							
								            # and the components loaded dynamically by load_component
							 | 
						||
| 
								 | 
							
								            # as it doesn't have the namespace.
							 | 
						||
| 
								 | 
							
								            return _component
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        _namespace = attributes.get("_namespace", module)
							 | 
						||
| 
								 | 
							
								        ComponentRegistry.namespace_to_package[_namespace] = module
							 | 
						||
| 
								 | 
							
								        ComponentRegistry.registry.add(module)
							 | 
						||
| 
								 | 
							
								        ComponentRegistry.children_props[_namespace][name] = attributes.get(
							 | 
						||
| 
								 | 
							
								            "_children_props"
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return _component
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def is_number(s):
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        float(s)
							 | 
						||
| 
								 | 
							
								        return True
							 | 
						||
| 
								 | 
							
								    except ValueError:
							 | 
						||
| 
								 | 
							
								        return False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _check_if_has_indexable_children(item):
							 | 
						||
| 
								 | 
							
								    if not hasattr(item, "children") or (
							 | 
						||
| 
								 | 
							
								        not isinstance(item.children, Component)
							 | 
						||
| 
								 | 
							
								        and not isinstance(item.children, (tuple, MutableSequence))
							 | 
						||
| 
								 | 
							
								    ):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        raise KeyError
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Component(metaclass=ComponentMeta):
							 | 
						||
| 
								 | 
							
								    _children_props = []
							 | 
						||
| 
								 | 
							
								    _base_nodes = ["children"]
							 | 
						||
| 
								 | 
							
								    _namespace: str
							 | 
						||
| 
								 | 
							
								    _type: str
							 | 
						||
| 
								 | 
							
								    _prop_names: typing.List[str]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _valid_wildcard_attributes: typing.List[str]
							 | 
						||
| 
								 | 
							
								    available_wildcard_properties: typing.List[str]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    class _UNDEFINED:
							 | 
						||
| 
								 | 
							
								        def __repr__(self):
							 | 
						||
| 
								 | 
							
								            return "undefined"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def __str__(self):
							 | 
						||
| 
								 | 
							
								            return "undefined"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    UNDEFINED = _UNDEFINED()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    class _REQUIRED:
							 | 
						||
| 
								 | 
							
								        def __repr__(self):
							 | 
						||
| 
								 | 
							
								            return "required"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        def __str__(self):
							 | 
						||
| 
								 | 
							
								            return "required"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    REQUIRED = _REQUIRED()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __init__(self, **kwargs):
							 | 
						||
| 
								 | 
							
								        self._validate_deprecation()
							 | 
						||
| 
								 | 
							
								        import dash  # pylint: disable=import-outside-toplevel, cyclic-import
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for k, v in list(kwargs.items()):
							 | 
						||
| 
								 | 
							
								            # pylint: disable=no-member
							 | 
						||
| 
								 | 
							
								            k_in_propnames = k in self._prop_names
							 | 
						||
| 
								 | 
							
								            k_in_wildcards = any(
							 | 
						||
| 
								 | 
							
								                k.startswith(w) for w in self._valid_wildcard_attributes
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								            # e.g. "The dash_core_components.Dropdown component (version 1.6.0)
							 | 
						||
| 
								 | 
							
								            # with the ID "my-dropdown"
							 | 
						||
| 
								 | 
							
								            id_suffix = f' with the ID "{kwargs["id"]}"' if "id" in kwargs else ""
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                # Get fancy error strings that have the version numbers
							 | 
						||
| 
								 | 
							
								                error_string_prefix = "The `{}.{}` component (version {}){}"
							 | 
						||
| 
								 | 
							
								                # These components are part of dash now, so extract the dash version:
							 | 
						||
| 
								 | 
							
								                dash_packages = {
							 | 
						||
| 
								 | 
							
								                    "dash_html_components": "html",
							 | 
						||
| 
								 | 
							
								                    "dash_core_components": "dcc",
							 | 
						||
| 
								 | 
							
								                    "dash_table": "dash_table",
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								                if self._namespace in dash_packages:
							 | 
						||
| 
								 | 
							
								                    error_string_prefix = error_string_prefix.format(
							 | 
						||
| 
								 | 
							
								                        dash_packages[self._namespace],
							 | 
						||
| 
								 | 
							
								                        self._type,
							 | 
						||
| 
								 | 
							
								                        dash.__version__,
							 | 
						||
| 
								 | 
							
								                        id_suffix,
							 | 
						||
| 
								 | 
							
								                    )
							 | 
						||
| 
								 | 
							
								                else:
							 | 
						||
| 
								 | 
							
								                    # Otherwise import the package and extract the version number
							 | 
						||
| 
								 | 
							
								                    error_string_prefix = error_string_prefix.format(
							 | 
						||
| 
								 | 
							
								                        self._namespace,
							 | 
						||
| 
								 | 
							
								                        self._type,
							 | 
						||
| 
								 | 
							
								                        getattr(__import__(self._namespace), "__version__", "unknown"),
							 | 
						||
| 
								 | 
							
								                        id_suffix,
							 | 
						||
| 
								 | 
							
								                    )
							 | 
						||
| 
								 | 
							
								            except ImportError:
							 | 
						||
| 
								 | 
							
								                # Our tests create mock components with libraries that
							 | 
						||
| 
								 | 
							
								                # aren't importable
							 | 
						||
| 
								 | 
							
								                error_string_prefix = f"The `{self._type}` component{id_suffix}"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if not k_in_propnames and not k_in_wildcards:
							 | 
						||
| 
								 | 
							
								                allowed_args = ", ".join(
							 | 
						||
| 
								 | 
							
								                    sorted(self._prop_names)
							 | 
						||
| 
								 | 
							
								                )  # pylint: disable=no-member
							 | 
						||
| 
								 | 
							
								                raise TypeError(
							 | 
						||
| 
								 | 
							
								                    f"{error_string_prefix} received an unexpected keyword argument: `{k}`"
							 | 
						||
| 
								 | 
							
								                    f"\nAllowed arguments: {allowed_args}"
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if k not in self._base_nodes and isinstance(v, Component):
							 | 
						||
| 
								 | 
							
								                raise TypeError(
							 | 
						||
| 
								 | 
							
								                    error_string_prefix
							 | 
						||
| 
								 | 
							
								                    + " detected a Component for a prop other than `children`\n"
							 | 
						||
| 
								 | 
							
								                    + f"Prop {k} has value {v!r}\n\n"
							 | 
						||
| 
								 | 
							
								                    + "Did you forget to wrap multiple `children` in an array?\n"
							 | 
						||
| 
								 | 
							
								                    + 'For example, it must be html.Div(["a", "b", "c"]) not html.Div("a", "b", "c")\n'
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            if k == "id":
							 | 
						||
| 
								 | 
							
								                if isinstance(v, dict):
							 | 
						||
| 
								 | 
							
								                    for id_key, id_val in v.items():
							 | 
						||
| 
								 | 
							
								                        if not isinstance(id_key, str):
							 | 
						||
| 
								 | 
							
								                            raise TypeError(
							 | 
						||
| 
								 | 
							
								                                "dict id keys must be strings,\n"
							 | 
						||
| 
								 | 
							
								                                + f"found {id_key!r} in id {v!r}"
							 | 
						||
| 
								 | 
							
								                            )
							 | 
						||
| 
								 | 
							
								                        if not isinstance(id_val, (str, int, float, bool)):
							 | 
						||
| 
								 | 
							
								                            raise TypeError(
							 | 
						||
| 
								 | 
							
								                                "dict id values must be strings, numbers or bools,\n"
							 | 
						||
| 
								 | 
							
								                                + f"found {id_val!r} in id {v!r}"
							 | 
						||
| 
								 | 
							
								                            )
							 | 
						||
| 
								 | 
							
								                elif not isinstance(v, str):
							 | 
						||
| 
								 | 
							
								                    raise TypeError(f"`id` prop must be a string or dict, not {v!r}")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            setattr(self, k, v)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _set_random_id(self):
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if hasattr(self, "id"):
							 | 
						||
| 
								 | 
							
								            return getattr(self, "id")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        kind = f"`{self._namespace}.{self._type}`"  # pylint: disable=no-member
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if getattr(self, "persistence", False):
							 | 
						||
| 
								 | 
							
								            raise RuntimeError(
							 | 
						||
| 
								 | 
							
								                f"""
							 | 
						||
| 
								 | 
							
								                Attempting to use an auto-generated ID with the `persistence` prop.
							 | 
						||
| 
								 | 
							
								                This is prohibited because persistence is tied to component IDs and
							 | 
						||
| 
								 | 
							
								                auto-generated IDs can easily change.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                Please assign an explicit ID to this {kind} component.
							 | 
						||
| 
								 | 
							
								                """
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        if "dash_snapshots" in sys.modules:
							 | 
						||
| 
								 | 
							
								            raise RuntimeError(
							 | 
						||
| 
								 | 
							
								                f"""
							 | 
						||
| 
								 | 
							
								                Attempting to use an auto-generated ID in an app with `dash_snapshots`.
							 | 
						||
| 
								 | 
							
								                This is prohibited because snapshots saves the whole app layout,
							 | 
						||
| 
								 | 
							
								                including component IDs, and auto-generated IDs can easily change.
							 | 
						||
| 
								 | 
							
								                Callbacks referencing the new IDs will not work with old snapshots.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                Please assign an explicit ID to this {kind} component.
							 | 
						||
| 
								 | 
							
								                """
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        v = str(uuid.UUID(int=rd.randint(0, 2**128)))
							 | 
						||
| 
								 | 
							
								        setattr(self, "id", v)
							 | 
						||
| 
								 | 
							
								        return v
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def to_plotly_json(self):
							 | 
						||
| 
								 | 
							
								        # Add normal properties
							 | 
						||
| 
								 | 
							
								        props = {
							 | 
						||
| 
								 | 
							
								            p: getattr(self, p)
							 | 
						||
| 
								 | 
							
								            for p in self._prop_names  # pylint: disable=no-member
							 | 
						||
| 
								 | 
							
								            if hasattr(self, p)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        # Add the wildcard properties data-* and aria-*
							 | 
						||
| 
								 | 
							
								        props.update(
							 | 
						||
| 
								 | 
							
								            {
							 | 
						||
| 
								 | 
							
								                k: getattr(self, k)
							 | 
						||
| 
								 | 
							
								                for k in self.__dict__
							 | 
						||
| 
								 | 
							
								                if any(
							 | 
						||
| 
								 | 
							
								                    k.startswith(w)
							 | 
						||
| 
								 | 
							
								                    # pylint:disable=no-member
							 | 
						||
| 
								 | 
							
								                    for w in self._valid_wildcard_attributes
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        as_json = {
							 | 
						||
| 
								 | 
							
								            "props": props,
							 | 
						||
| 
								 | 
							
								            "type": self._type,  # pylint: disable=no-member
							 | 
						||
| 
								 | 
							
								            "namespace": self._namespace,  # pylint: disable=no-member
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return as_json
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # pylint: disable=too-many-branches, too-many-return-statements
							 | 
						||
| 
								 | 
							
								    # pylint: disable=redefined-builtin, inconsistent-return-statements
							 | 
						||
| 
								 | 
							
								    def _get_set_or_delete(self, id, operation, new_item=None):
							 | 
						||
| 
								 | 
							
								        _check_if_has_indexable_children(self)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # pylint: disable=access-member-before-definition,
							 | 
						||
| 
								 | 
							
								        # pylint: disable=attribute-defined-outside-init
							 | 
						||
| 
								 | 
							
								        if isinstance(self.children, Component):
							 | 
						||
| 
								 | 
							
								            if getattr(self.children, "id", None) is not None:
							 | 
						||
| 
								 | 
							
								                # Woohoo! It's the item that we're looking for
							 | 
						||
| 
								 | 
							
								                if self.children.id == id:  # type: ignore[reportAttributeAccessIssue]
							 | 
						||
| 
								 | 
							
								                    if operation == "get":
							 | 
						||
| 
								 | 
							
								                        return self.children
							 | 
						||
| 
								 | 
							
								                    if operation == "set":
							 | 
						||
| 
								 | 
							
								                        self.children = new_item
							 | 
						||
| 
								 | 
							
								                        return
							 | 
						||
| 
								 | 
							
								                    if operation == "delete":
							 | 
						||
| 
								 | 
							
								                        self.children = None
							 | 
						||
| 
								 | 
							
								                        return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Recursively dig into its subtree
							 | 
						||
| 
								 | 
							
								            try:
							 | 
						||
| 
								 | 
							
								                if operation == "get":
							 | 
						||
| 
								 | 
							
								                    return self.children.__getitem__(id)
							 | 
						||
| 
								 | 
							
								                if operation == "set":
							 | 
						||
| 
								 | 
							
								                    self.children.__setitem__(id, new_item)
							 | 
						||
| 
								 | 
							
								                    return
							 | 
						||
| 
								 | 
							
								                if operation == "delete":
							 | 
						||
| 
								 | 
							
								                    self.children.__delitem__(id)
							 | 
						||
| 
								 | 
							
								                    return
							 | 
						||
| 
								 | 
							
								            except KeyError:
							 | 
						||
| 
								 | 
							
								                pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # if children is like a list
							 | 
						||
| 
								 | 
							
								        if isinstance(self.children, (tuple, MutableSequence)):
							 | 
						||
| 
								 | 
							
								            for i, item in enumerate(self.children):  # type: ignore[reportOptionalIterable]
							 | 
						||
| 
								 | 
							
								                # If the item itself is the one we're looking for
							 | 
						||
| 
								 | 
							
								                if getattr(item, "id", None) == id:
							 | 
						||
| 
								 | 
							
								                    if operation == "get":
							 | 
						||
| 
								 | 
							
								                        return item
							 | 
						||
| 
								 | 
							
								                    if operation == "set":
							 | 
						||
| 
								 | 
							
								                        self.children[i] = new_item  # type: ignore[reportOptionalSubscript]
							 | 
						||
| 
								 | 
							
								                        return
							 | 
						||
| 
								 | 
							
								                    if operation == "delete":
							 | 
						||
| 
								 | 
							
								                        del self.children[i]  # type: ignore[reportOptionalSubscript]
							 | 
						||
| 
								 | 
							
								                        return
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # Otherwise, recursively dig into that item's subtree
							 | 
						||
| 
								 | 
							
								                # Make sure it's not like a string
							 | 
						||
| 
								 | 
							
								                elif isinstance(item, Component):
							 | 
						||
| 
								 | 
							
								                    try:
							 | 
						||
| 
								 | 
							
								                        if operation == "get":
							 | 
						||
| 
								 | 
							
								                            return item.__getitem__(id)
							 | 
						||
| 
								 | 
							
								                        if operation == "set":
							 | 
						||
| 
								 | 
							
								                            item.__setitem__(id, new_item)
							 | 
						||
| 
								 | 
							
								                            return
							 | 
						||
| 
								 | 
							
								                        if operation == "delete":
							 | 
						||
| 
								 | 
							
								                            item.__delitem__(id)
							 | 
						||
| 
								 | 
							
								                            return
							 | 
						||
| 
								 | 
							
								                    except KeyError:
							 | 
						||
| 
								 | 
							
								                        pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # The end of our branch
							 | 
						||
| 
								 | 
							
								        # If we were in a list, then this exception will get caught
							 | 
						||
| 
								 | 
							
								        raise KeyError(id)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Magic methods for a mapping interface:
							 | 
						||
| 
								 | 
							
								    # - __getitem__
							 | 
						||
| 
								 | 
							
								    # - __setitem__
							 | 
						||
| 
								 | 
							
								    # - __delitem__
							 | 
						||
| 
								 | 
							
								    # - __iter__
							 | 
						||
| 
								 | 
							
								    # - __len__
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __getitem__(self, id):  # pylint: disable=redefined-builtin
							 | 
						||
| 
								 | 
							
								        """Recursively find the element with the given ID through the tree of
							 | 
						||
| 
								 | 
							
								        children."""
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # A component's children can be undefined, a string, another component,
							 | 
						||
| 
								 | 
							
								        # or a list of components.
							 | 
						||
| 
								 | 
							
								        return self._get_set_or_delete(id, "get")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __setitem__(self, id, item):  # pylint: disable=redefined-builtin
							 | 
						||
| 
								 | 
							
								        """Set an element by its ID."""
							 | 
						||
| 
								 | 
							
								        return self._get_set_or_delete(id, "set", item)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __delitem__(self, id):  # pylint: disable=redefined-builtin
							 | 
						||
| 
								 | 
							
								        """Delete items by ID in the tree of children."""
							 | 
						||
| 
								 | 
							
								        return self._get_set_or_delete(id, "delete")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _traverse(self):
							 | 
						||
| 
								 | 
							
								        """Yield each item in the tree."""
							 | 
						||
| 
								 | 
							
								        for t in self._traverse_with_paths():
							 | 
						||
| 
								 | 
							
								            yield t[1]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @staticmethod
							 | 
						||
| 
								 | 
							
								    def _id_str(component):
							 | 
						||
| 
								 | 
							
								        id_ = stringify_id(getattr(component, "id", ""))
							 | 
						||
| 
								 | 
							
								        return id_ and f" (id={id_:s})"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _traverse_with_paths(self):
							 | 
						||
| 
								 | 
							
								        """Yield each item with its path in the tree."""
							 | 
						||
| 
								 | 
							
								        children = getattr(self, "children", None)
							 | 
						||
| 
								 | 
							
								        children_type = type(children).__name__
							 | 
						||
| 
								 | 
							
								        children_string = children_type + self._id_str(children)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # children is just a component
							 | 
						||
| 
								 | 
							
								        if isinstance(children, Component):
							 | 
						||
| 
								 | 
							
								            yield "[*] " + children_string, children
							 | 
						||
| 
								 | 
							
								            # pylint: disable=protected-access
							 | 
						||
| 
								 | 
							
								            for p, t in children._traverse_with_paths():
							 | 
						||
| 
								 | 
							
								                yield "\n".join(["[*] " + children_string, p]), t
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # children is a list of components
							 | 
						||
| 
								 | 
							
								        elif isinstance(children, (tuple, MutableSequence)):
							 | 
						||
| 
								 | 
							
								            for idx, i in enumerate(children):  # type: ignore[reportOptionalIterable]
							 | 
						||
| 
								 | 
							
								                list_path = f"[{idx:d}] {type(i).__name__:s}{self._id_str(i)}"
							 | 
						||
| 
								 | 
							
								                yield list_path, i
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                if isinstance(i, Component):
							 | 
						||
| 
								 | 
							
								                    # pylint: disable=protected-access
							 | 
						||
| 
								 | 
							
								                    for p, t in i._traverse_with_paths():
							 | 
						||
| 
								 | 
							
								                        yield "\n".join([list_path, p]), t
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _traverse_ids(self):
							 | 
						||
| 
								 | 
							
								        """Yield components with IDs in the tree of children."""
							 | 
						||
| 
								 | 
							
								        for t in self._traverse():
							 | 
						||
| 
								 | 
							
								            if isinstance(t, Component) and getattr(t, "id", None) is not None:
							 | 
						||
| 
								 | 
							
								                yield t
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __iter__(self):
							 | 
						||
| 
								 | 
							
								        """Yield IDs in the tree of children."""
							 | 
						||
| 
								 | 
							
								        for t in self._traverse_ids():
							 | 
						||
| 
								 | 
							
								            yield t.id  # type: ignore[reportAttributeAccessIssue]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __len__(self):
							 | 
						||
| 
								 | 
							
								        """Return the number of items in the tree."""
							 | 
						||
| 
								 | 
							
								        # TODO - Should we return the number of items that have IDs
							 | 
						||
| 
								 | 
							
								        # or just the number of items?
							 | 
						||
| 
								 | 
							
								        # The number of items is more intuitive but returning the number
							 | 
						||
| 
								 | 
							
								        # of IDs matches __iter__ better.
							 | 
						||
| 
								 | 
							
								        length = 0
							 | 
						||
| 
								 | 
							
								        if getattr(self, "children", None) is None:
							 | 
						||
| 
								 | 
							
								            length = 0
							 | 
						||
| 
								 | 
							
								        elif isinstance(self.children, Component):
							 | 
						||
| 
								 | 
							
								            length = 1
							 | 
						||
| 
								 | 
							
								            length += len(self.children)
							 | 
						||
| 
								 | 
							
								        elif isinstance(self.children, (tuple, MutableSequence)):
							 | 
						||
| 
								 | 
							
								            for c in self.children:  # type: ignore[reportOptionalIterable]
							 | 
						||
| 
								 | 
							
								                length += 1
							 | 
						||
| 
								 | 
							
								                if isinstance(c, Component):
							 | 
						||
| 
								 | 
							
								                    length += len(c)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            # string or number
							 | 
						||
| 
								 | 
							
								            length = 1
							 | 
						||
| 
								 | 
							
								        return length
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def __repr__(self):
							 | 
						||
| 
								 | 
							
								        # pylint: disable=no-member
							 | 
						||
| 
								 | 
							
								        props_with_values = [
							 | 
						||
| 
								 | 
							
								            c for c in self._prop_names if getattr(self, c, None) is not None
							 | 
						||
| 
								 | 
							
								        ] + [
							 | 
						||
| 
								 | 
							
								            c
							 | 
						||
| 
								 | 
							
								            for c in self.__dict__
							 | 
						||
| 
								 | 
							
								            if any(c.startswith(wc_attr) for wc_attr in self._valid_wildcard_attributes)
							 | 
						||
| 
								 | 
							
								        ]
							 | 
						||
| 
								 | 
							
								        if any(p != "children" for p in props_with_values):
							 | 
						||
| 
								 | 
							
								            props_string = ", ".join(
							 | 
						||
| 
								 | 
							
								                f"{p}={getattr(self, p)!r}" for p in props_with_values
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            props_string = repr(getattr(self, "children", None))
							 | 
						||
| 
								 | 
							
								        return f"{self._type}({props_string})"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def _validate_deprecation(self):
							 | 
						||
| 
								 | 
							
								        _type = getattr(self, "_type", "")
							 | 
						||
| 
								 | 
							
								        _ns = getattr(self, "_namespace", "")
							 | 
						||
| 
								 | 
							
								        deprecation_message = _deprecated_components.get(_ns, {}).get(_type)
							 | 
						||
| 
								 | 
							
								        if deprecation_message:
							 | 
						||
| 
								 | 
							
								            warnings.warn(DeprecationWarning(textwrap.dedent(deprecation_message)))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# Renderable node type.
							 | 
						||
| 
								 | 
							
								ComponentType = typing.Union[
							 | 
						||
| 
								 | 
							
								    str,
							 | 
						||
| 
								 | 
							
								    int,
							 | 
						||
| 
								 | 
							
								    float,
							 | 
						||
| 
								 | 
							
								    Component,
							 | 
						||
| 
								 | 
							
								    None,
							 | 
						||
| 
								 | 
							
								    typing.Sequence[typing.Union[str, int, float, Component, None]],
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								ComponentTemplate = typing.TypeVar("ComponentTemplate")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# This wrapper adds an argument given to generated Component.__init__
							 | 
						||
| 
								 | 
							
								# with the actual given parameters by the user as a list of string.
							 | 
						||
| 
								 | 
							
								# This is then checked in the generated init to check if required
							 | 
						||
| 
								 | 
							
								# props were provided.
							 | 
						||
| 
								 | 
							
								def _explicitize_args(func):
							 | 
						||
| 
								 | 
							
								    varnames = func.__code__.co_varnames
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def wrapper(*args, **kwargs):
							 | 
						||
| 
								 | 
							
								        if "_explicit_args" in kwargs:
							 | 
						||
| 
								 | 
							
								            raise Exception("Variable _explicit_args should not be set.")
							 | 
						||
| 
								 | 
							
								        kwargs["_explicit_args"] = list(
							 | 
						||
| 
								 | 
							
								            set(list(varnames[: len(args)]) + [k for k, _ in kwargs.items()])
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        if "self" in kwargs["_explicit_args"]:
							 | 
						||
| 
								 | 
							
								            kwargs["_explicit_args"].remove("self")
							 | 
						||
| 
								 | 
							
								        return func(*args, **kwargs)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    new_sig = inspect.signature(wrapper).replace(
							 | 
						||
| 
								 | 
							
								        parameters=list(inspect.signature(func).parameters.values())
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								    wrapper.__signature__ = new_sig  # type: ignore[reportFunctionMemberAccess]
							 | 
						||
| 
								 | 
							
								    return wrapper
							 |