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.
Files changed (101) hide show
  1. streamlit/commands/navigation.py +16 -4
  2. streamlit/commands/page_config.py +1 -2
  3. streamlit/elements/arrow.py +6 -5
  4. streamlit/elements/lib/column_types.py +101 -70
  5. streamlit/elements/widgets/button.py +18 -9
  6. streamlit/elements/widgets/chat.py +2 -1
  7. streamlit/elements/widgets/data_editor.py +6 -5
  8. streamlit/elements/widgets/number_input.py +3 -2
  9. streamlit/elements/widgets/slider.py +11 -3
  10. streamlit/errors.py +11 -9
  11. streamlit/navigation/page.py +2 -10
  12. streamlit/proto/ChatInput_pb2.py +6 -6
  13. streamlit/proto/ChatInput_pb2.pyi +5 -1
  14. streamlit/proto/ForwardMsg_pb2.py +12 -10
  15. streamlit/proto/PagesChanged_pb2.pyi +3 -1
  16. streamlit/runtime/app_session.py +1 -13
  17. streamlit/runtime/context.py +47 -2
  18. streamlit/runtime/pages_manager.py +61 -232
  19. streamlit/runtime/scriptrunner/script_runner.py +49 -2
  20. streamlit/runtime/scriptrunner_utils/script_run_context.py +1 -1
  21. streamlit/source_util.py +4 -90
  22. streamlit/static/index.html +1 -1
  23. streamlit/static/static/js/{FileDownload.esm.CWn5B5ZN.js → FileDownload.esm.BjRCwIvW.js} +1 -1
  24. streamlit/static/static/js/FileHelper.CGrcmoNK.js +5 -0
  25. streamlit/static/static/js/{FormClearHelper.BlCIuWCo.js → FormClearHelper.CyjIZnZM.js} +1 -1
  26. streamlit/static/static/js/{Hooks.DURid8_V.js → Hooks.-DbiQ7Am.js} +1 -1
  27. streamlit/static/static/js/{InputInstructions.gFFpIc6i.js → InputInstructions.CV_L9pmm.js} +1 -1
  28. streamlit/static/static/js/{ProgressBar.CtXtbXhv.js → ProgressBar.BMUfDSdU.js} +1 -1
  29. streamlit/static/static/js/{RenderInPortalIfExists.DKWNQmbm.js → RenderInPortalIfExists.DBak2tzf.js} +1 -1
  30. streamlit/static/static/js/{Toolbar.CKKwksQV.js → Toolbar.BpCHhFl7.js} +1 -1
  31. streamlit/static/static/js/{base-input.BUY9TLqO.js → base-input.B4K7gn3m.js} +1 -1
  32. streamlit/static/static/js/{checkbox.C4L_5Z9H.js → checkbox.CAX7YHDZ.js} +1 -1
  33. streamlit/static/static/js/{createSuper.DprEKsA6.js → createSuper.D9gT0sOi.js} +1 -1
  34. streamlit/static/static/js/{data-grid-overlay-editor.BArvtwSg.js → data-grid-overlay-editor.gLdGVwGm.js} +1 -1
  35. streamlit/static/static/js/{downloader.C5pxkE2v.js → downloader.DcsU6eyn.js} +1 -1
  36. streamlit/static/static/js/{es6.DmKjMqyQ.js → es6.dTL0Du7U.js} +2 -2
  37. streamlit/static/static/js/{iframeResizer.contentWindow.DhqbXIl1.js → iframeResizer.contentWindow.flH1YmDM.js} +1 -1
  38. streamlit/static/static/js/{index.D2-IYCcZ.js → index.-WtL21xT.js} +1 -1
  39. streamlit/static/static/js/{index.B-S4HX7x.js → index.B0VH6CCN.js} +1 -1
  40. streamlit/static/static/js/{index.xTncca7J.js → index.B6RoN18x.js} +1 -1
  41. streamlit/static/static/js/index.B7TcBuqC.js +1 -0
  42. streamlit/static/static/js/{index.B-vrwvgD.js → index.BCg2ff_8.js} +2 -2
  43. streamlit/static/static/js/{index.CvNhwseQ.js → index.BFxZ-0WR.js} +1 -1
  44. streamlit/static/static/js/{index.Cd03IYkG.js → index.BJIOzTyr.js} +1 -1
  45. streamlit/static/static/js/{index.DNr2W0gq.js → index.BMUI3dle.js} +1 -1
  46. streamlit/static/static/js/{index.KPwXWqBk.js → index.BUNYdCvS.js} +146 -146
  47. streamlit/static/static/js/{index.Bk3A478D.js → index.BV2qQuMO.js} +6 -6
  48. streamlit/static/static/js/{index.D2G24Udm.js → index.BX8IgiDA.js} +1 -1
  49. streamlit/static/static/js/{index.WAlW9lgW.js → index.BYdIAAme.js} +1 -1
  50. streamlit/static/static/js/{index.RFfgDsnM.js → index.BzXJd4o6.js} +1 -1
  51. streamlit/static/static/js/index.BzxT1LSG.js +1 -0
  52. streamlit/static/static/js/{index.NKOzzX__.js → index.CHfW08Eb.js} +1 -1
  53. streamlit/static/static/js/{index.c_V1HXTs.js → index.CWFsehBI.js} +1 -1
  54. streamlit/static/static/js/index.C_m4U-ik.js +201 -0
  55. streamlit/static/static/js/index.CgbCWpVz.js +4537 -0
  56. streamlit/static/static/js/{index.O5-Wn4Yr.js → index.ChXmN16b.js} +1 -1
  57. streamlit/static/static/js/{index.BUnWa5Cg.js → index.D-_iawK3.js} +1 -1
  58. streamlit/static/static/js/{index.BVHfAEko.js → index.D7qPBPzh.js} +1 -1
  59. streamlit/static/static/js/{index.BSczaRDm.js → index.D9Gtya_A.js} +1 -1
  60. streamlit/static/static/js/{index.oPGa5nFU.js → index.D9jWe5tN.js} +1 -1
  61. streamlit/static/static/js/{index.Bds7Anva.js → index.DB-0Yu8-.js} +1 -1
  62. streamlit/static/static/js/{index.CoQjULdH.js → index.DGu65w1V.js} +3 -3
  63. streamlit/static/static/js/index.DIHkG0HG.js +12 -0
  64. streamlit/static/static/js/{index.UE_L1IEZ.js → index.DIgKi8Bp.js} +1 -1
  65. streamlit/static/static/js/{index.BoBCblDb.js → index.DbiiV9to.js} +1 -1
  66. streamlit/static/static/js/{index.EIkP6R_d.js → index.DgSb0EiE.js} +1 -1
  67. streamlit/static/static/js/{index.RrEbViHi.js → index.DkoFwjhv.js} +1 -1
  68. streamlit/static/static/js/{index.doRPQ8od.js → index.DwAnNzqz.js} +1 -1
  69. streamlit/static/static/js/{index.ChEJa75P.js → index.PrjikacM.js} +1 -1
  70. streamlit/static/static/js/{index.BmnSGdtS.js → index.QEh4WZkI.js} +1 -1
  71. streamlit/static/static/js/{index.CXpOPWFV.js → index.iqovFu7V.js} +1 -1
  72. streamlit/static/static/js/{index.C3l2zbST.js → index.mRpR0u1n.js} +1 -1
  73. streamlit/static/static/js/{index.BEnZZwfW.js → index.u-XgtJxJ.js} +3 -3
  74. streamlit/static/static/js/{index.DtIKjlNZ.js → index.y-qJHV46.js} +1 -1
  75. streamlit/static/static/js/{input.EkJ_AsTP.js → input.DqgaXn2o.js} +1 -1
  76. streamlit/static/static/js/{memory.BFs17dZE.js → memory.DszNDPLb.js} +1 -1
  77. streamlit/static/static/js/{mergeWith.B4u0whL-.js → mergeWith.DFTSkXIf.js} +1 -1
  78. streamlit/static/static/js/{number-overlay-editor.DwazN43x.js → number-overlay-editor.C8_ha9YJ.js} +1 -1
  79. streamlit/static/static/js/{possibleConstructorReturn.CZfYLVBb.js → possibleConstructorReturn.cS4Ei6et.js} +1 -1
  80. streamlit/static/static/js/{sandbox.PjijA1v1.js → sandbox.Bj-BfqPP.js} +1 -1
  81. streamlit/static/static/js/{textarea.BGGnQXtw.js → textarea.BrvMyGxB.js} +1 -1
  82. streamlit/static/static/js/{timepicker.eLEnk1tw.js → timepicker.Ds8_GzH8.js} +2 -2
  83. streamlit/static/static/js/{toConsumableArray.CgKj9NUY.js → toConsumableArray.RfoD5uEy.js} +1 -1
  84. streamlit/static/static/js/{uniqueId.CPmOGhdZ.js → uniqueId.R_vr2E5T.js} +1 -1
  85. streamlit/static/static/js/{useBasicWidgetState.Bw_fxTtX.js → useBasicWidgetState.Cglgg1W1.js} +1 -1
  86. streamlit/static/static/js/{useOnInputChange.D8qi7pm4.js → useOnInputChange.CFTHBDsA.js} +1 -1
  87. streamlit/static/static/js/{withFullScreenWrapper.BjVufR9b.js → withFullScreenWrapper.BcyXoen-.js} +1 -1
  88. streamlit/testing/v1/app_test.py +8 -10
  89. streamlit/user_info.py +3 -0
  90. {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/METADATA +1 -1
  91. {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/RECORD +95 -95
  92. streamlit/static/static/js/FileHelper.Bii_mi1A.js +0 -5
  93. streamlit/static/static/js/index.BAfr2RC0.js +0 -3865
  94. streamlit/static/static/js/index.BCErGZwr.js +0 -1
  95. streamlit/static/static/js/index.CTqvpoOz.js +0 -12
  96. streamlit/static/static/js/index.DhcYAj3f.js +0 -1
  97. streamlit/static/static/js/index.u1gL_Cm_.js +0 -201
  98. {streamlit_nightly-1.42.3.dev20250226.data → streamlit_nightly-1.42.3.dev20250228.data}/scripts/streamlit.cmd +0 -0
  99. {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/WHEEL +0 -0
  100. {streamlit_nightly-1.42.3.dev20250226.dist-info → streamlit_nightly-1.42.3.dev20250228.dist-info}/entry_points.txt +0 -0
  101. {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\"\xc7\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.PagesChangedH\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')
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=1542
43
- _globals['_FORWARDMSG_SCRIPTFINISHEDSTATUS']._serialized_start=1370
44
- _globals['_FORWARDMSG_SCRIPTFINISHEDSTATUS']._serialized_end=1522
45
- _globals['_FORWARDMSGMETADATA']._serialized_start=1545
46
- _globals['_FORWARDMSGMETADATA']._serialized_end=1687
47
- _globals['_ELEMENTDIMENSIONSPEC']._serialized_start=1689
48
- _globals['_ELEMENTDIMENSIONSPEC']._serialized_end=1742
49
- _globals['_FORWARDMSGLIST']._serialized_start=1744
50
- _globals['_FORWARDMSGLIST']._serialized_end=1791
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
- """Message used to tell the client that the app's pages have changed."""
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
 
@@ -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 | PagesChanged, pages: dict[PageHash, PageInfo]
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()
@@ -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, read-only."""
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, read-only."""
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, 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