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.
Files changed (93) hide show
  1. streamlit/commands/navigation.py +16 -4
  2. streamlit/commands/page_config.py +1 -2
  3. streamlit/elements/widgets/button.py +5 -7
  4. streamlit/elements/widgets/chat.py +2 -1
  5. streamlit/errors.py +11 -9
  6. streamlit/navigation/page.py +2 -10
  7. streamlit/proto/ChatInput_pb2.py +6 -6
  8. streamlit/proto/ChatInput_pb2.pyi +5 -1
  9. streamlit/proto/ForwardMsg_pb2.py +12 -10
  10. streamlit/proto/PagesChanged_pb2.pyi +3 -1
  11. streamlit/runtime/app_session.py +1 -13
  12. streamlit/runtime/pages_manager.py +61 -232
  13. streamlit/runtime/scriptrunner/script_runner.py +49 -2
  14. streamlit/runtime/scriptrunner_utils/script_run_context.py +1 -1
  15. streamlit/source_util.py +4 -90
  16. streamlit/static/index.html +1 -1
  17. streamlit/static/static/js/{FileDownload.esm.CWn5B5ZN.js → FileDownload.esm.rdZtCw5-.js} +1 -1
  18. streamlit/static/static/js/FileHelper.BztOEKJy.js +5 -0
  19. streamlit/static/static/js/{FormClearHelper.BlCIuWCo.js → FormClearHelper.Drd1fMJc.js} +1 -1
  20. streamlit/static/static/js/{Hooks.DURid8_V.js → Hooks.DWd0hyk4.js} +1 -1
  21. streamlit/static/static/js/{InputInstructions.gFFpIc6i.js → InputInstructions.B7xrWTG0.js} +1 -1
  22. streamlit/static/static/js/{ProgressBar.CtXtbXhv.js → ProgressBar.CBgJiTH-.js} +1 -1
  23. streamlit/static/static/js/{RenderInPortalIfExists.DKWNQmbm.js → RenderInPortalIfExists.US28tQOn.js} +1 -1
  24. streamlit/static/static/js/{Toolbar.CKKwksQV.js → Toolbar.CAOt-xaL.js} +1 -1
  25. streamlit/static/static/js/{base-input.BUY9TLqO.js → base-input.CauNaJqr.js} +1 -1
  26. streamlit/static/static/js/{checkbox.C4L_5Z9H.js → checkbox.ChzWCtre.js} +1 -1
  27. streamlit/static/static/js/{createSuper.DprEKsA6.js → createSuper.D_39RJB6.js} +1 -1
  28. streamlit/static/static/js/{data-grid-overlay-editor.BArvtwSg.js → data-grid-overlay-editor.rIelkOVS.js} +1 -1
  29. streamlit/static/static/js/{downloader.C5pxkE2v.js → downloader.Dz5I28nG.js} +1 -1
  30. streamlit/static/static/js/{es6.DmKjMqyQ.js → es6.DwnEjMMn.js} +2 -2
  31. streamlit/static/static/js/{iframeResizer.contentWindow.DhqbXIl1.js → iframeResizer.contentWindow.BRiRifwd.js} +1 -1
  32. streamlit/static/static/js/{index.BEnZZwfW.js → index.BE6MTF-B.js} +1 -1
  33. streamlit/static/static/js/{index.c_V1HXTs.js → index.BUdhIGhT.js} +1 -1
  34. streamlit/static/static/js/{index.B-S4HX7x.js → index.BWeYb5ES.js} +1 -1
  35. streamlit/static/static/js/{index.CTqvpoOz.js → index.Bb4OB39A.js} +1 -1
  36. streamlit/static/static/js/{index.BoBCblDb.js → index.BrAW6bFJ.js} +1 -1
  37. streamlit/static/static/js/{index.CXpOPWFV.js → index.CAHGh74D.js} +1 -1
  38. streamlit/static/static/js/{index.UE_L1IEZ.js → index.CBre8Aui.js} +1 -1
  39. streamlit/static/static/js/{index.B-vrwvgD.js → index.CM12P2T1.js} +1 -1
  40. streamlit/static/static/js/{index.Cd03IYkG.js → index.CO3dZWKG.js} +1 -1
  41. streamlit/static/static/js/{index.CvNhwseQ.js → index.CUz1_nAm.js} +1 -1
  42. streamlit/static/static/js/{index.BVHfAEko.js → index.C_C365T5.js} +1 -1
  43. streamlit/static/static/js/{index.NKOzzX__.js → index.CmYiLKNX.js} +1 -1
  44. streamlit/static/static/js/{index.RFfgDsnM.js → index.CoUzNoRf.js} +1 -1
  45. streamlit/static/static/js/{index.RrEbViHi.js → index.Ct7MPsA3.js} +1 -1
  46. streamlit/static/static/js/{index.D2G24Udm.js → index.D1K6NCBQ.js} +1 -1
  47. streamlit/static/static/js/{index.CoQjULdH.js → index.D2xM-XzG.js} +1 -1
  48. streamlit/static/static/js/{index.D2-IYCcZ.js → index.D9rv1PQ0.js} +1 -1
  49. streamlit/static/static/js/{index.ChEJa75P.js → index.DIeegLbv.js} +1 -1
  50. streamlit/static/static/js/{index.oPGa5nFU.js → index.DJvwxFLn.js} +1 -1
  51. streamlit/static/static/js/{index.DtIKjlNZ.js → index.DKuQg2D4.js} +1 -1
  52. streamlit/static/static/js/{index.Bk3A478D.js → index.DWFLx6Tc.js} +6 -6
  53. streamlit/static/static/js/{index.WAlW9lgW.js → index.DYUhWSq6.js} +1 -1
  54. streamlit/static/static/js/{index.Bds7Anva.js → index.Db3jGJxa.js} +1 -1
  55. streamlit/static/static/js/{index.KPwXWqBk.js → index.DfNAkKAr.js} +145 -145
  56. streamlit/static/static/js/{index.EIkP6R_d.js → index.DgNQKEeQ.js} +1 -1
  57. streamlit/static/static/js/{index.BmnSGdtS.js → index.Dj91melR.js} +1 -1
  58. streamlit/static/static/js/{index.C3l2zbST.js → index.DkKZlAsv.js} +1 -1
  59. streamlit/static/static/js/index.DkgVNhWs.js +1 -0
  60. streamlit/static/static/js/{index.DNr2W0gq.js → index.Dzp8iBkF.js} +1 -1
  61. streamlit/static/static/js/index.LL2DLZZA.js +201 -0
  62. streamlit/static/static/js/index.ORHz8Y8P.js +4537 -0
  63. streamlit/static/static/js/{index.O5-Wn4Yr.js → index.V3Vj2d9m.js} +1 -1
  64. streamlit/static/static/js/{index.BUnWa5Cg.js → index.YNWoT4uW.js} +1 -1
  65. streamlit/static/static/js/index.aL_kqyvR.js +1 -0
  66. streamlit/static/static/js/{index.BSczaRDm.js → index.fP6rBxlc.js} +1 -1
  67. streamlit/static/static/js/{index.xTncca7J.js → index.pvQjxG7y.js} +1 -1
  68. streamlit/static/static/js/{index.doRPQ8od.js → index.wxpL02VY.js} +1 -1
  69. streamlit/static/static/js/{input.EkJ_AsTP.js → input.D_dn5-2t.js} +1 -1
  70. streamlit/static/static/js/{memory.BFs17dZE.js → memory.pnMTtWQR.js} +1 -1
  71. streamlit/static/static/js/{mergeWith.B4u0whL-.js → mergeWith.CRrYodeI.js} +1 -1
  72. streamlit/static/static/js/{number-overlay-editor.DwazN43x.js → number-overlay-editor.C65GBS2P.js} +1 -1
  73. streamlit/static/static/js/{possibleConstructorReturn.CZfYLVBb.js → possibleConstructorReturn.BnQk7lq_.js} +1 -1
  74. streamlit/static/static/js/{sandbox.PjijA1v1.js → sandbox.CrxXt1li.js} +1 -1
  75. streamlit/static/static/js/{textarea.BGGnQXtw.js → textarea.DiFyO5JY.js} +1 -1
  76. streamlit/static/static/js/{timepicker.eLEnk1tw.js → timepicker.B1Y7LIk4.js} +1 -1
  77. streamlit/static/static/js/{toConsumableArray.CgKj9NUY.js → toConsumableArray.BWpBUK-j.js} +1 -1
  78. streamlit/static/static/js/{uniqueId.CPmOGhdZ.js → uniqueId.7ecIUkUs.js} +1 -1
  79. streamlit/static/static/js/{useBasicWidgetState.Bw_fxTtX.js → useBasicWidgetState.deG0KPm0.js} +1 -1
  80. streamlit/static/static/js/{useOnInputChange.D8qi7pm4.js → useOnInputChange.CQ7sD7CO.js} +1 -1
  81. streamlit/static/static/js/{withFullScreenWrapper.BjVufR9b.js → withFullScreenWrapper.D0wnBu3k.js} +1 -1
  82. streamlit/testing/v1/app_test.py +8 -10
  83. {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/METADATA +1 -1
  84. {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/RECORD +88 -88
  85. streamlit/static/static/js/FileHelper.Bii_mi1A.js +0 -5
  86. streamlit/static/static/js/index.BAfr2RC0.js +0 -3865
  87. streamlit/static/static/js/index.BCErGZwr.js +0 -1
  88. streamlit/static/static/js/index.DhcYAj3f.js +0 -1
  89. streamlit/static/static/js/index.u1gL_Cm_.js +0 -201
  90. {streamlit_nightly-1.42.3.dev20250226.data → streamlit_nightly-1.42.3.dev20250227.data}/scripts/streamlit.cmd +0 -0
  91. {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/WHEEL +0 -0
  92. {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250227.dist-info}/entry_points.txt +0 -0
  93. {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, Callable, Final
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 based on the
215
- strategy. By default, PagesManager uses V1 which relies on the original
216
- assumption that there exists a `pages` directory with all the scripts.
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
- DefaultStrategy: type[PagesStrategyV1 | PagesStrategyV2] = PagesStrategyV1
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 self.pages_strategy.get_initial_active_script(
292
- page_script_hash, page_name
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
- return self.pages_strategy.get_pages()
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
- # Manually setting the pages indicates we are using MPA v2.
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
- # We assume the pages strategy is V2 cause this is used
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
- def register_pages_changed_callback(
321
- self,
322
- callback: Callable[[str], None],
323
- ) -> Callable[[], None]:
324
- """Register a callback to be called when the set of pages changes.
325
-
326
- The callback will be called with the path changed.
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.pages_strategy.register_pages_changed_callback(callback)
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.initial_active_script_hash
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 threading
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
- from streamlit.util import calc_md5
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
@@ -51,7 +51,7 @@
51
51
  <script>
52
52
  window.prerenderReady = false
53
53
  </script>
54
- <script type="module" crossorigin src="./static/js/index.KPwXWqBk.js"></script>
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.KPwXWqBk.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};
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};