streamlit-nightly 1.52.3.dev20251222__py3-none-any.whl → 1.52.3.dev20251223__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/elements/lib/column_types.py +9 -4
- streamlit/elements/vega_charts.py +78 -4
- streamlit/elements/widgets/slider.py +42 -14
- streamlit/runtime/caching/cache_resource_api.py +78 -7
- streamlit/runtime/caching/ttl_cleanup_cache.py +15 -1
- streamlit/static/index.html +1 -1
- streamlit/static/manifest.json +305 -294
- streamlit/static/static/js/{ErrorOutline.esm.BgAkhGsX.js → ErrorOutline.esm.CJ-r0mHP.js} +1 -1
- streamlit/static/static/js/{FileDownload.esm.Cwsj7v5J.js → FileDownload.esm.DTt9vXrY.js} +1 -1
- streamlit/static/static/js/{FileHelper.BB5EZLnL.js → FileHelper.qvIXOxD5.js} +1 -1
- streamlit/static/static/js/FormClearHelper.3txIi5U2.js +1 -0
- streamlit/static/static/js/{InputInstructions.D6QhFkyD.js → InputInstructions.JLDhda-k.js} +1 -1
- streamlit/static/static/js/{Particles.CidPim2l.js → Particles.DW-szZND.js} +1 -1
- streamlit/static/static/js/{ProgressBar.CrOrO0o_.js → ProgressBar.Bv1YUK2X.js} +1 -1
- streamlit/static/static/js/{StreamlitSyntaxHighlighter.DQjJTMAs.js → StreamlitSyntaxHighlighter.Cdk1H8L5.js} +1 -1
- streamlit/static/static/js/{TableChart.esm.CdDtVThp.js → TableChart.esm.JBXpd4Er.js} +1 -1
- streamlit/static/static/js/{Toolbar.TphlkZ7G.js → Toolbar.DM7tkRnn.js} +1 -1
- streamlit/static/static/js/{WidgetLabelHelpIconInline.ChY1AUrf.js → WidgetLabelHelpIconInline.BK3Q-tdZ.js} +1 -1
- streamlit/static/static/js/{base-input.BOOArKqg.js → base-input.LR6d1AQ3.js} +1 -1
- streamlit/static/static/js/{checkbox.DPljEbs9.js → checkbox.aiaUtfVC.js} +1 -1
- streamlit/static/static/js/{createDownloadLinkElement.DQwypHLM.js → createDownloadLinkElement.KZ488xay.js} +1 -1
- streamlit/static/static/js/data-grid-overlay-editor.Bvq3AnFd.js +1 -0
- streamlit/static/static/js/{downloader.B3_T79Wk.js → downloader.Cm5OkKk_.js} +1 -1
- streamlit/static/static/js/{embed.DzJ3O31q.js → embed.BmfnSGxd.js} +1 -1
- streamlit/static/static/js/{es6.nu6U2jox.js → es6.kmN9fr5V.js} +2 -2
- streamlit/static/static/js/formatMoment.C6Hwn6X5.js +1 -0
- streamlit/static/static/js/{formatNumber.DVmjGoTY.js → formatNumber.DGhYc_Mf.js} +1 -1
- streamlit/static/static/js/{iconPosition.B5iPID0O.js → iconPosition.BnUUQ30a.js} +1 -1
- streamlit/static/static/js/{iframeResizer.contentWindow.Bz08qZTS.js → iframeResizer.contentWindow.D3HlN2_u.js} +1 -1
- streamlit/static/static/js/index.-6iyrqXe.js +2 -0
- streamlit/static/static/js/{index.BUQfYsj_.js → index.4Py-BAlV.js} +27 -27
- streamlit/static/static/js/{index.DTbYStHw.js → index.ASGSMEuq.js} +1 -1
- streamlit/static/static/js/{index.DLe-dm7S.js → index.AoB_TJ4O.js} +1 -1
- streamlit/static/static/js/{index.BsV4PNSZ.js → index.BTnyrk_J.js} +1 -1
- streamlit/static/static/js/{index.DtbTlupq.js → index.BZfRhYeD.js} +1 -1
- streamlit/static/static/js/{index.Cf2AMry_.js → index.BawxRUvY.js} +1 -1
- streamlit/static/static/js/{index.DeSEzRe4.js → index.Bc2Sogpd.js} +1 -1
- streamlit/static/static/js/index.Be44uNWE.js +155 -0
- streamlit/static/static/js/{index.DcrK63ke.js → index.BfZaYl9Q.js} +1 -1
- streamlit/static/static/js/index.BkTA7mhD.js +1 -0
- streamlit/static/static/js/{index.B2Z-kv-M.js → index.BmZ9lqlh.js} +1 -1
- streamlit/static/static/js/{index.BO5Anc8A.js → index.BrUZv2-i.js} +1 -1
- streamlit/static/static/js/{index.D8NuaGYk.js → index.BwUt1N7e.js} +1 -1
- streamlit/static/static/js/{index.Br4GFP5N.js → index.BweYZuYB.js} +1 -1
- streamlit/static/static/js/{index.DtkLbyX6.js → index.BxxnmN7s.js} +1 -1
- streamlit/static/static/js/{index.B73wdIQu.js → index.Bzer1l17.js} +1 -1
- streamlit/static/static/js/{index.DCbhnbxV.js → index.C-LQI-jM.js} +1 -1
- streamlit/static/static/js/{index.C9Du9-Q6.js → index.CBP0Yj26.js} +1 -1
- streamlit/static/static/js/{index.B1VdM8pe.js → index.CL4i8dLy.js} +1 -1
- streamlit/static/static/js/{index.DDJXezSH.js → index.Cget4l_o.js} +1 -1
- streamlit/static/static/js/{index.DdA-ZlAG.js → index.Cgpx_JIE.js} +1 -1
- streamlit/static/static/js/{index.BjSwq8Vk.js → index.CkUcLi4S.js} +1 -1
- streamlit/static/static/js/{index.B3rRJBWJ.js → index.ClWPH1ol.js} +1 -1
- streamlit/static/static/js/{index.CqKxXZq8.js → index.CqXI0FvE.js} +1 -1
- streamlit/static/static/js/{index.BGeclTcP.js → index.CsDvdugJ.js} +1 -1
- streamlit/static/static/js/{index.D27T30iu.js → index.D3PFGrKx.js} +1 -1
- streamlit/static/static/js/{index.D-9tweQI.js → index.D9DmFe4f.js} +1 -1
- streamlit/static/static/js/{index.CcrP49gy.js → index.DA1H59ev.js} +1 -1
- streamlit/static/static/js/{index.CyT-IgZS.js → index.DCDzmKGh.js} +1 -1
- streamlit/static/static/js/{index.BRh6Nh1W.js → index.DGlNWX-d.js} +1 -1
- streamlit/static/static/js/{index.NDg_fgRk.js → index.DJyaagfL.js} +1 -1
- streamlit/static/static/js/{index.Cixc-04N.js → index.DKHCQGvc.js} +1 -1
- streamlit/static/static/js/{index.BN_-fNd4.js → index.DMvqRKrG.js} +1 -1
- streamlit/static/static/js/{index.B1BUTKEm.js → index.DSgAF5Ya.js} +1 -1
- streamlit/static/static/js/{index.CSLyVtqK.js → index.DXIXAgXZ.js} +1 -1
- streamlit/static/static/js/{index.D-_lSREs.js → index.DYU8DSsz.js} +1 -1
- streamlit/static/static/js/{index.Dz8r_pse.js → index.DcgZZBLA.js} +1 -1
- streamlit/static/static/js/{index.D_7wCuw7.js → index.DrEgBjHk.js} +1 -1
- streamlit/static/static/js/{index.nO-46oj3.js → index.DvJewWKZ.js} +1 -1
- streamlit/static/static/js/{index.DBz0IWKb.js → index.E7tHKIeN.js} +1 -1
- streamlit/static/static/js/{index.DZbnk5ZL.js → index.cJD6QuWU.js} +1 -1
- streamlit/static/static/js/index.efJCKg5J.js +1 -0
- streamlit/static/static/js/{index.BHI4Z_pP.js → index.g0M-BhA_.js} +1 -1
- streamlit/static/static/js/{index.tGMuPBOJ.js → index.gWwJtOw7.js} +1 -1
- streamlit/static/static/js/{index.CcW2XYDa.js → index.iHHnyqB6.js} +1 -1
- streamlit/static/static/js/{index.D_fCowhb.js → index.kWhskx02.js} +1 -1
- streamlit/static/static/js/{index.DE8uaKe5.js → index.koIpOVlc.js} +1 -1
- streamlit/static/static/js/{input.BHfbis07.js → input.BSL21NTV.js} +1 -1
- streamlit/static/static/js/{main.TRAx4Ikk.js → main.BpT2ATMT.js} +1 -1
- streamlit/static/static/js/{memory.B-erR_VO.js → memory.cB4wh9Fi.js} +1 -1
- streamlit/static/static/js/number-overlay-editor.Dq7eo7Iv.js +9 -0
- streamlit/static/static/js/{pandasStylerUtils.DWhP2I9U.js → pandasStylerUtils.CNWaNfX0.js} +1 -1
- streamlit/static/static/js/{sandbox.Y-3JnxXp.js → sandbox.SWFjuzzE.js} +1 -1
- streamlit/static/static/js/{styled-components.OcmJ_Gl-.js → styled-components.CL2wCoKu.js} +1 -1
- streamlit/static/static/js/{throttle.BzPEkdAP.js → throttle.tYQai0iG.js} +1 -1
- streamlit/static/static/js/{timepicker.DdPg7fr8.js → timepicker.BoIflsNb.js} +1 -1
- streamlit/static/static/js/{toConsumableArray.4euOQSUG.js → toConsumableArray.yJBHJHiJ.js} +1 -1
- streamlit/static/static/js/uniqueId.C3_1KC89.js +1 -0
- streamlit/static/static/js/useBasicWidgetState.gtwv2ATI.js +1 -0
- streamlit/static/static/js/{useIntlLocale.DtCpbCf6.js → useIntlLocale.CeFO_s8L.js} +1 -1
- streamlit/static/static/js/{useTextInputAutoExpand.FwwVA2cM.js → useTextInputAutoExpand.dVyX-6Qj.js} +1 -1
- streamlit/static/static/js/{useUpdateUiValue.CRipBFts.js → useUpdateUiValue.C8TwIzyl.js} +1 -1
- streamlit/static/static/js/{useWaveformController.G7nrxMWm.js → useWaveformController.DQcQ8iSx.js} +1 -1
- streamlit/static/static/js/{withCalculatedWidth.DZjNj8ew.js → withCalculatedWidth.CJBjGOx4.js} +1 -1
- streamlit/static/static/js/{withFullScreenWrapper.ExJtnKzw.js → withFullScreenWrapper.gfqK0Xv0.js} +1 -1
- {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251223.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251223.dist-info}/RECORD +101 -100
- streamlit/static/static/js/FormClearHelper.BHzs6HmO.js +0 -1
- streamlit/static/static/js/data-grid-overlay-editor.tab0U6JF.js +0 -1
- streamlit/static/static/js/index.CHlyWOoe.js +0 -1
- streamlit/static/static/js/index.DUHxUztP.js +0 -2
- streamlit/static/static/js/index.DUeWSrlP.js +0 -155
- streamlit/static/static/js/index._XD_enMk.js +0 -1
- streamlit/static/static/js/number-overlay-editor.CqgKHyI0.js +0 -9
- streamlit/static/static/js/uniqueId.D4g5K3jE.js +0 -1
- streamlit/static/static/js/useBasicWidgetState.D0XzcgDX.js +0 -1
- {streamlit_nightly-1.52.3.dev20251222.data → streamlit_nightly-1.52.3.dev20251223.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251223.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251223.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251223.dist-info}/top_level.txt +0 -0
|
@@ -45,6 +45,13 @@ NumberFormat: TypeAlias = Literal[
|
|
|
45
45
|
"bytes",
|
|
46
46
|
]
|
|
47
47
|
|
|
48
|
+
DateTimeFormat: TypeAlias = Literal[
|
|
49
|
+
"localized",
|
|
50
|
+
"distance",
|
|
51
|
+
"calendar",
|
|
52
|
+
"iso8601",
|
|
53
|
+
]
|
|
54
|
+
|
|
48
55
|
ColumnWidth: TypeAlias = Literal["small", "medium", "large"] | int
|
|
49
56
|
|
|
50
57
|
# Type alias that represents all available column types
|
|
@@ -194,9 +201,7 @@ class MultiselectColumnConfig(TypedDict):
|
|
|
194
201
|
|
|
195
202
|
class DatetimeColumnConfig(TypedDict):
|
|
196
203
|
type: Literal["datetime"]
|
|
197
|
-
format: NotRequired[
|
|
198
|
-
str | Literal["localized", "distance", "calendar", "iso8601"] | None
|
|
199
|
-
]
|
|
204
|
+
format: NotRequired[str | DateTimeFormat | None]
|
|
200
205
|
min_value: NotRequired[str | None]
|
|
201
206
|
max_value: NotRequired[str | None]
|
|
202
207
|
step: NotRequired[int | float | None]
|
|
@@ -1972,7 +1977,7 @@ def DatetimeColumn(
|
|
|
1972
1977
|
required: bool | None = None,
|
|
1973
1978
|
pinned: bool | None = None,
|
|
1974
1979
|
default: datetime.datetime | None = None,
|
|
1975
|
-
format: str |
|
|
1980
|
+
format: str | DateTimeFormat | None = None,
|
|
1976
1981
|
min_value: datetime.datetime | None = None,
|
|
1977
1982
|
max_value: datetime.datetime | None = None,
|
|
1978
1983
|
step: int | float | datetime.timedelta | None = None,
|
|
@@ -279,6 +279,41 @@ def _patch_null_legend_titles(spec: VegaLiteSpec) -> None:
|
|
|
279
279
|
legend["title"] = " "
|
|
280
280
|
|
|
281
281
|
|
|
282
|
+
def _has_nested_composition(spec: VegaLiteSpec) -> bool:
|
|
283
|
+
"""Check if a vconcat spec contains nested composition operators.
|
|
284
|
+
|
|
285
|
+
This function checks if a vconcat chart contains nested hconcat, vconcat,
|
|
286
|
+
concat, or layer operators. Such nested compositions don't work well with
|
|
287
|
+
the fit-x autosize type and can cause infinite extent errors.
|
|
288
|
+
|
|
289
|
+
In valid Vega-Lite specs, composition operators (hconcat, vconcat, concat, layer)
|
|
290
|
+
are always top-level keys of a view specification. They cannot be buried inside
|
|
291
|
+
encoding, mark, or other nested properties. This allows us to check only the
|
|
292
|
+
immediate children of vconcat for nested composition operators.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
spec : VegaLiteSpec
|
|
297
|
+
The Vega-Lite spec to check.
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
bool
|
|
302
|
+
True if the spec contains nested composition operators, False otherwise.
|
|
303
|
+
"""
|
|
304
|
+
# Check if vconcat contains nested compositions.
|
|
305
|
+
# We only need to check top-level keys of each child spec since composition
|
|
306
|
+
# operators are always top-level in valid Vega-Lite specs.
|
|
307
|
+
if "vconcat" in spec and isinstance(spec["vconcat"], list):
|
|
308
|
+
for item in spec["vconcat"]:
|
|
309
|
+
# Check if this item is a dict containing any composition operator
|
|
310
|
+
if isinstance(item, dict) and any(
|
|
311
|
+
k in item for k in ["hconcat", "vconcat", "concat", "layer"]
|
|
312
|
+
):
|
|
313
|
+
return True
|
|
314
|
+
return False
|
|
315
|
+
|
|
316
|
+
|
|
282
317
|
def _prepare_vega_lite_spec(
|
|
283
318
|
spec: VegaLiteSpec,
|
|
284
319
|
use_container_width: bool,
|
|
@@ -306,10 +341,33 @@ def _prepare_vega_lite_spec(
|
|
|
306
341
|
"encoding" in spec
|
|
307
342
|
and (any(x in spec["encoding"] for x in ["row", "column", "facet"]))
|
|
308
343
|
)
|
|
309
|
-
|
|
310
|
-
spec["autosize"] = {"type": "fit-x", "contains": "padding"}
|
|
344
|
+
has_nested_comp = _has_nested_composition(spec)
|
|
311
345
|
|
|
312
|
-
|
|
346
|
+
if "vconcat" in spec and use_container_width:
|
|
347
|
+
# For vconcat charts with container width stretching:
|
|
348
|
+
# - Simple vconcat: use fit-x (fits width only, better control)
|
|
349
|
+
# - Nested compositions (vconcat+hconcat): use pad (no automatic fitting)
|
|
350
|
+
# fit-x causes "Infinite extent" errors with nested hconcat (issue #13410)
|
|
351
|
+
#
|
|
352
|
+
# Known limitation: Nested compositions may overflow the container because
|
|
353
|
+
# Vega-Lite's width property only controls the plotting area (data marks),
|
|
354
|
+
# not the total SVG width which includes axes, labels, legends, and padding.
|
|
355
|
+
# The frontend sets spec.width to containerWidth, but with autosize: pad,
|
|
356
|
+
# Vega adds decorations on top, causing total SVG to exceed container bounds.
|
|
357
|
+
# This is a Vega-Lite architectural limitation similar to facet charts
|
|
358
|
+
# (see https://github.com/vega/vega-lite/issues/5219).
|
|
359
|
+
# Trade-off: Accept overflow to ensure charts render correctly rather than
|
|
360
|
+
# appear as empty elements with "Infinite extent" errors.
|
|
361
|
+
if has_nested_comp:
|
|
362
|
+
# use pad = "no automatic fitting" - accurate description of what's happening
|
|
363
|
+
# produces same overflow behavior as fit
|
|
364
|
+
spec["autosize"] = {"type": "pad", "contains": "padding"}
|
|
365
|
+
else:
|
|
366
|
+
spec["autosize"] = {"type": "fit-x", "contains": "padding"}
|
|
367
|
+
|
|
368
|
+
elif is_facet_chart or (has_nested_comp and not use_container_width):
|
|
369
|
+
# Facet charts and nested compositions without stretching use pad
|
|
370
|
+
# (no automatic sizing, uses natural/content size)
|
|
313
371
|
spec["autosize"] = {"type": "pad", "contains": "padding"}
|
|
314
372
|
|
|
315
373
|
else:
|
|
@@ -1780,6 +1838,10 @@ class VegaChartsMixin:
|
|
|
1780
1838
|
- Horizontal concatenation charts: the spec contains
|
|
1781
1839
|
``"hconcat"``.
|
|
1782
1840
|
- Repeat charts: the spec contains ``"repeat"``.
|
|
1841
|
+
- Nested composition charts: the spec contains ``"vconcat"``
|
|
1842
|
+
with nested ``"hconcat"``, ``"vconcat"``, ``"concat"``, or
|
|
1843
|
+
``"layer"`` operators (e.g., scatter plots with marginal
|
|
1844
|
+
histograms).
|
|
1783
1845
|
|
|
1784
1846
|
height : "content", "stretch", or int
|
|
1785
1847
|
The height of the chart element. This can be one of the following:
|
|
@@ -1999,6 +2061,10 @@ class VegaChartsMixin:
|
|
|
1999
2061
|
- Horizontal concatenation charts: the spec contains
|
|
2000
2062
|
``"hconcat"``.
|
|
2001
2063
|
- Repeat charts: the spec contains ``"repeat"``.
|
|
2064
|
+
- Nested composition charts: the spec contains ``"vconcat"``
|
|
2065
|
+
with nested ``"hconcat"``, ``"vconcat"``, ``"concat"``, or
|
|
2066
|
+
``"layer"`` operators (e.g., scatter plots with marginal
|
|
2067
|
+
histograms).
|
|
2002
2068
|
|
|
2003
2069
|
height : "content", "stretch", or int
|
|
2004
2070
|
The height of the chart element. This can be one of the following:
|
|
@@ -2263,13 +2329,21 @@ class VegaChartsMixin:
|
|
|
2263
2329
|
# those charts (see https://github.com/streamlit/streamlit/issues/9091).
|
|
2264
2330
|
# All other charts (including vertical concatenation) default to
|
|
2265
2331
|
# `width=stretch` unless width is provided.
|
|
2332
|
+
# Nested vconcat+hconcat charts (issue #13410) also don't work well
|
|
2333
|
+
# with width=stretch, so we treat them like hconcat charts.
|
|
2266
2334
|
is_facet_chart = "facet" in spec or (
|
|
2267
2335
|
"encoding" in spec
|
|
2268
2336
|
and (any(x in spec["encoding"] for x in ["row", "column", "facet"]))
|
|
2269
2337
|
)
|
|
2338
|
+
has_nested_comp = _has_nested_composition(spec)
|
|
2270
2339
|
width = (
|
|
2271
2340
|
"stretch"
|
|
2272
|
-
if not (
|
|
2341
|
+
if not (
|
|
2342
|
+
is_facet_chart
|
|
2343
|
+
or "hconcat" in spec
|
|
2344
|
+
or "repeat" in spec
|
|
2345
|
+
or has_nested_comp
|
|
2346
|
+
)
|
|
2273
2347
|
else "content"
|
|
2274
2348
|
)
|
|
2275
2349
|
|
|
@@ -62,6 +62,7 @@ from streamlit.runtime.state import (
|
|
|
62
62
|
|
|
63
63
|
if TYPE_CHECKING:
|
|
64
64
|
from streamlit.delta_generator import DeltaGenerator
|
|
65
|
+
from streamlit.elements.lib.column_types import DateTimeFormat, NumberFormat
|
|
65
66
|
from streamlit.elements.lib.layout_utils import WidthWithoutContent
|
|
66
67
|
|
|
67
68
|
SliderNumericT = TypeVar("SliderNumericT", int, float)
|
|
@@ -229,7 +230,7 @@ class SliderMixin:
|
|
|
229
230
|
max_value: None = None,
|
|
230
231
|
value: None = None,
|
|
231
232
|
step: int | None = None,
|
|
232
|
-
format: str | None = None,
|
|
233
|
+
format: str | NumberFormat | None = None,
|
|
233
234
|
key: Key | None = None,
|
|
234
235
|
help: str | None = None,
|
|
235
236
|
on_change: WidgetCallback | None = None,
|
|
@@ -251,7 +252,7 @@ class SliderMixin:
|
|
|
251
252
|
max_value: SliderNumericT | None = None,
|
|
252
253
|
value: SliderNumericT | None = None,
|
|
253
254
|
step: StepNumericT[SliderNumericT] | None = None,
|
|
254
|
-
format: str | None = None,
|
|
255
|
+
format: str | NumberFormat | None = None,
|
|
255
256
|
key: Key | None = None,
|
|
256
257
|
help: str | None = None,
|
|
257
258
|
on_change: WidgetCallback | None = None,
|
|
@@ -274,7 +275,7 @@ class SliderMixin:
|
|
|
274
275
|
*,
|
|
275
276
|
value: SliderNumericSpanT[SliderNumericT],
|
|
276
277
|
step: StepNumericT[SliderNumericT] | None = None,
|
|
277
|
-
format: str | None = None,
|
|
278
|
+
format: str | NumberFormat | None = None,
|
|
278
279
|
key: Key | None = None,
|
|
279
280
|
help: str | None = None,
|
|
280
281
|
on_change: WidgetCallback | None = None,
|
|
@@ -295,7 +296,7 @@ class SliderMixin:
|
|
|
295
296
|
max_value: SliderNumericT,
|
|
296
297
|
value: SliderNumericSpanT[SliderNumericT],
|
|
297
298
|
step: StepNumericT[SliderNumericT] | None = None,
|
|
298
|
-
format: str | None = None,
|
|
299
|
+
format: str | NumberFormat | None = None,
|
|
299
300
|
key: Key | None = None,
|
|
300
301
|
help: str | None = None,
|
|
301
302
|
on_change: WidgetCallback | None = None,
|
|
@@ -317,7 +318,7 @@ class SliderMixin:
|
|
|
317
318
|
max_value: SliderDatelikeT | None = None,
|
|
318
319
|
value: SliderDatelikeT | None = None,
|
|
319
320
|
step: StepDatelikeT | None = None,
|
|
320
|
-
format: str | None = None,
|
|
321
|
+
format: str | DateTimeFormat | None = None,
|
|
321
322
|
key: Key | None = None,
|
|
322
323
|
help: str | None = None,
|
|
323
324
|
on_change: WidgetCallback | None = None,
|
|
@@ -340,7 +341,7 @@ class SliderMixin:
|
|
|
340
341
|
max_value: SliderDatelikeT,
|
|
341
342
|
value: SliderDatelikeT | None = None,
|
|
342
343
|
step: StepDatelikeT | None = None,
|
|
343
|
-
format: str | None = None,
|
|
344
|
+
format: str | DateTimeFormat | None = None,
|
|
344
345
|
key: Key | None = None,
|
|
345
346
|
help: str | None = None,
|
|
346
347
|
on_change: WidgetCallback | None = None,
|
|
@@ -361,7 +362,7 @@ class SliderMixin:
|
|
|
361
362
|
*,
|
|
362
363
|
value: SliderDatelikeT,
|
|
363
364
|
step: StepDatelikeT | None = None,
|
|
364
|
-
format: str | None = None,
|
|
365
|
+
format: str | DateTimeFormat | None = None,
|
|
365
366
|
key: Key | None = None,
|
|
366
367
|
help: str | None = None,
|
|
367
368
|
on_change: WidgetCallback | None = None,
|
|
@@ -385,7 +386,7 @@ class SliderMixin:
|
|
|
385
386
|
| tuple[SliderDatelikeT]
|
|
386
387
|
| tuple[SliderDatelikeT, SliderDatelikeT],
|
|
387
388
|
step: StepDatelikeT | None = None,
|
|
388
|
-
format: str | None = None,
|
|
389
|
+
format: str | DateTimeFormat | None = None,
|
|
389
390
|
key: Key | None = None,
|
|
390
391
|
help: str | None = None,
|
|
391
392
|
on_change: WidgetCallback | None = None,
|
|
@@ -407,7 +408,7 @@ class SliderMixin:
|
|
|
407
408
|
value: SliderDatelikeSpanT[SliderDatelikeT],
|
|
408
409
|
/,
|
|
409
410
|
step: StepDatelikeT | None = None,
|
|
410
|
-
format: str | None = None,
|
|
411
|
+
format: str | DateTimeFormat | None = None,
|
|
411
412
|
key: Key | None = None,
|
|
412
413
|
help: str | None = None,
|
|
413
414
|
on_change: WidgetCallback | None = None,
|
|
@@ -517,16 +518,43 @@ class SliderMixin:
|
|
|
517
518
|
(or if max_value - min_value < 1 day)
|
|
518
519
|
|
|
519
520
|
format : str or None
|
|
520
|
-
A printf-style format string
|
|
521
|
-
display
|
|
522
|
-
|
|
523
|
-
|
|
521
|
+
A printf-style format string or a predefined format name controlling
|
|
522
|
+
how the interface should display values. This does not impact the
|
|
523
|
+
return value.
|
|
524
|
+
|
|
525
|
+
For integers and floats, you can use a printf-style format string
|
|
526
|
+
or one of the following predefined formats:
|
|
527
|
+
|
|
528
|
+
- ``"plain"``: Show the full number without formatting (e.g. ``1234.567``).
|
|
529
|
+
- ``"localized"``: Show the number in the user's locale format (e.g. ``1,234.567``).
|
|
530
|
+
- ``"percent"``: Show as a percentage (e.g. ``50%`` from ``0.5``).
|
|
531
|
+
- ``"dollar"``: Show as US dollars (e.g. ``$1,234.57``).
|
|
532
|
+
- ``"euro"``: Show as euros (e.g. ``€1,234.57``).
|
|
533
|
+
- ``"yen"``: Show as Japanese yen (e.g. ``¥1,235``).
|
|
534
|
+
- ``"compact"``: Show in compact notation (e.g. ``1.2K``).
|
|
535
|
+
- ``"scientific"``: Show in scientific notation (e.g. ``1.235E3``).
|
|
536
|
+
- ``"engineering"``: Show in engineering notation (e.g. ``1.235E3``).
|
|
537
|
+
- ``"accounting"``: Show in accounting format with parentheses for negatives.
|
|
538
|
+
- ``"bytes"``: Show in byte units (e.g. ``1.2KB``).
|
|
539
|
+
|
|
540
|
+
For information about printf-style format strings, see
|
|
524
541
|
`sprintf.js
|
|
525
542
|
<https://github.com/alexei/sprintf.js?tab=readme-ov-file#format-specification>`_.
|
|
526
543
|
For example, ``format="%0.1f"`` adjusts the displayed decimal
|
|
527
544
|
precision to only show one digit after the decimal.
|
|
528
545
|
|
|
529
|
-
For
|
|
546
|
+
For datetimes, dates, and times, you can use a momentJS format string
|
|
547
|
+
or one of the following predefined formats:
|
|
548
|
+
|
|
549
|
+
- ``"localized"``: Show in the user's locale format.
|
|
550
|
+
- ``"distance"``: Show as relative time (e.g. ``"2 hours ago"``).
|
|
551
|
+
- ``"calendar"``: Show as calendar time (e.g. ``"Tomorrow 12:00"``).
|
|
552
|
+
Works best with datetime values. For date-only values, displays
|
|
553
|
+
relative day names (e.g. ``"Yesterday"``). For time-only values,
|
|
554
|
+
this format may produce unexpected results.
|
|
555
|
+
- ``"iso8601"``: Show in ISO 8601 format.
|
|
556
|
+
|
|
557
|
+
For information about momentJS format strings, see
|
|
530
558
|
`momentJS <https://momentjs.com/docs/#/displaying/format/>`_.
|
|
531
559
|
For example, ``format="ddd ha"`` adjusts the displayed datetime to
|
|
532
560
|
show the day of the week and the hour ("Tue 8pm").
|
|
@@ -29,7 +29,6 @@ from typing import (
|
|
|
29
29
|
overload,
|
|
30
30
|
)
|
|
31
31
|
|
|
32
|
-
from cachetools import TTLCache
|
|
33
32
|
from typing_extensions import ParamSpec
|
|
34
33
|
|
|
35
34
|
import streamlit as st
|
|
@@ -41,6 +40,7 @@ from streamlit.runtime.caching.cache_utils import (
|
|
|
41
40
|
Cache,
|
|
42
41
|
CachedFunc,
|
|
43
42
|
CachedFuncInfo,
|
|
43
|
+
OnRelease,
|
|
44
44
|
make_cached_func_wrapper,
|
|
45
45
|
)
|
|
46
46
|
from streamlit.runtime.caching.cached_message_replay import (
|
|
@@ -48,6 +48,7 @@ from streamlit.runtime.caching.cached_message_replay import (
|
|
|
48
48
|
CachedResult,
|
|
49
49
|
MsgData,
|
|
50
50
|
)
|
|
51
|
+
from streamlit.runtime.caching.ttl_cleanup_cache import TTLCleanupCache
|
|
51
52
|
from streamlit.runtime.metrics_util import gather_metrics
|
|
52
53
|
from streamlit.runtime.stats import (
|
|
53
54
|
CACHE_MEMORY_FAMILY,
|
|
@@ -79,6 +80,10 @@ def _equal_validate_funcs(a: ValidateFunc | None, b: ValidateFunc | None) -> boo
|
|
|
79
80
|
return (a is None and b is None) or (a is not None and b is not None)
|
|
80
81
|
|
|
81
82
|
|
|
83
|
+
def _no_op_release(ignored: Any) -> None:
|
|
84
|
+
"""No-op OnRelease function."""
|
|
85
|
+
|
|
86
|
+
|
|
82
87
|
class ResourceCaches(StatsProvider):
|
|
83
88
|
"""Manages all ResourceCache instances."""
|
|
84
89
|
|
|
@@ -97,6 +102,7 @@ class ResourceCaches(StatsProvider):
|
|
|
97
102
|
max_entries: int | float | None,
|
|
98
103
|
ttl: float | timedelta | str | None,
|
|
99
104
|
validate: ValidateFunc | None,
|
|
105
|
+
on_release: OnRelease,
|
|
100
106
|
) -> ResourceCache[Any]:
|
|
101
107
|
"""Return the mem cache for the given key.
|
|
102
108
|
|
|
@@ -127,15 +133,22 @@ class ResourceCaches(StatsProvider):
|
|
|
127
133
|
max_entries=max_entries,
|
|
128
134
|
ttl_seconds=ttl_seconds,
|
|
129
135
|
validate=validate,
|
|
136
|
+
on_release=on_release,
|
|
130
137
|
)
|
|
131
138
|
self._function_caches[key] = cache
|
|
132
139
|
return cache
|
|
133
140
|
|
|
134
141
|
def clear_all(self) -> None:
|
|
135
142
|
"""Clear all resource caches."""
|
|
143
|
+
# Hold the lock long enough to copy the caches.
|
|
136
144
|
with self._caches_lock:
|
|
145
|
+
caches = list(self._function_caches.values())
|
|
137
146
|
self._function_caches = {}
|
|
138
147
|
|
|
148
|
+
# Clear each cache to ensure any on_release functions are called.
|
|
149
|
+
for cache in caches:
|
|
150
|
+
cache.clear()
|
|
151
|
+
|
|
139
152
|
def get_stats(
|
|
140
153
|
self, _family_names: Sequence[str] | None = None
|
|
141
154
|
) -> dict[str, list[CacheStat]]:
|
|
@@ -182,6 +195,7 @@ class CachedResourceFuncInfo(CachedFuncInfo[P, R]):
|
|
|
182
195
|
validate: ValidateFunc | None,
|
|
183
196
|
hash_funcs: HashFuncsDict | None = None,
|
|
184
197
|
show_time: bool = False,
|
|
198
|
+
on_release: OnRelease | None = None,
|
|
185
199
|
) -> None:
|
|
186
200
|
super().__init__(
|
|
187
201
|
func,
|
|
@@ -192,6 +206,7 @@ class CachedResourceFuncInfo(CachedFuncInfo[P, R]):
|
|
|
192
206
|
self.max_entries = max_entries
|
|
193
207
|
self.ttl = ttl
|
|
194
208
|
self.validate = validate
|
|
209
|
+
self.on_release = on_release or _no_op_release
|
|
195
210
|
|
|
196
211
|
@property
|
|
197
212
|
def cache_type(self) -> CacheType:
|
|
@@ -213,6 +228,7 @@ class CachedResourceFuncInfo(CachedFuncInfo[P, R]):
|
|
|
213
228
|
max_entries=self.max_entries,
|
|
214
229
|
ttl=self.ttl,
|
|
215
230
|
validate=self.validate,
|
|
231
|
+
on_release=self.on_release,
|
|
216
232
|
)
|
|
217
233
|
|
|
218
234
|
|
|
@@ -264,6 +280,7 @@ class CacheResourceAPI:
|
|
|
264
280
|
show_time: bool = False,
|
|
265
281
|
validate: ValidateFunc | None = None,
|
|
266
282
|
hash_funcs: HashFuncsDict | None = None,
|
|
283
|
+
on_release: OnRelease | None = None,
|
|
267
284
|
) -> CachedFunc[P, R] | Callable[[Callable[P, R]], CachedFunc[P, R]]:
|
|
268
285
|
return self._decorator( # ty: ignore[missing-argument]
|
|
269
286
|
func, # ty: ignore[invalid-argument-type]
|
|
@@ -273,6 +290,7 @@ class CacheResourceAPI:
|
|
|
273
290
|
show_time=show_time,
|
|
274
291
|
validate=validate,
|
|
275
292
|
hash_funcs=hash_funcs,
|
|
293
|
+
on_release=on_release,
|
|
276
294
|
)
|
|
277
295
|
|
|
278
296
|
def _decorator(
|
|
@@ -285,6 +303,7 @@ class CacheResourceAPI:
|
|
|
285
303
|
show_time: bool = False,
|
|
286
304
|
validate: ValidateFunc | None,
|
|
287
305
|
hash_funcs: HashFuncsDict | None = None,
|
|
306
|
+
on_release: OnRelease | None = None,
|
|
288
307
|
) -> CachedFunc[P, R] | Callable[[Callable[P, R]], CachedFunc[P, R]]:
|
|
289
308
|
"""Decorator to cache functions that return global resources (e.g. database connections, ML models).
|
|
290
309
|
|
|
@@ -340,16 +359,21 @@ class CacheResourceAPI:
|
|
|
340
359
|
- A number specifying the time in seconds.
|
|
341
360
|
- A string specifying the time in a format supported by `Pandas's
|
|
342
361
|
Timedelta constructor <https://pandas.pydata.org/docs/reference/api/pandas.Timedelta.html>`_,
|
|
343
|
-
e.g. ``"1d"``, ``"1.5 days"``, or ``"1h23s"``.
|
|
362
|
+
e.g. ``"1d"``, ``"1.5 days"``, or ``"1h23s"``. Note that number strings
|
|
363
|
+
without units are treated by Pandas as nanoseconds.
|
|
344
364
|
- A ``timedelta`` object from `Python's built-in datetime library
|
|
345
365
|
<https://docs.python.org/3/library/datetime.html#timedelta-objects>`_,
|
|
346
366
|
e.g. ``timedelta(days=1)``.
|
|
347
367
|
|
|
368
|
+
Changes to this value will trigger a new cache to be created.
|
|
369
|
+
|
|
348
370
|
max_entries : int or None
|
|
349
371
|
The maximum number of entries to keep in the cache, or None
|
|
350
372
|
for an unbounded cache. When a new entry is added to a full cache,
|
|
351
373
|
the oldest cached entry will be removed. Defaults to None.
|
|
352
374
|
|
|
375
|
+
Changes to this value will trigger a new cache to be created.
|
|
376
|
+
|
|
353
377
|
show_spinner : bool or str
|
|
354
378
|
Enable the spinner. Default is True to show a spinner when there is
|
|
355
379
|
a "cache miss" and the cached resource is being created. If string,
|
|
@@ -362,7 +386,7 @@ class CacheResourceAPI:
|
|
|
362
386
|
format is not configurable.
|
|
363
387
|
|
|
364
388
|
validate : callable or None
|
|
365
|
-
An optional validation function for cached
|
|
389
|
+
An optional validation function for cached resources. ``validate`` is called
|
|
366
390
|
each time the cached value is accessed. It receives the cached value as
|
|
367
391
|
its only parameter and it must return a boolean. If ``validate`` returns
|
|
368
392
|
False, the current cached value is discarded, and the decorated function
|
|
@@ -377,6 +401,20 @@ class CacheResourceAPI:
|
|
|
377
401
|
the provided function to generate a hash for it. See below for an example
|
|
378
402
|
of how this can be used.
|
|
379
403
|
|
|
404
|
+
on_release : callable or None
|
|
405
|
+
If set, a function to call when a cache entry is removed from the cache.
|
|
406
|
+
The removed item will be provided to the function as an argument.
|
|
407
|
+
|
|
408
|
+
This is only useful for caches which will remove entries normally: Those
|
|
409
|
+
with ``max_entries`` or ``ttl`` settings. Note that TTL expiration does not
|
|
410
|
+
happen on all reads - so ``ttl`` should not be used to guarantee timely
|
|
411
|
+
cleanup, only cleanup when expired resources are accessed. Also note that
|
|
412
|
+
expiration can happen on any app render or load, so care should be taken
|
|
413
|
+
to ensure that ``on_release`` functions are thread-safe and do not rely on
|
|
414
|
+
session state.
|
|
415
|
+
|
|
416
|
+
This will NOT be called when an app is shut down.
|
|
417
|
+
|
|
380
418
|
Example
|
|
381
419
|
-------
|
|
382
420
|
>>> import streamlit as st
|
|
@@ -472,6 +510,7 @@ class CacheResourceAPI:
|
|
|
472
510
|
ttl=ttl,
|
|
473
511
|
validate=validate,
|
|
474
512
|
hash_funcs=hash_funcs,
|
|
513
|
+
on_release=on_release,
|
|
475
514
|
)
|
|
476
515
|
)
|
|
477
516
|
|
|
@@ -484,6 +523,7 @@ class CacheResourceAPI:
|
|
|
484
523
|
ttl=ttl,
|
|
485
524
|
validate=validate,
|
|
486
525
|
hash_funcs=hash_funcs,
|
|
526
|
+
on_release=on_release,
|
|
487
527
|
)
|
|
488
528
|
)
|
|
489
529
|
|
|
@@ -503,12 +543,24 @@ class ResourceCache(Cache[R]):
|
|
|
503
543
|
ttl_seconds: float,
|
|
504
544
|
validate: ValidateFunc | None,
|
|
505
545
|
display_name: str,
|
|
546
|
+
on_release: OnRelease,
|
|
506
547
|
) -> None:
|
|
507
548
|
super().__init__()
|
|
549
|
+
|
|
550
|
+
def wrapped_on_release(result: CachedResult[R]) -> None:
|
|
551
|
+
# Note that exceptions raised here will bubble out to the calling scope,
|
|
552
|
+
# which will then treat them as user script errors.
|
|
553
|
+
# This is also how exceptions thrown when generating cache values are
|
|
554
|
+
# treated.
|
|
555
|
+
on_release(result.value)
|
|
556
|
+
|
|
508
557
|
self.key = key
|
|
509
558
|
self.display_name = display_name
|
|
510
|
-
self._mem_cache:
|
|
511
|
-
maxsize=max_entries,
|
|
559
|
+
self._mem_cache: TTLCleanupCache[str, CachedResult[R]] = TTLCleanupCache(
|
|
560
|
+
maxsize=max_entries,
|
|
561
|
+
ttl=ttl_seconds,
|
|
562
|
+
timer=cache_utils.TTLCACHE_TIMER,
|
|
563
|
+
on_release=wrapped_on_release,
|
|
512
564
|
)
|
|
513
565
|
self._mem_cache_lock = threading.Lock()
|
|
514
566
|
self.validate = validate
|
|
@@ -551,9 +603,28 @@ class ResourceCache(Cache[R]):
|
|
|
551
603
|
def _clear(self, key: str | None = None) -> None:
|
|
552
604
|
with self._mem_cache_lock:
|
|
553
605
|
if key is None:
|
|
554
|
-
|
|
606
|
+
# Clear the whole cache.
|
|
607
|
+
# TTLCleanupCache will stop a clear() execution when an exception is
|
|
608
|
+
# thrown by an on_release. To ensure that our clear() actually flushes
|
|
609
|
+
# the cache and calls all cleanup functions, we clear each item
|
|
610
|
+
# individually. We also collect exceptions for logging.
|
|
611
|
+
errors: list[Exception] = []
|
|
612
|
+
while len(self._mem_cache) > 0:
|
|
613
|
+
try:
|
|
614
|
+
# TTLCleanupCache only reliably calls on_release for popitem -
|
|
615
|
+
# so just use that.
|
|
616
|
+
self._mem_cache.popitem()
|
|
617
|
+
except Exception as e: # noqa: PERF203 (we require a tight scope)
|
|
618
|
+
errors.append(e)
|
|
619
|
+
|
|
620
|
+
# Log all errors encountered at warning. This could potentially result in a
|
|
621
|
+
# lot of log spam in the worst case - but for resources, a huge cache is very
|
|
622
|
+
# unlikely.
|
|
623
|
+
for error in errors:
|
|
624
|
+
_LOGGER.warning("Error clearing resource cache: %s", error)
|
|
555
625
|
elif key in self._mem_cache:
|
|
556
|
-
|
|
626
|
+
# Note: This code path does not seem to be reachable through public APIs.
|
|
627
|
+
self._mem_cache.safe_del(key)
|
|
557
628
|
|
|
558
629
|
def get_stats(
|
|
559
630
|
self, _family_names: Sequence[str] | None = None
|
|
@@ -30,7 +30,12 @@ V = TypeVar("V")
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class TTLCleanupCache(TTLCache[K, V]):
|
|
33
|
-
"""A TTLCache that supports hooks called when items are released.
|
|
33
|
+
"""A TTLCache that supports hooks called when items are released.
|
|
34
|
+
|
|
35
|
+
Note that item release only happens reliably when done automatically due to TTL
|
|
36
|
+
or maxsize expiration - and specifically does not happen when using ``del``. To
|
|
37
|
+
remove an item and have on_release be called, use safe_del.
|
|
38
|
+
"""
|
|
34
39
|
|
|
35
40
|
def __init__(
|
|
36
41
|
self,
|
|
@@ -69,3 +74,12 @@ class TTLCleanupCache(TTLCache[K, V]):
|
|
|
69
74
|
self._on_release(value)
|
|
70
75
|
|
|
71
76
|
return items
|
|
77
|
+
|
|
78
|
+
def safe_del(self, key: K) -> None:
|
|
79
|
+
"""Delete that calls _on_release."""
|
|
80
|
+
has_value = key in self
|
|
81
|
+
old_value = self.get(key)
|
|
82
|
+
del self[key]
|
|
83
|
+
# Check has_value, not None, to allow for None values.
|
|
84
|
+
if has_value:
|
|
85
|
+
self._on_release(old_value)
|
streamlit/static/index.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
<script>
|
|
38
38
|
window.prerenderReady = false
|
|
39
39
|
</script>
|
|
40
|
-
<script type="module" crossorigin src="./static/js/index.
|
|
40
|
+
<script type="module" crossorigin src="./static/js/index.Be44uNWE.js"></script>
|
|
41
41
|
<link rel="stylesheet" crossorigin href="./static/css/index.BUP6fTcR.css">
|
|
42
42
|
</head>
|
|
43
43
|
<body>
|