streamlit-nightly 1.53.2.dev20260125__py3-none-any.whl → 1.53.2.dev20260128__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/logo.py +81 -25
- streamlit/config.py +11 -0
- streamlit/deprecation_util.py +19 -1
- streamlit/elements/arrow.py +2 -1
- streamlit/elements/lib/built_in_chart_utils.py +2 -2
- streamlit/elements/lib/options_selector_utils.py +72 -22
- streamlit/elements/widgets/select_slider.py +123 -37
- streamlit/hello/plotting_demo.py +19 -12
- streamlit/proto/Logo_pb2.py +5 -3
- streamlit/proto/Logo_pb2.pyi +25 -1
- streamlit/proto/NewSession_pb2.py +24 -22
- streamlit/proto/NewSession_pb2.pyi +23 -1
- streamlit/proto/Slider_pb2.py +6 -6
- streamlit/proto/Slider_pb2.pyi +9 -1
- streamlit/runtime/app_session.py +19 -0
- streamlit/runtime/scriptrunner/script_runner.py +17 -0
- streamlit/runtime/scriptrunner_utils/script_run_context.py +13 -10
- streamlit/runtime/state/__init__.py +7 -1
- streamlit/runtime/state/common.py +13 -0
- streamlit/runtime/state/query_params.py +494 -6
- streamlit/runtime/state/session_state.py +178 -3
- streamlit/runtime/state/widgets.py +26 -1
- streamlit/static/index.html +1 -1
- streamlit/static/manifest.json +299 -299
- streamlit/static/static/js/{ErrorOutline.esm.CIFYUdwC.js → ErrorOutline.esm.D71F8ziR.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.DWVTnTHm.js → FileDownload.esm.yTkppsJy.js} +1 -1
- streamlit/static/static/js/{FileHelper.BPYQIPd1.js → FileHelper.hUOqtbwa.js} +1 -1
- streamlit/static/static/js/{FormClearHelper.CypmvhYZ.js → FormClearHelper.DN8D_YXO.js} +1 -1
- streamlit/static/static/js/{InputInstructions.Bi62hDTQ.js → InputInstructions.DbssY6d4.js} +1 -1
- streamlit/static/static/js/{Particles.yebG0VuV.js → Particles.BznyVdfo.js} +1 -1
- streamlit/static/static/js/{ProgressBar.Dy9CI6w4.js → ProgressBar.C5uBOtcx.js} +1 -1
- streamlit/static/static/js/{StreamlitSyntaxHighlighter.Btk92CPv.js → StreamlitSyntaxHighlighter.Nf1895x-.js} +1 -1
- streamlit/static/static/js/{TableChart.esm.DBeVaFNt.js → TableChart.esm.DHKzVs3a.js} +1 -1
- streamlit/static/static/js/{Toolbar.DC2Tp-qb.js → Toolbar.CQsWYXer.js} +1 -1
- streamlit/static/static/js/{WidgetLabelHelpIconInline.3DnEd9BK.js → WidgetLabelHelpIconInline.6xCU76OE.js} +1 -1
- streamlit/static/static/js/{base-input.7Sj6pVk0.js → base-input.Cs-E6S71.js} +1 -1
- streamlit/static/static/js/{checkbox.CcUx3XuQ.js → checkbox.OTGupu18.js} +1 -1
- streamlit/static/static/js/{createDownloadLinkElement.DZuwkCqy.js → createDownloadLinkElement.DnBEQQbK.js} +1 -1
- streamlit/static/static/js/{data-grid-overlay-editor.Dw-AewlN.js → data-grid-overlay-editor.COiiMi5r.js} +1 -1
- streamlit/static/static/js/{downloader.Bsx5M2Du.js → downloader.K0GUNeuj.js} +1 -1
- streamlit/static/static/js/embed.o8HvK3mH.js +193 -0
- streamlit/static/static/js/{es6.BpAqZaR_.js → es6.BHy5pqTP.js} +2 -2
- streamlit/static/static/js/{formatNumber.DjehVPVS.js → formatNumber.BK7h0k2z.js} +1 -1
- streamlit/static/static/js/{iconPosition.D02OPE-d.js → iconPosition.2YynQUxu.js} +1 -1
- streamlit/static/static/js/{iframeResizer.contentWindow.xtstqPd7.js → iframeResizer.contentWindow.D5h3hQuU.js} +1 -1
- streamlit/static/static/js/{index.5H98WqjT.js → index.5zqfJ-in.js} +1 -1
- streamlit/static/static/js/{index.B5tD5YeV.js → index.6c-qDsD7.js} +1 -1
- streamlit/static/static/js/{index.CA0RmxJF.js → index.8MlRyIxN.js} +1 -1
- streamlit/static/static/js/{index.DSSapl3Q.js → index.BIqcOZ_u.js} +1 -1
- streamlit/static/static/js/{index.DJjSqPAx.js → index.BPdmXoYW.js} +1 -1
- streamlit/static/static/js/{index.iXzAofuY.js → index.BZ-GJVxB.js} +2 -2
- streamlit/static/static/js/{index.CKUBdVQ9.js → index.BfMPq234.js} +1 -1
- streamlit/static/static/js/{index.B8-HOwf1.js → index.Bfo1cXfC.js} +1 -1
- streamlit/static/static/js/{index.-faJDV20.js → index.Bgf49D1Z.js} +1 -1
- streamlit/static/static/js/{index.CgARjn28.js → index.Bqmx23jK.js} +1 -1
- streamlit/static/static/js/{index.D6Z9hKJY.js → index.BtRWcqZV.js} +1 -1
- streamlit/static/static/js/{index.ZIA43eTF.js → index.BtuskCwg.js} +1 -1
- streamlit/static/static/js/{index.BV6XgCij.js → index.BzTVI_BY.js} +1 -1
- streamlit/static/static/js/{index.DDu_qTm0.js → index.C2EoeVjP.js} +1 -1
- streamlit/static/static/js/{index.8FPw0_gD.js → index.C65jHNhe.js} +1 -1
- streamlit/static/static/js/{index.D6J2UPzF.js → index.C6wyTXhz.js} +1 -1
- streamlit/static/static/js/{index.CGbvkEtg.js → index.C7wst9Tm.js} +1 -1
- streamlit/static/static/js/{index.BIcJe97b.js → index.COh5V_89.js} +1 -1
- streamlit/static/static/js/index.CSPY26T2.js +1 -0
- streamlit/static/static/js/{index.CEwnDCn9.js → index.CUkhn-vu.js} +1 -1
- streamlit/static/static/js/{index.DO2T-QzF.js → index.CX0KdFyR.js} +1 -1
- streamlit/static/static/js/{index.BDlI2pRp.js → index.CYhhEdja.js} +1 -1
- streamlit/static/static/js/{index.DgLRJfs3.js → index.CZf7Go1Z.js} +1 -1
- streamlit/static/static/js/{index.DZv5AoR1.js → index.Cb03y5I8.js} +1 -1
- streamlit/static/static/js/{index.BVhVdVeE.js → index.CdsyTabv.js} +1 -1
- streamlit/static/static/js/{index.JL0uGAeJ.js → index.CgVv04GM.js} +1 -1
- streamlit/static/static/js/index.CjRU8O1O.js +2 -0
- streamlit/static/static/js/{index.BqfJJr3c.js → index.CwtpGPHA.js} +1 -1
- streamlit/static/static/js/{index.iF5zYERg.js → index.CxWzt6oi.js} +1 -1
- streamlit/static/static/js/{index.m3dn5Bai.js → index.DBPWUJsj.js} +5 -5
- streamlit/static/static/js/{index.D9RL5sRp.js → index.DJfMW0Gy.js} +1 -1
- streamlit/static/static/js/{index.BOkpEbJS.js → index.DLUSo6de.js} +1 -1
- streamlit/static/static/js/{index.S-mjkUeF.js → index.DL_yE83J.js} +1 -1
- streamlit/static/static/js/{index.BK9S5qug.js → index.DVRCyxMp.js} +1 -1
- streamlit/static/static/js/{index.D_TIyPF4.js → index.Dc5-tFdw.js} +1 -1
- streamlit/static/static/js/index.DcngUOyD.js +2 -0
- streamlit/static/static/js/{index.B9gbSNsw.js → index.Dh3PJIlq.js} +1 -1
- streamlit/static/static/js/{index.BvZbnSMC.js → index.DlgcEr0f.js} +1 -1
- streamlit/static/static/js/{index.Bo1ztye0.js → index.DxGXuhh6.js} +1 -1
- streamlit/static/static/js/{index.x1B588Xu.js → index.DxfYCrPp.js} +1 -1
- streamlit/static/static/js/{index.CyDHwK5P.js → index.HmRK3HyC.js} +1 -1
- streamlit/static/static/js/{index.DdxofXV8.js → index.TjMWsKSH.js} +3 -3
- streamlit/static/static/js/{index.Bri1T2TS.js → index.VwDKazgt.js} +1 -1
- streamlit/static/static/js/{index.BB_iwaVr.js → index.aCorc3Yt.js} +34 -34
- streamlit/static/static/js/{index.CdRwiHPm.js → index.cfuZ69LI.js} +1 -1
- streamlit/static/static/js/{index.C9v49R-a.js → index.hlAfdSqC.js} +1 -1
- streamlit/static/static/js/{index.BGTMh3Uu.js → index.iUV9rb8C.js} +1 -1
- streamlit/static/static/js/{index.XFMDBL5n.js → index.q0ceUXt6.js} +1 -1
- streamlit/static/static/js/{input.VYKyGuhi.js → input.CXGIJ7D6.js} +1 -1
- streamlit/static/static/js/{main.u5Bb3MY7.js → main.CCVkbuxC.js} +1 -1
- streamlit/static/static/js/{memory.BOMt4yAV.js → memory.CNbnYs2A.js} +1 -1
- streamlit/static/static/js/{number-overlay-editor.CihlAHgl.js → number-overlay-editor.CvI6wkld.js} +1 -1
- streamlit/static/static/js/{pandasStylerUtils.BuqSgXpk.js → pandasStylerUtils.CFSReOTm.js} +1 -1
- streamlit/static/static/js/{sandbox.COGR4pqz.js → sandbox.Bld0L3us.js} +1 -1
- streamlit/static/static/js/{styled-components.BEf3c4IJ.js → styled-components.BoUHK6TA.js} +1 -1
- streamlit/static/static/js/{throttle.Bl-XsA9N.js → throttle.ByDFm7WV.js} +1 -1
- streamlit/static/static/js/{timepicker.B-HgBYlK.js → timepicker.CN6CUZEL.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.BrQebwtE.js → toConsumableArray.DwMycSpg.js} +1 -1
- streamlit/static/static/js/uniqueId.DcCWa2cf.js +1 -0
- streamlit/static/static/js/{useBasicWidgetState.8WwISl9r.js → useBasicWidgetState.Bg0ZMUt5.js} +1 -1
- streamlit/static/static/js/{useIntlLocale.D37LWdCR.js → useIntlLocale.DgBUDcPA.js} +1 -1
- streamlit/static/static/js/{useTextInputAutoExpand.Bb_KqJvq.js → useTextInputAutoExpand.DDBezxks.js} +1 -1
- streamlit/static/static/js/{useUpdateUiValue.D1BLS5t7.js → useUpdateUiValue.Df1h6fXC.js} +1 -1
- streamlit/static/static/js/{useWaveformController.Ce0-qTws.js → useWaveformController.DbWw5MEk.js} +1 -1
- streamlit/static/static/js/{withCalculatedWidth.BX2K3UVv.js → withCalculatedWidth.YaK0HIIP.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.CqfGs8T2.js → withFullScreenWrapper.CcWCKoY8.js} +1 -1
- streamlit/testing/v1/element_tree.py +23 -8
- streamlit/web/bootstrap.py +5 -2
- streamlit/web/server/server_util.py +22 -0
- {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/RECORD +120 -120
- streamlit/static/static/js/embed.C7by6AoE.js +0 -195
- streamlit/static/static/js/index.Bhy8EBYI.js +0 -2
- streamlit/static/static/js/index.C5ehUqNt.js +0 -2
- streamlit/static/static/js/index.m4WkwGMu.js +0 -1
- streamlit/static/static/js/uniqueId.8R4hbkYl.js +0 -1
- {streamlit_nightly-1.53.2.dev20260125.data → streamlit_nightly-1.53.2.dev20260128.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.53.2.dev20260125.dist-info → streamlit_nightly-1.53.2.dev20260128.dist-info}/top_level.txt +0 -0
streamlit/commands/logo.py
CHANGED
|
@@ -23,8 +23,10 @@ from streamlit.elements.lib.image_utils import AtomicImage, image_to_url
|
|
|
23
23
|
from streamlit.elements.lib.layout_utils import LayoutConfig
|
|
24
24
|
from streamlit.errors import StreamlitAPIException
|
|
25
25
|
from streamlit.proto.ForwardMsg_pb2 import ForwardMsg
|
|
26
|
+
from streamlit.proto.Logo_pb2 import Logo as LogoProto
|
|
26
27
|
from streamlit.runtime.metrics_util import gather_metrics
|
|
27
28
|
from streamlit.runtime.scriptrunner_utils.script_run_context import get_script_run_ctx
|
|
29
|
+
from streamlit.string_util import is_emoji, validate_material_icon
|
|
28
30
|
|
|
29
31
|
|
|
30
32
|
def _invalid_logo_text(field_name: str) -> str:
|
|
@@ -35,13 +37,59 @@ def _invalid_logo_text(field_name: str) -> str:
|
|
|
35
37
|
)
|
|
36
38
|
|
|
37
39
|
|
|
40
|
+
def _process_logo_image(
|
|
41
|
+
image: AtomicImage | str, image_id: str
|
|
42
|
+
) -> tuple[LogoProto.ImageType.ValueType, str]:
|
|
43
|
+
"""Detects the image type and prepares the image data for the frontend.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
image :
|
|
48
|
+
The image that was provided by the user. Can be an image file,
|
|
49
|
+
emoji, or material icon string.
|
|
50
|
+
image_id : str
|
|
51
|
+
The image ID used when serving local images via the media file manager.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
tuple[ImageType, str]
|
|
56
|
+
The detected image type and the prepared image data.
|
|
57
|
+
|
|
58
|
+
Raises
|
|
59
|
+
------
|
|
60
|
+
StreamlitAPIException
|
|
61
|
+
If the image is an empty string or a plain text string that is not
|
|
62
|
+
a valid file path, URL, emoji, or material icon.
|
|
63
|
+
"""
|
|
64
|
+
ImageType = LogoProto.ImageType # noqa: N806
|
|
65
|
+
|
|
66
|
+
# Check if it's a material icon
|
|
67
|
+
if isinstance(image, str) and image.startswith(":material"):
|
|
68
|
+
return ImageType.ICON, validate_material_icon(image)
|
|
69
|
+
|
|
70
|
+
# Check if it's an emoji
|
|
71
|
+
if isinstance(image, str) and is_emoji(image):
|
|
72
|
+
return ImageType.EMOJI, image
|
|
73
|
+
|
|
74
|
+
# Otherwise, treat it as an image file
|
|
75
|
+
image_url = image_to_url(
|
|
76
|
+
image,
|
|
77
|
+
layout_config=LayoutConfig(width="content"),
|
|
78
|
+
clamp=False,
|
|
79
|
+
channels="RGB",
|
|
80
|
+
output_format="auto",
|
|
81
|
+
image_id=image_id,
|
|
82
|
+
)
|
|
83
|
+
return ImageType.IMAGE, image_url
|
|
84
|
+
|
|
85
|
+
|
|
38
86
|
@gather_metrics("logo")
|
|
39
87
|
def logo(
|
|
40
|
-
image: AtomicImage,
|
|
88
|
+
image: AtomicImage | str,
|
|
41
89
|
*, # keyword-only args:
|
|
42
90
|
size: Literal["small", "medium", "large"] = "medium",
|
|
43
91
|
link: str | None = None,
|
|
44
|
-
icon_image: AtomicImage | None = None,
|
|
92
|
+
icon_image: AtomicImage | str | None = None,
|
|
45
93
|
) -> None:
|
|
46
94
|
r"""
|
|
47
95
|
Renders a logo in the upper-left corner of your app and its sidebar.
|
|
@@ -60,12 +108,26 @@ def logo(
|
|
|
60
108
|
|
|
61
109
|
Parameters
|
|
62
110
|
----------
|
|
63
|
-
image: Anything supported by st.image (except list)
|
|
111
|
+
image: Anything supported by st.image (except list), emoji, or icon
|
|
64
112
|
The image to display in the upper-left corner of your app and its
|
|
65
|
-
sidebar.
|
|
66
|
-
a list. If ``icon_image`` is also provided, then Streamlit will only
|
|
113
|
+
sidebar. If ``icon_image`` is also provided, then Streamlit will only
|
|
67
114
|
display ``image`` in the sidebar.
|
|
68
115
|
|
|
116
|
+
In addition to any of the types supported by |st.image|_ (except list),
|
|
117
|
+
the following strings are valid:
|
|
118
|
+
|
|
119
|
+
- A single-character emoji. For example, you can set ``image="🏠"``
|
|
120
|
+
or ``image="🚀"``. Emoji short codes are not supported.
|
|
121
|
+
|
|
122
|
+
- An icon from the Material Symbols library (rounded style) in the
|
|
123
|
+
format ``":material/icon_name:"`` where "icon_name" is the name
|
|
124
|
+
of the icon in snake case.
|
|
125
|
+
|
|
126
|
+
For example, ``image=":material/home:"`` will display the
|
|
127
|
+
Home icon. Find additional icons in the `Material Symbols \
|
|
128
|
+
<https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded>`_
|
|
129
|
+
font library.
|
|
130
|
+
|
|
69
131
|
Streamlit scales the image to a max height set by ``size`` and a max
|
|
70
132
|
width to fit within the sidebar.
|
|
71
133
|
|
|
@@ -84,15 +146,19 @@ def logo(
|
|
|
84
146
|
The external URL to open when a user clicks on the logo. The URL must
|
|
85
147
|
start with "\http://" or "\https://". If ``link`` is ``None`` (default),
|
|
86
148
|
the logo will not include a hyperlink.
|
|
87
|
-
icon_image: Anything supported by st.image (except list) or None
|
|
149
|
+
icon_image: Anything supported by st.image (except list), emoji, icon, or None
|
|
88
150
|
An optional, typically smaller image to replace ``image`` in the
|
|
89
|
-
upper-left corner when the sidebar is closed.
|
|
90
|
-
types supported by ``st.image`` except a list. If ``icon_image`` is
|
|
151
|
+
upper-left corner when the sidebar is closed. If ``icon_image`` is
|
|
91
152
|
``None`` (default), Streamlit will always display ``image`` in the
|
|
92
153
|
upper-left corner, regardless of whether the sidebar is open or closed.
|
|
93
154
|
Otherwise, Streamlit will render ``icon_image`` in the upper-left
|
|
94
155
|
corner of the app when the sidebar is closed.
|
|
95
156
|
|
|
157
|
+
In addition to any of the types supported by ``st.image`` (except list),
|
|
158
|
+
this also accepts single-character emojis (e.g., ``"🏠"``) and Material
|
|
159
|
+
icons (e.g., ``":material/home:"``). See the ``image`` parameter for
|
|
160
|
+
more details on these formats.
|
|
161
|
+
|
|
96
162
|
Streamlit scales the image to a max height set by ``size`` and a max
|
|
97
163
|
width to fit within the sidebar. If the sidebar is closed, the max
|
|
98
164
|
width is retained from when it was last open.
|
|
@@ -143,15 +209,9 @@ def logo(
|
|
|
143
209
|
fwd_msg = ForwardMsg()
|
|
144
210
|
|
|
145
211
|
try:
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
clamp=False,
|
|
150
|
-
channels="RGB",
|
|
151
|
-
output_format="auto",
|
|
152
|
-
image_id="logo",
|
|
153
|
-
)
|
|
154
|
-
fwd_msg.logo.image = image_url
|
|
212
|
+
image_type, image_value = _process_logo_image(image, "logo")
|
|
213
|
+
fwd_msg.logo.image = image_value
|
|
214
|
+
fwd_msg.logo.image_type = image_type
|
|
155
215
|
except Exception as ex:
|
|
156
216
|
raise StreamlitAPIException(_invalid_logo_text("image")) from ex
|
|
157
217
|
|
|
@@ -167,15 +227,11 @@ def logo(
|
|
|
167
227
|
|
|
168
228
|
if icon_image:
|
|
169
229
|
try:
|
|
170
|
-
|
|
171
|
-
icon_image,
|
|
172
|
-
layout_config=LayoutConfig(width="content"),
|
|
173
|
-
clamp=False,
|
|
174
|
-
channels="RGB",
|
|
175
|
-
output_format="auto",
|
|
176
|
-
image_id="icon-image",
|
|
230
|
+
icon_image_type, icon_image_value = _process_logo_image(
|
|
231
|
+
icon_image, "icon-image"
|
|
177
232
|
)
|
|
178
|
-
fwd_msg.logo.icon_image =
|
|
233
|
+
fwd_msg.logo.icon_image = icon_image_value
|
|
234
|
+
fwd_msg.logo.icon_image_type = icon_image_type
|
|
179
235
|
except Exception as ex:
|
|
180
236
|
raise StreamlitAPIException(_invalid_logo_text("icon_image")) from ex
|
|
181
237
|
|
streamlit/config.py
CHANGED
|
@@ -617,6 +617,17 @@ _create_option(
|
|
|
617
617
|
scriptable=True,
|
|
618
618
|
)
|
|
619
619
|
|
|
620
|
+
_create_option(
|
|
621
|
+
"client.showErrorLinks",
|
|
622
|
+
description="""
|
|
623
|
+
Controls whether to show external help links (Google, ChatGPT) in
|
|
624
|
+
error displays. Can be "auto" (shows on localhost only), true (always
|
|
625
|
+
show), or false (never show).
|
|
626
|
+
""",
|
|
627
|
+
default_val="auto",
|
|
628
|
+
type_=str,
|
|
629
|
+
)
|
|
630
|
+
|
|
620
631
|
# Config Section: Runner #
|
|
621
632
|
|
|
622
633
|
_create_section("runner", "Settings for how Streamlit executes your script")
|
streamlit/deprecation_util.py
CHANGED
|
@@ -21,12 +21,17 @@ from typing import Any, Final, TypeVar, cast
|
|
|
21
21
|
import streamlit
|
|
22
22
|
from streamlit import config
|
|
23
23
|
from streamlit.logger import get_logger
|
|
24
|
+
from streamlit.util import calc_md5
|
|
24
25
|
|
|
25
26
|
_LOGGER: Final = get_logger(__name__)
|
|
26
27
|
|
|
27
28
|
TFunc = TypeVar("TFunc", bound=Callable[..., Any])
|
|
28
29
|
TObj = TypeVar("TObj", bound=object)
|
|
29
30
|
|
|
31
|
+
# Set to track which deprecation warnings have been shown (by message hash)
|
|
32
|
+
# when show_once=True is used
|
|
33
|
+
_shown_warnings: set[str] = set()
|
|
34
|
+
|
|
30
35
|
|
|
31
36
|
def _error_details_in_browser_enabled() -> bool:
|
|
32
37
|
"""True if we should print deprecation warnings to the browser.
|
|
@@ -42,7 +47,9 @@ def _error_details_in_browser_enabled() -> bool:
|
|
|
42
47
|
)
|
|
43
48
|
|
|
44
49
|
|
|
45
|
-
def show_deprecation_warning(
|
|
50
|
+
def show_deprecation_warning(
|
|
51
|
+
message: str, show_in_browser: bool = True, show_once: bool = False
|
|
52
|
+
) -> None:
|
|
46
53
|
"""Show a deprecation warning message.
|
|
47
54
|
|
|
48
55
|
Parameters
|
|
@@ -55,7 +62,18 @@ def show_deprecation_warning(message: str, show_in_browser: bool = True) -> None
|
|
|
55
62
|
set `client.showErrorDetails` to "full" or the legacy True value. All
|
|
56
63
|
other values ("stacktrace", "type", "none", False) will hide deprecation
|
|
57
64
|
warnings in the browser (but still log them to the console).
|
|
65
|
+
show_once : bool, default=False
|
|
66
|
+
If True, the warning will only be shown once per unique message (based on
|
|
67
|
+
message hash). Subsequent calls with the same message will be skipped.
|
|
68
|
+
This is useful for warnings that may be triggered many times during a
|
|
69
|
+
script run.
|
|
58
70
|
"""
|
|
71
|
+
if show_once:
|
|
72
|
+
message_hash = calc_md5(message)
|
|
73
|
+
if message_hash in _shown_warnings:
|
|
74
|
+
return
|
|
75
|
+
_shown_warnings.add(message_hash)
|
|
76
|
+
|
|
59
77
|
if _error_details_in_browser_enabled() and show_in_browser:
|
|
60
78
|
streamlit.warning(message)
|
|
61
79
|
|
streamlit/elements/arrow.py
CHANGED
|
@@ -983,7 +983,8 @@ class ArrowMixin:
|
|
|
983
983
|
" If you have a specific use-case that requires the `add_rows` "
|
|
984
984
|
"functionality, please tell us via this "
|
|
985
985
|
"[issue on Github](https://github.com/streamlit/streamlit/issues/13063).",
|
|
986
|
-
show_in_browser=
|
|
986
|
+
show_in_browser=True,
|
|
987
|
+
show_once=True,
|
|
987
988
|
)
|
|
988
989
|
|
|
989
990
|
return _arrow_add_rows(self.dg, data, **kwargs)
|
|
@@ -862,9 +862,9 @@ def _maybe_melt(
|
|
|
862
862
|
color_column = _MELTED_COLOR_COLUMN_NAME
|
|
863
863
|
|
|
864
864
|
columns_to_leave_alone = [x_column]
|
|
865
|
-
if size_column:
|
|
865
|
+
if size_column and size_column not in columns_to_leave_alone:
|
|
866
866
|
columns_to_leave_alone.append(size_column)
|
|
867
|
-
if sort_column:
|
|
867
|
+
if sort_column and sort_column not in columns_to_leave_alone:
|
|
868
868
|
columns_to_leave_alone.append(sort_column)
|
|
869
869
|
|
|
870
870
|
df = _melt_data(
|
|
@@ -297,6 +297,7 @@ def validate_and_sync_value_with_options(
|
|
|
297
297
|
This function has a side-effect: if the value is not found in the options
|
|
298
298
|
and a key is provided, it will update session state with the new value.
|
|
299
299
|
|
|
300
|
+
|
|
300
301
|
Parameters
|
|
301
302
|
----------
|
|
302
303
|
current_value
|
|
@@ -321,29 +322,16 @@ def validate_and_sync_value_with_options(
|
|
|
321
322
|
if current_value is None:
|
|
322
323
|
return current_value, False
|
|
323
324
|
|
|
324
|
-
#
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
return current_value, False
|
|
332
|
-
except ValueError:
|
|
333
|
-
pass # Fall through to reset logic below
|
|
334
|
-
else:
|
|
335
|
-
# For non-Enum values, use format_func comparison. This handles custom objects
|
|
336
|
-
# without __eq__ where widget values are deepcopied and the deepcopied instances
|
|
337
|
-
# would fail identity comparison with ==.
|
|
338
|
-
try:
|
|
339
|
-
formatted_value = format_func(current_value)
|
|
340
|
-
except Exception:
|
|
341
|
-
# format_func failed - value is invalid
|
|
342
|
-
formatted_value = None
|
|
343
|
-
|
|
344
|
-
formatted_options_set = {format_func(o) for o in opt}
|
|
345
|
-
if formatted_value is not None and formatted_value in formatted_options_set:
|
|
325
|
+
# Use format_func comparison for all values. This correctly handles:
|
|
326
|
+
# - Custom objects without __eq__ (deepcopied instances)
|
|
327
|
+
# - Enum values (already from current class due to serde deserialization)
|
|
328
|
+
formatted_options_set = {format_func(o) for o in opt}
|
|
329
|
+
try:
|
|
330
|
+
formatted_value = format_func(current_value)
|
|
331
|
+
if formatted_value in formatted_options_set:
|
|
346
332
|
return current_value, False
|
|
333
|
+
except Exception: # noqa: S110
|
|
334
|
+
pass # format_func failed - value is invalid, fall through to reset
|
|
347
335
|
|
|
348
336
|
# Value not in options - reset to default
|
|
349
337
|
if default_index is not None and len(opt) > 0:
|
|
@@ -424,3 +412,65 @@ def validate_and_sync_multiselect_value_with_options(
|
|
|
424
412
|
get_session_state().reset_state_value(str(key), valid_values)
|
|
425
413
|
|
|
426
414
|
return valid_values, True
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def validate_and_sync_range_value_with_options(
|
|
418
|
+
current_value: tuple[T, T],
|
|
419
|
+
opt: Sequence[T],
|
|
420
|
+
default_indices: list[int],
|
|
421
|
+
key: str | int | None,
|
|
422
|
+
format_func: Callable[[Any], str] = str,
|
|
423
|
+
) -> tuple[tuple[T, T], bool]:
|
|
424
|
+
"""Validate a range value (tuple of two values) against options.
|
|
425
|
+
|
|
426
|
+
If either value in the range is not found in options, the entire range is
|
|
427
|
+
reset to the default. This function has a side-effect: if the values are
|
|
428
|
+
invalid and a key is provided, it will update session state with the new value.
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
current_value
|
|
433
|
+
The current range value (tuple of two values) to validate.
|
|
434
|
+
opt
|
|
435
|
+
The sequence of valid options.
|
|
436
|
+
default_indices
|
|
437
|
+
The default indices to reset to if value is invalid. Should contain
|
|
438
|
+
at least one index; if only one index is provided, the second default
|
|
439
|
+
will be the last option.
|
|
440
|
+
key
|
|
441
|
+
The widget key for session state updates.
|
|
442
|
+
format_func
|
|
443
|
+
Function to format options for comparison. Used to compare values by their
|
|
444
|
+
string representation instead of using == directly.
|
|
445
|
+
|
|
446
|
+
Returns
|
|
447
|
+
-------
|
|
448
|
+
tuple[tuple[T, T], bool]
|
|
449
|
+
A tuple of (validated_value, value_was_reset).
|
|
450
|
+
"""
|
|
451
|
+
if len(opt) == 0:
|
|
452
|
+
return current_value, False
|
|
453
|
+
|
|
454
|
+
formatted_options_set = {format_func(o) for o in opt}
|
|
455
|
+
|
|
456
|
+
def is_valid(val: Any) -> bool:
|
|
457
|
+
"""Check if a value exists in options via format_func comparison."""
|
|
458
|
+
try:
|
|
459
|
+
return format_func(val) in formatted_options_set
|
|
460
|
+
except Exception:
|
|
461
|
+
return False
|
|
462
|
+
|
|
463
|
+
def get_default_range() -> tuple[T, T]:
|
|
464
|
+
"""Get the default range value."""
|
|
465
|
+
end_idx = default_indices[1] if len(default_indices) > 1 else len(opt) - 1
|
|
466
|
+
return (opt[default_indices[0]], opt[end_idx])
|
|
467
|
+
|
|
468
|
+
# Validate both values in the range.
|
|
469
|
+
if is_valid(current_value[0]) and is_valid(current_value[1]):
|
|
470
|
+
return current_value, False
|
|
471
|
+
|
|
472
|
+
# Either value is invalid - reset entire range.
|
|
473
|
+
new_value = get_default_range()
|
|
474
|
+
if key is not None:
|
|
475
|
+
get_session_state().reset_state_value(str(key), new_value)
|
|
476
|
+
return new_value, True
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
-
from dataclasses import dataclass
|
|
18
17
|
from textwrap import dedent
|
|
19
18
|
from typing import (
|
|
20
19
|
TYPE_CHECKING,
|
|
@@ -30,9 +29,12 @@ from streamlit.dataframe_util import OptionSequence, convert_anything_to_list
|
|
|
30
29
|
from streamlit.elements.lib.form_utils import current_form_id
|
|
31
30
|
from streamlit.elements.lib.layout_utils import LayoutConfig, validate_width
|
|
32
31
|
from streamlit.elements.lib.options_selector_utils import (
|
|
32
|
+
create_mappings,
|
|
33
33
|
index_,
|
|
34
34
|
maybe_coerce_enum,
|
|
35
35
|
maybe_coerce_enum_sequence,
|
|
36
|
+
validate_and_sync_range_value_with_options,
|
|
37
|
+
validate_and_sync_value_with_options,
|
|
36
38
|
)
|
|
37
39
|
from streamlit.elements.lib.policies import (
|
|
38
40
|
check_widget_policies,
|
|
@@ -72,37 +74,79 @@ def _is_range_value(value: T | Sequence[T]) -> TypeGuard[Sequence[T]]:
|
|
|
72
74
|
return isinstance(value, (list, tuple))
|
|
73
75
|
|
|
74
76
|
|
|
75
|
-
@dataclass
|
|
76
77
|
class SelectSliderSerde(Generic[T]):
|
|
77
|
-
|
|
78
|
-
value: list[int]
|
|
79
|
-
is_range_value: bool
|
|
78
|
+
"""Serializer/deserializer for select_slider widget values.
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
Uses formatted option strings for robust handling of dynamic option changes.
|
|
81
|
+
"""
|
|
83
82
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
options: Sequence[T],
|
|
86
|
+
*,
|
|
87
|
+
formatted_option_to_index: dict[str, int],
|
|
88
|
+
default_indices: list[int],
|
|
89
|
+
format_func: Callable[[Any], str] = str,
|
|
90
|
+
) -> None:
|
|
91
|
+
self.options = options
|
|
92
|
+
self.formatted_option_to_index = formatted_option_to_index
|
|
93
|
+
self.default_indices = default_indices
|
|
94
|
+
self.format_func = format_func
|
|
95
|
+
|
|
96
|
+
def _get_default(self, is_range: bool) -> T | tuple[T, T]:
|
|
97
|
+
"""Return the default value based on default_indices."""
|
|
98
|
+
if is_range or len(self.default_indices) >= 2:
|
|
99
|
+
end_idx = (
|
|
100
|
+
self.default_indices[1]
|
|
101
|
+
if len(self.default_indices) > 1
|
|
102
|
+
else len(self.options) - 1
|
|
103
|
+
)
|
|
104
|
+
return (self.options[self.default_indices[0]], self.options[end_idx])
|
|
105
|
+
return self.options[self.default_indices[0]]
|
|
88
106
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
107
|
+
def serialize(self, v: T | tuple[T, T] | list[T]) -> list[str]:
|
|
108
|
+
"""Convert option value(s) to formatted string list."""
|
|
109
|
+
# Check if v is a single option (handles options that are tuples/lists)
|
|
110
|
+
try:
|
|
111
|
+
formatted = self.format_func(v)
|
|
112
|
+
if formatted in self.formatted_option_to_index:
|
|
113
|
+
return [formatted]
|
|
114
|
+
except Exception: # noqa: S110
|
|
115
|
+
pass
|
|
94
116
|
|
|
95
|
-
#
|
|
96
|
-
|
|
117
|
+
# Handle as range/sequence
|
|
118
|
+
if isinstance(v, (tuple, list)):
|
|
119
|
+
return [self.format_func(x) for x in v]
|
|
97
120
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
121
|
+
return [self.format_func(v)]
|
|
122
|
+
|
|
123
|
+
def deserialize(self, ui_value: list[str] | None) -> T | tuple[T, T]:
|
|
124
|
+
"""Convert formatted string list back to option value(s)."""
|
|
125
|
+
is_range = ui_value is not None and len(ui_value) >= 2
|
|
126
|
+
|
|
127
|
+
if not ui_value:
|
|
128
|
+
return self._get_default(is_range=len(self.default_indices) >= 2)
|
|
129
|
+
|
|
130
|
+
# Look up each string value
|
|
131
|
+
results: list[tuple[int, T]] = []
|
|
132
|
+
for i, s in enumerate(ui_value):
|
|
133
|
+
idx = self.formatted_option_to_index.get(s)
|
|
134
|
+
if idx is not None and idx < len(self.options):
|
|
135
|
+
results.append((idx, self.options[idx]))
|
|
136
|
+
else:
|
|
137
|
+
# Fallback to default for this position
|
|
138
|
+
default_idx = self.default_indices[
|
|
139
|
+
min(i, len(self.default_indices) - 1)
|
|
140
|
+
]
|
|
141
|
+
results.append((default_idx, self.options[default_idx]))
|
|
142
|
+
|
|
143
|
+
if is_range and len(results) >= 2:
|
|
144
|
+
# Ensure start <= end by returning deserialized range value in ascending order
|
|
145
|
+
if results[0][0] > results[1][0]:
|
|
146
|
+
return (results[1][1], results[0][1])
|
|
147
|
+
return (results[0][1], results[1][1])
|
|
148
|
+
|
|
149
|
+
return results[0][1]
|
|
106
150
|
|
|
107
151
|
|
|
108
152
|
class SelectSliderMixin:
|
|
@@ -378,16 +422,18 @@ class SelectSliderMixin:
|
|
|
378
422
|
# Convert element to index of the elements
|
|
379
423
|
slider_value = as_index_list(value)
|
|
380
424
|
|
|
425
|
+
# Create formatted options and mapping for string-based storage
|
|
426
|
+
formatted_options, formatted_option_to_option_index = create_mappings(
|
|
427
|
+
opt, format_func
|
|
428
|
+
)
|
|
429
|
+
|
|
381
430
|
element_id = compute_and_register_element_id(
|
|
382
431
|
"select_slider",
|
|
383
432
|
user_key=key,
|
|
384
|
-
|
|
385
|
-
# changes to the options (and implicitly their formatting) in the
|
|
386
|
-
# identity computation as those can invalidate the current value.
|
|
387
|
-
key_as_main_identity={"options", "format_func"},
|
|
433
|
+
key_as_main_identity=True,
|
|
388
434
|
dg=self.dg,
|
|
389
435
|
label=label,
|
|
390
|
-
options=
|
|
436
|
+
options=formatted_options,
|
|
391
437
|
value=slider_value,
|
|
392
438
|
help=help,
|
|
393
439
|
width=width,
|
|
@@ -403,7 +449,7 @@ class SelectSliderMixin:
|
|
|
403
449
|
slider_proto.max = len(opt) - 1
|
|
404
450
|
slider_proto.step = 1 # default for index changes
|
|
405
451
|
slider_proto.data_type = SliderProto.INT
|
|
406
|
-
slider_proto.options[:] =
|
|
452
|
+
slider_proto.options[:] = formatted_options
|
|
407
453
|
slider_proto.form_id = current_form_id(self.dg)
|
|
408
454
|
slider_proto.disabled = disabled
|
|
409
455
|
slider_proto.label_visibility.value = get_label_visibility_proto_value(
|
|
@@ -415,7 +461,12 @@ class SelectSliderMixin:
|
|
|
415
461
|
validate_width(width)
|
|
416
462
|
layout_config = LayoutConfig(width=width)
|
|
417
463
|
|
|
418
|
-
serde = SelectSliderSerde(
|
|
464
|
+
serde = SelectSliderSerde(
|
|
465
|
+
opt,
|
|
466
|
+
formatted_option_to_index=formatted_option_to_option_index,
|
|
467
|
+
default_indices=slider_value,
|
|
468
|
+
format_func=format_func,
|
|
469
|
+
)
|
|
419
470
|
|
|
420
471
|
widget_state = register_widget(
|
|
421
472
|
slider_proto.id,
|
|
@@ -425,7 +476,7 @@ class SelectSliderMixin:
|
|
|
425
476
|
deserializer=serde.deserialize,
|
|
426
477
|
serializer=serde.serialize,
|
|
427
478
|
ctx=ctx,
|
|
428
|
-
value_type="
|
|
479
|
+
value_type="string_array_value",
|
|
429
480
|
)
|
|
430
481
|
if isinstance(widget_state.value, tuple):
|
|
431
482
|
widget_state = maybe_coerce_enum_sequence(
|
|
@@ -434,15 +485,50 @@ class SelectSliderMixin:
|
|
|
434
485
|
else:
|
|
435
486
|
widget_state = maybe_coerce_enum(widget_state, options, opt)
|
|
436
487
|
|
|
437
|
-
|
|
438
|
-
|
|
488
|
+
# Validate the current value against the new options.
|
|
489
|
+
# If the value is no longer valid (not in options), reset to default.
|
|
490
|
+
# This handles the case where options change dynamically and the
|
|
491
|
+
# previously selected value is no longer available.
|
|
492
|
+
# Determine if we're dealing with a range value based on the actual
|
|
493
|
+
# widget state value, not just the value parameter (range can come from
|
|
494
|
+
# session state even when value param is None).
|
|
495
|
+
actual_is_range = isinstance(widget_state.value, tuple)
|
|
496
|
+
if actual_is_range:
|
|
497
|
+
# Range value: validate using range-specific function.
|
|
498
|
+
range_value = cast("tuple[T, T]", widget_state.value)
|
|
499
|
+
validated_range, value_needs_reset = (
|
|
500
|
+
validate_and_sync_range_value_with_options(
|
|
501
|
+
range_value,
|
|
502
|
+
opt,
|
|
503
|
+
slider_value,
|
|
504
|
+
key,
|
|
505
|
+
format_func,
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
current_value: T | tuple[T, T] = validated_range
|
|
509
|
+
else:
|
|
510
|
+
# Single value: use the standard validation function.
|
|
511
|
+
validated_single, value_needs_reset = validate_and_sync_value_with_options(
|
|
512
|
+
widget_state.value,
|
|
513
|
+
opt,
|
|
514
|
+
slider_value[0],
|
|
515
|
+
key,
|
|
516
|
+
format_func,
|
|
517
|
+
)
|
|
518
|
+
# validated_single is guaranteed to be T (not None) because
|
|
519
|
+
# deserialize() always returns a default value, never None.
|
|
520
|
+
current_value = cast("T", validated_single)
|
|
521
|
+
|
|
522
|
+
if value_needs_reset or widget_state.value_changed:
|
|
523
|
+
serialized_value = serde.serialize(current_value)
|
|
524
|
+
slider_proto.raw_value[:] = serialized_value
|
|
439
525
|
slider_proto.set_value = True
|
|
440
526
|
|
|
441
527
|
if ctx:
|
|
442
528
|
save_for_app_testing(ctx, element_id, format_func)
|
|
443
529
|
|
|
444
530
|
self.dg._enqueue("slider", slider_proto, layout_config=layout_config)
|
|
445
|
-
return
|
|
531
|
+
return current_value
|
|
446
532
|
|
|
447
533
|
@property
|
|
448
534
|
def dg(self) -> DeltaGenerator:
|
streamlit/hello/plotting_demo.py
CHANGED
|
@@ -23,16 +23,24 @@ from streamlit.hello.utils import show_code
|
|
|
23
23
|
def plotting_demo() -> None:
|
|
24
24
|
progress_bar = st.sidebar.progress(0)
|
|
25
25
|
status_text = st.sidebar.empty()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
chart = st.empty()
|
|
27
|
+
|
|
28
|
+
# Initialize with one data point
|
|
29
|
+
data = np.random.randn(1, 1) # noqa: NPY002
|
|
30
|
+
chart.line_chart(data)
|
|
31
|
+
|
|
32
|
+
for i in range(1, 51):
|
|
33
|
+
# Generate new rows based on the last value (random walk)
|
|
34
|
+
new_rows = data[-1, :] + np.random.randn(5, 1).cumsum(axis=0) # noqa: NPY002
|
|
35
|
+
# Append new rows to existing data
|
|
36
|
+
data = np.concatenate([data, new_rows])
|
|
37
|
+
# Update the chart with full data
|
|
38
|
+
chart.line_chart(data)
|
|
39
|
+
# Scale progress to show 0-100% with 50 iterations
|
|
40
|
+
progress = i * 2
|
|
41
|
+
status_text.text(f"{progress}% complete")
|
|
42
|
+
progress_bar.progress(progress)
|
|
43
|
+
time.sleep(0.01)
|
|
36
44
|
|
|
37
45
|
progress_bar.empty()
|
|
38
46
|
|
|
@@ -47,8 +55,7 @@ st.title("Plotting demo")
|
|
|
47
55
|
st.write(
|
|
48
56
|
"""
|
|
49
57
|
This demo illustrates a combination of plotting and animation with
|
|
50
|
-
Streamlit. We're generating a bunch of random numbers in a loop
|
|
51
|
-
5 seconds. Enjoy!
|
|
58
|
+
Streamlit. We're generating a bunch of random numbers in a loop. Enjoy!
|
|
52
59
|
"""
|
|
53
60
|
)
|
|
54
61
|
plotting_demo()
|
streamlit/proto/Logo_pb2.py
CHANGED
|
@@ -14,7 +14,7 @@ _sym_db = _symbol_database.Default()
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1astreamlit/proto/Logo.proto\"
|
|
17
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1astreamlit/proto/Logo.proto\"\xc1\x01\n\x04Logo\x12\r\n\x05image\x18\x01 \x01(\t\x12\x0c\n\x04link\x18\x02 \x01(\t\x12\x12\n\nicon_image\x18\x03 \x01(\t\x12\x0c\n\x04size\x18\x04 \x01(\t\x12#\n\nimage_type\x18\x05 \x01(\x0e\x32\x0f.Logo.ImageType\x12(\n\x0ficon_image_type\x18\x06 \x01(\x0e\x32\x0f.Logo.ImageType\"+\n\tImageType\x12\t\n\x05IMAGE\x10\x00\x12\t\n\x05\x45MOJI\x10\x01\x12\x08\n\x04ICON\x10\x02\x42)\n\x1c\x63om.snowflake.apps.streamlitB\tLogoProtob\x06proto3')
|
|
18
18
|
|
|
19
19
|
_globals = globals()
|
|
20
20
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
|
@@ -22,6 +22,8 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'streamlit.proto.Logo_pb2',
|
|
|
22
22
|
if not _descriptor._USE_C_DESCRIPTORS:
|
|
23
23
|
_globals['DESCRIPTOR']._loaded_options = None
|
|
24
24
|
_globals['DESCRIPTOR']._serialized_options = b'\n\034com.snowflake.apps.streamlitB\tLogoProto'
|
|
25
|
-
_globals['_LOGO']._serialized_start=
|
|
26
|
-
_globals['_LOGO']._serialized_end=
|
|
25
|
+
_globals['_LOGO']._serialized_start=31
|
|
26
|
+
_globals['_LOGO']._serialized_end=224
|
|
27
|
+
_globals['_LOGO_IMAGETYPE']._serialized_start=181
|
|
28
|
+
_globals['_LOGO_IMAGETYPE']._serialized_end=224
|
|
27
29
|
# @@protoc_insertion_point(module_scope)
|