streamlit-nightly 1.42.3.dev20250226__py2.py3-none-any.whl → 1.42.3.dev20250228__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/arrow.py +6 -5
- streamlit/elements/lib/column_types.py +101 -70
- streamlit/elements/widgets/button.py +18 -9
- streamlit/elements/widgets/chat.py +2 -1
- streamlit/elements/widgets/data_editor.py +6 -5
- streamlit/elements/widgets/number_input.py +3 -2
- streamlit/elements/widgets/slider.py +11 -3
- 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/context.py +47 -2
- 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.BjRCwIvW.js} +1 -1
- streamlit/static/static/js/FileHelper.CGrcmoNK.js +5 -0
- streamlit/static/static/js/{FormClearHelper.BlCIuWCo.js → FormClearHelper.CyjIZnZM.js} +1 -1
- streamlit/static/static/js/{Hooks.DURid8_V.js → Hooks.-DbiQ7Am.js} +1 -1
- streamlit/static/static/js/{InputInstructions.gFFpIc6i.js → InputInstructions.CV_L9pmm.js} +1 -1
- streamlit/static/static/js/{ProgressBar.CtXtbXhv.js → ProgressBar.BMUfDSdU.js} +1 -1
- streamlit/static/static/js/{RenderInPortalIfExists.DKWNQmbm.js → RenderInPortalIfExists.DBak2tzf.js} +1 -1
- streamlit/static/static/js/{Toolbar.CKKwksQV.js → Toolbar.BpCHhFl7.js} +1 -1
- streamlit/static/static/js/{base-input.BUY9TLqO.js → base-input.B4K7gn3m.js} +1 -1
- streamlit/static/static/js/{checkbox.C4L_5Z9H.js → checkbox.CAX7YHDZ.js} +1 -1
- streamlit/static/static/js/{createSuper.DprEKsA6.js → createSuper.D9gT0sOi.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.BArvtwSg.js → data-grid-overlay-editor.gLdGVwGm.js} +1 -1
- streamlit/static/static/js/{downloader.C5pxkE2v.js → downloader.DcsU6eyn.js} +1 -1
- streamlit/static/static/js/{es6.DmKjMqyQ.js → es6.dTL0Du7U.js} +2 -2
- streamlit/static/static/js/{iframeResizer.contentWindow.DhqbXIl1.js → iframeResizer.contentWindow.flH1YmDM.js} +1 -1
- streamlit/static/static/js/{index.D2-IYCcZ.js → index.-WtL21xT.js} +1 -1
- streamlit/static/static/js/{index.B-S4HX7x.js → index.B0VH6CCN.js} +1 -1
- streamlit/static/static/js/{index.xTncca7J.js → index.B6RoN18x.js} +1 -1
- streamlit/static/static/js/index.B7TcBuqC.js +1 -0
- streamlit/static/static/js/{index.B-vrwvgD.js → index.BCg2ff_8.js} +2 -2
- streamlit/static/static/js/{index.CvNhwseQ.js → index.BFxZ-0WR.js} +1 -1
- streamlit/static/static/js/{index.Cd03IYkG.js → index.BJIOzTyr.js} +1 -1
- streamlit/static/static/js/{index.DNr2W0gq.js → index.BMUI3dle.js} +1 -1
- streamlit/static/static/js/{index.KPwXWqBk.js → index.BUNYdCvS.js} +146 -146
- streamlit/static/static/js/{index.Bk3A478D.js → index.BV2qQuMO.js} +6 -6
- streamlit/static/static/js/{index.D2G24Udm.js → index.BX8IgiDA.js} +1 -1
- streamlit/static/static/js/{index.WAlW9lgW.js → index.BYdIAAme.js} +1 -1
- streamlit/static/static/js/{index.RFfgDsnM.js → index.BzXJd4o6.js} +1 -1
- streamlit/static/static/js/index.BzxT1LSG.js +1 -0
- streamlit/static/static/js/{index.NKOzzX__.js → index.CHfW08Eb.js} +1 -1
- streamlit/static/static/js/{index.c_V1HXTs.js → index.CWFsehBI.js} +1 -1
- streamlit/static/static/js/index.C_m4U-ik.js +201 -0
- streamlit/static/static/js/index.CgbCWpVz.js +4537 -0
- streamlit/static/static/js/{index.O5-Wn4Yr.js → index.ChXmN16b.js} +1 -1
- streamlit/static/static/js/{index.BUnWa5Cg.js → index.D-_iawK3.js} +1 -1
- streamlit/static/static/js/{index.BVHfAEko.js → index.D7qPBPzh.js} +1 -1
- streamlit/static/static/js/{index.BSczaRDm.js → index.D9Gtya_A.js} +1 -1
- streamlit/static/static/js/{index.oPGa5nFU.js → index.D9jWe5tN.js} +1 -1
- streamlit/static/static/js/{index.Bds7Anva.js → index.DB-0Yu8-.js} +1 -1
- streamlit/static/static/js/{index.CoQjULdH.js → index.DGu65w1V.js} +3 -3
- streamlit/static/static/js/index.DIHkG0HG.js +12 -0
- streamlit/static/static/js/{index.UE_L1IEZ.js → index.DIgKi8Bp.js} +1 -1
- streamlit/static/static/js/{index.BoBCblDb.js → index.DbiiV9to.js} +1 -1
- streamlit/static/static/js/{index.EIkP6R_d.js → index.DgSb0EiE.js} +1 -1
- streamlit/static/static/js/{index.RrEbViHi.js → index.DkoFwjhv.js} +1 -1
- streamlit/static/static/js/{index.doRPQ8od.js → index.DwAnNzqz.js} +1 -1
- streamlit/static/static/js/{index.ChEJa75P.js → index.PrjikacM.js} +1 -1
- streamlit/static/static/js/{index.BmnSGdtS.js → index.QEh4WZkI.js} +1 -1
- streamlit/static/static/js/{index.CXpOPWFV.js → index.iqovFu7V.js} +1 -1
- streamlit/static/static/js/{index.C3l2zbST.js → index.mRpR0u1n.js} +1 -1
- streamlit/static/static/js/{index.BEnZZwfW.js → index.u-XgtJxJ.js} +3 -3
- streamlit/static/static/js/{index.DtIKjlNZ.js → index.y-qJHV46.js} +1 -1
- streamlit/static/static/js/{input.EkJ_AsTP.js → input.DqgaXn2o.js} +1 -1
- streamlit/static/static/js/{memory.BFs17dZE.js → memory.DszNDPLb.js} +1 -1
- streamlit/static/static/js/{mergeWith.B4u0whL-.js → mergeWith.DFTSkXIf.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.DwazN43x.js → number-overlay-editor.C8_ha9YJ.js} +1 -1
- streamlit/static/static/js/{possibleConstructorReturn.CZfYLVBb.js → possibleConstructorReturn.cS4Ei6et.js} +1 -1
- streamlit/static/static/js/{sandbox.PjijA1v1.js → sandbox.Bj-BfqPP.js} +1 -1
- streamlit/static/static/js/{textarea.BGGnQXtw.js → textarea.BrvMyGxB.js} +1 -1
- streamlit/static/static/js/{timepicker.eLEnk1tw.js → timepicker.Ds8_GzH8.js} +2 -2
- streamlit/static/static/js/{toConsumableArray.CgKj9NUY.js → toConsumableArray.RfoD5uEy.js} +1 -1
- streamlit/static/static/js/{uniqueId.CPmOGhdZ.js → uniqueId.R_vr2E5T.js} +1 -1
- streamlit/static/static/js/{useBasicWidgetState.Bw_fxTtX.js → useBasicWidgetState.Cglgg1W1.js} +1 -1
- streamlit/static/static/js/{useOnInputChange.D8qi7pm4.js → useOnInputChange.CFTHBDsA.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.BjVufR9b.js → withFullScreenWrapper.BcyXoen-.js} +1 -1
- streamlit/testing/v1/app_test.py +8 -10
- streamlit/user_info.py +3 -0
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/RECORD +95 -95
- 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.CTqvpoOz.js +0 -12
- 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.dev20250228.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/top_level.txt +0 -0
@@ -30,7 +30,7 @@ from streamlit.proto import SessionStatus_pb2 as streamlit_dot_proto_dot_Session
|
|
30
30
|
from streamlit.proto import AuthRedirect_pb2 as streamlit_dot_proto_dot_AuthRedirect__pb2
|
31
31
|
|
32
32
|
|
33
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n streamlit/proto/ForwardMsg.proto\x1a\x1fstreamlit/proto/AutoRerun.proto\x1a\x1cstreamlit/proto/Common.proto\x1a\x1bstreamlit/proto/Delta.proto\x1a\x1dstreamlit/proto/GitInfo.proto\x1a\x1astreamlit/proto/Logo.proto\x1a streamlit/proto/Navigation.proto\x1a streamlit/proto/NewSession.proto\x1a streamlit/proto/PageConfig.proto\x1a\x1estreamlit/proto/PageInfo.proto\x1a!streamlit/proto/PageProfile.proto\x1a\"streamlit/proto/PageNotFound.proto\x1a\"streamlit/proto/PagesChanged.proto\x1a#streamlit/proto/ParentMessage.proto\x1a\"streamlit/proto/SessionEvent.proto\x1a#streamlit/proto/SessionStatus.proto\x1a\"streamlit/proto/AuthRedirect.proto\"\
|
33
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n streamlit/proto/ForwardMsg.proto\x1a\x1fstreamlit/proto/AutoRerun.proto\x1a\x1cstreamlit/proto/Common.proto\x1a\x1bstreamlit/proto/Delta.proto\x1a\x1dstreamlit/proto/GitInfo.proto\x1a\x1astreamlit/proto/Logo.proto\x1a streamlit/proto/Navigation.proto\x1a streamlit/proto/NewSession.proto\x1a streamlit/proto/PageConfig.proto\x1a\x1estreamlit/proto/PageInfo.proto\x1a!streamlit/proto/PageProfile.proto\x1a\"streamlit/proto/PageNotFound.proto\x1a\"streamlit/proto/PagesChanged.proto\x1a#streamlit/proto/ParentMessage.proto\x1a\"streamlit/proto/SessionEvent.proto\x1a#streamlit/proto/SessionStatus.proto\x1a\"streamlit/proto/AuthRedirect.proto\"\xcb\x07\n\nForwardMsg\x12\x0c\n\x04hash\x18\x01 \x01(\t\x12%\n\x08metadata\x18\x02 \x01(\x0b\x32\x13.ForwardMsgMetadata\x12\"\n\x0bnew_session\x18\x04 \x01(\x0b\x32\x0b.NewSessionH\x00\x12\x17\n\x05\x64\x65lta\x18\x05 \x01(\x0b\x32\x06.DeltaH\x00\x12&\n\x11page_info_changed\x18\x0c \x01(\x0b\x32\t.PageInfoH\x00\x12*\n\x13page_config_changed\x18\r \x01(\x0b\x32\x0b.PageConfigH\x00\x12;\n\x0fscript_finished\x18\x06 \x01(\x0e\x32 .ForwardMsg.ScriptFinishedStatusH\x00\x12$\n\x10git_info_changed\x18\x0e \x01(\x0b\x32\x08.GitInfoH\x00\x12$\n\x0cpage_profile\x18\x12 \x01(\x0b\x32\x0c.PageProfileH\x00\x12\x30\n\x16session_status_changed\x18\t \x01(\x0b\x32\x0e.SessionStatusH\x00\x12&\n\rsession_event\x18\n \x01(\x0b\x32\r.SessionEventH\x00\x12!\n\nnavigation\x18\x17 \x01(\x0b\x32\x0b.NavigationH\x00\x12\'\n\x0epage_not_found\x18\x0f \x01(\x0b\x32\r.PageNotFoundH\x00\x12*\n\rpages_changed\x18\x10 \x01(\x0b\x32\r.PagesChangedB\x02\x18\x01H\x00\x12/\n\x12\x66ile_urls_response\x18\x13 \x01(\x0b\x32\x11.FileURLsResponseH\x00\x12 \n\nauto_rerun\x18\x15 \x01(\x0b\x32\n.AutoRerunH\x00\x12\x15\n\x04logo\x18\x16 \x01(\x0b\x32\x05.LogoH\x00\x12&\n\rauth_redirect\x18\x18 \x01(\x0b\x32\r.AuthRedirectH\x00\x12(\n\x0eparent_message\x18\x14 \x01(\x0b\x32\x0e.ParentMessageH\x00\x12\x12\n\x08ref_hash\x18\x0b \x01(\tH\x00\x12\x1d\n\x15\x64\x65\x62ug_last_backmsg_id\x18\x11 \x01(\t\"\x98\x01\n\x14ScriptFinishedStatus\x12\x19\n\x15\x46INISHED_SUCCESSFULLY\x10\x00\x12\x1f\n\x1b\x46INISHED_WITH_COMPILE_ERROR\x10\x01\x12\x1c\n\x18\x46INISHED_EARLY_FOR_RERUN\x10\x02\x12&\n\"FINISHED_FRAGMENT_RUN_SUCCESSFULLY\x10\x03\x42\x06\n\x04typeJ\x04\x08\x07\x10\x08J\x04\x08\x08\x10\t\"\x8e\x01\n\x12\x46orwardMsgMetadata\x12\x11\n\tcacheable\x18\x01 \x01(\x08\x12\x12\n\ndelta_path\x18\x02 \x03(\r\x12\x35\n\x16\x65lement_dimension_spec\x18\x03 \x01(\x0b\x32\x15.ElementDimensionSpec\x12\x1a\n\x12\x61\x63tive_script_hash\x18\x04 \x01(\t\"5\n\x14\x45lementDimensionSpec\x12\r\n\x05width\x18\x01 \x01(\r\x12\x0e\n\x06height\x18\x02 \x01(\r\"/\n\x0e\x46orwardMsgList\x12\x1d\n\x08messages\x18\x01 \x03(\x0b\x32\x0b.ForwardMsgB/\n\x1c\x63om.snowflake.apps.streamlitB\x0f\x46orwardMsgProtob\x06proto3')
|
34
34
|
|
35
35
|
_globals = globals()
|
36
36
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
@@ -38,14 +38,16 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'streamlit.proto.ForwardMsg_
|
|
38
38
|
if not _descriptor._USE_C_DESCRIPTORS:
|
39
39
|
_globals['DESCRIPTOR']._loaded_options = None
|
40
40
|
_globals['DESCRIPTOR']._serialized_options = b'\n\034com.snowflake.apps.streamlitB\017ForwardMsgProto'
|
41
|
+
_globals['_FORWARDMSG'].fields_by_name['pages_changed']._loaded_options = None
|
42
|
+
_globals['_FORWARDMSG'].fields_by_name['pages_changed']._serialized_options = b'\030\001'
|
41
43
|
_globals['_FORWARDMSG']._serialized_start=575
|
42
|
-
_globals['_FORWARDMSG']._serialized_end=
|
43
|
-
_globals['_FORWARDMSG_SCRIPTFINISHEDSTATUS']._serialized_start=
|
44
|
-
_globals['_FORWARDMSG_SCRIPTFINISHEDSTATUS']._serialized_end=
|
45
|
-
_globals['_FORWARDMSGMETADATA']._serialized_start=
|
46
|
-
_globals['_FORWARDMSGMETADATA']._serialized_end=
|
47
|
-
_globals['_ELEMENTDIMENSIONSPEC']._serialized_start=
|
48
|
-
_globals['_ELEMENTDIMENSIONSPEC']._serialized_end=
|
49
|
-
_globals['_FORWARDMSGLIST']._serialized_start=
|
50
|
-
_globals['_FORWARDMSGLIST']._serialized_end=
|
44
|
+
_globals['_FORWARDMSG']._serialized_end=1546
|
45
|
+
_globals['_FORWARDMSG_SCRIPTFINISHEDSTATUS']._serialized_start=1374
|
46
|
+
_globals['_FORWARDMSG_SCRIPTFINISHEDSTATUS']._serialized_end=1526
|
47
|
+
_globals['_FORWARDMSGMETADATA']._serialized_start=1549
|
48
|
+
_globals['_FORWARDMSGMETADATA']._serialized_end=1691
|
49
|
+
_globals['_ELEMENTDIMENSIONSPEC']._serialized_start=1693
|
50
|
+
_globals['_ELEMENTDIMENSIONSPEC']._serialized_end=1746
|
51
|
+
_globals['_FORWARDMSGLIST']._serialized_start=1748
|
52
|
+
_globals['_FORWARDMSGLIST']._serialized_end=1795
|
51
53
|
# @@protoc_insertion_point(module_scope)
|
@@ -29,7 +29,9 @@ DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
|
|
29
29
|
|
30
30
|
@typing.final
|
31
31
|
class PagesChanged(google.protobuf.message.Message):
|
32
|
-
"""
|
32
|
+
"""DEPRECATED - We don't use the proto anymore.
|
33
|
+
Message used to tell the client that the app's pages have changed.
|
34
|
+
"""
|
33
35
|
|
34
36
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
35
37
|
|
streamlit/runtime/app_session.py
CHANGED
@@ -50,7 +50,6 @@ from streamlit.watcher import LocalSourcesWatcher
|
|
50
50
|
|
51
51
|
if TYPE_CHECKING:
|
52
52
|
from streamlit.proto.BackMsg_pb2 import BackMsg
|
53
|
-
from streamlit.proto.PagesChanged_pb2 import PagesChanged
|
54
53
|
from streamlit.runtime.script_data import ScriptData
|
55
54
|
from streamlit.runtime.scriptrunner.script_cache import ScriptCache
|
56
55
|
from streamlit.runtime.state import SessionState
|
@@ -200,9 +199,6 @@ class AppSession:
|
|
200
199
|
self._stop_config_listener = config.on_config_parsed(
|
201
200
|
self._on_source_file_changed, force_connect=True
|
202
201
|
)
|
203
|
-
self._stop_pages_listener = self._pages_manager.register_pages_changed_callback(
|
204
|
-
self._on_pages_changed
|
205
|
-
)
|
206
202
|
secrets_singleton.file_change_listener.connect(self._on_secrets_file_changed)
|
207
203
|
|
208
204
|
def disconnect_file_watchers(self) -> None:
|
@@ -494,14 +490,6 @@ class AppSession:
|
|
494
490
|
# `_on_source_file_changed` just for this purpose sounded finicky.
|
495
491
|
self._on_source_file_changed()
|
496
492
|
|
497
|
-
def _on_pages_changed(self, _) -> None:
|
498
|
-
msg = ForwardMsg()
|
499
|
-
self._populate_app_pages(msg.pages_changed, self._pages_manager.get_pages())
|
500
|
-
self._enqueue_forward_msg(msg)
|
501
|
-
|
502
|
-
if self._local_sources_watcher is not None:
|
503
|
-
self._local_sources_watcher.update_watched_pages()
|
504
|
-
|
505
493
|
def _clear_queue(self, fragment_ids_this_run: list[str] | None = None) -> None:
|
506
494
|
self._browser_queue.clear(
|
507
495
|
retain_lifecycle_msgs=True, fragment_ids_this_run=fragment_ids_this_run
|
@@ -886,7 +874,7 @@ class AppSession:
|
|
886
874
|
self._enqueue_forward_msg(msg)
|
887
875
|
|
888
876
|
def _populate_app_pages(
|
889
|
-
self, msg: NewSession
|
877
|
+
self, msg: NewSession, pages: dict[PageHash, PageInfo]
|
890
878
|
) -> None:
|
891
879
|
for page_script_hash, page_info in pages.items():
|
892
880
|
page_proto = msg.app_pages.add()
|
streamlit/runtime/context.py
CHANGED
@@ -146,6 +146,8 @@ class ContextProxy:
|
|
146
146
|
|
147
147
|
Examples
|
148
148
|
--------
|
149
|
+
**Example 1: Access all available headers**
|
150
|
+
|
149
151
|
Show a dictionary of headers (with only the last instance of any
|
150
152
|
repeated key):
|
151
153
|
|
@@ -153,6 +155,8 @@ class ContextProxy:
|
|
153
155
|
>>>
|
154
156
|
>>> st.context.headers
|
155
157
|
|
158
|
+
**Example 2: Access a specific header**
|
159
|
+
|
156
160
|
Show the value of a specific header (or the last instance if it's
|
157
161
|
repeated):
|
158
162
|
|
@@ -183,12 +187,16 @@ class ContextProxy:
|
|
183
187
|
|
184
188
|
Examples
|
185
189
|
--------
|
190
|
+
**Example 1: Access all available cookies**
|
191
|
+
|
186
192
|
Show a dictionary of cookies:
|
187
193
|
|
188
194
|
>>> import streamlit as st
|
189
195
|
>>>
|
190
196
|
>>> st.context.cookies
|
191
197
|
|
198
|
+
**Example 2: Access a specific cookie**
|
199
|
+
|
192
200
|
Show the value of a specific cookie:
|
193
201
|
|
194
202
|
>>> import streamlit as st
|
@@ -209,7 +217,26 @@ class ContextProxy:
|
|
209
217
|
@property
|
210
218
|
@gather_metrics("context.timezone")
|
211
219
|
def timezone(self) -> str | None:
|
212
|
-
"""The timezone of the user's browser
|
220
|
+
"""The read-only timezone of the user's browser.
|
221
|
+
|
222
|
+
Example
|
223
|
+
-------
|
224
|
+
Access the user's timezone, and format a datetime to display locally:
|
225
|
+
|
226
|
+
>>> import streamlit as st
|
227
|
+
>>> from datetime import datetime, timezone
|
228
|
+
>>> import pytz
|
229
|
+
>>>
|
230
|
+
>>> tz = st.context.timezone
|
231
|
+
>>> tz_obj = pytz.timezone(tz)
|
232
|
+
>>>
|
233
|
+
>>> now = datetime.now(timezone.utc)
|
234
|
+
>>>
|
235
|
+
>>> f"The user's timezone is {tz}."
|
236
|
+
>>> f"The UTC time is {now}."
|
237
|
+
>>> f"The user's local time is {now.astimezone(tz_obj)}"
|
238
|
+
|
239
|
+
"""
|
213
240
|
ctx = get_script_run_ctx()
|
214
241
|
|
215
242
|
if ctx is None or ctx.context_info is None:
|
@@ -219,7 +246,25 @@ class ContextProxy:
|
|
219
246
|
@property
|
220
247
|
@gather_metrics("context.timezone_offset")
|
221
248
|
def timezone_offset(self) -> int | None:
|
222
|
-
"""The timezone offset of the user's browser
|
249
|
+
"""The read-only timezone offset of the user's browser.
|
250
|
+
|
251
|
+
Example
|
252
|
+
-------
|
253
|
+
Access the user's timezone offset, and format a datetime to display locally:
|
254
|
+
|
255
|
+
>>> import streamlit as st
|
256
|
+
>>> from datetime import datetime, timezone, timedelta
|
257
|
+
>>>
|
258
|
+
>>> tzoff = st.context.timezone_offset
|
259
|
+
>>> tz_obj = timezone(-timedelta(minutes=tzoff))
|
260
|
+
>>>
|
261
|
+
>>> now = datetime.now(timezone.utc)
|
262
|
+
>>>
|
263
|
+
>>> f"The user's timezone is {tz}."
|
264
|
+
>>> f"The UTC time is {now}."
|
265
|
+
>>> f"The user's local time is {now.astimezone(tz_obj)}"
|
266
|
+
|
267
|
+
"""
|
223
268
|
ctx = get_script_run_ctx()
|
224
269
|
if ctx is None or ctx.context_info is None:
|
225
270
|
return None
|
@@ -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
|