streamlit-nightly 1.33.1.dev20240408__py2.py3-none-any.whl → 1.33.1.dev20240412__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit/__init__.py +4 -1
- streamlit/components/lib/__init__.py +13 -0
- streamlit/components/lib/local_component_registry.py +82 -0
- streamlit/components/types/__init__.py +13 -0
- streamlit/components/types/base_component_registry.py +98 -0
- streamlit/components/types/base_custom_component.py +137 -0
- streamlit/components/v1/__init__.py +7 -3
- streamlit/components/v1/component_registry.py +103 -0
- streamlit/components/v1/components.py +9 -375
- streamlit/components/v1/custom_component.py +241 -0
- streamlit/delta_generator.py +54 -36
- streamlit/elements/dialog_decorator.py +166 -0
- streamlit/elements/image.py +5 -3
- streamlit/elements/layouts.py +19 -0
- streamlit/elements/lib/dialog.py +148 -0
- streamlit/errors.py +6 -0
- streamlit/proto/Block_pb2.py +26 -22
- streamlit/proto/Block_pb2.pyi +43 -3
- streamlit/proto/Common_pb2.py +1 -1
- streamlit/runtime/runtime.py +12 -0
- streamlit/runtime/scriptrunner/script_run_context.py +3 -0
- streamlit/runtime/scriptrunner/script_runner.py +16 -0
- streamlit/runtime/state/query_params.py +28 -11
- streamlit/runtime/state/query_params_proxy.py +51 -3
- streamlit/runtime/state/session_state.py +3 -0
- streamlit/static/asset-manifest.json +4 -4
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/{1168.3029456a.chunk.js → 1168.1d6408e6.chunk.js} +1 -1
- streamlit/static/static/js/8427.d30dffe1.chunk.js +1 -0
- streamlit/static/static/js/main.46540eaf.js +2 -0
- streamlit/web/server/component_request_handler.py +2 -2
- streamlit/web/server/server.py +1 -2
- {streamlit_nightly-1.33.1.dev20240408.dist-info → streamlit_nightly-1.33.1.dev20240412.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.33.1.dev20240408.dist-info → streamlit_nightly-1.33.1.dev20240412.dist-info}/RECORD +39 -30
- streamlit/static/static/js/8427.b0ed496b.chunk.js +0 -1
- streamlit/static/static/js/main.285df334.js +0 -2
- /streamlit/static/static/js/{main.285df334.js.LICENSE.txt → main.46540eaf.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.33.1.dev20240408.data → streamlit_nightly-1.33.1.dev20240412.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.33.1.dev20240408.dist-info → streamlit_nightly-1.33.1.dev20240412.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.33.1.dev20240408.dist-info → streamlit_nightly-1.33.1.dev20240412.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.33.1.dev20240408.dist-info → streamlit_nightly-1.33.1.dev20240412.dist-info}/top_level.txt +0 -0
@@ -12,378 +12,12 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
import
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
import
|
24
|
-
from streamlit import type_util, util
|
25
|
-
from streamlit.elements.form import current_form_id
|
26
|
-
from streamlit.errors import StreamlitAPIException
|
27
|
-
from streamlit.logger import get_logger
|
28
|
-
from streamlit.proto.Components_pb2 import ArrowTable as ArrowTableProto
|
29
|
-
from streamlit.proto.Components_pb2 import SpecialArg
|
30
|
-
from streamlit.proto.Element_pb2 import Element
|
31
|
-
from streamlit.runtime.metrics_util import gather_metrics
|
32
|
-
from streamlit.runtime.scriptrunner import get_script_run_ctx
|
33
|
-
from streamlit.runtime.state import NoValue, register_widget
|
34
|
-
from streamlit.runtime.state.common import compute_widget_id
|
35
|
-
from streamlit.type_util import to_bytes
|
36
|
-
|
37
|
-
if TYPE_CHECKING:
|
38
|
-
from streamlit.delta_generator import DeltaGenerator
|
39
|
-
|
40
|
-
_LOGGER: Final = get_logger(__name__)
|
41
|
-
|
42
|
-
|
43
|
-
class MarshallComponentException(StreamlitAPIException):
|
44
|
-
"""Class for exceptions generated during custom component marshalling."""
|
45
|
-
|
46
|
-
pass
|
47
|
-
|
48
|
-
|
49
|
-
class CustomComponent:
|
50
|
-
"""A Custom Component declaration."""
|
51
|
-
|
52
|
-
def __init__(
|
53
|
-
self,
|
54
|
-
name: str,
|
55
|
-
path: str | None = None,
|
56
|
-
url: str | None = None,
|
57
|
-
):
|
58
|
-
if (path is None and url is None) or (path is not None and url is not None):
|
59
|
-
raise StreamlitAPIException(
|
60
|
-
"Either 'path' or 'url' must be set, but not both."
|
61
|
-
)
|
62
|
-
|
63
|
-
self.name = name
|
64
|
-
self.path = path
|
65
|
-
self.url = url
|
66
|
-
|
67
|
-
def __repr__(self) -> str:
|
68
|
-
return util.repr_(self)
|
69
|
-
|
70
|
-
@property
|
71
|
-
def abspath(self) -> str | None:
|
72
|
-
"""The absolute path that the component is served from."""
|
73
|
-
if self.path is None:
|
74
|
-
return None
|
75
|
-
return os.path.abspath(self.path)
|
76
|
-
|
77
|
-
def __call__(
|
78
|
-
self,
|
79
|
-
*args,
|
80
|
-
default: Any = None,
|
81
|
-
key: str | None = None,
|
82
|
-
**kwargs,
|
83
|
-
) -> Any:
|
84
|
-
"""An alias for create_instance."""
|
85
|
-
return self.create_instance(*args, default=default, key=key, **kwargs)
|
86
|
-
|
87
|
-
@gather_metrics("create_instance")
|
88
|
-
def create_instance(
|
89
|
-
self,
|
90
|
-
*args,
|
91
|
-
default: Any = None,
|
92
|
-
key: str | None = None,
|
93
|
-
**kwargs,
|
94
|
-
) -> Any:
|
95
|
-
"""Create a new instance of the component.
|
96
|
-
|
97
|
-
Parameters
|
98
|
-
----------
|
99
|
-
*args
|
100
|
-
Must be empty; all args must be named. (This parameter exists to
|
101
|
-
enforce correct use of the function.)
|
102
|
-
default: any or None
|
103
|
-
The default return value for the component. This is returned when
|
104
|
-
the component's frontend hasn't yet specified a value with
|
105
|
-
`setComponentValue`.
|
106
|
-
key: str or None
|
107
|
-
If not None, this is the user key we use to generate the
|
108
|
-
component's "widget ID".
|
109
|
-
**kwargs
|
110
|
-
Keyword args to pass to the component.
|
111
|
-
|
112
|
-
Returns
|
113
|
-
-------
|
114
|
-
any or None
|
115
|
-
The component's widget value.
|
116
|
-
|
117
|
-
"""
|
118
|
-
if len(args) > 0:
|
119
|
-
raise MarshallComponentException(f"Argument '{args[0]}' needs a label")
|
120
|
-
|
121
|
-
try:
|
122
|
-
import pyarrow
|
123
|
-
|
124
|
-
from streamlit.components.v1 import component_arrow
|
125
|
-
except ImportError:
|
126
|
-
raise StreamlitAPIException(
|
127
|
-
"""To use Custom Components in Streamlit, you need to install
|
128
|
-
PyArrow. To do so locally:
|
129
|
-
|
130
|
-
`pip install pyarrow`
|
131
|
-
|
132
|
-
And if you're using Streamlit Cloud, add "pyarrow" to your requirements.txt."""
|
133
|
-
)
|
134
|
-
|
135
|
-
# In addition to the custom kwargs passed to the component, we also
|
136
|
-
# send the special 'default' and 'key' params to the component
|
137
|
-
# frontend.
|
138
|
-
all_args = dict(kwargs, **{"default": default, "key": key})
|
139
|
-
|
140
|
-
json_args = {}
|
141
|
-
special_args = []
|
142
|
-
for arg_name, arg_val in all_args.items():
|
143
|
-
if type_util.is_bytes_like(arg_val):
|
144
|
-
bytes_arg = SpecialArg()
|
145
|
-
bytes_arg.key = arg_name
|
146
|
-
bytes_arg.bytes = to_bytes(arg_val)
|
147
|
-
special_args.append(bytes_arg)
|
148
|
-
elif type_util.is_dataframe_like(arg_val):
|
149
|
-
dataframe_arg = SpecialArg()
|
150
|
-
dataframe_arg.key = arg_name
|
151
|
-
component_arrow.marshall(dataframe_arg.arrow_dataframe.data, arg_val)
|
152
|
-
special_args.append(dataframe_arg)
|
153
|
-
else:
|
154
|
-
json_args[arg_name] = arg_val
|
155
|
-
|
156
|
-
try:
|
157
|
-
serialized_json_args = json.dumps(json_args)
|
158
|
-
except Exception as ex:
|
159
|
-
raise MarshallComponentException(
|
160
|
-
"Could not convert component args to JSON", ex
|
161
|
-
)
|
162
|
-
|
163
|
-
def marshall_component(
|
164
|
-
dg: DeltaGenerator, element: Element
|
165
|
-
) -> Any | type[NoValue]:
|
166
|
-
element.component_instance.component_name = self.name
|
167
|
-
element.component_instance.form_id = current_form_id(dg)
|
168
|
-
if self.url is not None:
|
169
|
-
element.component_instance.url = self.url
|
170
|
-
|
171
|
-
# Normally, a widget's element_hash (which determines
|
172
|
-
# its identity across multiple runs of an app) is computed
|
173
|
-
# by hashing its arguments. This means that, if any of the arguments
|
174
|
-
# to the widget are changed, Streamlit considers it a new widget
|
175
|
-
# instance and it loses its previous state.
|
176
|
-
#
|
177
|
-
# However! If a *component* has a `key` argument, then the
|
178
|
-
# component's hash identity is determined by entirely by
|
179
|
-
# `component_name + url + key`. This means that, when `key`
|
180
|
-
# exists, the component will maintain its identity even when its
|
181
|
-
# other arguments change, and the component's iframe won't be
|
182
|
-
# remounted on the frontend.
|
183
|
-
|
184
|
-
def marshall_element_args():
|
185
|
-
element.component_instance.json_args = serialized_json_args
|
186
|
-
element.component_instance.special_args.extend(special_args)
|
187
|
-
|
188
|
-
ctx = get_script_run_ctx()
|
189
|
-
|
190
|
-
if key is None:
|
191
|
-
marshall_element_args()
|
192
|
-
id = compute_widget_id(
|
193
|
-
"component_instance",
|
194
|
-
user_key=key,
|
195
|
-
name=self.name,
|
196
|
-
form_id=current_form_id(dg),
|
197
|
-
url=self.url,
|
198
|
-
key=key,
|
199
|
-
json_args=serialized_json_args,
|
200
|
-
special_args=special_args,
|
201
|
-
page=ctx.page_script_hash if ctx else None,
|
202
|
-
)
|
203
|
-
else:
|
204
|
-
id = compute_widget_id(
|
205
|
-
"component_instance",
|
206
|
-
user_key=key,
|
207
|
-
name=self.name,
|
208
|
-
form_id=current_form_id(dg),
|
209
|
-
url=self.url,
|
210
|
-
key=key,
|
211
|
-
page=ctx.page_script_hash if ctx else None,
|
212
|
-
)
|
213
|
-
element.component_instance.id = id
|
214
|
-
|
215
|
-
def deserialize_component(ui_value, widget_id=""):
|
216
|
-
# ui_value is an object from json, an ArrowTable proto, or a bytearray
|
217
|
-
return ui_value
|
218
|
-
|
219
|
-
component_state = register_widget(
|
220
|
-
element_type="component_instance",
|
221
|
-
element_proto=element.component_instance,
|
222
|
-
user_key=key,
|
223
|
-
widget_func_name=self.name,
|
224
|
-
deserializer=deserialize_component,
|
225
|
-
serializer=lambda x: x,
|
226
|
-
ctx=ctx,
|
227
|
-
)
|
228
|
-
widget_value = component_state.value
|
229
|
-
|
230
|
-
if key is not None:
|
231
|
-
marshall_element_args()
|
232
|
-
|
233
|
-
if widget_value is None:
|
234
|
-
widget_value = default
|
235
|
-
elif isinstance(widget_value, ArrowTableProto):
|
236
|
-
widget_value = component_arrow.arrow_proto_to_dataframe(widget_value)
|
237
|
-
|
238
|
-
# widget_value will be either None or whatever the component's most
|
239
|
-
# recent setWidgetValue value is. We coerce None -> NoValue,
|
240
|
-
# because that's what DeltaGenerator._enqueue expects.
|
241
|
-
return widget_value if widget_value is not None else NoValue
|
242
|
-
|
243
|
-
# We currently only support writing to st._main, but this will change
|
244
|
-
# when we settle on an improved API in a post-layout world.
|
245
|
-
dg = streamlit._main
|
246
|
-
|
247
|
-
element = Element()
|
248
|
-
return_value = marshall_component(dg, element)
|
249
|
-
result = dg._enqueue(
|
250
|
-
"component_instance", element.component_instance, return_value
|
251
|
-
)
|
252
|
-
|
253
|
-
return result
|
254
|
-
|
255
|
-
def __eq__(self, other) -> bool:
|
256
|
-
"""Equality operator."""
|
257
|
-
return (
|
258
|
-
isinstance(other, CustomComponent)
|
259
|
-
and self.name == other.name
|
260
|
-
and self.path == other.path
|
261
|
-
and self.url == other.url
|
262
|
-
)
|
263
|
-
|
264
|
-
def __ne__(self, other) -> bool:
|
265
|
-
"""Inequality operator."""
|
266
|
-
return not self == other
|
267
|
-
|
268
|
-
def __str__(self) -> str:
|
269
|
-
return f"'{self.name}': {self.path if self.path is not None else self.url}"
|
270
|
-
|
271
|
-
|
272
|
-
def declare_component(
|
273
|
-
name: str,
|
274
|
-
path: str | None = None,
|
275
|
-
url: str | None = None,
|
276
|
-
) -> CustomComponent:
|
277
|
-
"""Create and register a custom component.
|
278
|
-
|
279
|
-
Parameters
|
280
|
-
----------
|
281
|
-
name: str
|
282
|
-
A short, descriptive name for the component. Like, "slider".
|
283
|
-
path: str or None
|
284
|
-
The path to serve the component's frontend files from. Either
|
285
|
-
`path` or `url` must be specified, but not both.
|
286
|
-
url: str or None
|
287
|
-
The URL that the component is served from. Either `path` or `url`
|
288
|
-
must be specified, but not both.
|
289
|
-
|
290
|
-
Returns
|
291
|
-
-------
|
292
|
-
CustomComponent
|
293
|
-
A CustomComponent that can be called like a function.
|
294
|
-
Calling the component will create a new instance of the component
|
295
|
-
in the Streamlit app.
|
296
|
-
|
297
|
-
"""
|
298
|
-
|
299
|
-
# Get our stack frame.
|
300
|
-
current_frame = inspect.currentframe()
|
301
|
-
assert current_frame is not None
|
302
|
-
|
303
|
-
# Get the stack frame of our calling function.
|
304
|
-
caller_frame = current_frame.f_back
|
305
|
-
assert caller_frame is not None
|
306
|
-
|
307
|
-
# Get the caller's module name. `__name__` gives us the module's
|
308
|
-
# fully-qualified name, which includes its package.
|
309
|
-
module = inspect.getmodule(caller_frame)
|
310
|
-
assert module is not None
|
311
|
-
module_name = module.__name__
|
312
|
-
|
313
|
-
# If the caller was the main module that was executed (that is, if the
|
314
|
-
# user executed `python my_component.py`), then this name will be
|
315
|
-
# "__main__" instead of the actual package name. In this case, we use
|
316
|
-
# the main module's filename, sans `.py` extension, as the component name.
|
317
|
-
if module_name == "__main__":
|
318
|
-
file_path = inspect.getfile(caller_frame)
|
319
|
-
filename = os.path.basename(file_path)
|
320
|
-
module_name, _ = os.path.splitext(filename)
|
321
|
-
|
322
|
-
# Build the component name.
|
323
|
-
component_name = f"{module_name}.{name}"
|
324
|
-
|
325
|
-
# Create our component object, and register it.
|
326
|
-
component = CustomComponent(name=component_name, path=path, url=url)
|
327
|
-
ComponentRegistry.instance().register_component(component)
|
328
|
-
|
329
|
-
return component
|
330
|
-
|
331
|
-
|
332
|
-
class ComponentRegistry:
|
333
|
-
_instance_lock: threading.Lock = threading.Lock()
|
334
|
-
_instance: ComponentRegistry | None = None
|
335
|
-
|
336
|
-
@classmethod
|
337
|
-
def instance(cls) -> ComponentRegistry:
|
338
|
-
"""Returns the singleton ComponentRegistry"""
|
339
|
-
# We use a double-checked locking optimization to avoid the overhead
|
340
|
-
# of acquiring the lock in the common case:
|
341
|
-
# https://en.wikipedia.org/wiki/Double-checked_locking
|
342
|
-
if cls._instance is None:
|
343
|
-
with cls._instance_lock:
|
344
|
-
if cls._instance is None:
|
345
|
-
cls._instance = ComponentRegistry()
|
346
|
-
return cls._instance
|
347
|
-
|
348
|
-
def __init__(self):
|
349
|
-
self._components: dict[str, CustomComponent] = {}
|
350
|
-
self._lock = threading.Lock()
|
351
|
-
|
352
|
-
def __repr__(self) -> str:
|
353
|
-
return util.repr_(self)
|
354
|
-
|
355
|
-
def register_component(self, component: CustomComponent) -> None:
|
356
|
-
"""Register a CustomComponent.
|
357
|
-
|
358
|
-
Parameters
|
359
|
-
----------
|
360
|
-
component : CustomComponent
|
361
|
-
The component to register.
|
362
|
-
"""
|
363
|
-
|
364
|
-
# Validate the component's path
|
365
|
-
abspath = component.abspath
|
366
|
-
if abspath is not None and not os.path.isdir(abspath):
|
367
|
-
raise StreamlitAPIException(f"No such component directory: '{abspath}'")
|
368
|
-
|
369
|
-
with self._lock:
|
370
|
-
existing = self._components.get(component.name)
|
371
|
-
self._components[component.name] = component
|
372
|
-
|
373
|
-
if existing is not None and component != existing:
|
374
|
-
_LOGGER.warning(
|
375
|
-
"%s overriding previously-registered %s",
|
376
|
-
component,
|
377
|
-
existing,
|
378
|
-
)
|
379
|
-
|
380
|
-
_LOGGER.debug("Registered component %s", component)
|
381
|
-
|
382
|
-
def get_component_path(self, name: str) -> str | None:
|
383
|
-
"""Return the filesystem path for the component with the given name.
|
384
|
-
|
385
|
-
If no such component is registered, or if the component exists but is
|
386
|
-
being served from a URL, return None instead.
|
387
|
-
"""
|
388
|
-
component = self._components.get(name, None)
|
389
|
-
return component.abspath if component is not None else None
|
15
|
+
# The components.py file exists because existing custom components have started
|
16
|
+
# to rely on internals of the components package. For example, streamlit-option-menu accesses
|
17
|
+
# [register_widget](https://github.com/victoryhb/streamlit-option-menu/blob/master/streamlit_option_menu/streamlit_callback.py#L28),
|
18
|
+
# which is only a transitive import through `streamlit.components.v1.custom_component`.
|
19
|
+
# Since we do not know what other internals are used out in the wild, let's try to
|
20
|
+
# model the old behavior and not to break things.
|
21
|
+
|
22
|
+
from streamlit.components.v1.component_registry import declare_component
|
23
|
+
from streamlit.components.v1.custom_component import *
|
@@ -0,0 +1,241 @@
|
|
1
|
+
# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
from __future__ import annotations
|
16
|
+
|
17
|
+
import json
|
18
|
+
from typing import TYPE_CHECKING, Any
|
19
|
+
|
20
|
+
from streamlit import _main, type_util
|
21
|
+
from streamlit.components.types.base_custom_component import BaseCustomComponent
|
22
|
+
from streamlit.elements.form import current_form_id
|
23
|
+
from streamlit.errors import StreamlitAPIException
|
24
|
+
from streamlit.proto.Components_pb2 import ArrowTable as ArrowTableProto
|
25
|
+
from streamlit.proto.Components_pb2 import SpecialArg
|
26
|
+
from streamlit.proto.Element_pb2 import Element
|
27
|
+
from streamlit.runtime.metrics_util import gather_metrics
|
28
|
+
from streamlit.runtime.scriptrunner import get_script_run_ctx
|
29
|
+
from streamlit.runtime.state import NoValue, register_widget
|
30
|
+
from streamlit.runtime.state.common import compute_widget_id
|
31
|
+
from streamlit.type_util import to_bytes
|
32
|
+
|
33
|
+
if TYPE_CHECKING:
|
34
|
+
from streamlit.delta_generator import DeltaGenerator
|
35
|
+
|
36
|
+
|
37
|
+
class MarshallComponentException(StreamlitAPIException):
|
38
|
+
"""Class for exceptions generated during custom component marshalling."""
|
39
|
+
|
40
|
+
pass
|
41
|
+
|
42
|
+
|
43
|
+
class CustomComponent(BaseCustomComponent):
|
44
|
+
"""A Custom Component declaration."""
|
45
|
+
|
46
|
+
def __call__(
|
47
|
+
self,
|
48
|
+
*args,
|
49
|
+
default: Any = None,
|
50
|
+
key: str | None = None,
|
51
|
+
**kwargs,
|
52
|
+
) -> Any:
|
53
|
+
"""An alias for create_instance."""
|
54
|
+
return self.create_instance(*args, default=default, key=key, **kwargs)
|
55
|
+
|
56
|
+
@gather_metrics("create_instance")
|
57
|
+
def create_instance(
|
58
|
+
self,
|
59
|
+
*args,
|
60
|
+
default: Any = None,
|
61
|
+
key: str | None = None,
|
62
|
+
**kwargs,
|
63
|
+
) -> Any:
|
64
|
+
"""Create a new instance of the component.
|
65
|
+
|
66
|
+
Parameters
|
67
|
+
----------
|
68
|
+
*args
|
69
|
+
Must be empty; all args must be named. (This parameter exists to
|
70
|
+
enforce correct use of the function.)
|
71
|
+
default: any or None
|
72
|
+
The default return value for the component. This is returned when
|
73
|
+
the component's frontend hasn't yet specified a value with
|
74
|
+
`setComponentValue`.
|
75
|
+
key: str or None
|
76
|
+
If not None, this is the user key we use to generate the
|
77
|
+
component's "widget ID".
|
78
|
+
**kwargs
|
79
|
+
Keyword args to pass to the component.
|
80
|
+
|
81
|
+
Returns
|
82
|
+
-------
|
83
|
+
any or None
|
84
|
+
The component's widget value.
|
85
|
+
|
86
|
+
"""
|
87
|
+
if len(args) > 0:
|
88
|
+
raise MarshallComponentException(f"Argument '{args[0]}' needs a label")
|
89
|
+
|
90
|
+
try:
|
91
|
+
import pyarrow
|
92
|
+
|
93
|
+
from streamlit.components.v1 import component_arrow
|
94
|
+
except ImportError:
|
95
|
+
raise StreamlitAPIException(
|
96
|
+
"""To use Custom Components in Streamlit, you need to install
|
97
|
+
PyArrow. To do so locally:
|
98
|
+
|
99
|
+
`pip install pyarrow`
|
100
|
+
|
101
|
+
And if you're using Streamlit Cloud, add "pyarrow" to your requirements.txt."""
|
102
|
+
)
|
103
|
+
|
104
|
+
# In addition to the custom kwargs passed to the component, we also
|
105
|
+
# send the special 'default' and 'key' params to the component
|
106
|
+
# frontend.
|
107
|
+
all_args = dict(kwargs, **{"default": default, "key": key})
|
108
|
+
|
109
|
+
json_args = {}
|
110
|
+
special_args = []
|
111
|
+
for arg_name, arg_val in all_args.items():
|
112
|
+
if type_util.is_bytes_like(arg_val):
|
113
|
+
bytes_arg = SpecialArg()
|
114
|
+
bytes_arg.key = arg_name
|
115
|
+
bytes_arg.bytes = to_bytes(arg_val)
|
116
|
+
special_args.append(bytes_arg)
|
117
|
+
elif type_util.is_dataframe_like(arg_val):
|
118
|
+
dataframe_arg = SpecialArg()
|
119
|
+
dataframe_arg.key = arg_name
|
120
|
+
component_arrow.marshall(dataframe_arg.arrow_dataframe.data, arg_val)
|
121
|
+
special_args.append(dataframe_arg)
|
122
|
+
else:
|
123
|
+
json_args[arg_name] = arg_val
|
124
|
+
|
125
|
+
try:
|
126
|
+
serialized_json_args = json.dumps(json_args)
|
127
|
+
except Exception as ex:
|
128
|
+
raise MarshallComponentException(
|
129
|
+
"Could not convert component args to JSON", ex
|
130
|
+
)
|
131
|
+
|
132
|
+
def marshall_component(
|
133
|
+
dg: DeltaGenerator, element: Element
|
134
|
+
) -> Any | type[NoValue]:
|
135
|
+
element.component_instance.component_name = self.name
|
136
|
+
element.component_instance.form_id = current_form_id(dg)
|
137
|
+
if self.url is not None:
|
138
|
+
element.component_instance.url = self.url
|
139
|
+
|
140
|
+
# Normally, a widget's element_hash (which determines
|
141
|
+
# its identity across multiple runs of an app) is computed
|
142
|
+
# by hashing its arguments. This means that, if any of the arguments
|
143
|
+
# to the widget are changed, Streamlit considers it a new widget
|
144
|
+
# instance and it loses its previous state.
|
145
|
+
#
|
146
|
+
# However! If a *component* has a `key` argument, then the
|
147
|
+
# component's hash identity is determined by entirely by
|
148
|
+
# `component_name + url + key`. This means that, when `key`
|
149
|
+
# exists, the component will maintain its identity even when its
|
150
|
+
# other arguments change, and the component's iframe won't be
|
151
|
+
# remounted on the frontend.
|
152
|
+
|
153
|
+
def marshall_element_args():
|
154
|
+
element.component_instance.json_args = serialized_json_args
|
155
|
+
element.component_instance.special_args.extend(special_args)
|
156
|
+
|
157
|
+
ctx = get_script_run_ctx()
|
158
|
+
|
159
|
+
if key is None:
|
160
|
+
marshall_element_args()
|
161
|
+
computed_id = compute_widget_id(
|
162
|
+
"component_instance",
|
163
|
+
user_key=key,
|
164
|
+
name=self.name,
|
165
|
+
form_id=current_form_id(dg),
|
166
|
+
url=self.url,
|
167
|
+
key=key,
|
168
|
+
json_args=serialized_json_args,
|
169
|
+
special_args=special_args,
|
170
|
+
page=ctx.page_script_hash if ctx else None,
|
171
|
+
)
|
172
|
+
else:
|
173
|
+
computed_id = compute_widget_id(
|
174
|
+
"component_instance",
|
175
|
+
user_key=key,
|
176
|
+
name=self.name,
|
177
|
+
form_id=current_form_id(dg),
|
178
|
+
url=self.url,
|
179
|
+
key=key,
|
180
|
+
page=ctx.page_script_hash if ctx else None,
|
181
|
+
)
|
182
|
+
element.component_instance.id = computed_id
|
183
|
+
|
184
|
+
def deserialize_component(ui_value, widget_id=""):
|
185
|
+
# ui_value is an object from json, an ArrowTable proto, or a bytearray
|
186
|
+
return ui_value
|
187
|
+
|
188
|
+
component_state = register_widget(
|
189
|
+
element_type="component_instance",
|
190
|
+
element_proto=element.component_instance,
|
191
|
+
user_key=key,
|
192
|
+
widget_func_name=self.name,
|
193
|
+
deserializer=deserialize_component,
|
194
|
+
serializer=lambda x: x,
|
195
|
+
ctx=ctx,
|
196
|
+
)
|
197
|
+
widget_value = component_state.value
|
198
|
+
|
199
|
+
if key is not None:
|
200
|
+
marshall_element_args()
|
201
|
+
|
202
|
+
if widget_value is None:
|
203
|
+
widget_value = default
|
204
|
+
elif isinstance(widget_value, ArrowTableProto):
|
205
|
+
widget_value = component_arrow.arrow_proto_to_dataframe(widget_value)
|
206
|
+
|
207
|
+
# widget_value will be either None or whatever the component's most
|
208
|
+
# recent setWidgetValue value is. We coerce None -> NoValue,
|
209
|
+
# because that's what DeltaGenerator._enqueue expects.
|
210
|
+
return widget_value if widget_value is not None else NoValue
|
211
|
+
|
212
|
+
# We currently only support writing to st._main, but this will change
|
213
|
+
# when we settle on an improved API in a post-layout world.
|
214
|
+
dg = _main
|
215
|
+
|
216
|
+
element = Element()
|
217
|
+
return_value = marshall_component(dg, element)
|
218
|
+
result = dg._enqueue(
|
219
|
+
"component_instance", element.component_instance, return_value
|
220
|
+
)
|
221
|
+
|
222
|
+
return result
|
223
|
+
|
224
|
+
def __eq__(self, other) -> bool:
|
225
|
+
"""Equality operator."""
|
226
|
+
return (
|
227
|
+
isinstance(other, CustomComponent)
|
228
|
+
and self.name == other.name
|
229
|
+
and self.path == other.path
|
230
|
+
and self.url == other.url
|
231
|
+
and self.module_name == other.module_name
|
232
|
+
)
|
233
|
+
|
234
|
+
def __ne__(self, other) -> bool:
|
235
|
+
"""Inequality operator."""
|
236
|
+
|
237
|
+
# we have to use "not X == Y"" here because if we use "X != Y" we call __ne__ again and end up in recursion
|
238
|
+
return not self == other
|
239
|
+
|
240
|
+
def __str__(self) -> str:
|
241
|
+
return f"'{self.name}': {self.path if self.path is not None else self.url}"
|