streamlit-nightly 1.32.3.dev20240326__py2.py3-none-any.whl → 1.32.3.dev20240329__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 +6 -3
- streamlit/components/v1/__init__.py +3 -17
- streamlit/components/v1/{custom_component.py → components.py} +159 -11
- streamlit/delta_generator.py +5 -0
- streamlit/elements/html.py +83 -0
- streamlit/elements/widgets/time_widgets.py +5 -21
- streamlit/errors.py +0 -6
- streamlit/proto/AutoRerun_pb2.py +25 -0
- streamlit/proto/AutoRerun_pb2.pyi +48 -0
- streamlit/proto/ClientState_pb2.py +3 -3
- streamlit/proto/ClientState_pb2.pyi +4 -1
- streamlit/proto/Delta_pb2.py +2 -2
- streamlit/proto/Delta_pb2.pyi +4 -1
- streamlit/proto/Element_pb2.py +4 -3
- streamlit/proto/Element_pb2.pyi +9 -4
- streamlit/proto/ForwardMsg_pb2.py +10 -9
- streamlit/proto/ForwardMsg_pb2.pyi +12 -3
- streamlit/proto/Html_pb2.py +26 -0
- streamlit/proto/Html_pb2.pyi +45 -0
- streamlit/proto/NewSession_pb2.py +24 -24
- streamlit/proto/NewSession_pb2.pyi +8 -1
- streamlit/proto/PageProfile_pb2.py +6 -6
- streamlit/proto/PageProfile_pb2.pyi +4 -1
- streamlit/runtime/app_session.py +67 -24
- streamlit/runtime/caching/cache_data_api.py +3 -3
- streamlit/runtime/caching/cache_resource_api.py +2 -2
- streamlit/runtime/fragment.py +239 -0
- streamlit/runtime/metrics_util.py +17 -9
- streamlit/runtime/runtime.py +6 -12
- streamlit/runtime/scriptrunner/script_requests.py +53 -37
- streamlit/runtime/scriptrunner/script_run_context.py +15 -2
- streamlit/runtime/scriptrunner/script_runner.py +63 -14
- streamlit/runtime/state/common.py +2 -0
- streamlit/runtime/state/session_state.py +51 -7
- streamlit/runtime/state/widgets.py +10 -2
- streamlit/static/asset-manifest.json +26 -24
- streamlit/static/index.html +1 -1
- streamlit/static/static/css/2411.8b8f33d6.chunk.css +1 -0
- streamlit/static/static/css/3092.95a45cfe.chunk.css +1 -0
- streamlit/static/static/css/43.e3b876c5.chunk.css +1 -0
- streamlit/static/static/js/1074.73973756.chunk.js +1 -0
- streamlit/static/static/js/1451.3b0a3e31.chunk.js +1 -0
- streamlit/static/static/js/1792.b8efa879.chunk.js +1 -0
- streamlit/static/static/js/2411.a8823789.chunk.js +2 -0
- streamlit/static/static/js/2634.1249dc7a.chunk.js +1 -0
- streamlit/static/static/js/2736.779ccbc1.chunk.js +2 -0
- streamlit/static/static/js/2736.779ccbc1.chunk.js.LICENSE.txt +60 -0
- streamlit/static/static/js/{3092.3d4df25e.chunk.js → 3092.ad569cc8.chunk.js} +1 -1
- streamlit/static/static/js/3513.e3e7300a.chunk.js +1 -0
- streamlit/static/static/js/4132.49bf3f2c.chunk.js.LICENSE.txt +4 -4
- streamlit/static/static/js/4177.69f9f18d.chunk.js +1 -0
- streamlit/static/static/js/4319.a6745434.chunk.js +1 -0
- streamlit/static/static/js/{4477.2555c11a.chunk.js → 4477.e10e4373.chunk.js} +1 -1
- streamlit/static/static/js/{4666.99f3abc3.chunk.js → 4666.b694c5a9.chunk.js} +1 -1
- streamlit/static/static/js/5106.44f0ff51.chunk.js +1 -0
- streamlit/static/static/js/5379.6571574f.chunk.js +1 -0
- streamlit/static/static/js/6013.8e80e091.chunk.js +1 -0
- streamlit/static/static/js/6718.802da17e.chunk.js +1 -0
- streamlit/static/static/js/7175.be4076bc.chunk.js +1 -0
- streamlit/static/static/js/{7602.f0420392.chunk.js → 7602.6175e969.chunk.js} +1 -1
- streamlit/static/static/js/{8492.e6dab83f.chunk.js → 8492.f56c9d4c.chunk.js} +1 -1
- streamlit/static/static/js/8691.9ccf7f89.chunk.js +1 -0
- streamlit/static/static/js/937.a1248039.chunk.js +2 -0
- streamlit/static/static/js/937.a1248039.chunk.js.LICENSE.txt +1 -0
- streamlit/static/static/js/main.356407e8.js +2 -0
- streamlit/testing/v1/local_script_runner.py +2 -0
- streamlit/time_util.py +88 -0
- streamlit/web/server/component_request_handler.py +2 -2
- streamlit/web/server/server.py +2 -1
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/RECORD +77 -73
- streamlit/components/lib/__init__.py +0 -13
- streamlit/components/lib/local_component_registry.py +0 -82
- streamlit/components/types/__init__.py +0 -13
- streamlit/components/types/base_component_registry.py +0 -98
- streamlit/components/types/base_custom_component.py +0 -137
- streamlit/components/v1/component_registry.py +0 -103
- streamlit/static/static/css/2411.81b3d18f.chunk.css +0 -1
- streamlit/static/static/css/3092.f719e2e6.chunk.css +0 -1
- streamlit/static/static/css/43.c24b25fa.chunk.css +0 -1
- streamlit/static/static/js/1074.71719df6.chunk.js +0 -1
- streamlit/static/static/js/1451.e3be1711.chunk.js +0 -1
- streamlit/static/static/js/1792.16c16498.chunk.js +0 -1
- streamlit/static/static/js/2411.b389bf4e.chunk.js +0 -2
- streamlit/static/static/js/2736.17fbad1a.chunk.js +0 -2
- streamlit/static/static/js/2736.17fbad1a.chunk.js.LICENSE.txt +0 -60
- streamlit/static/static/js/3513.57cff89c.chunk.js +0 -1
- streamlit/static/static/js/4177.ab9a7aa1.chunk.js +0 -1
- streamlit/static/static/js/4319.213fc321.chunk.js +0 -1
- streamlit/static/static/js/5106.22187bfc.chunk.js +0 -1
- streamlit/static/static/js/5379.e466522d.chunk.js +0 -1
- streamlit/static/static/js/6013.75c92264.chunk.js +0 -1
- streamlit/static/static/js/6718.97945fc6.chunk.js +0 -1
- streamlit/static/static/js/7175.8c1b4d38.chunk.js +0 -1
- streamlit/static/static/js/8691.24a5792f.chunk.js +0 -1
- streamlit/static/static/js/main.7fde7092.js +0 -2
- /streamlit/static/static/js/{2411.b389bf4e.chunk.js.LICENSE.txt → 2411.a8823789.chunk.js.LICENSE.txt} +0 -0
- /streamlit/static/static/js/{main.7fde7092.js.LICENSE.txt → main.356407e8.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.32.3.dev20240326.data → streamlit_nightly-1.32.3.dev20240329.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.32.3.dev20240326.dist-info → streamlit_nightly-1.32.3.dev20240329.dist-info}/top_level.txt +0 -0
streamlit/runtime/app_session.py
CHANGED
@@ -38,6 +38,7 @@ from streamlit.proto.NewSession_pb2 import (
|
|
38
38
|
from streamlit.proto.PagesChanged_pb2 import PagesChanged
|
39
39
|
from streamlit.runtime import caching, legacy_caching
|
40
40
|
from streamlit.runtime.forward_msg_queue import ForwardMsgQueue
|
41
|
+
from streamlit.runtime.fragment import FragmentStorage, MemoryFragmentStorage
|
41
42
|
from streamlit.runtime.metrics_util import Installation
|
42
43
|
from streamlit.runtime.script_data import ScriptData
|
43
44
|
from streamlit.runtime.scriptrunner import RerunData, ScriptRunner, ScriptRunnerEvent
|
@@ -161,6 +162,8 @@ class AppSession:
|
|
161
162
|
|
162
163
|
self._debug_last_backmsg_id: str | None = None
|
163
164
|
|
165
|
+
self._fragment_storage: FragmentStorage = MemoryFragmentStorage()
|
166
|
+
|
164
167
|
_LOGGER.debug("AppSession initialized (id=%s)", self.id)
|
165
168
|
|
166
169
|
def __del__(self) -> None:
|
@@ -353,26 +356,33 @@ class AppSession:
|
|
353
356
|
return
|
354
357
|
|
355
358
|
if client_state:
|
359
|
+
fragment_id = client_state.fragment_id
|
360
|
+
|
356
361
|
rerun_data = RerunData(
|
357
362
|
client_state.query_string,
|
358
363
|
client_state.widget_states,
|
359
364
|
client_state.page_script_hash,
|
360
365
|
client_state.page_name,
|
366
|
+
fragment_id_queue=[fragment_id] if fragment_id else [],
|
361
367
|
)
|
362
368
|
else:
|
363
369
|
rerun_data = RerunData()
|
364
370
|
|
365
371
|
if self._scriptrunner is not None:
|
366
|
-
if
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
#
|
372
|
+
if (
|
373
|
+
bool(config.get_option("runner.fastReruns"))
|
374
|
+
and not rerun_data.fragment_id_queue
|
375
|
+
):
|
376
|
+
# If fastReruns is enabled and this is *not* a rerun of a fragment,
|
377
|
+
# we don't send rerun requests to our existing ScriptRunner. Instead, we
|
378
|
+
# tell it to shut down. We'll then spin up a new ScriptRunner, below, to
|
379
|
+
# handle the rerun immediately.
|
371
380
|
self._scriptrunner.request_stop()
|
372
381
|
self._scriptrunner = None
|
373
382
|
else:
|
374
|
-
# fastReruns is not enabled
|
375
|
-
#
|
383
|
+
# Either fastReruns is not enabled or this RERUN request is a request to
|
384
|
+
# run a fragment. We send our current ScriptRunner a rerun request, and
|
385
|
+
# if it's accepted, we're done.
|
376
386
|
success = self._scriptrunner.request_rerun(rerun_data)
|
377
387
|
if success:
|
378
388
|
return
|
@@ -400,6 +410,7 @@ class AppSession:
|
|
400
410
|
script_cache=self._script_cache,
|
401
411
|
initial_rerun_data=initial_rerun_data,
|
402
412
|
user_info=self._user_info,
|
413
|
+
fragment_storage=self._fragment_storage,
|
403
414
|
)
|
404
415
|
self._scriptrunner.on_event.connect(self._on_scriptrunner_event)
|
405
416
|
self._scriptrunner.start()
|
@@ -464,6 +475,7 @@ class AppSession:
|
|
464
475
|
exception: BaseException | None = None,
|
465
476
|
client_state: ClientState | None = None,
|
466
477
|
page_script_hash: str | None = None,
|
478
|
+
fragment_ids_this_run: set[str] | None = None,
|
467
479
|
) -> None:
|
468
480
|
"""Called when our ScriptRunner emits an event.
|
469
481
|
|
@@ -473,7 +485,13 @@ class AppSession:
|
|
473
485
|
"""
|
474
486
|
self._event_loop.call_soon_threadsafe(
|
475
487
|
lambda: self._handle_scriptrunner_event_on_event_loop(
|
476
|
-
sender,
|
488
|
+
sender,
|
489
|
+
event,
|
490
|
+
forward_msg,
|
491
|
+
exception,
|
492
|
+
client_state,
|
493
|
+
page_script_hash,
|
494
|
+
fragment_ids_this_run,
|
477
495
|
)
|
478
496
|
)
|
479
497
|
|
@@ -485,6 +503,7 @@ class AppSession:
|
|
485
503
|
exception: BaseException | None = None,
|
486
504
|
client_state: ClientState | None = None,
|
487
505
|
page_script_hash: str | None = None,
|
506
|
+
fragment_ids_this_run: set[str] | None = None,
|
488
507
|
) -> None:
|
489
508
|
"""Handle a ScriptRunner event.
|
490
509
|
|
@@ -515,6 +534,11 @@ class AppSession:
|
|
515
534
|
page_script_hash : str | None
|
516
535
|
A hash of the script path corresponding to the page currently being
|
517
536
|
run. Set only for the SCRIPT_STARTED event.
|
537
|
+
|
538
|
+
fragment_ids_this_run : set[str] | None
|
539
|
+
The fragment IDs of the fragments being executed in this script run. Only
|
540
|
+
set for the SCRIPT_STARTED event. If this value is falsy, this script run
|
541
|
+
must be for the full script.
|
518
542
|
"""
|
519
543
|
|
520
544
|
assert (
|
@@ -540,30 +564,43 @@ class AppSession:
|
|
540
564
|
page_script_hash is not None
|
541
565
|
), "page_script_hash must be set for the SCRIPT_STARTED event"
|
542
566
|
|
543
|
-
|
567
|
+
# When running the full script, we clear the browser ForwardMsg queue since
|
568
|
+
# anything from a previous script run that has yet to be sent to the browser
|
569
|
+
# will be overwritten. For fragment runs, however, we don't want to do this
|
570
|
+
# as the ForwardMsgs in the queue may not correspond to the running
|
571
|
+
# fragment, so dropping the messages may result in the app missing
|
572
|
+
# information.
|
573
|
+
if not fragment_ids_this_run:
|
574
|
+
self._clear_queue()
|
575
|
+
|
544
576
|
self._enqueue_forward_msg(
|
545
|
-
self._create_new_session_message(
|
577
|
+
self._create_new_session_message(
|
578
|
+
page_script_hash, fragment_ids_this_run
|
579
|
+
)
|
546
580
|
)
|
547
581
|
|
548
582
|
elif (
|
549
583
|
event == ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS
|
550
584
|
or event == ScriptRunnerEvent.SCRIPT_STOPPED_WITH_COMPILE_ERROR
|
585
|
+
or event == ScriptRunnerEvent.FRAGMENT_STOPPED_WITH_SUCCESS
|
551
586
|
):
|
552
587
|
if self._state != AppSessionState.SHUTDOWN_REQUESTED:
|
553
588
|
self._state = AppSessionState.APP_NOT_RUNNING
|
554
589
|
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
ForwardMsg.
|
559
|
-
|
560
|
-
|
561
|
-
)
|
562
|
-
self._enqueue_forward_msg(script_finished_msg)
|
590
|
+
if event == ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS:
|
591
|
+
status = ForwardMsg.FINISHED_SUCCESSFULLY
|
592
|
+
elif event == ScriptRunnerEvent.FRAGMENT_STOPPED_WITH_SUCCESS:
|
593
|
+
status = ForwardMsg.FINISHED_FRAGMENT_RUN_SUCCESSFULLY
|
594
|
+
else:
|
595
|
+
status = ForwardMsg.FINISHED_WITH_COMPILE_ERROR
|
563
596
|
|
597
|
+
self._enqueue_forward_msg(self._create_script_finished_message(status))
|
564
598
|
self._debug_last_backmsg_id = None
|
565
599
|
|
566
|
-
if
|
600
|
+
if (
|
601
|
+
event == ScriptRunnerEvent.SCRIPT_STOPPED_WITH_SUCCESS
|
602
|
+
or event == ScriptRunnerEvent.FRAGMENT_STOPPED_WITH_SUCCESS
|
603
|
+
):
|
567
604
|
# The script completed successfully: update our
|
568
605
|
# LocalSourcesWatcher to account for any source code changes
|
569
606
|
# that change which modules should be watched.
|
@@ -582,11 +619,12 @@ class AppSession:
|
|
582
619
|
self._enqueue_forward_msg(msg)
|
583
620
|
|
584
621
|
elif event == ScriptRunnerEvent.SCRIPT_STOPPED_FOR_RERUN:
|
585
|
-
script_finished_msg = self._create_script_finished_message(
|
586
|
-
ForwardMsg.FINISHED_EARLY_FOR_RERUN
|
587
|
-
)
|
588
622
|
self._state = AppSessionState.APP_NOT_RUNNING
|
589
|
-
self._enqueue_forward_msg(
|
623
|
+
self._enqueue_forward_msg(
|
624
|
+
self._create_script_finished_message(
|
625
|
+
ForwardMsg.FINISHED_EARLY_FOR_RERUN
|
626
|
+
)
|
627
|
+
)
|
590
628
|
if self._local_sources_watcher:
|
591
629
|
self._local_sources_watcher.update_watched_modules()
|
592
630
|
|
@@ -630,7 +668,9 @@ class AppSession:
|
|
630
668
|
msg.session_event.script_changed_on_disk = True
|
631
669
|
return msg
|
632
670
|
|
633
|
-
def _create_new_session_message(
|
671
|
+
def _create_new_session_message(
|
672
|
+
self, page_script_hash: str, fragment_ids_this_run: set[str] | None = None
|
673
|
+
) -> ForwardMsg:
|
634
674
|
"""Create and return a new_session ForwardMsg."""
|
635
675
|
msg = ForwardMsg()
|
636
676
|
|
@@ -639,6 +679,9 @@ class AppSession:
|
|
639
679
|
msg.new_session.main_script_path = self._script_data.main_script_path
|
640
680
|
msg.new_session.page_script_hash = page_script_hash
|
641
681
|
|
682
|
+
if fragment_ids_this_run:
|
683
|
+
msg.new_session.fragment_ids_this_run.extend(fragment_ids_this_run)
|
684
|
+
|
642
685
|
_populate_app_pages(msg.new_session, self._script_data.main_script_path)
|
643
686
|
_populate_config_msg(msg.new_session.config)
|
644
687
|
_populate_theme_msg(msg.new_session.custom_theme)
|
@@ -58,9 +58,9 @@ from streamlit.runtime.caching.storage.dummy_cache_storage import (
|
|
58
58
|
MemoryCacheStorageManager,
|
59
59
|
)
|
60
60
|
from streamlit.runtime.metrics_util import gather_metrics
|
61
|
-
from streamlit.runtime.runtime_util import duration_to_seconds
|
62
61
|
from streamlit.runtime.scriptrunner.script_run_context import get_script_run_ctx
|
63
62
|
from streamlit.runtime.stats import CacheStat, CacheStatsProvider, group_stats
|
63
|
+
from streamlit.time_util import time_to_seconds
|
64
64
|
|
65
65
|
_LOGGER: Final = get_logger(__name__)
|
66
66
|
|
@@ -154,7 +154,7 @@ class DataCaches(CacheStatsProvider):
|
|
154
154
|
If it doesn't exist, create a new one with the given params.
|
155
155
|
"""
|
156
156
|
|
157
|
-
ttl_seconds =
|
157
|
+
ttl_seconds = time_to_seconds(ttl, coerce_none_to_inf=False)
|
158
158
|
|
159
159
|
# Get the existing cache, if it exists, and validate that its params
|
160
160
|
# haven't changed.
|
@@ -254,7 +254,7 @@ class DataCaches(CacheStatsProvider):
|
|
254
254
|
CacheStorageContext.
|
255
255
|
"""
|
256
256
|
|
257
|
-
ttl_seconds =
|
257
|
+
ttl_seconds = time_to_seconds(ttl, coerce_none_to_inf=False)
|
258
258
|
|
259
259
|
cache_context = self.create_cache_storage_context(
|
260
260
|
function_key="DUMMY_KEY",
|
@@ -45,9 +45,9 @@ from streamlit.runtime.caching.cached_message_replay import (
|
|
45
45
|
)
|
46
46
|
from streamlit.runtime.caching.hashing import HashFuncsDict
|
47
47
|
from streamlit.runtime.metrics_util import gather_metrics
|
48
|
-
from streamlit.runtime.runtime_util import duration_to_seconds
|
49
48
|
from streamlit.runtime.scriptrunner.script_run_context import get_script_run_ctx
|
50
49
|
from streamlit.runtime.stats import CacheStat, CacheStatsProvider, group_stats
|
50
|
+
from streamlit.time_util import time_to_seconds
|
51
51
|
|
52
52
|
_LOGGER: Final = get_logger(__name__)
|
53
53
|
|
@@ -89,7 +89,7 @@ class ResourceCaches(CacheStatsProvider):
|
|
89
89
|
if max_entries is None:
|
90
90
|
max_entries = math.inf
|
91
91
|
|
92
|
-
ttl_seconds =
|
92
|
+
ttl_seconds = time_to_seconds(ttl)
|
93
93
|
|
94
94
|
# Get the existing cache, if it exists, and validate that its params
|
95
95
|
# haven't changed.
|
@@ -0,0 +1,239 @@
|
|
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 contextlib
|
18
|
+
import hashlib
|
19
|
+
import inspect
|
20
|
+
from abc import abstractmethod
|
21
|
+
from copy import deepcopy
|
22
|
+
from datetime import timedelta
|
23
|
+
from functools import wraps
|
24
|
+
from typing import Any, Callable, Protocol, TypeVar, overload
|
25
|
+
|
26
|
+
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
27
|
+
from streamlit.runtime.metrics_util import gather_metrics
|
28
|
+
from streamlit.runtime.scriptrunner import get_script_run_ctx
|
29
|
+
from streamlit.time_util import time_to_seconds
|
30
|
+
|
31
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
32
|
+
Fragment = Callable[[], Any]
|
33
|
+
|
34
|
+
|
35
|
+
class FragmentStorage(Protocol):
|
36
|
+
"""A key-value store for Fragments. Used to implement the @st.experimental_fragment
|
37
|
+
decorator.
|
38
|
+
|
39
|
+
We intentionally define this as its own protocol despite how generic it appears to
|
40
|
+
be at first glance. The reason why is that, in any case where fragments aren't just
|
41
|
+
stored as Python closures in memory, storing and retrieving Fragments will generally
|
42
|
+
involve serializing and deserializing function bytecode, which is a tricky aspect
|
43
|
+
to implementing FragmentStorages that won't generally appear with our other *Storage
|
44
|
+
protocols.
|
45
|
+
"""
|
46
|
+
|
47
|
+
@abstractmethod
|
48
|
+
def get(self, key: str) -> Fragment:
|
49
|
+
"""Returns the stored fragment for the given key."""
|
50
|
+
raise NotImplementedError
|
51
|
+
|
52
|
+
@abstractmethod
|
53
|
+
def set(self, key: str, value: Fragment) -> None:
|
54
|
+
"""Saves a fragment under the given key."""
|
55
|
+
raise NotImplementedError
|
56
|
+
|
57
|
+
@abstractmethod
|
58
|
+
def delete(self, key: str) -> None:
|
59
|
+
"""Delete the fragment corresponding to the given key."""
|
60
|
+
raise NotImplementedError
|
61
|
+
|
62
|
+
@abstractmethod
|
63
|
+
def clear(self) -> None:
|
64
|
+
"""Remove all fragments saved in this FragmentStorage."""
|
65
|
+
raise NotImplementedError
|
66
|
+
|
67
|
+
|
68
|
+
# NOTE: Ideally, we'd like to add a MemoryFragmentStorageStatProvider implementation to
|
69
|
+
# keep track of memory usage due to fragments, but doing something like this ends up
|
70
|
+
# being difficult in practice as the memory usage of a closure is hard to measure (the
|
71
|
+
# vendored implementation of pympler.asizeof that we use elsewhere is unable to measure
|
72
|
+
# the size of a function).
|
73
|
+
class MemoryFragmentStorage(FragmentStorage):
|
74
|
+
"""A simple, memory-backed implementation of FragmentStorage.
|
75
|
+
|
76
|
+
MemoryFragmentStorage is just a wrapper around a plain Python dict that complies with
|
77
|
+
the FragmentStorage protocol.
|
78
|
+
"""
|
79
|
+
|
80
|
+
def __init__(self):
|
81
|
+
self._fragments: dict[str, Fragment] = {}
|
82
|
+
|
83
|
+
def get(self, key: str) -> Fragment:
|
84
|
+
return self._fragments[key]
|
85
|
+
|
86
|
+
def set(self, key: str, value: Fragment) -> None:
|
87
|
+
self._fragments[key] = value
|
88
|
+
|
89
|
+
def delete(self, key: str) -> None:
|
90
|
+
del self._fragments[key]
|
91
|
+
|
92
|
+
def clear(self) -> None:
|
93
|
+
self._fragments.clear()
|
94
|
+
|
95
|
+
|
96
|
+
@overload
|
97
|
+
def fragment(
|
98
|
+
func: F,
|
99
|
+
*,
|
100
|
+
run_every: int | float | timedelta | str | None = None,
|
101
|
+
) -> F:
|
102
|
+
...
|
103
|
+
|
104
|
+
|
105
|
+
# Support being able to pass parameters to this decorator (that is, being able to write
|
106
|
+
# `@fragment(run_every=5.0)`).
|
107
|
+
@overload
|
108
|
+
def fragment(
|
109
|
+
func: None = None,
|
110
|
+
*,
|
111
|
+
run_every: int | float | timedelta | str | None = None,
|
112
|
+
) -> Callable[[F], F]:
|
113
|
+
...
|
114
|
+
|
115
|
+
|
116
|
+
@gather_metrics("experimental_fragment")
|
117
|
+
def fragment(
|
118
|
+
func: F | None = None,
|
119
|
+
*,
|
120
|
+
run_every: int | float | timedelta | str | None = None,
|
121
|
+
) -> Callable[[F], F] | F:
|
122
|
+
"""Allow a function to be run independently of the full script.
|
123
|
+
|
124
|
+
Functions decorated with ``@st.experimental_fragment`` are handled specially within
|
125
|
+
an app: when a widget created within an invocation of the function (a fragment) is
|
126
|
+
interacted with, then only that fragment is rerun rather than the full streamlit app.
|
127
|
+
|
128
|
+
Parameters
|
129
|
+
----------
|
130
|
+
run_every: int, float, timedelta, str, or None
|
131
|
+
If set, fragments created from this function rerun periodically at the specified
|
132
|
+
time interval.
|
133
|
+
|
134
|
+
Example
|
135
|
+
-------
|
136
|
+
The following example demonstrates basic usage of ``@st.experimental_fragment``. In
|
137
|
+
this app, clicking on the "rerun full script" button will increment both counters,
|
138
|
+
but the "rerun fragment" button will only increment the counter within the fragment.
|
139
|
+
|
140
|
+
```python3
|
141
|
+
import streamlit as st
|
142
|
+
|
143
|
+
if "script_runs" not in st.session_state:
|
144
|
+
st.session_state.script_runs = 0
|
145
|
+
st.session_state.fragment_runs = 0
|
146
|
+
|
147
|
+
@st.experimental_fragment
|
148
|
+
def fragment():
|
149
|
+
st.button("rerun fragment")
|
150
|
+
st.write(f"fragment runs: {st.session_state.fragment_runs}")
|
151
|
+
st.session_state.fragment_runs += 1
|
152
|
+
|
153
|
+
fragment()
|
154
|
+
|
155
|
+
st.button("rerun full script")
|
156
|
+
st.write(f"full script runs: {st.session_state.script_runs}")
|
157
|
+
st.session_state.script_runs += 1
|
158
|
+
```
|
159
|
+
"""
|
160
|
+
|
161
|
+
if func is None:
|
162
|
+
# Support passing the params via function decorator
|
163
|
+
def wrapper(f: F) -> F:
|
164
|
+
return fragment(
|
165
|
+
func=f,
|
166
|
+
run_every=run_every,
|
167
|
+
)
|
168
|
+
|
169
|
+
return wrapper
|
170
|
+
else:
|
171
|
+
non_optional_func = func
|
172
|
+
|
173
|
+
@wraps(non_optional_func)
|
174
|
+
def wrap(*args, **kwargs):
|
175
|
+
from streamlit.delta_generator import dg_stack
|
176
|
+
|
177
|
+
ctx = get_script_run_ctx()
|
178
|
+
if ctx is None:
|
179
|
+
return
|
180
|
+
|
181
|
+
cursors_snapshot = deepcopy(ctx.cursors)
|
182
|
+
dg_stack_snapshot = deepcopy(dg_stack.get())
|
183
|
+
active_dg = dg_stack_snapshot[-1]
|
184
|
+
h = hashlib.new("md5")
|
185
|
+
h.update(
|
186
|
+
f"{non_optional_func.__module__}.{non_optional_func.__qualname__}{active_dg._get_delta_path_str()}".encode(
|
187
|
+
"utf-8"
|
188
|
+
)
|
189
|
+
)
|
190
|
+
fragment_id = h.hexdigest()
|
191
|
+
|
192
|
+
def wrapped_fragment():
|
193
|
+
import streamlit as st
|
194
|
+
|
195
|
+
# NOTE: We need to call get_script_run_ctx here again and can't just use the
|
196
|
+
# value of ctx from above captured by the closure because subsequent
|
197
|
+
# fragment runs will generally run in a new script run, thus we'll have a
|
198
|
+
# new ctx.
|
199
|
+
ctx = get_script_run_ctx(suppress_warning=True)
|
200
|
+
assert ctx is not None
|
201
|
+
|
202
|
+
if ctx.fragment_ids_this_run:
|
203
|
+
# This script run is a run of one or more fragments. We restore the
|
204
|
+
# state of ctx.cursors and dg_stack to the snapshots we took when this
|
205
|
+
# fragment was declared.
|
206
|
+
ctx.cursors = deepcopy(cursors_snapshot)
|
207
|
+
dg_stack.set(deepcopy(dg_stack_snapshot))
|
208
|
+
else:
|
209
|
+
# Otherwise, we must be in a full script run. We need to temporarily set
|
210
|
+
# ctx.current_fragment_id so that elements corresponding to this
|
211
|
+
# fragment get tagged with the appropriate ID. ctx.current_fragment_id
|
212
|
+
# gets reset after the fragment function finishes running.
|
213
|
+
ctx.current_fragment_id = fragment_id
|
214
|
+
|
215
|
+
try:
|
216
|
+
with st.container():
|
217
|
+
result = non_optional_func(*args, **kwargs)
|
218
|
+
finally:
|
219
|
+
ctx.current_fragment_id = None
|
220
|
+
|
221
|
+
return result
|
222
|
+
|
223
|
+
ctx.fragment_storage.set(fragment_id, wrapped_fragment)
|
224
|
+
|
225
|
+
if run_every:
|
226
|
+
msg = ForwardMsg()
|
227
|
+
msg.auto_rerun.interval = time_to_seconds(run_every)
|
228
|
+
msg.auto_rerun.fragment_id = fragment_id
|
229
|
+
ctx.enqueue(msg)
|
230
|
+
|
231
|
+
return wrapped_fragment()
|
232
|
+
|
233
|
+
with contextlib.suppress(AttributeError):
|
234
|
+
# Make this a well-behaved decorator by preserving important function
|
235
|
+
# attributes.
|
236
|
+
wrap.__dict__.update(non_optional_func.__dict__)
|
237
|
+
wrap.__signature__ = inspect.signature(non_optional_func) # type: ignore
|
238
|
+
|
239
|
+
return wrap
|
@@ -427,12 +427,17 @@ def create_page_profile_message(
|
|
427
427
|
uncaught_exception: str | None = None,
|
428
428
|
) -> ForwardMsg:
|
429
429
|
"""Create and return the full PageProfile ForwardMsg."""
|
430
|
+
# Local import to prevent circular dependencies
|
431
|
+
from streamlit.runtime.scriptrunner import get_script_run_ctx
|
432
|
+
|
430
433
|
msg = ForwardMsg()
|
431
|
-
msg.page_profile
|
432
|
-
|
433
|
-
|
434
|
+
page_profile = msg.page_profile
|
435
|
+
|
436
|
+
page_profile.commands.extend(commands)
|
437
|
+
page_profile.exec_time = exec_time
|
438
|
+
page_profile.prep_time = prep_time
|
434
439
|
|
435
|
-
|
440
|
+
page_profile.headless = config.get_option("server.headless")
|
436
441
|
|
437
442
|
# Collect all config options that have been manually set
|
438
443
|
config_options: set[str] = set()
|
@@ -447,7 +452,7 @@ def create_page_profile_message(
|
|
447
452
|
option_name = f"{option_name}:default"
|
448
453
|
config_options.add(option_name)
|
449
454
|
|
450
|
-
|
455
|
+
page_profile.config.extend(config_options)
|
451
456
|
|
452
457
|
# Check the predefined set of modules for attribution
|
453
458
|
attributions: set[str] = {
|
@@ -456,11 +461,14 @@ def create_page_profile_message(
|
|
456
461
|
if attribution in sys.modules
|
457
462
|
}
|
458
463
|
|
459
|
-
|
460
|
-
|
461
|
-
|
464
|
+
page_profile.os = str(sys.platform)
|
465
|
+
page_profile.timezone = str(time.tzname)
|
466
|
+
page_profile.attributions.extend(attributions)
|
462
467
|
|
463
468
|
if uncaught_exception:
|
464
|
-
|
469
|
+
page_profile.uncaught_exception = uncaught_exception
|
470
|
+
|
471
|
+
if ctx := get_script_run_ctx():
|
472
|
+
page_profile.is_fragment_run = bool(ctx.current_fragment_id)
|
465
473
|
|
466
474
|
return msg
|
streamlit/runtime/runtime.py
CHANGED
@@ -22,8 +22,6 @@ from enum import Enum
|
|
22
22
|
from typing import TYPE_CHECKING, Awaitable, Final, NamedTuple
|
23
23
|
|
24
24
|
from streamlit import config
|
25
|
-
from streamlit.components.lib.local_component_registry import LocalComponentRegistry
|
26
|
-
from streamlit.components.types.base_component_registry import BaseComponentRegistry
|
27
25
|
from streamlit.logger import get_logger
|
28
26
|
from streamlit.proto.BackMsg_pb2 import BackMsg
|
29
27
|
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
@@ -98,11 +96,6 @@ class RuntimeConfig:
|
|
98
96
|
default_factory=LocalDiskCacheStorageManager
|
99
97
|
)
|
100
98
|
|
101
|
-
# The ComponentRegistry instance to use.
|
102
|
-
component_registry: BaseComponentRegistry = field(
|
103
|
-
default_factory=LocalComponentRegistry
|
104
|
-
)
|
105
|
-
|
106
99
|
# The SessionManager class to be used.
|
107
100
|
session_manager_class: type[SessionManager] = WebsocketSessionManager
|
108
101
|
|
@@ -112,6 +105,12 @@ class RuntimeConfig:
|
|
112
105
|
# True if the command used to start Streamlit was `streamlit hello`.
|
113
106
|
is_hello: bool = False
|
114
107
|
|
108
|
+
# TODO(vdonato): Eventually add a new fragment_storage_class field enabling the code
|
109
|
+
# creating a new Streamlit Runtime to configure the FragmentStorage instances
|
110
|
+
# created by each new AppSession. We choose not to do this for now to avoid adding
|
111
|
+
# additional complexity to RuntimeConfig/SessionManager/etc when it's unlikely
|
112
|
+
# we'll have a custom implementation of this class anytime soon.
|
113
|
+
|
115
114
|
|
116
115
|
class RuntimeState(Enum):
|
117
116
|
INITIAL = "INITIAL"
|
@@ -196,7 +195,6 @@ class Runtime:
|
|
196
195
|
self._state = RuntimeState.INITIAL
|
197
196
|
|
198
197
|
# Initialize managers
|
199
|
-
self._component_registry = config.component_registry
|
200
198
|
self._message_cache = ForwardMsgCache()
|
201
199
|
self._uploaded_file_mgr = config.uploaded_file_manager
|
202
200
|
self._media_file_mgr = MediaFileManager(storage=config.media_file_storage)
|
@@ -222,10 +220,6 @@ class Runtime:
|
|
222
220
|
def state(self) -> RuntimeState:
|
223
221
|
return self._state
|
224
222
|
|
225
|
-
@property
|
226
|
-
def component_registry(self) -> BaseComponentRegistry:
|
227
|
-
return self._component_registry
|
228
|
-
|
229
223
|
@property
|
230
224
|
def message_cache(self) -> ForwardMsgCache:
|
231
225
|
return self._message_cache
|