streamlit-nightly 1.37.2.dev20240818__py2.py3-none-any.whl → 1.37.2.dev20240820__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/dataframe_util.py +70 -71
- streamlit/elements/image.py +15 -25
- streamlit/elements/lib/built_in_chart_utils.py +1 -1
- streamlit/elements/lib/options_selector_utils.py +3 -3
- streamlit/elements/lib/policies.py +10 -9
- streamlit/elements/widgets/radio.py +2 -2
- streamlit/elements/widgets/select_slider.py +2 -2
- streamlit/elements/widgets/selectbox.py +2 -2
- streamlit/runtime/caching/__init__.py +1 -11
- streamlit/runtime/caching/cache_data_api.py +11 -83
- streamlit/runtime/caching/cache_errors.py +13 -9
- streamlit/runtime/caching/cache_resource_api.py +9 -58
- streamlit/runtime/caching/cache_utils.py +7 -12
- streamlit/runtime/caching/cached_message_replay.py +29 -185
- streamlit/runtime/caching/legacy_cache_api.py +15 -11
- streamlit/runtime/scriptrunner_utils/script_run_context.py +9 -4
- streamlit/runtime/state/widgets.py +0 -5
- streamlit/static/asset-manifest.json +7 -7
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/1307.74bce9ab.chunk.js +1 -0
- streamlit/static/static/js/3599.eaeac234.chunk.js +5 -0
- streamlit/static/static/js/6013.fb4531df.chunk.js +5 -0
- streamlit/static/static/js/7175.70728640.chunk.js +5 -0
- streamlit/static/static/js/8691.93a29403.chunk.js +5 -0
- streamlit/static/static/js/main.ff81c7a3.js +28 -0
- {streamlit_nightly-1.37.2.dev20240818.dist-info → streamlit_nightly-1.37.2.dev20240820.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.37.2.dev20240818.dist-info → streamlit_nightly-1.37.2.dev20240820.dist-info}/RECORD +32 -32
- {streamlit_nightly-1.37.2.dev20240818.dist-info → streamlit_nightly-1.37.2.dev20240820.dist-info}/WHEEL +1 -1
- streamlit/static/static/js/1307.5225662c.chunk.js +0 -1
- streamlit/static/static/js/3599.17480cdf.chunk.js +0 -5
- streamlit/static/static/js/6013.fc3867be.chunk.js +0 -5
- streamlit/static/static/js/7175.ed4a2f0d.chunk.js +0 -5
- streamlit/static/static/js/8691.885f6268.chunk.js +0 -5
- streamlit/static/static/js/main.90c4efd0.js +0 -28
- /streamlit/static/static/js/{main.90c4efd0.js.LICENSE.txt → main.ff81c7a3.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.37.2.dev20240818.data → streamlit_nightly-1.37.2.dev20240820.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.37.2.dev20240818.dist-info → streamlit_nightly-1.37.2.dev20240820.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.37.2.dev20240818.dist-info → streamlit_nightly-1.37.2.dev20240820.dist-info}/top_level.txt +0 -0
@@ -29,7 +29,7 @@ CACHE_DOCS_URL = "https://docs.streamlit.io/develop/concepts/architecture/cachin
|
|
29
29
|
def get_cached_func_name_md(func: Any) -> str:
|
30
30
|
"""Get markdown representation of the function name."""
|
31
31
|
if hasattr(func, "__name__"):
|
32
|
-
return "
|
32
|
+
return f"`{func.__name__}()`"
|
33
33
|
elif hasattr(type(func), "__name__"):
|
34
34
|
return f"`{type(func).__name__}`"
|
35
35
|
return f"`{type(func)}`"
|
@@ -105,9 +105,10 @@ class CacheReplayClosureError(StreamlitAPIException):
|
|
105
105
|
|
106
106
|
msg = (
|
107
107
|
f"""
|
108
|
-
While running {func_name}, a streamlit element is called on some layout block
|
109
|
-
This is incompatible with replaying the cached
|
110
|
-
the referenced block might not exist when
|
108
|
+
While running {func_name}, a streamlit element is called on some layout block
|
109
|
+
created outside the function. This is incompatible with replaying the cached
|
110
|
+
effect of that element, because the the referenced block might not exist when
|
111
|
+
the replay happens.
|
111
112
|
|
112
113
|
How to fix this:
|
113
114
|
* Move the creation of $THING inside {func_name}.
|
@@ -124,11 +125,14 @@ class UnserializableReturnValueError(MarkdownFormattedException):
|
|
124
125
|
MarkdownFormattedException.__init__(
|
125
126
|
self,
|
126
127
|
f"""
|
127
|
-
Cannot serialize the return value (of type {get_return_value_type(return_value)})
|
128
|
-
`st.cache_data` uses
|
129
|
-
|
130
|
-
|
131
|
-
|
128
|
+
Cannot serialize the return value (of type {get_return_value_type(return_value)})
|
129
|
+
in {get_cached_func_name_md(func)}. `st.cache_data` uses
|
130
|
+
[pickle](https://docs.python.org/3/library/pickle.html) to serialize the
|
131
|
+
function's return value and safely store it in the cache
|
132
|
+
without mutating the original object. Please convert the return value to a
|
133
|
+
pickle-serializable type. If you want to cache unserializable objects such
|
134
|
+
as database connections or Tensorflow sessions, use `st.cache_resource`
|
135
|
+
instead (see [our docs]({CACHE_DOCS_URL}) for differences).""",
|
132
136
|
)
|
133
137
|
|
134
138
|
|
@@ -37,13 +37,10 @@ from streamlit.runtime.caching.cache_utils import (
|
|
37
37
|
from streamlit.runtime.caching.cached_message_replay import (
|
38
38
|
CachedMessageReplayContext,
|
39
39
|
CachedResult,
|
40
|
-
ElementMsgData,
|
41
40
|
MsgData,
|
42
|
-
MultiCacheResults,
|
43
41
|
show_widget_replay_deprecation,
|
44
42
|
)
|
45
43
|
from streamlit.runtime.metrics_util import gather_metrics
|
46
|
-
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
47
44
|
from streamlit.runtime.stats import CacheStat, CacheStatsProvider, group_stats
|
48
45
|
from streamlit.time_util import time_to_seconds
|
49
46
|
|
@@ -83,7 +80,6 @@ class ResourceCaches(CacheStatsProvider):
|
|
83
80
|
max_entries: int | float | None,
|
84
81
|
ttl: float | timedelta | str | None,
|
85
82
|
validate: ValidateFunc | None,
|
86
|
-
allow_widgets: bool,
|
87
83
|
) -> ResourceCache:
|
88
84
|
"""Return the mem cache for the given key.
|
89
85
|
|
@@ -114,7 +110,6 @@ class ResourceCaches(CacheStatsProvider):
|
|
114
110
|
max_entries=max_entries,
|
115
111
|
ttl_seconds=ttl_seconds,
|
116
112
|
validate=validate,
|
117
|
-
allow_widgets=allow_widgets,
|
118
113
|
)
|
119
114
|
self._function_caches[key] = cache
|
120
115
|
return cache
|
@@ -155,13 +150,11 @@ class CachedResourceFuncInfo(CachedFuncInfo):
|
|
155
150
|
max_entries: int | None,
|
156
151
|
ttl: float | timedelta | str | None,
|
157
152
|
validate: ValidateFunc | None,
|
158
|
-
allow_widgets: bool,
|
159
153
|
hash_funcs: HashFuncsDict | None = None,
|
160
154
|
):
|
161
155
|
super().__init__(
|
162
156
|
func,
|
163
157
|
show_spinner=show_spinner,
|
164
|
-
allow_widgets=allow_widgets,
|
165
158
|
hash_funcs=hash_funcs,
|
166
159
|
)
|
167
160
|
self.max_entries = max_entries
|
@@ -188,7 +181,6 @@ class CachedResourceFuncInfo(CachedFuncInfo):
|
|
188
181
|
max_entries=self.max_entries,
|
189
182
|
ttl=self.ttl,
|
190
183
|
validate=self.validate,
|
191
|
-
allow_widgets=self.allow_widgets,
|
192
184
|
)
|
193
185
|
|
194
186
|
|
@@ -315,9 +307,11 @@ class CacheResourceAPI:
|
|
315
307
|
|
316
308
|
experimental_allow_widgets : bool
|
317
309
|
Allow widgets to be used in the cached function. Defaults to False.
|
318
|
-
|
319
|
-
|
320
|
-
|
310
|
+
|
311
|
+
.. deprecated::
|
312
|
+
The cached widget replay functionality was removed in 1.38. Please
|
313
|
+
remove the ``experimental_allow_widgets`` parameter from your
|
314
|
+
caching decorators.
|
321
315
|
|
322
316
|
hash_funcs : dict or None
|
323
317
|
Mapping of types or fully qualified names to hash functions.
|
@@ -327,10 +321,6 @@ class CacheResourceAPI:
|
|
327
321
|
the provided function to generate a hash for it. See below for an example
|
328
322
|
of how this can be used.
|
329
323
|
|
330
|
-
.. deprecated::
|
331
|
-
``experimental_allow_widgets`` is deprecated and will be removed in
|
332
|
-
a later version.
|
333
|
-
|
334
324
|
Example
|
335
325
|
-------
|
336
326
|
>>> import streamlit as st
|
@@ -426,7 +416,6 @@ class CacheResourceAPI:
|
|
426
416
|
max_entries=max_entries,
|
427
417
|
ttl=ttl,
|
428
418
|
validate=validate,
|
429
|
-
allow_widgets=experimental_allow_widgets,
|
430
419
|
hash_funcs=hash_funcs,
|
431
420
|
)
|
432
421
|
)
|
@@ -438,7 +427,6 @@ class CacheResourceAPI:
|
|
438
427
|
max_entries=max_entries,
|
439
428
|
ttl=ttl,
|
440
429
|
validate=validate,
|
441
|
-
allow_widgets=experimental_allow_widgets,
|
442
430
|
hash_funcs=hash_funcs,
|
443
431
|
)
|
444
432
|
)
|
@@ -459,17 +447,15 @@ class ResourceCache(Cache):
|
|
459
447
|
ttl_seconds: float,
|
460
448
|
validate: ValidateFunc | None,
|
461
449
|
display_name: str,
|
462
|
-
allow_widgets: bool,
|
463
450
|
):
|
464
451
|
super().__init__()
|
465
452
|
self.key = key
|
466
453
|
self.display_name = display_name
|
467
|
-
self._mem_cache: TTLCache[str,
|
454
|
+
self._mem_cache: TTLCache[str, CachedResult] = TTLCache(
|
468
455
|
maxsize=max_entries, ttl=ttl_seconds, timer=cache_utils.TTLCACHE_TIMER
|
469
456
|
)
|
470
457
|
self._mem_cache_lock = threading.Lock()
|
471
458
|
self.validate = validate
|
472
|
-
self.allow_widgets = allow_widgets
|
473
459
|
|
474
460
|
@property
|
475
461
|
def max_entries(self) -> float:
|
@@ -488,24 +474,11 @@ class ResourceCache(Cache):
|
|
488
474
|
# key does not exist in cache.
|
489
475
|
raise CacheKeyNotFoundError()
|
490
476
|
|
491
|
-
|
492
|
-
|
493
|
-
ctx = get_script_run_ctx()
|
494
|
-
if not ctx:
|
495
|
-
# ScriptRunCtx does not exist (we're probably running in "raw" mode).
|
496
|
-
raise CacheKeyNotFoundError()
|
497
|
-
|
498
|
-
widget_key = multi_results.get_current_widget_key(ctx, CacheType.RESOURCE)
|
499
|
-
if widget_key not in multi_results.results:
|
500
|
-
# widget_key does not exist in cache (this combination of widgets hasn't been
|
501
|
-
# seen for the value_key yet).
|
502
|
-
raise CacheKeyNotFoundError()
|
503
|
-
|
504
|
-
result = multi_results.results[widget_key]
|
477
|
+
result = self._mem_cache[key]
|
505
478
|
|
506
479
|
if self.validate is not None and not self.validate(result.value):
|
507
480
|
# Validate failed: delete the entry and raise an error.
|
508
|
-
del
|
481
|
+
del self._mem_cache[key]
|
509
482
|
raise CacheKeyNotFoundError()
|
510
483
|
|
511
484
|
return result
|
@@ -513,33 +486,11 @@ class ResourceCache(Cache):
|
|
513
486
|
@gather_metrics("_cache_resource_object")
|
514
487
|
def write_result(self, key: str, value: Any, messages: list[MsgData]) -> None:
|
515
488
|
"""Write a value and associated messages to the cache."""
|
516
|
-
ctx = get_script_run_ctx()
|
517
|
-
if ctx is None:
|
518
|
-
return
|
519
|
-
|
520
489
|
main_id = st._main.id
|
521
490
|
sidebar_id = st.sidebar.id
|
522
|
-
if self.allow_widgets:
|
523
|
-
widgets = {
|
524
|
-
msg.widget_metadata.widget_id
|
525
|
-
for msg in messages
|
526
|
-
if isinstance(msg, ElementMsgData) and msg.widget_metadata is not None
|
527
|
-
}
|
528
|
-
else:
|
529
|
-
widgets = set()
|
530
491
|
|
531
492
|
with self._mem_cache_lock:
|
532
|
-
|
533
|
-
multi_results = self._mem_cache[key]
|
534
|
-
except KeyError:
|
535
|
-
multi_results = MultiCacheResults(widget_ids=widgets, results={})
|
536
|
-
|
537
|
-
multi_results.widget_ids.update(widgets)
|
538
|
-
widget_key = multi_results.get_current_widget_key(ctx, CacheType.RESOURCE)
|
539
|
-
|
540
|
-
result = CachedResult(value, messages, main_id, sidebar_id)
|
541
|
-
multi_results.results[widget_key] = result
|
542
|
-
self._mem_cache[key] = multi_results
|
493
|
+
self._mem_cache[key] = CachedResult(value, messages, main_id, sidebar_id)
|
543
494
|
|
544
495
|
def _clear(self, key: str | None = None) -> None:
|
545
496
|
with self._mem_cache_lock:
|
@@ -16,6 +16,7 @@
|
|
16
16
|
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
|
+
import contextlib
|
19
20
|
import functools
|
20
21
|
import hashlib
|
21
22
|
import inspect
|
@@ -125,12 +126,10 @@ class CachedFuncInfo:
|
|
125
126
|
self,
|
126
127
|
func: FunctionType,
|
127
128
|
show_spinner: bool | str,
|
128
|
-
allow_widgets: bool,
|
129
129
|
hash_funcs: HashFuncsDict | None,
|
130
130
|
):
|
131
131
|
self.func = func
|
132
132
|
self.show_spinner = show_spinner
|
133
|
-
self.allow_widgets = allow_widgets
|
134
133
|
self.hash_funcs = hash_funcs
|
135
134
|
|
136
135
|
@property
|
@@ -230,11 +229,9 @@ class CachedFunc:
|
|
230
229
|
hash_funcs=self._info.hash_funcs,
|
231
230
|
)
|
232
231
|
|
233
|
-
|
232
|
+
with contextlib.suppress(CacheKeyNotFoundError):
|
234
233
|
cached_result = cache.read_result(value_key)
|
235
234
|
return self._handle_cache_hit(cached_result)
|
236
|
-
except CacheKeyNotFoundError:
|
237
|
-
pass
|
238
235
|
return self._handle_cache_miss(cache, value_key, func_args, func_kwargs)
|
239
236
|
|
240
237
|
def _handle_cache_hit(self, result: CachedResult) -> Any:
|
@@ -279,17 +276,14 @@ class CachedFunc:
|
|
279
276
|
# We've acquired the lock - but another thread may have acquired it first
|
280
277
|
# and already computed the value. So we need to test for a cache hit again,
|
281
278
|
# before computing.
|
282
|
-
|
279
|
+
with contextlib.suppress(CacheKeyNotFoundError):
|
283
280
|
cached_result = cache.read_result(value_key)
|
284
281
|
# Another thread computed the value before us. Early exit!
|
285
282
|
return self._handle_cache_hit(cached_result)
|
286
283
|
|
287
|
-
except CacheKeyNotFoundError:
|
288
|
-
pass
|
289
|
-
|
290
284
|
# We acquired the lock before any other thread. Compute the value!
|
291
285
|
with self._info.cached_message_replay_ctx.calling_cached_function(
|
292
|
-
self._info.func
|
286
|
+
self._info.func
|
293
287
|
):
|
294
288
|
computed_value = self._info.func(*func_args, **func_kwargs)
|
295
289
|
|
@@ -329,8 +323,10 @@ class CachedFunc:
|
|
329
323
|
|
330
324
|
Parameters
|
331
325
|
----------
|
326
|
+
|
332
327
|
*args: Any
|
333
328
|
Arguments of the cached functions.
|
329
|
+
|
334
330
|
**kwargs: Any
|
335
331
|
Keyword arguments of the cached function.
|
336
332
|
|
@@ -471,8 +467,7 @@ def _make_function_key(cache_type: CacheType, func: FunctionType) -> str:
|
|
471
467
|
source_code, hasher=func_hasher, cache_type=cache_type, hash_source=func
|
472
468
|
)
|
473
469
|
|
474
|
-
|
475
|
-
return cache_key
|
470
|
+
return func_hasher.hexdigest()
|
476
471
|
|
477
472
|
|
478
473
|
def _get_positional_arg_name(func: FunctionType, arg_index: int) -> str | None:
|
@@ -15,7 +15,6 @@
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
17
|
import contextlib
|
18
|
-
import hashlib
|
19
18
|
import threading
|
20
19
|
from dataclasses import dataclass
|
21
20
|
from typing import TYPE_CHECKING, Any, Iterator, Literal, Union
|
@@ -24,12 +23,9 @@ import streamlit as st
|
|
24
23
|
from streamlit import runtime, util
|
25
24
|
from streamlit.deprecation_util import show_deprecation_warning
|
26
25
|
from streamlit.runtime.caching.cache_errors import CacheReplayClosureError
|
27
|
-
from streamlit.runtime.caching.hashing import update_hash
|
28
26
|
from streamlit.runtime.scriptrunner_utils.script_run_context import (
|
29
|
-
|
30
|
-
get_script_run_ctx,
|
27
|
+
in_cached_function,
|
31
28
|
)
|
32
|
-
from streamlit.util import HASHLIB_KWARGS
|
33
29
|
|
34
30
|
if TYPE_CHECKING:
|
35
31
|
from types import FunctionType
|
@@ -39,18 +35,6 @@ if TYPE_CHECKING:
|
|
39
35
|
from streamlit.delta_generator import DeltaGenerator
|
40
36
|
from streamlit.proto.Block_pb2 import Block
|
41
37
|
from streamlit.runtime.caching.cache_type import CacheType
|
42
|
-
from streamlit.runtime.state.common import WidgetMetadata
|
43
|
-
|
44
|
-
|
45
|
-
@dataclass(frozen=True)
|
46
|
-
class WidgetMsgMetadata:
|
47
|
-
"""Everything needed for replaying a widget and treating it as an implicit
|
48
|
-
argument to a cached function, beyond what is stored for all elements.
|
49
|
-
"""
|
50
|
-
|
51
|
-
widget_id: str
|
52
|
-
widget_value: Any
|
53
|
-
metadata: WidgetMetadata[Any]
|
54
38
|
|
55
39
|
|
56
40
|
@dataclass(frozen=True)
|
@@ -65,7 +49,6 @@ class ElementMsgData:
|
|
65
49
|
"""An element's message and related metadata for
|
66
50
|
replaying that element's function call.
|
67
51
|
|
68
|
-
widget_metadata is filled in if and only if this element is a widget.
|
69
52
|
media_data is filled in iff this is a media element (image, audio, video).
|
70
53
|
"""
|
71
54
|
|
@@ -73,7 +56,6 @@ class ElementMsgData:
|
|
73
56
|
message: Message
|
74
57
|
id_of_dg_called_on: str
|
75
58
|
returned_dgs_id: str
|
76
|
-
widget_metadata: WidgetMsgMetadata | None = None
|
77
59
|
media_data: list[MediaMsgData] | None = None
|
78
60
|
|
79
61
|
|
@@ -86,62 +68,6 @@ class BlockMsgData:
|
|
86
68
|
|
87
69
|
MsgData = Union[ElementMsgData, BlockMsgData]
|
88
70
|
|
89
|
-
"""
|
90
|
-
Note [Cache result structure]
|
91
|
-
|
92
|
-
The cache for a decorated function's results is split into two parts to enable
|
93
|
-
handling widgets invoked by the function.
|
94
|
-
|
95
|
-
Widgets act as implicit additional inputs to the cached function, so they should
|
96
|
-
be used when deriving the cache key. However, we don't know what widgets are
|
97
|
-
involved without first calling the function! So, we use the first execution
|
98
|
-
of the function with a particular cache key to record what widgets are used,
|
99
|
-
and use the current values of those widgets to derive a second cache key to
|
100
|
-
look up the function execution's results. The combination of first and second
|
101
|
-
cache keys act as one true cache key, just split up because the second part depends
|
102
|
-
on the first.
|
103
|
-
|
104
|
-
We need to treat widgets as implicit arguments of the cached function, because
|
105
|
-
the behavior of the function, including what elements are created and what it
|
106
|
-
returns, can be and usually will be influenced by the values of those widgets.
|
107
|
-
For example:
|
108
|
-
> @st.cache_data
|
109
|
-
> def example_fn(x):
|
110
|
-
> y = x + 1
|
111
|
-
> if st.checkbox("hi"):
|
112
|
-
> st.write("you checked the thing")
|
113
|
-
> y = 0
|
114
|
-
> return y
|
115
|
-
>
|
116
|
-
> example_fn(2)
|
117
|
-
|
118
|
-
If the checkbox is checked, the function call should return 0 and the checkbox and
|
119
|
-
message should be rendered. If the checkbox isn't checked, only the checkbox should
|
120
|
-
render, and the function will return 3.
|
121
|
-
|
122
|
-
|
123
|
-
There is a small catch in this. Since what widgets execute could depend on the values of
|
124
|
-
any prior widgets, if we replace the `st.write` call in the example with a slider,
|
125
|
-
the first time it runs, we would miss the slider because it wasn't called,
|
126
|
-
so when we later execute the function with the checkbox checked, the widget cache key
|
127
|
-
would not include the state of the slider, and would incorrectly get a cache hit
|
128
|
-
for a different slider value.
|
129
|
-
|
130
|
-
In principle the cache could be function+args key -> 1st widget key -> 2nd widget key
|
131
|
-
... -> final widget key, with each widget dependent on the exact values of the widgets
|
132
|
-
seen prior. This would prevent unnecessary cache misses due to differing values of widgets
|
133
|
-
that wouldn't affect the function's execution because they aren't even created.
|
134
|
-
But this would add even more complexity and both conceptual and runtime overhead, so it is
|
135
|
-
unclear if it would be worth doing.
|
136
|
-
|
137
|
-
Instead, we can keep the widgets as one cache key, and if we encounter a new widget
|
138
|
-
while executing the function, we just update the list of widgets to include it.
|
139
|
-
This will cause existing cached results to be invalidated, which is bad, but to
|
140
|
-
avoid it we would need to keep around the full list of widgets and values for each
|
141
|
-
widget cache key so we could compute the updated key, which is probably too expensive
|
142
|
-
to be worth it.
|
143
|
-
"""
|
144
|
-
|
145
71
|
|
146
72
|
@dataclass
|
147
73
|
class CachedResult:
|
@@ -155,32 +81,6 @@ class CachedResult:
|
|
155
81
|
sidebar_id: str
|
156
82
|
|
157
83
|
|
158
|
-
@dataclass
|
159
|
-
class MultiCacheResults:
|
160
|
-
"""Widgets called by a cache-decorated function, and a mapping of the
|
161
|
-
widget-derived cache key to the final results of executing the function.
|
162
|
-
"""
|
163
|
-
|
164
|
-
widget_ids: set[str]
|
165
|
-
results: dict[str, CachedResult]
|
166
|
-
|
167
|
-
def get_current_widget_key(
|
168
|
-
self, ctx: ScriptRunContext, cache_type: CacheType
|
169
|
-
) -> str:
|
170
|
-
state = ctx.session_state
|
171
|
-
# Compute the key using only widgets that have values. A missing widget
|
172
|
-
# can be ignored because we only care about getting different keys
|
173
|
-
# for different widget values, and for that purpose doing nothing
|
174
|
-
# to the running hash is just as good as including the widget with a
|
175
|
-
# sentinel value. But by excluding it, we might get to reuse a result
|
176
|
-
# saved before we knew about that widget.
|
177
|
-
widget_values = [
|
178
|
-
(wid, state[wid]) for wid in sorted(self.widget_ids) if wid in state
|
179
|
-
]
|
180
|
-
widget_key = _make_widget_key(widget_values, cache_type)
|
181
|
-
return widget_key
|
182
|
-
|
183
|
-
|
184
84
|
"""
|
185
85
|
Note [DeltaGenerator method invocation]
|
186
86
|
There are two top level DG instances defined for all apps:
|
@@ -224,52 +124,37 @@ class CachedMessageReplayContext(threading.local):
|
|
224
124
|
self._cached_message_stack: list[list[MsgData]] = []
|
225
125
|
self._seen_dg_stack: list[set[str]] = []
|
226
126
|
self._most_recent_messages: list[MsgData] = []
|
227
|
-
self._registered_metadata: WidgetMetadata[Any] | None = None
|
228
127
|
self._media_data: list[MediaMsgData] = []
|
229
128
|
self._cache_type = cache_type
|
230
|
-
self._allow_widgets: bool = False
|
231
129
|
|
232
130
|
def __repr__(self) -> str:
|
233
131
|
return util.repr_(self)
|
234
132
|
|
235
133
|
@contextlib.contextmanager
|
236
|
-
def calling_cached_function(
|
237
|
-
self, func: FunctionType, allow_widgets: bool
|
238
|
-
) -> Iterator[None]:
|
134
|
+
def calling_cached_function(self, func: FunctionType) -> Iterator[None]:
|
239
135
|
"""Context manager that should wrap the invocation of a cached function.
|
240
|
-
It allows us to track any `st.foo` messages that are generated from inside the
|
241
|
-
for playback during cache retrieval.
|
136
|
+
It allows us to track any `st.foo` messages that are generated from inside the
|
137
|
+
function for playback during cache retrieval.
|
242
138
|
"""
|
243
139
|
self._cached_message_stack.append([])
|
244
140
|
self._seen_dg_stack.append(set())
|
245
|
-
self._allow_widgets = allow_widgets
|
246
|
-
|
247
141
|
nested_call = False
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
# even if it was allowed.
|
256
|
-
self._allow_widgets = False
|
257
|
-
nested_call = True
|
258
|
-
|
259
|
-
if not self._allow_widgets:
|
260
|
-
# If we're in a cached function that disallows widget usage, we need to set
|
261
|
-
# the disallow_cached_widget_usage to true for this cached function run
|
262
|
-
# to prevent widget usage (triggers a warning).
|
263
|
-
ctx.disallow_cached_widget_usage = True
|
142
|
+
if in_cached_function.get():
|
143
|
+
nested_call = True
|
144
|
+
# If we're in a cached function. To disallow usage of widget-like element,
|
145
|
+
# we need to set the in_cached_function to true for this cached function run
|
146
|
+
# to prevent widget usage (triggers a warning).
|
147
|
+
in_cached_function.set(True)
|
148
|
+
|
264
149
|
try:
|
265
150
|
yield
|
266
151
|
finally:
|
267
152
|
self._most_recent_messages = self._cached_message_stack.pop()
|
268
153
|
self._seen_dg_stack.pop()
|
269
|
-
if
|
270
|
-
# Reset the
|
154
|
+
if not nested_call:
|
155
|
+
# Reset the in_cached_function flag. But only if this
|
271
156
|
# is not nested inside a cached function that disallows widget usage.
|
272
|
-
|
157
|
+
in_cached_function.set(False)
|
273
158
|
|
274
159
|
def save_element_message(
|
275
160
|
self,
|
@@ -288,23 +173,6 @@ class CachedMessageReplayContext(threading.local):
|
|
288
173
|
if len(self._cached_message_stack) >= 1:
|
289
174
|
id_to_save = self.select_dg_to_save(invoked_dg_id, used_dg_id)
|
290
175
|
|
291
|
-
# Widget replay is deprecated and will be removed soon:
|
292
|
-
# https://github.com/streamlit/streamlit/pull/8817.
|
293
|
-
# Therefore, its fine to keep this part a bit messy for now.
|
294
|
-
|
295
|
-
if (
|
296
|
-
hasattr(element_proto, "id")
|
297
|
-
and element_proto.id
|
298
|
-
and self._registered_metadata
|
299
|
-
):
|
300
|
-
# The element has an ID and has associated widget metadata
|
301
|
-
# -> looks like a valid registered widget
|
302
|
-
widget_meta = WidgetMsgMetadata(
|
303
|
-
element_proto.id, None, metadata=self._registered_metadata
|
304
|
-
)
|
305
|
-
else:
|
306
|
-
widget_meta = None
|
307
|
-
|
308
176
|
media_data = self._media_data
|
309
177
|
|
310
178
|
element_msg_data = ElementMsgData(
|
@@ -312,17 +180,14 @@ class CachedMessageReplayContext(threading.local):
|
|
312
180
|
element_proto,
|
313
181
|
id_to_save,
|
314
182
|
returned_dg_id,
|
315
|
-
widget_meta,
|
316
183
|
media_data,
|
317
184
|
)
|
318
185
|
for msgs in self._cached_message_stack:
|
319
|
-
|
320
|
-
msgs.append(element_msg_data)
|
186
|
+
msgs.append(element_msg_data)
|
321
187
|
|
322
188
|
# Reset instance state, now that it has been used for the
|
323
189
|
# associated element.
|
324
190
|
self._media_data = []
|
325
|
-
self._registered_metadata = None
|
326
191
|
|
327
192
|
for s in self._seen_dg_stack:
|
328
193
|
s.add(returned_dg_id)
|
@@ -355,9 +220,6 @@ class CachedMessageReplayContext(threading.local):
|
|
355
220
|
else:
|
356
221
|
return invoked_id
|
357
222
|
|
358
|
-
def save_widget_metadata(self, metadata: WidgetMetadata[Any]) -> None:
|
359
|
-
self._registered_metadata = metadata
|
360
|
-
|
361
223
|
def save_image_data(
|
362
224
|
self, image_data: bytes | str, mimetype: str, image_id: str
|
363
225
|
) -> None:
|
@@ -384,24 +246,15 @@ def replay_cached_messages(
|
|
384
246
|
call is on one of them.
|
385
247
|
"""
|
386
248
|
from streamlit.delta_generator import DeltaGenerator
|
387
|
-
from streamlit.runtime.state.widgets import register_widget_from_metadata
|
388
249
|
|
389
250
|
# Maps originally recorded dg ids to this script run's version of that dg
|
390
|
-
returned_dgs: dict[str, DeltaGenerator] = {
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
251
|
+
returned_dgs: dict[str, DeltaGenerator] = {
|
252
|
+
result.main_id: st._main,
|
253
|
+
result.sidebar_id: st.sidebar,
|
254
|
+
}
|
395
255
|
try:
|
396
256
|
for msg in result.messages:
|
397
257
|
if isinstance(msg, ElementMsgData):
|
398
|
-
if msg.widget_metadata is not None:
|
399
|
-
register_widget_from_metadata(
|
400
|
-
msg.widget_metadata.metadata,
|
401
|
-
ctx,
|
402
|
-
None,
|
403
|
-
msg.delta_type,
|
404
|
-
)
|
405
258
|
if msg.media_data is not None:
|
406
259
|
for data in msg.media_data:
|
407
260
|
runtime.get_instance().media_file_mgr.add(
|
@@ -415,31 +268,22 @@ def replay_cached_messages(
|
|
415
268
|
dg = returned_dgs[msg.id_of_dg_called_on]
|
416
269
|
new_dg = dg._block(msg.message)
|
417
270
|
returned_dgs[msg.returned_dgs_id] = new_dg
|
418
|
-
except KeyError:
|
419
|
-
raise CacheReplayClosureError(cache_type, cached_func)
|
420
|
-
|
421
|
-
|
422
|
-
def _make_widget_key(widgets: list[tuple[str, Any]], cache_type: CacheType) -> str:
|
423
|
-
"""Generate a key for the given list of widgets used in a cache-decorated function.
|
424
|
-
|
425
|
-
Keys are generated by hashing the IDs and values of the widgets in the given list.
|
426
|
-
"""
|
427
|
-
func_hasher = hashlib.new("md5", **HASHLIB_KWARGS)
|
428
|
-
for widget_id_val in widgets:
|
429
|
-
update_hash(widget_id_val, func_hasher, cache_type)
|
430
|
-
|
431
|
-
return func_hasher.hexdigest()
|
271
|
+
except KeyError as ex:
|
272
|
+
raise CacheReplayClosureError(cache_type, cached_func) from ex
|
432
273
|
|
433
274
|
|
434
275
|
def show_widget_replay_deprecation(
|
435
276
|
decorator: Literal["cache_data", "cache_resource"],
|
436
277
|
) -> None:
|
437
278
|
show_deprecation_warning(
|
438
|
-
"The
|
279
|
+
"The cached widget replay feature was removed in 1.38. The "
|
280
|
+
"`experimental_allow_widgets` parameter will also be removed "
|
439
281
|
"in a future release. Please remove the `experimental_allow_widgets` parameter "
|
440
282
|
f"from the `@st.{decorator}` decorator and move all widget commands outside of "
|
441
|
-
"cached functions.\n\nTo speed up your app, we recommend moving your widgets
|
442
|
-
"Find out more about fragments in
|
443
|
-
"
|
444
|
-
"
|
283
|
+
"cached functions.\n\nTo speed up your app, we recommend moving your widgets "
|
284
|
+
"into fragments. Find out more about fragments in "
|
285
|
+
"[our docs](https://docs.streamlit.io/develop/api-reference/execution-flow/st.fragment). "
|
286
|
+
"\n\nIf you have a specific use-case that requires the "
|
287
|
+
"`experimental_allow_widgets` functionality, please tell us via an "
|
288
|
+
"[issue on Github](https://github.com/streamlit/streamlit/issues)."
|
445
289
|
)
|