streamlit-nightly 1.52.3.dev20251222__py3-none-any.whl → 1.52.3.dev20251224__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 (110) hide show
  1. streamlit/elements/lib/column_types.py +9 -4
  2. streamlit/elements/vega_charts.py +78 -4
  3. streamlit/elements/widgets/slider.py +42 -14
  4. streamlit/runtime/caching/cache_resource_api.py +78 -7
  5. streamlit/runtime/caching/ttl_cleanup_cache.py +15 -1
  6. streamlit/static/index.html +1 -1
  7. streamlit/static/manifest.json +305 -294
  8. streamlit/static/static/js/{ErrorOutline.esm.BgAkhGsX.js → ErrorOutline.esm.CJ-r0mHP.js} +1 -1
  9. streamlit/static/static/js/{FileDownload.esm.Cwsj7v5J.js → FileDownload.esm.DTt9vXrY.js} +1 -1
  10. streamlit/static/static/js/{FileHelper.BB5EZLnL.js → FileHelper.qvIXOxD5.js} +1 -1
  11. streamlit/static/static/js/FormClearHelper.3txIi5U2.js +1 -0
  12. streamlit/static/static/js/{InputInstructions.D6QhFkyD.js → InputInstructions.JLDhda-k.js} +1 -1
  13. streamlit/static/static/js/{Particles.CidPim2l.js → Particles.DW-szZND.js} +1 -1
  14. streamlit/static/static/js/{ProgressBar.CrOrO0o_.js → ProgressBar.Bv1YUK2X.js} +1 -1
  15. streamlit/static/static/js/{StreamlitSyntaxHighlighter.DQjJTMAs.js → StreamlitSyntaxHighlighter.Cdk1H8L5.js} +1 -1
  16. streamlit/static/static/js/{TableChart.esm.CdDtVThp.js → TableChart.esm.JBXpd4Er.js} +1 -1
  17. streamlit/static/static/js/{Toolbar.TphlkZ7G.js → Toolbar.DM7tkRnn.js} +1 -1
  18. streamlit/static/static/js/{WidgetLabelHelpIconInline.ChY1AUrf.js → WidgetLabelHelpIconInline.BK3Q-tdZ.js} +1 -1
  19. streamlit/static/static/js/{base-input.BOOArKqg.js → base-input.LR6d1AQ3.js} +1 -1
  20. streamlit/static/static/js/{checkbox.DPljEbs9.js → checkbox.aiaUtfVC.js} +1 -1
  21. streamlit/static/static/js/{createDownloadLinkElement.DQwypHLM.js → createDownloadLinkElement.KZ488xay.js} +1 -1
  22. streamlit/static/static/js/data-grid-overlay-editor.Bvq3AnFd.js +1 -0
  23. streamlit/static/static/js/{downloader.B3_T79Wk.js → downloader.Cm5OkKk_.js} +1 -1
  24. streamlit/static/static/js/{embed.DzJ3O31q.js → embed.BmfnSGxd.js} +1 -1
  25. streamlit/static/static/js/{es6.nu6U2jox.js → es6.kmN9fr5V.js} +2 -2
  26. streamlit/static/static/js/formatMoment.C6Hwn6X5.js +1 -0
  27. streamlit/static/static/js/{formatNumber.DVmjGoTY.js → formatNumber.DGhYc_Mf.js} +1 -1
  28. streamlit/static/static/js/{iconPosition.B5iPID0O.js → iconPosition.BnUUQ30a.js} +1 -1
  29. streamlit/static/static/js/{iframeResizer.contentWindow.Bz08qZTS.js → iframeResizer.contentWindow.D3HlN2_u.js} +1 -1
  30. streamlit/static/static/js/index.-6iyrqXe.js +2 -0
  31. streamlit/static/static/js/{index.BUQfYsj_.js → index.4Py-BAlV.js} +27 -27
  32. streamlit/static/static/js/{index.DTbYStHw.js → index.ASGSMEuq.js} +1 -1
  33. streamlit/static/static/js/{index.DLe-dm7S.js → index.AoB_TJ4O.js} +1 -1
  34. streamlit/static/static/js/{index.BsV4PNSZ.js → index.BTnyrk_J.js} +1 -1
  35. streamlit/static/static/js/{index.DtbTlupq.js → index.BZfRhYeD.js} +1 -1
  36. streamlit/static/static/js/{index.Cf2AMry_.js → index.BawxRUvY.js} +1 -1
  37. streamlit/static/static/js/{index.DeSEzRe4.js → index.Bc2Sogpd.js} +1 -1
  38. streamlit/static/static/js/index.Be44uNWE.js +155 -0
  39. streamlit/static/static/js/{index.DcrK63ke.js → index.BfZaYl9Q.js} +1 -1
  40. streamlit/static/static/js/index.BkTA7mhD.js +1 -0
  41. streamlit/static/static/js/{index.B2Z-kv-M.js → index.BmZ9lqlh.js} +1 -1
  42. streamlit/static/static/js/{index.BO5Anc8A.js → index.BrUZv2-i.js} +1 -1
  43. streamlit/static/static/js/{index.D8NuaGYk.js → index.BwUt1N7e.js} +1 -1
  44. streamlit/static/static/js/{index.Br4GFP5N.js → index.BweYZuYB.js} +1 -1
  45. streamlit/static/static/js/{index.DtkLbyX6.js → index.BxxnmN7s.js} +1 -1
  46. streamlit/static/static/js/{index.B73wdIQu.js → index.Bzer1l17.js} +1 -1
  47. streamlit/static/static/js/{index.DCbhnbxV.js → index.C-LQI-jM.js} +1 -1
  48. streamlit/static/static/js/{index.C9Du9-Q6.js → index.CBP0Yj26.js} +1 -1
  49. streamlit/static/static/js/{index.B1VdM8pe.js → index.CL4i8dLy.js} +1 -1
  50. streamlit/static/static/js/{index.DDJXezSH.js → index.Cget4l_o.js} +1 -1
  51. streamlit/static/static/js/{index.DdA-ZlAG.js → index.Cgpx_JIE.js} +1 -1
  52. streamlit/static/static/js/{index.BjSwq8Vk.js → index.CkUcLi4S.js} +1 -1
  53. streamlit/static/static/js/{index.B3rRJBWJ.js → index.ClWPH1ol.js} +1 -1
  54. streamlit/static/static/js/{index.CqKxXZq8.js → index.CqXI0FvE.js} +1 -1
  55. streamlit/static/static/js/{index.BGeclTcP.js → index.CsDvdugJ.js} +1 -1
  56. streamlit/static/static/js/{index.D27T30iu.js → index.D3PFGrKx.js} +1 -1
  57. streamlit/static/static/js/{index.D-9tweQI.js → index.D9DmFe4f.js} +1 -1
  58. streamlit/static/static/js/{index.CcrP49gy.js → index.DA1H59ev.js} +1 -1
  59. streamlit/static/static/js/{index.CyT-IgZS.js → index.DCDzmKGh.js} +1 -1
  60. streamlit/static/static/js/{index.BRh6Nh1W.js → index.DGlNWX-d.js} +1 -1
  61. streamlit/static/static/js/{index.NDg_fgRk.js → index.DJyaagfL.js} +1 -1
  62. streamlit/static/static/js/{index.Cixc-04N.js → index.DKHCQGvc.js} +1 -1
  63. streamlit/static/static/js/{index.BN_-fNd4.js → index.DMvqRKrG.js} +1 -1
  64. streamlit/static/static/js/{index.B1BUTKEm.js → index.DSgAF5Ya.js} +1 -1
  65. streamlit/static/static/js/{index.CSLyVtqK.js → index.DXIXAgXZ.js} +1 -1
  66. streamlit/static/static/js/{index.D-_lSREs.js → index.DYU8DSsz.js} +1 -1
  67. streamlit/static/static/js/{index.Dz8r_pse.js → index.DcgZZBLA.js} +1 -1
  68. streamlit/static/static/js/{index.D_7wCuw7.js → index.DrEgBjHk.js} +1 -1
  69. streamlit/static/static/js/{index.nO-46oj3.js → index.DvJewWKZ.js} +1 -1
  70. streamlit/static/static/js/{index.DBz0IWKb.js → index.E7tHKIeN.js} +1 -1
  71. streamlit/static/static/js/{index.DZbnk5ZL.js → index.cJD6QuWU.js} +1 -1
  72. streamlit/static/static/js/index.efJCKg5J.js +1 -0
  73. streamlit/static/static/js/{index.BHI4Z_pP.js → index.g0M-BhA_.js} +1 -1
  74. streamlit/static/static/js/{index.tGMuPBOJ.js → index.gWwJtOw7.js} +1 -1
  75. streamlit/static/static/js/{index.CcW2XYDa.js → index.iHHnyqB6.js} +1 -1
  76. streamlit/static/static/js/{index.D_fCowhb.js → index.kWhskx02.js} +1 -1
  77. streamlit/static/static/js/{index.DE8uaKe5.js → index.koIpOVlc.js} +1 -1
  78. streamlit/static/static/js/{input.BHfbis07.js → input.BSL21NTV.js} +1 -1
  79. streamlit/static/static/js/{main.TRAx4Ikk.js → main.BpT2ATMT.js} +1 -1
  80. streamlit/static/static/js/{memory.B-erR_VO.js → memory.cB4wh9Fi.js} +1 -1
  81. streamlit/static/static/js/number-overlay-editor.Dq7eo7Iv.js +9 -0
  82. streamlit/static/static/js/{pandasStylerUtils.DWhP2I9U.js → pandasStylerUtils.CNWaNfX0.js} +1 -1
  83. streamlit/static/static/js/{sandbox.Y-3JnxXp.js → sandbox.SWFjuzzE.js} +1 -1
  84. streamlit/static/static/js/{styled-components.OcmJ_Gl-.js → styled-components.CL2wCoKu.js} +1 -1
  85. streamlit/static/static/js/{throttle.BzPEkdAP.js → throttle.tYQai0iG.js} +1 -1
  86. streamlit/static/static/js/{timepicker.DdPg7fr8.js → timepicker.BoIflsNb.js} +1 -1
  87. streamlit/static/static/js/{toConsumableArray.4euOQSUG.js → toConsumableArray.yJBHJHiJ.js} +1 -1
  88. streamlit/static/static/js/uniqueId.C3_1KC89.js +1 -0
  89. streamlit/static/static/js/useBasicWidgetState.gtwv2ATI.js +1 -0
  90. streamlit/static/static/js/{useIntlLocale.DtCpbCf6.js → useIntlLocale.CeFO_s8L.js} +1 -1
  91. streamlit/static/static/js/{useTextInputAutoExpand.FwwVA2cM.js → useTextInputAutoExpand.dVyX-6Qj.js} +1 -1
  92. streamlit/static/static/js/{useUpdateUiValue.CRipBFts.js → useUpdateUiValue.C8TwIzyl.js} +1 -1
  93. streamlit/static/static/js/{useWaveformController.G7nrxMWm.js → useWaveformController.DQcQ8iSx.js} +1 -1
  94. streamlit/static/static/js/{withCalculatedWidth.DZjNj8ew.js → withCalculatedWidth.CJBjGOx4.js} +1 -1
  95. streamlit/static/static/js/{withFullScreenWrapper.ExJtnKzw.js → withFullScreenWrapper.gfqK0Xv0.js} +1 -1
  96. {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251224.dist-info}/METADATA +1 -1
  97. {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251224.dist-info}/RECORD +101 -100
  98. streamlit/static/static/js/FormClearHelper.BHzs6HmO.js +0 -1
  99. streamlit/static/static/js/data-grid-overlay-editor.tab0U6JF.js +0 -1
  100. streamlit/static/static/js/index.CHlyWOoe.js +0 -1
  101. streamlit/static/static/js/index.DUHxUztP.js +0 -2
  102. streamlit/static/static/js/index.DUeWSrlP.js +0 -155
  103. streamlit/static/static/js/index._XD_enMk.js +0 -1
  104. streamlit/static/static/js/number-overlay-editor.CqgKHyI0.js +0 -9
  105. streamlit/static/static/js/uniqueId.D4g5K3jE.js +0 -1
  106. streamlit/static/static/js/useBasicWidgetState.D0XzcgDX.js +0 -1
  107. {streamlit_nightly-1.52.3.dev20251222.data → streamlit_nightly-1.52.3.dev20251224.data}/scripts/streamlit.cmd +0 -0
  108. {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251224.dist-info}/WHEEL +0 -0
  109. {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251224.dist-info}/entry_points.txt +0 -0
  110. {streamlit_nightly-1.52.3.dev20251222.dist-info → streamlit_nightly-1.52.3.dev20251224.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 | Literal["localized", "distance", "calendar", "iso8601"] | None = None,
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
- if "vconcat" in spec and use_container_width:
310
- spec["autosize"] = {"type": "fit-x", "contains": "padding"}
344
+ has_nested_comp = _has_nested_composition(spec)
311
345
 
312
- elif is_facet_chart:
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 (is_facet_chart or "hconcat" in spec or "repeat" in spec)
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 controlling how the interface should
521
- display numbers. This does not impact the return value.
522
-
523
- For information about formatting integers and floats, see
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 information about formatting datetimes, dates, and times, see
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 data. ``validate`` is called
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: TTLCache[str, CachedResult[R]] = TTLCache(
511
- maxsize=max_entries, ttl=ttl_seconds, timer=cache_utils.TTLCACHE_TIMER
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
- self._mem_cache.clear()
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
- del self._mem_cache[key]
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)
@@ -37,7 +37,7 @@
37
37
  <script>
38
38
  window.prerenderReady = false
39
39
  </script>
40
- <script type="module" crossorigin src="./static/js/index.DUeWSrlP.js"></script>
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>