streamlit-nightly 1.42.3.dev20250226__py2.py3-none-any.whl → 1.42.3.dev20250227__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/commands/navigation.py +16 -4
- streamlit/commands/page_config.py +1 -2
- streamlit/elements/widgets/button.py +5 -7
- streamlit/elements/widgets/chat.py +2 -1
- streamlit/errors.py +11 -9
- streamlit/navigation/page.py +2 -10
- streamlit/proto/ChatInput_pb2.py +6 -6
- streamlit/proto/ChatInput_pb2.pyi +5 -1
- streamlit/proto/ForwardMsg_pb2.py +12 -10
- streamlit/proto/PagesChanged_pb2.pyi +3 -1
- streamlit/runtime/app_session.py +1 -13
- streamlit/runtime/pages_manager.py +61 -232
- streamlit/runtime/scriptrunner/script_runner.py +49 -2
- streamlit/runtime/scriptrunner_utils/script_run_context.py +1 -1
- streamlit/source_util.py +4 -90
- streamlit/static/index.html +1 -1
- streamlit/static/static/js/{FileDownload.esm.CWn5B5ZN.js → FileDownload.esm.rdZtCw5-.js} +1 -1
- streamlit/static/static/js/FileHelper.BztOEKJy.js +5 -0
- streamlit/static/static/js/{FormClearHelper.BlCIuWCo.js → FormClearHelper.Drd1fMJc.js} +1 -1
- streamlit/static/static/js/{Hooks.DURid8_V.js → Hooks.DWd0hyk4.js} +1 -1
- streamlit/static/static/js/{InputInstructions.gFFpIc6i.js → InputInstructions.B7xrWTG0.js} +1 -1
- streamlit/static/static/js/{ProgressBar.CtXtbXhv.js → ProgressBar.CBgJiTH-.js} +1 -1
- streamlit/static/static/js/{RenderInPortalIfExists.DKWNQmbm.js → RenderInPortalIfExists.US28tQOn.js} +1 -1
- streamlit/static/static/js/{Toolbar.CKKwksQV.js → Toolbar.CAOt-xaL.js} +1 -1
- streamlit/static/static/js/{base-input.BUY9TLqO.js → base-input.CauNaJqr.js} +1 -1
- streamlit/static/static/js/{checkbox.C4L_5Z9H.js → checkbox.ChzWCtre.js} +1 -1
- streamlit/static/static/js/{createSuper.DprEKsA6.js → createSuper.D_39RJB6.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.BArvtwSg.js → data-grid-overlay-editor.rIelkOVS.js} +1 -1
- streamlit/static/static/js/{downloader.C5pxkE2v.js → downloader.Dz5I28nG.js} +1 -1
- streamlit/static/static/js/{es6.DmKjMqyQ.js → es6.DwnEjMMn.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.DhqbXIl1.js → iframeResizer.contentWindow.BRiRifwd.js} +1 -1
- streamlit/static/static/js/{index.BEnZZwfW.js → index.BE6MTF-B.js} +1 -1
- streamlit/static/static/js/{index.c_V1HXTs.js → index.BUdhIGhT.js} +1 -1
- streamlit/static/static/js/{index.B-S4HX7x.js → index.BWeYb5ES.js} +1 -1
- streamlit/static/static/js/{index.CTqvpoOz.js → index.Bb4OB39A.js} +1 -1
- streamlit/static/static/js/{index.BoBCblDb.js → index.BrAW6bFJ.js} +1 -1
- streamlit/static/static/js/{index.CXpOPWFV.js → index.CAHGh74D.js} +1 -1
- streamlit/static/static/js/{index.UE_L1IEZ.js → index.CBre8Aui.js} +1 -1
- streamlit/static/static/js/{index.B-vrwvgD.js → index.CM12P2T1.js} +1 -1
- streamlit/static/static/js/{index.Cd03IYkG.js → index.CO3dZWKG.js} +1 -1
- streamlit/static/static/js/{index.CvNhwseQ.js → index.CUz1_nAm.js} +1 -1
- streamlit/static/static/js/{index.BVHfAEko.js → index.C_C365T5.js} +1 -1
- streamlit/static/static/js/{index.NKOzzX__.js → index.CmYiLKNX.js} +1 -1
- streamlit/static/static/js/{index.RFfgDsnM.js → index.CoUzNoRf.js} +1 -1
- streamlit/static/static/js/{index.RrEbViHi.js → index.Ct7MPsA3.js} +1 -1
- streamlit/static/static/js/{index.D2G24Udm.js → index.D1K6NCBQ.js} +1 -1
- streamlit/static/static/js/{index.CoQjULdH.js → index.D2xM-XzG.js} +1 -1
- streamlit/static/static/js/{index.D2-IYCcZ.js → index.D9rv1PQ0.js} +1 -1
- streamlit/static/static/js/{index.ChEJa75P.js → index.DIeegLbv.js} +1 -1
- streamlit/static/static/js/{index.oPGa5nFU.js → index.DJvwxFLn.js} +1 -1
- streamlit/static/static/js/{index.DtIKjlNZ.js → index.DKuQg2D4.js} +1 -1
- streamlit/static/static/js/{index.Bk3A478D.js → index.DWFLx6Tc.js} +6 -6
- streamlit/static/static/js/{index.WAlW9lgW.js → index.DYUhWSq6.js} +1 -1
- streamlit/static/static/js/{index.Bds7Anva.js → index.Db3jGJxa.js} +1 -1
- streamlit/static/static/js/{index.KPwXWqBk.js → index.DfNAkKAr.js} +145 -145
- streamlit/static/static/js/{index.EIkP6R_d.js → index.DgNQKEeQ.js} +1 -1
- streamlit/static/static/js/{index.BmnSGdtS.js → index.Dj91melR.js} +1 -1
- streamlit/static/static/js/{index.C3l2zbST.js → index.DkKZlAsv.js} +1 -1
- streamlit/static/static/js/index.DkgVNhWs.js +1 -0
- streamlit/static/static/js/{index.DNr2W0gq.js → index.Dzp8iBkF.js} +1 -1
- streamlit/static/static/js/index.LL2DLZZA.js +201 -0
- streamlit/static/static/js/index.ORHz8Y8P.js +4537 -0
- streamlit/static/static/js/{index.O5-Wn4Yr.js → index.V3Vj2d9m.js} +1 -1
- streamlit/static/static/js/{index.BUnWa5Cg.js → index.YNWoT4uW.js} +1 -1
- streamlit/static/static/js/index.aL_kqyvR.js +1 -0
- streamlit/static/static/js/{index.BSczaRDm.js → index.fP6rBxlc.js} +1 -1
- streamlit/static/static/js/{index.xTncca7J.js → index.pvQjxG7y.js} +1 -1
- streamlit/static/static/js/{index.doRPQ8od.js → index.wxpL02VY.js} +1 -1
- streamlit/static/static/js/{input.EkJ_AsTP.js → input.D_dn5-2t.js} +1 -1
- streamlit/static/static/js/{memory.BFs17dZE.js → memory.pnMTtWQR.js} +1 -1
- streamlit/static/static/js/{mergeWith.B4u0whL-.js → mergeWith.CRrYodeI.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.DwazN43x.js → number-overlay-editor.C65GBS2P.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.CZfYLVBb.js → possibleConstructorReturn.BnQk7lq_.js} +1 -1
- streamlit/static/static/js/{sandbox.PjijA1v1.js → sandbox.CrxXt1li.js} +1 -1
- streamlit/static/static/js/{textarea.BGGnQXtw.js → textarea.DiFyO5JY.js} +1 -1
- streamlit/static/static/js/{timepicker.eLEnk1tw.js → timepicker.B1Y7LIk4.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.CgKj9NUY.js → toConsumableArray.BWpBUK-j.js} +1 -1
- streamlit/static/static/js/{uniqueId.CPmOGhdZ.js → uniqueId.7ecIUkUs.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.Bw_fxTtX.js → useBasicWidgetState.deG0KPm0.js} +1 -1
- streamlit/static/static/js/{useOnInputChange.D8qi7pm4.js → useOnInputChange.CQ7sD7CO.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.BjVufR9b.js → withFullScreenWrapper.D0wnBu3k.js} +1 -1
- streamlit/testing/v1/app_test.py +8 -10
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/RECORD +88 -88
- streamlit/static/static/js/FileHelper.Bii_mi1A.js +0 -5
- streamlit/static/static/js/index.BAfr2RC0.js +0 -3865
- streamlit/static/static/js/index.BCErGZwr.js +0 -1
- streamlit/static/static/js/index.DhcYAj3f.js +0 -1
- streamlit/static/static/js/index.u1gL_Cm_.js +0 -201
- {streamlit_nightly-1.42.3.dev20250226.data → streamlit_nightly-1.42.3.dev20250227.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/top_level.txt +0 -0
@@ -14,15 +14,11 @@
|
|
14
14
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
|
-
import os
|
18
|
-
import threading
|
19
17
|
from pathlib import Path
|
20
|
-
from typing import TYPE_CHECKING, Any,
|
18
|
+
from typing import TYPE_CHECKING, Any, Final
|
21
19
|
|
22
|
-
from streamlit import source_util
|
23
20
|
from streamlit.logger import get_logger
|
24
21
|
from streamlit.util import calc_md5
|
25
|
-
from streamlit.watcher import watch_dir
|
26
22
|
|
27
23
|
if TYPE_CHECKING:
|
28
24
|
from streamlit.runtime.scriptrunner.script_cache import ScriptCache
|
@@ -31,198 +27,15 @@ if TYPE_CHECKING:
|
|
31
27
|
_LOGGER: Final = get_logger(__name__)
|
32
28
|
|
33
29
|
|
34
|
-
class PagesStrategyV1:
|
35
|
-
"""
|
36
|
-
Strategy for MPA v1. This strategy handles pages being set directly
|
37
|
-
by a call to `st.navigation`. The key differences here are:
|
38
|
-
- The pages are defined by the existence of a `pages` directory
|
39
|
-
- We will ensure one watcher is watching the scripts in the directory.
|
40
|
-
- Only one script runs for a full rerun.
|
41
|
-
- We know at the beginning the intended page script to run.
|
42
|
-
|
43
|
-
NOTE: Thread safety of the pages is handled by the source_util module
|
44
|
-
"""
|
45
|
-
|
46
|
-
is_watching_pages_dir: bool = False
|
47
|
-
pages_watcher_lock = threading.Lock()
|
48
|
-
|
49
|
-
# This is a static method because we only want to watch the pages directory
|
50
|
-
# once on initial load.
|
51
|
-
@staticmethod
|
52
|
-
def watch_pages_dir(pages_manager: PagesManager):
|
53
|
-
with PagesStrategyV1.pages_watcher_lock:
|
54
|
-
if PagesStrategyV1.is_watching_pages_dir:
|
55
|
-
return
|
56
|
-
|
57
|
-
def _handle_page_changed(_path: str) -> None:
|
58
|
-
source_util.invalidate_pages_cache()
|
59
|
-
|
60
|
-
pages_dir = pages_manager.main_script_parent / "pages"
|
61
|
-
watch_dir(
|
62
|
-
str(pages_dir),
|
63
|
-
_handle_page_changed,
|
64
|
-
glob_pattern="*.py",
|
65
|
-
allow_nonexistent=True,
|
66
|
-
)
|
67
|
-
PagesStrategyV1.is_watching_pages_dir = True
|
68
|
-
|
69
|
-
def __init__(self, pages_manager: PagesManager, setup_watcher: bool = True):
|
70
|
-
self.pages_manager = pages_manager
|
71
|
-
|
72
|
-
if setup_watcher:
|
73
|
-
PagesStrategyV1.watch_pages_dir(pages_manager)
|
74
|
-
|
75
|
-
@property
|
76
|
-
def initial_active_script_hash(self) -> PageHash:
|
77
|
-
return self.pages_manager.current_page_script_hash
|
78
|
-
|
79
|
-
def get_initial_active_script(
|
80
|
-
self, page_script_hash: PageHash, page_name: PageName
|
81
|
-
) -> PageInfo | None:
|
82
|
-
pages = self.get_pages()
|
83
|
-
|
84
|
-
if page_script_hash:
|
85
|
-
return pages.get(page_script_hash, None)
|
86
|
-
elif not page_script_hash and page_name:
|
87
|
-
# If a user navigates directly to a non-main page of an app, we get
|
88
|
-
# the first script run request before the list of pages has been
|
89
|
-
# sent to the frontend. In this case, we choose the first script
|
90
|
-
# with a name matching the requested page name.
|
91
|
-
return next(
|
92
|
-
filter(
|
93
|
-
# There seems to be this weird bug with mypy where it
|
94
|
-
# thinks that p can be None (which is impossible given the
|
95
|
-
# types of pages), so we add `p and` at the beginning of
|
96
|
-
# the predicate to circumvent this.
|
97
|
-
lambda p: p and (p["page_name"] == page_name),
|
98
|
-
pages.values(),
|
99
|
-
),
|
100
|
-
None,
|
101
|
-
)
|
102
|
-
|
103
|
-
# If no information about what page to run is given, default to
|
104
|
-
# running the main page.
|
105
|
-
# Safe because pages will at least contain the app's main page.
|
106
|
-
main_page_info = list(pages.values())[0]
|
107
|
-
return main_page_info
|
108
|
-
|
109
|
-
def get_pages(self) -> dict[PageHash, PageInfo]:
|
110
|
-
return source_util.get_pages(self.pages_manager.main_script_path)
|
111
|
-
|
112
|
-
def register_pages_changed_callback(
|
113
|
-
self,
|
114
|
-
callback: Callable[[str], None],
|
115
|
-
) -> Callable[[], None]:
|
116
|
-
return source_util.register_pages_changed_callback(callback)
|
117
|
-
|
118
|
-
def set_pages(self, _pages: dict[PageHash, PageInfo]) -> None:
|
119
|
-
raise NotImplementedError("Unable to set pages in this V1 strategy")
|
120
|
-
|
121
|
-
def get_page_script(self, _fallback_page_hash: PageHash) -> PageInfo | None:
|
122
|
-
raise NotImplementedError("Unable to get page script in this V1 strategy")
|
123
|
-
|
124
|
-
|
125
|
-
class PagesStrategyV2:
|
126
|
-
"""
|
127
|
-
Strategy for MPA v2. This strategy handles pages being set directly
|
128
|
-
by a call to `st.navigation`. The key differences here are:
|
129
|
-
- The pages are set directly by the user
|
130
|
-
- The initial active script will always be the main script
|
131
|
-
- More than one script can run in a single app run (sequentially),
|
132
|
-
so we must keep track of the active script hash
|
133
|
-
- We rely on pages manager to retrieve the intended page script per run
|
134
|
-
|
135
|
-
NOTE: We don't provide any locks on the pages since the pages are not
|
136
|
-
shared across sessions. Only the user script thread can write to
|
137
|
-
pages and the event loop thread only reads
|
138
|
-
"""
|
139
|
-
|
140
|
-
def __init__(self, pages_manager: PagesManager, **kwargs):
|
141
|
-
self.pages_manager = pages_manager
|
142
|
-
self._pages: dict[PageHash, PageInfo] | None = None
|
143
|
-
|
144
|
-
def get_initial_active_script(
|
145
|
-
self, page_script_hash: PageHash, page_name: PageName
|
146
|
-
) -> PageInfo:
|
147
|
-
return {
|
148
|
-
# We always run the main script in V2 as it's the common code
|
149
|
-
"script_path": self.pages_manager.main_script_path,
|
150
|
-
"page_script_hash": page_script_hash
|
151
|
-
or self.pages_manager.main_script_hash, # Default Hash
|
152
|
-
}
|
153
|
-
|
154
|
-
@property
|
155
|
-
def initial_active_script_hash(self) -> PageHash:
|
156
|
-
return self.pages_manager.main_script_hash
|
157
|
-
|
158
|
-
def get_page_script(self, fallback_page_hash: PageHash) -> PageInfo | None:
|
159
|
-
if self._pages is None:
|
160
|
-
return None
|
161
|
-
|
162
|
-
if self.pages_manager.intended_page_script_hash:
|
163
|
-
# We assume that if initial page hash is specified, that a page should
|
164
|
-
# exist, so we check out the page script hash or the default page hash
|
165
|
-
# as a backup
|
166
|
-
return self._pages.get(
|
167
|
-
self.pages_manager.intended_page_script_hash,
|
168
|
-
self._pages.get(fallback_page_hash, None),
|
169
|
-
)
|
170
|
-
elif self.pages_manager.intended_page_name:
|
171
|
-
# If a user navigates directly to a non-main page of an app, the
|
172
|
-
# the page name can identify the page script to run
|
173
|
-
return next(
|
174
|
-
filter(
|
175
|
-
# There seems to be this weird bug with mypy where it
|
176
|
-
# thinks that p can be None (which is impossible given the
|
177
|
-
# types of pages), so we add `p and` at the beginning of
|
178
|
-
# the predicate to circumvent this.
|
179
|
-
lambda p: p
|
180
|
-
and (p["url_pathname"] == self.pages_manager.intended_page_name),
|
181
|
-
self._pages.values(),
|
182
|
-
),
|
183
|
-
None,
|
184
|
-
)
|
185
|
-
|
186
|
-
return self._pages.get(fallback_page_hash, None)
|
187
|
-
|
188
|
-
def get_pages(self) -> dict[PageHash, PageInfo]:
|
189
|
-
# If pages are not set, provide the common page info where
|
190
|
-
# - the main script path is the executing script to start
|
191
|
-
# - the page script hash and name reflects the intended page requested
|
192
|
-
return self._pages or {
|
193
|
-
self.pages_manager.main_script_hash: {
|
194
|
-
"page_script_hash": self.pages_manager.intended_page_script_hash or "",
|
195
|
-
"page_name": self.pages_manager.intended_page_name or "",
|
196
|
-
"icon": "",
|
197
|
-
"script_path": self.pages_manager.main_script_path,
|
198
|
-
}
|
199
|
-
}
|
200
|
-
|
201
|
-
def set_pages(self, pages: dict[PageHash, PageInfo]) -> None:
|
202
|
-
self._pages = pages
|
203
|
-
|
204
|
-
def register_pages_changed_callback(
|
205
|
-
self,
|
206
|
-
callback: Callable[[str], None],
|
207
|
-
) -> Callable[[], None]:
|
208
|
-
# V2 strategy does not handle any pages changed event
|
209
|
-
return lambda: None
|
210
|
-
|
211
|
-
|
212
30
|
class PagesManager:
|
213
31
|
"""
|
214
|
-
PagesManager is responsible for managing the set of pages
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
If the `pages` are being set directly, the strategy is switched to V2.
|
219
|
-
This indicates someone has written an `st.navigation` call in their app
|
220
|
-
which informs us of the pages.
|
221
|
-
|
222
|
-
NOTE: Each strategy handles its own thread safety when accessing the pages
|
32
|
+
PagesManager is responsible for managing the set of pages that make up
|
33
|
+
the entire application. At the start we assume the main script is the
|
34
|
+
only page. As the script runs, the main script can call `st.navigation`
|
35
|
+
to set the set of pages that make up the app.
|
223
36
|
"""
|
224
37
|
|
225
|
-
|
38
|
+
uses_pages_directory: bool | None = None
|
226
39
|
|
227
40
|
def __init__(
|
228
41
|
self,
|
@@ -232,11 +45,23 @@ class PagesManager:
|
|
232
45
|
):
|
233
46
|
self._main_script_path = main_script_path
|
234
47
|
self._main_script_hash: PageHash = calc_md5(main_script_path)
|
235
|
-
self.pages_strategy = PagesManager.DefaultStrategy(self, **kwargs)
|
236
48
|
self._script_cache = script_cache
|
237
49
|
self._intended_page_script_hash: PageHash | None = None
|
238
50
|
self._intended_page_name: PageName | None = None
|
239
51
|
self._current_page_script_hash: PageHash = ""
|
52
|
+
self._pages: dict[PageHash, PageInfo] | None = None
|
53
|
+
# A relic of v1 of Multipage apps, we performed special handling
|
54
|
+
# for apps with a pages directory. We will keep this flag around
|
55
|
+
# for now to maintain the behavior for apps that were created with
|
56
|
+
# the pages directory feature.
|
57
|
+
#
|
58
|
+
# NOTE: we will update the feature if the flag has not been set
|
59
|
+
# this means that if users use v2 behavior, the flag will
|
60
|
+
# always be set to False
|
61
|
+
if PagesManager.uses_pages_directory is None:
|
62
|
+
PagesManager.uses_pages_directory = Path(
|
63
|
+
self.main_script_parent / "pages"
|
64
|
+
).exists()
|
240
65
|
|
241
66
|
@property
|
242
67
|
def main_script_path(self) -> ScriptPath:
|
@@ -262,14 +87,6 @@ class PagesManager:
|
|
262
87
|
def intended_page_script_hash(self) -> PageHash | None:
|
263
88
|
return self._intended_page_script_hash
|
264
89
|
|
265
|
-
@property
|
266
|
-
def initial_active_script_hash(self) -> PageHash:
|
267
|
-
return self.pages_strategy.initial_active_script_hash
|
268
|
-
|
269
|
-
@property
|
270
|
-
def mpa_version(self) -> int:
|
271
|
-
return 2 if isinstance(self.pages_strategy, PagesStrategyV2) else 1
|
272
|
-
|
273
90
|
def set_current_page_script_hash(self, page_script_hash: PageHash) -> None:
|
274
91
|
self._current_page_script_hash = page_script_hash
|
275
92
|
|
@@ -288,45 +105,57 @@ class PagesManager:
|
|
288
105
|
def get_initial_active_script(
|
289
106
|
self, page_script_hash: PageHash, page_name: PageName
|
290
107
|
) -> PageInfo | None:
|
291
|
-
return
|
292
|
-
|
293
|
-
|
108
|
+
return {
|
109
|
+
# We always run the main script in V2 as it's the common code
|
110
|
+
"script_path": self.main_script_path,
|
111
|
+
"page_script_hash": page_script_hash
|
112
|
+
or self.main_script_hash, # Default Hash
|
113
|
+
}
|
294
114
|
|
295
115
|
def get_pages(self) -> dict[PageHash, PageInfo]:
|
296
|
-
|
116
|
+
# If pages are not set, provide the common page info where
|
117
|
+
# - the main script path is the executing script to start
|
118
|
+
# - the page script hash and name reflects the intended page requested
|
119
|
+
return self._pages or {
|
120
|
+
self.main_script_hash: {
|
121
|
+
"page_script_hash": self.intended_page_script_hash or "",
|
122
|
+
"page_name": self.intended_page_name or "",
|
123
|
+
"icon": "",
|
124
|
+
"script_path": self.main_script_path,
|
125
|
+
}
|
126
|
+
}
|
297
127
|
|
298
128
|
def set_pages(self, pages: dict[PageHash, PageInfo]) -> None:
|
299
|
-
|
300
|
-
if isinstance(self.pages_strategy, PagesStrategyV1):
|
301
|
-
if os.path.exists(self.main_script_parent / "pages"):
|
302
|
-
_LOGGER.warning(
|
303
|
-
"st.navigation was called in an app with a pages/ directory. "
|
304
|
-
"This may cause unusual app behavior. You may want to rename the "
|
305
|
-
"pages/ directory."
|
306
|
-
)
|
307
|
-
PagesManager.DefaultStrategy = PagesStrategyV2
|
308
|
-
self.pages_strategy = PagesStrategyV2(self)
|
309
|
-
|
310
|
-
self.pages_strategy.set_pages(pages)
|
129
|
+
self._pages = pages
|
311
130
|
|
312
131
|
def get_page_script(self, fallback_page_hash: PageHash = "") -> PageInfo | None:
|
313
|
-
|
314
|
-
# in the st.navigation call, but we just swallow the error
|
315
|
-
try:
|
316
|
-
return self.pages_strategy.get_page_script(fallback_page_hash)
|
317
|
-
except NotImplementedError:
|
132
|
+
if self._pages is None:
|
318
133
|
return None
|
319
134
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
135
|
+
if self.intended_page_script_hash:
|
136
|
+
# We assume that if initial page hash is specified, that a page should
|
137
|
+
# exist, so we check out the page script hash or the default page hash
|
138
|
+
# as a backup
|
139
|
+
return self._pages.get(
|
140
|
+
self.intended_page_script_hash,
|
141
|
+
self._pages.get(fallback_page_hash, None),
|
142
|
+
)
|
143
|
+
elif self.intended_page_name:
|
144
|
+
# If a user navigates directly to a non-main page of an app, the
|
145
|
+
# the page name can identify the page script to run
|
146
|
+
return next(
|
147
|
+
filter(
|
148
|
+
# There seems to be this weird bug with mypy where it
|
149
|
+
# thinks that p can be None (which is impossible given the
|
150
|
+
# types of pages), so we add `p and` at the beginning of
|
151
|
+
# the predicate to circumvent this.
|
152
|
+
lambda p: p and (p["url_pathname"] == self.intended_page_name),
|
153
|
+
self._pages.values(),
|
154
|
+
),
|
155
|
+
None,
|
156
|
+
)
|
328
157
|
|
329
|
-
return self.
|
158
|
+
return self._pages.get(fallback_page_hash, None)
|
330
159
|
|
331
160
|
def get_page_script_byte_code(self, script_path: str) -> Any:
|
332
161
|
if self._script_cache is None:
|
@@ -21,7 +21,7 @@ import types
|
|
21
21
|
from contextlib import contextmanager
|
22
22
|
from enum import Enum
|
23
23
|
from timeit import default_timer as timer
|
24
|
-
from typing import TYPE_CHECKING, Callable, Final
|
24
|
+
from typing import TYPE_CHECKING, Callable, Final, Literal, cast
|
25
25
|
|
26
26
|
from blinker import Signal
|
27
27
|
|
@@ -34,6 +34,7 @@ from streamlit.runtime.metrics_util import (
|
|
34
34
|
create_page_profile_message,
|
35
35
|
to_microseconds,
|
36
36
|
)
|
37
|
+
from streamlit.runtime.pages_manager import PagesManager
|
37
38
|
from streamlit.runtime.scriptrunner.exec_code import (
|
38
39
|
exec_func_with_error_handling,
|
39
40
|
modified_sys_path,
|
@@ -58,10 +59,10 @@ from streamlit.runtime.state import (
|
|
58
59
|
SafeSessionState,
|
59
60
|
SessionState,
|
60
61
|
)
|
62
|
+
from streamlit.source_util import page_sort_key
|
61
63
|
|
62
64
|
if TYPE_CHECKING:
|
63
65
|
from streamlit.runtime.fragment import FragmentStorage
|
64
|
-
from streamlit.runtime.pages_manager import PagesManager
|
65
66
|
from streamlit.runtime.scriptrunner.script_cache import ScriptCache
|
66
67
|
from streamlit.runtime.uploaded_file_manager import UploadedFileManager
|
67
68
|
|
@@ -116,6 +117,50 @@ it in the future.
|
|
116
117
|
"""
|
117
118
|
|
118
119
|
|
120
|
+
# For projects that have a pages folder, we assume that this is a script that
|
121
|
+
# is designed to leverage our original v1 version of multi-page apps. This
|
122
|
+
# function will be called to run the script in lieu of the main script. This
|
123
|
+
# function simulates the v1 setup using the modern v2 commands (st.navigation)
|
124
|
+
def _mpa_v1(main_script_path: str):
|
125
|
+
from pathlib import Path
|
126
|
+
|
127
|
+
from streamlit.commands.navigation import PageType, _navigation
|
128
|
+
from streamlit.navigation.page import StreamlitPage
|
129
|
+
|
130
|
+
# Select the folder that should be used for the pages:
|
131
|
+
MAIN_SCRIPT_PATH = Path(main_script_path).resolve()
|
132
|
+
PAGES_FOLDER = MAIN_SCRIPT_PATH.parent / "pages"
|
133
|
+
|
134
|
+
# Read out the my_pages folder and create a page for every script:
|
135
|
+
pages = PAGES_FOLDER.glob("*.py")
|
136
|
+
pages = sorted(
|
137
|
+
[page for page in pages if page.name.endswith(".py")], key=page_sort_key
|
138
|
+
)
|
139
|
+
|
140
|
+
# Use this script as the main page and
|
141
|
+
main_page = StreamlitPage(MAIN_SCRIPT_PATH, default=True)
|
142
|
+
all_pages = [main_page] + [
|
143
|
+
StreamlitPage(PAGES_FOLDER / page.name) for page in pages
|
144
|
+
]
|
145
|
+
# Initialize the navigation with all the pages:
|
146
|
+
position: Literal["sidebar", "hidden"] = (
|
147
|
+
"hidden"
|
148
|
+
if config.get_option("client.showSidebarNavigation") is False
|
149
|
+
else "sidebar"
|
150
|
+
)
|
151
|
+
page = _navigation(
|
152
|
+
cast(list[PageType], all_pages),
|
153
|
+
position=position,
|
154
|
+
expanded=False,
|
155
|
+
)
|
156
|
+
|
157
|
+
if page._page != main_page._page:
|
158
|
+
# Only run the page if it is not pointing to this script:
|
159
|
+
page.run()
|
160
|
+
# Finish the script execution here to only run the selected page
|
161
|
+
raise StopException()
|
162
|
+
|
163
|
+
|
119
164
|
class ScriptRunner:
|
120
165
|
def __init__(
|
121
166
|
self,
|
@@ -590,6 +635,8 @@ class ScriptRunner:
|
|
590
635
|
pass
|
591
636
|
|
592
637
|
else:
|
638
|
+
if PagesManager.uses_pages_directory:
|
639
|
+
_mpa_v1(self._main_script_path)
|
593
640
|
exec(code, module.__dict__)
|
594
641
|
self._fragment_storage.clear(
|
595
642
|
new_fragment_ids=ctx.new_fragment_ids
|
@@ -149,7 +149,7 @@ class ScriptRunContext:
|
|
149
149
|
self.query_string = query_string
|
150
150
|
self.context_info = context_info
|
151
151
|
self.pages_manager.set_current_page_script_hash(page_script_hash)
|
152
|
-
self._active_script_hash = self.pages_manager.
|
152
|
+
self._active_script_hash = self.pages_manager.main_script_hash
|
153
153
|
# Permit set_page_config when the ScriptRunContext is reused on a rerun
|
154
154
|
self._set_page_config_allowed = True
|
155
155
|
self._has_script_started = False
|
streamlit/source_util.py
CHANGED
@@ -15,16 +15,15 @@
|
|
15
15
|
from __future__ import annotations
|
16
16
|
|
17
17
|
import re
|
18
|
-
import
|
19
|
-
from pathlib import Path
|
20
|
-
from typing import Callable, Final, TypedDict
|
18
|
+
from typing import TYPE_CHECKING, Final, TypedDict
|
21
19
|
|
22
|
-
from blinker import Signal
|
23
20
|
from typing_extensions import NotRequired, TypeAlias
|
24
21
|
|
25
22
|
from streamlit.logger import get_logger
|
26
23
|
from streamlit.string_util import extract_leading_emoji
|
27
|
-
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
from pathlib import Path
|
28
27
|
|
29
28
|
_LOGGER: Final = get_logger(__name__)
|
30
29
|
|
@@ -97,88 +96,3 @@ def page_icon_and_name(script_path: Path) -> tuple[str, str]:
|
|
97
96
|
).strip() or extraction.group(1)
|
98
97
|
|
99
98
|
return extract_leading_emoji(icon_and_name)
|
100
|
-
|
101
|
-
|
102
|
-
_pages_cache_lock = threading.RLock()
|
103
|
-
_cached_pages: dict[PageHash, PageInfo] | None = None
|
104
|
-
_on_pages_changed = Signal(doc="Emitted when the pages directory is changed")
|
105
|
-
|
106
|
-
|
107
|
-
def invalidate_pages_cache() -> None:
|
108
|
-
global _cached_pages
|
109
|
-
|
110
|
-
_LOGGER.debug("Pages directory changed")
|
111
|
-
with _pages_cache_lock:
|
112
|
-
_cached_pages = None
|
113
|
-
|
114
|
-
_on_pages_changed.send()
|
115
|
-
|
116
|
-
|
117
|
-
def get_pages(main_script_path_str: ScriptPath) -> dict[PageHash, PageInfo]:
|
118
|
-
global _cached_pages
|
119
|
-
|
120
|
-
# Avoid taking the lock if the pages cache hasn't been invalidated.
|
121
|
-
precached_pages = _cached_pages
|
122
|
-
if precached_pages is not None:
|
123
|
-
return precached_pages
|
124
|
-
|
125
|
-
with _pages_cache_lock:
|
126
|
-
# The cache may have been repopulated while we were waiting to grab
|
127
|
-
# the lock.
|
128
|
-
if _cached_pages is not None:
|
129
|
-
return _cached_pages
|
130
|
-
|
131
|
-
main_script_path = Path(main_script_path_str)
|
132
|
-
main_page_icon, main_page_name = page_icon_and_name(main_script_path)
|
133
|
-
main_script_hash = calc_md5(main_script_path_str)
|
134
|
-
|
135
|
-
# NOTE: We include the script_hash in the dict even though it is
|
136
|
-
# already used as the key because that occasionally makes things
|
137
|
-
# easier for us when we need to iterate over pages.
|
138
|
-
pages: dict[PageHash, PageInfo] = {
|
139
|
-
main_script_hash: {
|
140
|
-
"page_script_hash": main_script_hash,
|
141
|
-
"page_name": main_page_name,
|
142
|
-
"icon": main_page_icon,
|
143
|
-
"script_path": str(main_script_path.resolve()),
|
144
|
-
}
|
145
|
-
}
|
146
|
-
|
147
|
-
pages_dir = main_script_path.parent / "pages"
|
148
|
-
page_scripts = sorted(
|
149
|
-
[
|
150
|
-
f
|
151
|
-
for f in pages_dir.glob("*.py")
|
152
|
-
if not f.name.startswith(".") and not f.name == "__init__.py"
|
153
|
-
],
|
154
|
-
key=page_sort_key,
|
155
|
-
)
|
156
|
-
|
157
|
-
for script_path in page_scripts:
|
158
|
-
script_path_str = str(script_path.resolve())
|
159
|
-
pi, pn = page_icon_and_name(script_path)
|
160
|
-
psh = calc_md5(script_path_str)
|
161
|
-
|
162
|
-
pages[psh] = {
|
163
|
-
"page_script_hash": psh,
|
164
|
-
"page_name": pn,
|
165
|
-
"icon": pi,
|
166
|
-
"script_path": script_path_str,
|
167
|
-
}
|
168
|
-
|
169
|
-
_cached_pages = pages
|
170
|
-
|
171
|
-
return pages
|
172
|
-
|
173
|
-
|
174
|
-
def register_pages_changed_callback(
|
175
|
-
callback: Callable[[str], None],
|
176
|
-
) -> Callable[[], None]:
|
177
|
-
def disconnect():
|
178
|
-
_on_pages_changed.disconnect(callback)
|
179
|
-
|
180
|
-
# weak=False so that we have control of when the pages changed
|
181
|
-
# callback is deregistered.
|
182
|
-
_on_pages_changed.connect(callback, weak=False)
|
183
|
-
|
184
|
-
return disconnect
|
streamlit/static/index.html
CHANGED
@@ -51,7 +51,7 @@
|
|
51
51
|
<script>
|
52
52
|
window.prerenderReady = false
|
53
53
|
</script>
|
54
|
-
<script type="module" crossorigin src="./static/js/index.
|
54
|
+
<script type="module" crossorigin src="./static/js/index.DfNAkKAr.js"></script>
|
55
55
|
<link rel="stylesheet" crossorigin href="./static/css/index.DpJG_94W.css">
|
56
56
|
</head>
|
57
57
|
<body>
|
@@ -1 +1 @@
|
|
1
|
-
import{r as e,E as n,_ as a}from"./index.
|
1
|
+
import{r as e,E as n,_ as a}from"./index.DfNAkKAr.js";var o=e.forwardRef(function(t,r){var l={fill:"currentColor",xmlns:"http://www.w3.org/2000/svg"};return e.createElement(n,a({iconAttrs:l,iconVerticalAlign:"middle",iconViewBox:"0 0 24 24"},t,{ref:r}),e.createElement("path",{fill:"none",d:"M0 0h24v24H0V0z"}),e.createElement("path",{d:"M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z"}))});o.displayName="Delete";var i=e.forwardRef(function(t,r){var l={fill:"currentColor",xmlns:"http://www.w3.org/2000/svg"};return e.createElement(n,a({iconAttrs:l,iconVerticalAlign:"middle",iconViewBox:"0 0 24 24"},t,{ref:r}),e.createElement("rect",{width:24,height:24,fill:"none"}),e.createElement("path",{d:"M18 15v3H6v-3H4v3c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-3h-2zm-1-4l-1.41-1.41L13 12.17V4h-2v8.17L8.41 9.59 7 11l5 5 5-5z"}))});i.displayName="FileDownload";export{o as D,i as F};
|