Mise à jour de Monitor.py et autres scripts
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import threading
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
from streamlit import util
|
||||
from streamlit.components.types.base_component_registry import BaseComponentRegistry
|
||||
from streamlit.errors import StreamlitAPIException
|
||||
from streamlit.logger import get_logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from streamlit.components.types.base_custom_component import BaseCustomComponent
|
||||
|
||||
_LOGGER: Final = get_logger(__name__)
|
||||
|
||||
|
||||
class LocalComponentRegistry(BaseComponentRegistry):
|
||||
def __init__(self) -> None:
|
||||
self._components: dict[str, BaseCustomComponent] = {}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return util.repr_(self)
|
||||
|
||||
def register_component(self, component: BaseCustomComponent) -> None:
|
||||
"""Register a CustomComponent.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
component : BaseCustomComponent
|
||||
The component to register.
|
||||
"""
|
||||
|
||||
# Validate the component's path
|
||||
abspath = component.abspath
|
||||
if abspath is not None and not os.path.isdir(abspath):
|
||||
raise StreamlitAPIException(f"No such component directory: '{abspath}'")
|
||||
|
||||
with self._lock:
|
||||
existing = self._components.get(component.name)
|
||||
self._components[component.name] = component
|
||||
|
||||
if existing is not None and component != existing:
|
||||
_LOGGER.warning(
|
||||
"%s overriding previously-registered %s",
|
||||
component,
|
||||
existing,
|
||||
)
|
||||
|
||||
_LOGGER.debug("Registered component %s", component)
|
||||
|
||||
def get_component_path(self, name: str) -> str | None:
|
||||
"""Return the filesystem path for the component with the given name.
|
||||
|
||||
If no such component is registered, or if the component exists but is
|
||||
being served from a URL, return None instead.
|
||||
"""
|
||||
component = self._components.get(name, None)
|
||||
return component.abspath if component is not None else None
|
||||
|
||||
def get_module_name(self, name: str) -> str | None:
|
||||
component = self._components.get(name, None)
|
||||
return component.module_name if component is not None else None
|
||||
|
||||
def get_component(self, name: str) -> BaseCustomComponent | None:
|
||||
return self._components.get(name, None)
|
||||
|
||||
def get_components(self) -> list[BaseCustomComponent]:
|
||||
return list(self._components.values())
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
@@ -0,0 +1,99 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import TYPE_CHECKING, Protocol
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from streamlit.components.types.base_custom_component import BaseCustomComponent
|
||||
|
||||
|
||||
class BaseComponentRegistry(Protocol):
|
||||
"""Interface for ComponentRegistries."""
|
||||
|
||||
@abstractmethod
|
||||
def register_component(self, component: BaseCustomComponent) -> None:
|
||||
"""Register a CustomComponent.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
component : CustomComponent
|
||||
The component to register.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_component_path(self, name: str) -> str | None:
|
||||
"""Return the filesystem path for the component with the given name.
|
||||
|
||||
If no such component is registered, or if the component exists but is
|
||||
being served from a URL, return None instead.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: name of the component
|
||||
|
||||
Returns
|
||||
-------
|
||||
str or None
|
||||
The name of the specified component or None if no component with the given name has been registered.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_module_name(self, name: str) -> str | None:
|
||||
"""Return the module name for the component with the given name.
|
||||
|
||||
If no such component is registered, return None instead.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: name of the component
|
||||
|
||||
Returns
|
||||
-------
|
||||
str or None
|
||||
The module_name of the specified component or None if no component with the given name has been registered.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_component(self, name: str) -> BaseCustomComponent | None:
|
||||
"""Return the registered component with the given name.
|
||||
|
||||
If no such component is registered, return None instead.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: name of the component
|
||||
|
||||
Returns
|
||||
-------
|
||||
component or None
|
||||
The component with the provided name or None if component with the given name has been registered.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_components(self) -> list[BaseCustomComponent]:
|
||||
"""Returns a list of custom components that are registered in this registry.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[CustomComponents]
|
||||
A list of registered custom components.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,150 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from streamlit import util
|
||||
from streamlit.errors import StreamlitAPIException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from streamlit.runtime.state.common import WidgetCallback
|
||||
|
||||
|
||||
class MarshallComponentException(StreamlitAPIException):
|
||||
"""Class for exceptions generated during custom component marshalling."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BaseCustomComponent(ABC):
|
||||
"""Interface for CustomComponents."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
path: str | None = None,
|
||||
url: str | None = None,
|
||||
module_name: str | None = None,
|
||||
):
|
||||
if (path is None and url is None) or (path is not None and url is not None):
|
||||
raise StreamlitAPIException(
|
||||
"Either 'path' or 'url' must be set, but not both."
|
||||
)
|
||||
|
||||
self._name = name
|
||||
self._path = path
|
||||
self._url = url
|
||||
self._module_name = module_name
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return util.repr_(self)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
*args,
|
||||
default: Any = None,
|
||||
key: str | None = None,
|
||||
on_change: WidgetCallback | None = None,
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
"""An alias for create_instance."""
|
||||
return self.create_instance(
|
||||
*args,
|
||||
default=default,
|
||||
key=key,
|
||||
on_change=on_change,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@property
|
||||
def abspath(self) -> str | None:
|
||||
if self._path is None:
|
||||
return None
|
||||
return os.path.abspath(self._path)
|
||||
|
||||
@property
|
||||
def module_name(self) -> str | None:
|
||||
return self._module_name
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def path(self) -> str | None:
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def url(self) -> str | None:
|
||||
return self._url
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"'{self.name}': {self.path if self.path is not None else self.url}"
|
||||
|
||||
@abstractmethod
|
||||
def __eq__(self, other) -> bool:
|
||||
"""Equality operator."""
|
||||
return NotImplemented
|
||||
|
||||
@abstractmethod
|
||||
def __ne__(self, other) -> bool:
|
||||
"""Inequality operator."""
|
||||
return NotImplemented
|
||||
|
||||
@abstractmethod
|
||||
def create_instance(
|
||||
self,
|
||||
*args,
|
||||
default: Any = None,
|
||||
key: str | None = None,
|
||||
on_change: WidgetCallback | None = None,
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
"""Create a new instance of the component.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*args
|
||||
Must be empty; all args must be named. (This parameter exists to
|
||||
enforce correct use of the function.)
|
||||
default: any or None
|
||||
The default return value for the component. This is returned when
|
||||
the component's frontend hasn't yet specified a value with
|
||||
`setComponentValue`.
|
||||
key: str or None
|
||||
If not None, this is the user key we use to generate the
|
||||
component's "widget ID".
|
||||
on_change: WidgetCallback or None
|
||||
An optional callback invoked when the widget's value changes. No arguments are passed to it.
|
||||
**kwargs
|
||||
Keyword args to pass to the component.
|
||||
|
||||
Raises
|
||||
------
|
||||
MarshallComponentException
|
||||
Raised when args is not empty or component cannot be marshalled.
|
||||
StreamlitAPIException
|
||||
Raised when PyArrow is not installed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
any or None
|
||||
The component's widget value.
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Contains the files and modules for the exposed API."""
|
||||
|
||||
import streamlit
|
||||
from streamlit.components.v1.component_registry import declare_component
|
||||
|
||||
# `html` and `iframe` are part of Custom Components, so they appear in this
|
||||
# `streamlit.components.v1` namespace.
|
||||
html = streamlit._main._html
|
||||
iframe = streamlit._main._iframe
|
||||
|
||||
__all__ = [
|
||||
"declare_component",
|
||||
"html",
|
||||
"iframe",
|
||||
]
|
||||
@@ -0,0 +1,141 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Data marshalling utilities for ArrowTable protobufs, which are used by
|
||||
CustomComponent for dataframe serialization.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from streamlit import dataframe_util
|
||||
from streamlit.elements.lib import pandas_styler_utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pandas import DataFrame, Index, Series
|
||||
|
||||
from streamlit.proto.Components_pb2 import ArrowTable as ArrowTableProto
|
||||
|
||||
|
||||
def _maybe_tuple_to_list(item: Any) -> Any:
|
||||
"""Convert a tuple to a list. Leave as is if it's not a tuple."""
|
||||
return list(item) if isinstance(item, tuple) else item
|
||||
|
||||
|
||||
def marshall(
|
||||
proto: ArrowTableProto, data: Any, default_uuid: str | None = None
|
||||
) -> None:
|
||||
"""Marshall data into an ArrowTable proto.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
proto : proto.ArrowTable
|
||||
Output. The protobuf for a Streamlit ArrowTable proto.
|
||||
|
||||
data : pandas.DataFrame, pandas.Styler, numpy.ndarray, Iterable, dict, or None
|
||||
Something that is or can be converted to a dataframe.
|
||||
|
||||
"""
|
||||
if dataframe_util.is_pandas_styler(data):
|
||||
pandas_styler_utils.marshall_styler(proto, data, default_uuid) # type: ignore
|
||||
|
||||
df = dataframe_util.convert_anything_to_pandas_df(data)
|
||||
_marshall_index(proto, df.index)
|
||||
_marshall_columns(proto, df.columns)
|
||||
_marshall_data(proto, df)
|
||||
|
||||
|
||||
def _marshall_index(proto: ArrowTableProto, index: Index) -> None:
|
||||
"""Marshall pandas.DataFrame index into an ArrowTable proto.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
proto : proto.ArrowTable
|
||||
Output. The protobuf for a Streamlit ArrowTable proto.
|
||||
|
||||
index : pd.Index
|
||||
Index to use for resulting frame.
|
||||
Will default to RangeIndex (0, 1, 2, ..., n) if no index is provided.
|
||||
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
index = map(_maybe_tuple_to_list, index.values)
|
||||
index_df = pd.DataFrame(index)
|
||||
proto.index = dataframe_util.convert_pandas_df_to_arrow_bytes(index_df)
|
||||
|
||||
|
||||
def _marshall_columns(proto: ArrowTableProto, columns: Series) -> None:
|
||||
"""Marshall pandas.DataFrame columns into an ArrowTable proto.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
proto : proto.ArrowTable
|
||||
Output. The protobuf for a Streamlit ArrowTable proto.
|
||||
|
||||
columns : Series
|
||||
Column labels to use for resulting frame.
|
||||
Will default to RangeIndex (0, 1, 2, ..., n) if no column labels are provided.
|
||||
|
||||
"""
|
||||
import pandas as pd
|
||||
|
||||
columns = map(_maybe_tuple_to_list, columns.values)
|
||||
columns_df = pd.DataFrame(columns)
|
||||
proto.columns = dataframe_util.convert_pandas_df_to_arrow_bytes(columns_df)
|
||||
|
||||
|
||||
def _marshall_data(proto: ArrowTableProto, df: DataFrame) -> None:
|
||||
"""Marshall pandas.DataFrame data into an ArrowTable proto.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
proto : proto.ArrowTable
|
||||
Output. The protobuf for a Streamlit ArrowTable proto.
|
||||
|
||||
df : pandas.DataFrame
|
||||
A dataframe to marshall.
|
||||
|
||||
"""
|
||||
proto.data = dataframe_util.convert_pandas_df_to_arrow_bytes(df)
|
||||
|
||||
|
||||
def arrow_proto_to_dataframe(proto: ArrowTableProto) -> DataFrame:
|
||||
"""Convert ArrowTable proto to pandas.DataFrame.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
proto : proto.ArrowTable
|
||||
Output. pandas.DataFrame
|
||||
|
||||
"""
|
||||
|
||||
if dataframe_util.is_pyarrow_version_less_than("14.0.1"):
|
||||
raise RuntimeError(
|
||||
"The installed pyarrow version is not compatible with this component. "
|
||||
"Please upgrade to 14.0.1 or higher: pip install -U pyarrow"
|
||||
)
|
||||
|
||||
import pandas as pd
|
||||
|
||||
data = dataframe_util.convert_arrow_bytes_to_pandas_df(proto.data)
|
||||
index = dataframe_util.convert_arrow_bytes_to_pandas_df(proto.index)
|
||||
columns = dataframe_util.convert_arrow_bytes_to_pandas_df(proto.columns)
|
||||
|
||||
return pd.DataFrame(
|
||||
data.to_numpy(),
|
||||
index=index.to_numpy().T.tolist(),
|
||||
columns=columns.to_numpy().T.tolist(),
|
||||
)
|
||||
@@ -0,0 +1,130 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from streamlit.components.v1.custom_component import CustomComponent
|
||||
from streamlit.runtime import get_instance
|
||||
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from types import FrameType
|
||||
|
||||
from streamlit.components.types.base_component_registry import BaseComponentRegistry
|
||||
|
||||
|
||||
def _get_module_name(caller_frame: FrameType) -> str:
|
||||
# Get the caller's module name. `__name__` gives us the module's
|
||||
# fully-qualified name, which includes its package.
|
||||
module = inspect.getmodule(caller_frame)
|
||||
assert module is not None
|
||||
module_name = module.__name__
|
||||
|
||||
# If the caller was the main module that was executed (that is, if the
|
||||
# user executed `python my_component.py`), then this name will be
|
||||
# "__main__" instead of the actual package name. In this case, we use
|
||||
# the main module's filename, sans `.py` extension, as the component name.
|
||||
if module_name == "__main__":
|
||||
file_path = inspect.getfile(caller_frame)
|
||||
filename = os.path.basename(file_path)
|
||||
module_name, _ = os.path.splitext(filename)
|
||||
|
||||
return module_name
|
||||
|
||||
|
||||
def declare_component(
|
||||
name: str,
|
||||
path: str | Path | None = None,
|
||||
url: str | None = None,
|
||||
) -> CustomComponent:
|
||||
"""Create a custom component and register it if there is a ``ScriptRunContext``.
|
||||
|
||||
The component is not registered when there is no ``ScriptRunContext``.
|
||||
This can happen when a ``CustomComponent`` is executed as standalone
|
||||
command (e.g. for testing).
|
||||
|
||||
To use this function, import it from the ``streamlit.components.v1``
|
||||
module.
|
||||
|
||||
.. warning::
|
||||
Using ``st.components.v1.declare_component`` directly (instead of
|
||||
importing its module) is deprecated and will be disallowed in a later
|
||||
version.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
A short, descriptive name for the component, like "slider".
|
||||
|
||||
path: str, Path, or None
|
||||
The path to serve the component's frontend files from. The path should
|
||||
be absolute. If ``path`` is ``None`` (default), Streamlit will serve
|
||||
the component from the location in ``url``. Either ``path`` or ``url``
|
||||
must be specified, but not both.
|
||||
|
||||
url: str or None
|
||||
The URL that the component is served from. If ``url`` is ``None``
|
||||
(default), Streamlit will serve the component from the location in
|
||||
``path``. Either ``path`` or ``url`` must be specified, but not both.
|
||||
|
||||
Returns
|
||||
-------
|
||||
CustomComponent
|
||||
A ``CustomComponent`` that can be called like a function.
|
||||
Calling the component will create a new instance of the component
|
||||
in the Streamlit app.
|
||||
|
||||
"""
|
||||
if path is not None and isinstance(path, Path):
|
||||
path = str(path)
|
||||
|
||||
# Get our stack frame.
|
||||
current_frame: FrameType | None = inspect.currentframe()
|
||||
assert current_frame is not None
|
||||
# Get the stack frame of our calling function.
|
||||
caller_frame = current_frame.f_back
|
||||
assert caller_frame is not None
|
||||
module_name = _get_module_name(caller_frame)
|
||||
|
||||
# Build the component name.
|
||||
component_name = f"{module_name}.{name}"
|
||||
|
||||
# Create our component object, and register it.
|
||||
component = CustomComponent(
|
||||
name=component_name, path=path, url=url, module_name=module_name
|
||||
)
|
||||
# the ctx can be None if a custom component script is run outside of Streamlit, e.g. via 'python ...'
|
||||
ctx = get_script_run_ctx()
|
||||
if ctx is not None:
|
||||
get_instance().component_registry.register_component(component)
|
||||
return component
|
||||
|
||||
|
||||
# Keep for backwards-compatibility for now as we don't know whether existing custom
|
||||
# components use this method. We made significant refactors to the custom component
|
||||
# registry code in https://github.com/streamlit/streamlit/pull/8193 and after
|
||||
# that is out in the wild, we can follow-up with more refactorings, e.g. remove
|
||||
# the following class and method. When we do that, we should conduct some testing with
|
||||
# popular custom components.
|
||||
class ComponentRegistry:
|
||||
@classmethod
|
||||
def instance(cls) -> BaseComponentRegistry:
|
||||
"""Returns the ComponentRegistry of the runtime instance."""
|
||||
|
||||
return get_instance().component_registry
|
||||
@@ -0,0 +1,38 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# The components.py file exists because existing custom components have started
|
||||
# to rely on internals of the components package. For example, streamlit-option-menu accesses
|
||||
# [register_widget](https://github.com/victoryhb/streamlit-option-menu/blob/master/streamlit_option_menu/streamlit_callback.py#L28),
|
||||
# which is only a transitive import through `streamlit.components.v1.custom_component`.
|
||||
# Since we do not know what other internals are used out in the wild, let's try to
|
||||
# model the old behavior and not to break things.
|
||||
|
||||
# This should be cleaned up in the future, e.g. as part of components v2.
|
||||
|
||||
from streamlit.components.v1.component_registry import (
|
||||
declare_component,
|
||||
)
|
||||
from streamlit.components.v1.custom_component import (
|
||||
CustomComponent,
|
||||
MarshallComponentException,
|
||||
)
|
||||
from streamlit.runtime.state import register_widget
|
||||
|
||||
__all__ = [
|
||||
"CustomComponent",
|
||||
"declare_component",
|
||||
"MarshallComponentException",
|
||||
"register_widget",
|
||||
]
|
||||
@@ -0,0 +1,243 @@
|
||||
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2025)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from streamlit.components.types.base_custom_component import BaseCustomComponent
|
||||
from streamlit.dataframe_util import is_dataframe_like
|
||||
from streamlit.delta_generator_singletons import get_dg_singleton_instance
|
||||
from streamlit.elements.lib.form_utils import current_form_id
|
||||
from streamlit.elements.lib.policies import check_cache_replay_rules
|
||||
from streamlit.elements.lib.utils import compute_and_register_element_id
|
||||
from streamlit.errors import StreamlitAPIException
|
||||
from streamlit.proto.Components_pb2 import ArrowTable as ArrowTableProto
|
||||
from streamlit.proto.Components_pb2 import SpecialArg
|
||||
from streamlit.proto.Element_pb2 import Element
|
||||
from streamlit.runtime.metrics_util import gather_metrics
|
||||
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
||||
from streamlit.runtime.state import register_widget
|
||||
from streamlit.type_util import is_bytes_like, to_bytes
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from streamlit.delta_generator import DeltaGenerator
|
||||
from streamlit.runtime.state.common import WidgetCallback
|
||||
|
||||
|
||||
class MarshallComponentException(StreamlitAPIException):
|
||||
"""Class for exceptions generated during custom component marshalling."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CustomComponent(BaseCustomComponent):
|
||||
"""A Custom Component declaration."""
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
*args,
|
||||
default: Any = None,
|
||||
key: str | None = None,
|
||||
on_change: WidgetCallback | None = None,
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
"""An alias for create_instance."""
|
||||
return self.create_instance(
|
||||
*args,
|
||||
default=default,
|
||||
key=key,
|
||||
on_change=on_change,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@gather_metrics("create_instance")
|
||||
def create_instance(
|
||||
self,
|
||||
*args,
|
||||
default: Any = None,
|
||||
key: str | None = None,
|
||||
on_change: WidgetCallback | None = None,
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
"""Create a new instance of the component.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
*args
|
||||
Must be empty; all args must be named. (This parameter exists to
|
||||
enforce correct use of the function.)
|
||||
default: any or None
|
||||
The default return value for the component. This is returned when
|
||||
the component's frontend hasn't yet specified a value with
|
||||
`setComponentValue`.
|
||||
key: str or None
|
||||
If not None, this is the user key we use to generate the
|
||||
component's "widget ID".
|
||||
on_change: WidgetCallback or None
|
||||
An optional callback invoked when the widget's value changes. No arguments are passed to it.
|
||||
**kwargs
|
||||
Keyword args to pass to the component.
|
||||
|
||||
Returns
|
||||
-------
|
||||
any or None
|
||||
The component's widget value.
|
||||
|
||||
"""
|
||||
if len(args) > 0:
|
||||
raise MarshallComponentException(f"Argument '{args[0]}' needs a label")
|
||||
|
||||
try:
|
||||
import pyarrow # noqa: F401, ICN001
|
||||
|
||||
from streamlit.components.v1 import component_arrow
|
||||
except ImportError:
|
||||
raise StreamlitAPIException(
|
||||
"""To use Custom Components in Streamlit, you need to install
|
||||
PyArrow. To do so locally:
|
||||
|
||||
`pip install pyarrow`
|
||||
|
||||
And if you're using Streamlit Cloud, add "pyarrow" to your requirements.txt."""
|
||||
)
|
||||
|
||||
check_cache_replay_rules()
|
||||
# In addition to the custom kwargs passed to the component, we also
|
||||
# send the special 'default' and 'key' params to the component
|
||||
# frontend.
|
||||
all_args = dict(kwargs, default=default, key=key)
|
||||
|
||||
json_args = {}
|
||||
special_args = []
|
||||
for arg_name, arg_val in all_args.items():
|
||||
if is_bytes_like(arg_val):
|
||||
bytes_arg = SpecialArg()
|
||||
bytes_arg.key = arg_name
|
||||
bytes_arg.bytes = to_bytes(arg_val)
|
||||
special_args.append(bytes_arg)
|
||||
elif is_dataframe_like(arg_val):
|
||||
dataframe_arg = SpecialArg()
|
||||
dataframe_arg.key = arg_name
|
||||
component_arrow.marshall(dataframe_arg.arrow_dataframe.data, arg_val)
|
||||
special_args.append(dataframe_arg)
|
||||
else:
|
||||
json_args[arg_name] = arg_val
|
||||
|
||||
try:
|
||||
serialized_json_args = json.dumps(json_args)
|
||||
except Exception as ex:
|
||||
raise MarshallComponentException(
|
||||
"Could not convert component args to JSON", ex
|
||||
)
|
||||
|
||||
def marshall_component(dg: DeltaGenerator, element: Element) -> Any:
|
||||
element.component_instance.component_name = self.name
|
||||
element.component_instance.form_id = current_form_id(dg)
|
||||
if self.url is not None:
|
||||
element.component_instance.url = self.url
|
||||
|
||||
# Normally, a widget's element_hash (which determines
|
||||
# its identity across multiple runs of an app) is computed
|
||||
# by hashing its arguments. This means that, if any of the arguments
|
||||
# to the widget are changed, Streamlit considers it a new widget
|
||||
# instance and it loses its previous state.
|
||||
#
|
||||
# However! If a *component* has a `key` argument, then the
|
||||
# component's hash identity is determined by entirely by
|
||||
# `component_name + url + key`. This means that, when `key`
|
||||
# exists, the component will maintain its identity even when its
|
||||
# other arguments change, and the component's iframe won't be
|
||||
# remounted on the frontend.
|
||||
|
||||
def marshall_element_args():
|
||||
element.component_instance.json_args = serialized_json_args
|
||||
element.component_instance.special_args.extend(special_args)
|
||||
|
||||
ctx = get_script_run_ctx()
|
||||
|
||||
if key is None:
|
||||
marshall_element_args()
|
||||
computed_id = compute_and_register_element_id(
|
||||
"component_instance",
|
||||
user_key=key,
|
||||
form_id=current_form_id(dg),
|
||||
name=self.name,
|
||||
url=self.url,
|
||||
json_args=serialized_json_args,
|
||||
special_args=special_args,
|
||||
)
|
||||
else:
|
||||
computed_id = compute_and_register_element_id(
|
||||
"component_instance",
|
||||
user_key=key,
|
||||
form_id=current_form_id(dg),
|
||||
name=self.name,
|
||||
url=self.url,
|
||||
)
|
||||
element.component_instance.id = computed_id
|
||||
|
||||
def deserialize_component(ui_value, widget_id=""):
|
||||
# ui_value is an object from json, an ArrowTable proto, or a bytearray
|
||||
return ui_value
|
||||
|
||||
component_state = register_widget(
|
||||
element.component_instance.id,
|
||||
deserializer=deserialize_component,
|
||||
serializer=lambda x: x,
|
||||
ctx=ctx,
|
||||
on_change_handler=on_change,
|
||||
value_type="json_value",
|
||||
)
|
||||
widget_value = component_state.value
|
||||
|
||||
if key is not None:
|
||||
marshall_element_args()
|
||||
|
||||
if widget_value is None:
|
||||
widget_value = default
|
||||
elif isinstance(widget_value, ArrowTableProto):
|
||||
widget_value = component_arrow.arrow_proto_to_dataframe(widget_value)
|
||||
return widget_value
|
||||
|
||||
# We currently only support writing to st._main, but this will change
|
||||
# when we settle on an improved API in a post-layout world.
|
||||
dg = get_dg_singleton_instance().main_dg
|
||||
|
||||
element = Element()
|
||||
return_value = marshall_component(dg, element)
|
||||
|
||||
dg._enqueue("component_instance", element.component_instance)
|
||||
return return_value
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
"""Equality operator."""
|
||||
return (
|
||||
isinstance(other, CustomComponent)
|
||||
and self.name == other.name
|
||||
and self.path == other.path
|
||||
and self.url == other.url
|
||||
and self.module_name == other.module_name
|
||||
)
|
||||
|
||||
def __ne__(self, other) -> bool:
|
||||
"""Inequality operator."""
|
||||
|
||||
# we have to use "not X == Y"" here because if we use "X != Y"
|
||||
# we call __ne__ again and end up in recursion
|
||||
return not self == other
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"'{self.name}': {self.path if self.path is not None else self.url}"
|
||||
Reference in New Issue
Block a user