streamlit-nightly 1.35.1.dev20240523__py2.py3-none-any.whl → 1.35.1.dev20240525__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. streamlit/__init__.py +4 -5
  2. streamlit/config.py +1 -15
  3. streamlit/elements/dialog_decorator.py +5 -3
  4. streamlit/elements/utils.py +3 -6
  5. streamlit/runtime/app_session.py +1 -2
  6. streamlit/runtime/caching/__init__.py +5 -0
  7. streamlit/runtime/caching/legacy_cache_api.py +164 -0
  8. streamlit/runtime/fragment.py +91 -80
  9. streamlit/runtime/runtime.py +0 -2
  10. streamlit/static/asset-manifest.json +2 -2
  11. streamlit/static/index.html +1 -1
  12. streamlit/static/static/js/{main.7e42f54d.js → main.e93f99a3.js} +2 -2
  13. streamlit/web/cli.py +1 -8
  14. {streamlit_nightly-1.35.1.dev20240523.dist-info → streamlit_nightly-1.35.1.dev20240525.dist-info}/METADATA +1 -1
  15. {streamlit_nightly-1.35.1.dev20240523.dist-info → streamlit_nightly-1.35.1.dev20240525.dist-info}/RECORD +20 -22
  16. streamlit/runtime/legacy_caching/__init__.py +0 -17
  17. streamlit/runtime/legacy_caching/caching.py +0 -810
  18. streamlit/runtime/legacy_caching/hashing.py +0 -1005
  19. /streamlit/static/static/js/{main.7e42f54d.js.LICENSE.txt → main.e93f99a3.js.LICENSE.txt} +0 -0
  20. {streamlit_nightly-1.35.1.dev20240523.data → streamlit_nightly-1.35.1.dev20240525.data}/scripts/streamlit.cmd +0 -0
  21. {streamlit_nightly-1.35.1.dev20240523.dist-info → streamlit_nightly-1.35.1.dev20240525.dist-info}/WHEEL +0 -0
  22. {streamlit_nightly-1.35.1.dev20240523.dist-info → streamlit_nightly-1.35.1.dev20240525.dist-info}/entry_points.txt +0 -0
  23. {streamlit_nightly-1.35.1.dev20240523.dist-info → streamlit_nightly-1.35.1.dev20240525.dist-info}/top_level.txt +0 -0
@@ -1,810 +0,0 @@
1
- # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
-
15
- """A library of caching utilities."""
16
-
17
- from __future__ import annotations
18
-
19
- import contextlib
20
- import functools
21
- import hashlib
22
- import inspect
23
- import math
24
- import os
25
- import pickle
26
- import shutil
27
- import threading
28
- import time
29
- from collections import namedtuple
30
- from dataclasses import dataclass
31
- from typing import Any, Callable, Final, Iterator, TypeVar, cast, overload
32
-
33
- from cachetools import TTLCache
34
-
35
- import streamlit as st
36
- from streamlit import config, file_util, util
37
- from streamlit.deprecation_util import show_deprecation_warning
38
- from streamlit.elements.spinner import spinner
39
- from streamlit.error_util import handle_uncaught_app_exception
40
- from streamlit.errors import StreamlitAPIWarning
41
- from streamlit.logger import get_logger
42
- from streamlit.runtime.caching import CACHE_DOCS_URL
43
- from streamlit.runtime.caching.cache_type import CacheType, get_decorator_api_name
44
- from streamlit.runtime.legacy_caching.hashing import (
45
- HashFuncsDict,
46
- HashReason,
47
- update_hash,
48
- )
49
- from streamlit.runtime.metrics_util import gather_metrics
50
- from streamlit.runtime.stats import CacheStat, CacheStatsProvider
51
- from streamlit.util import HASHLIB_KWARGS
52
-
53
- _LOGGER: Final = get_logger(__name__)
54
-
55
- # The timer function we use with TTLCache. This is the default timer func, but
56
- # is exposed here as a constant so that it can be patched in unit tests.
57
- _TTLCACHE_TIMER = time.monotonic
58
-
59
-
60
- _CacheEntry = namedtuple("_CacheEntry", ["value", "hash"])
61
- _DiskCacheEntry = namedtuple("_DiskCacheEntry", ["value"])
62
-
63
- # When we show the "st.cache is deprecated" warning, we make a recommendation about which new
64
- # cache decorator to switch to for the following data types:
65
- NEW_CACHE_FUNC_RECOMMENDATIONS: dict[str, CacheType] = {
66
- # cache_data recommendations:
67
- "str": CacheType.DATA,
68
- "float": CacheType.DATA,
69
- "int": CacheType.DATA,
70
- "bytes": CacheType.DATA,
71
- "bool": CacheType.DATA,
72
- "datetime.datetime": CacheType.DATA,
73
- "pandas.DataFrame": CacheType.DATA,
74
- "pandas.Series": CacheType.DATA,
75
- "numpy.bool_": CacheType.DATA,
76
- "numpy.bool8": CacheType.DATA,
77
- "numpy.ndarray": CacheType.DATA,
78
- "numpy.float_": CacheType.DATA,
79
- "numpy.float16": CacheType.DATA,
80
- "numpy.float32": CacheType.DATA,
81
- "numpy.float64": CacheType.DATA,
82
- "numpy.float96": CacheType.DATA,
83
- "numpy.float128": CacheType.DATA,
84
- "numpy.int_": CacheType.DATA,
85
- "numpy.int8": CacheType.DATA,
86
- "numpy.int16": CacheType.DATA,
87
- "numpy.int32": CacheType.DATA,
88
- "numpy.int64": CacheType.DATA,
89
- "numpy.intp": CacheType.DATA,
90
- "numpy.uint8": CacheType.DATA,
91
- "numpy.uint16": CacheType.DATA,
92
- "numpy.uint32": CacheType.DATA,
93
- "numpy.uint64": CacheType.DATA,
94
- "numpy.uintp": CacheType.DATA,
95
- "PIL.Image.Image": CacheType.DATA,
96
- "plotly.graph_objects.Figure": CacheType.DATA,
97
- "matplotlib.figure.Figure": CacheType.DATA,
98
- "altair.Chart": CacheType.DATA,
99
- # cache_resource recommendations:
100
- "pyodbc.Connection": CacheType.RESOURCE,
101
- "pymongo.mongo_client.MongoClient": CacheType.RESOURCE,
102
- "mysql.connector.MySQLConnection": CacheType.RESOURCE,
103
- "psycopg2.connection": CacheType.RESOURCE,
104
- "psycopg2.extensions.connection": CacheType.RESOURCE,
105
- "snowflake.connector.connection.SnowflakeConnection": CacheType.RESOURCE,
106
- "snowflake.snowpark.sessions.Session": CacheType.RESOURCE,
107
- "sqlalchemy.engine.base.Engine": CacheType.RESOURCE,
108
- "sqlite3.Connection": CacheType.RESOURCE,
109
- "torch.nn.Module": CacheType.RESOURCE,
110
- "tensorflow.keras.Model": CacheType.RESOURCE,
111
- "tensorflow.Module": CacheType.RESOURCE,
112
- "tensorflow.compat.v1.Session": CacheType.RESOURCE,
113
- "transformers.Pipeline": CacheType.RESOURCE,
114
- "transformers.PreTrainedTokenizer": CacheType.RESOURCE,
115
- "transformers.PreTrainedTokenizerFast": CacheType.RESOURCE,
116
- "transformers.PreTrainedTokenizerBase": CacheType.RESOURCE,
117
- "transformers.PreTrainedModel": CacheType.RESOURCE,
118
- "transformers.TFPreTrainedModel": CacheType.RESOURCE,
119
- "transformers.FlaxPreTrainedModel": CacheType.RESOURCE,
120
- }
121
-
122
-
123
- def _make_deprecation_warning(cached_value: Any) -> str:
124
- """Build a deprecation warning string for a cache function that has returned the given
125
- value.
126
- """
127
- typename = type(cached_value).__qualname__
128
- cache_type_rec = NEW_CACHE_FUNC_RECOMMENDATIONS.get(typename)
129
- if cache_type_rec is not None:
130
- # We have a recommended cache func for the cached value:
131
- return (
132
- f"`st.cache` is deprecated. Please use one of Streamlit's new caching commands,\n"
133
- f"`st.cache_data` or `st.cache_resource`. Based on this function's return value\n"
134
- f"of type `{typename}`, we recommend using `st.{get_decorator_api_name(cache_type_rec)}`.\n\n"
135
- f"More information [in our docs]({CACHE_DOCS_URL})."
136
- )
137
-
138
- # We do not have a recommended cache func for the cached value:
139
- return (
140
- f"`st.cache` is deprecated. Please use one of Streamlit's new caching commands,\n"
141
- f"`st.cache_data` or `st.cache_resource`.\n\n"
142
- f"More information [in our docs]({CACHE_DOCS_URL})."
143
- )
144
-
145
-
146
- @dataclass
147
- class MemCache:
148
- cache: TTLCache
149
- display_name: str
150
-
151
-
152
- class _MemCaches(CacheStatsProvider):
153
- """Manages all in-memory st.cache caches"""
154
-
155
- def __init__(self):
156
- # Contains a cache object for each st.cache'd function
157
- self._lock = threading.RLock()
158
- self._function_caches: dict[str, MemCache] = {}
159
-
160
- def __repr__(self) -> str:
161
- return util.repr_(self)
162
-
163
- def get_cache(
164
- self,
165
- key: str,
166
- max_entries: float | None,
167
- ttl: float | None,
168
- display_name: str = "",
169
- ) -> MemCache:
170
- """Return the mem cache for the given key.
171
-
172
- If it doesn't exist, create a new one with the given params.
173
- """
174
-
175
- if max_entries is None:
176
- max_entries = math.inf
177
- if ttl is None:
178
- ttl = math.inf
179
-
180
- if not isinstance(max_entries, (int, float)):
181
- raise RuntimeError("max_entries must be an int")
182
- if not isinstance(ttl, (int, float)):
183
- raise RuntimeError("ttl must be a float")
184
-
185
- # Get the existing cache, if it exists, and validate that its params
186
- # haven't changed.
187
- with self._lock:
188
- mem_cache = self._function_caches.get(key)
189
- if (
190
- mem_cache is not None
191
- and mem_cache.cache.ttl == ttl
192
- and mem_cache.cache.maxsize == max_entries
193
- ):
194
- return mem_cache
195
-
196
- # Create a new cache object and put it in our dict
197
- _LOGGER.debug(
198
- "Creating new mem_cache (key=%s, max_entries=%s, ttl=%s)",
199
- key,
200
- max_entries,
201
- ttl,
202
- )
203
- ttl_cache = TTLCache(maxsize=max_entries, ttl=ttl, timer=_TTLCACHE_TIMER)
204
- mem_cache = MemCache(ttl_cache, display_name)
205
- self._function_caches[key] = mem_cache
206
- return mem_cache
207
-
208
- def clear(self) -> None:
209
- """Clear all caches"""
210
- with self._lock:
211
- self._function_caches = {}
212
-
213
- def get_stats(self) -> list[CacheStat]:
214
- with self._lock:
215
- # Shallow-clone our caches. We don't want to hold the global
216
- # lock during stats-gathering.
217
- function_caches = self._function_caches.copy()
218
-
219
- # Lazy-load vendored package to prevent import of numpy
220
- from streamlit.vendor.pympler.asizeof import asizeof
221
-
222
- stats = [
223
- CacheStat("st_cache", cache.display_name, asizeof(c))
224
- for cache in function_caches.values()
225
- for c in cache.cache
226
- ]
227
- return stats
228
-
229
-
230
- # Our singleton _MemCaches instance
231
- _mem_caches = _MemCaches()
232
-
233
-
234
- # A thread-local counter that's incremented when we enter @st.cache
235
- # and decremented when we exit.
236
- class ThreadLocalCacheInfo(threading.local):
237
- def __init__(self):
238
- self.cached_func_stack: list[Callable[..., Any]] = []
239
-
240
- def __repr__(self) -> str:
241
- return util.repr_(self)
242
-
243
-
244
- _cache_info = ThreadLocalCacheInfo()
245
-
246
-
247
- @contextlib.contextmanager
248
- def _calling_cached_function(func: Callable[..., Any]) -> Iterator[None]:
249
- _cache_info.cached_func_stack.append(func)
250
- try:
251
- yield
252
- finally:
253
- _cache_info.cached_func_stack.pop()
254
-
255
-
256
- def _read_from_mem_cache(
257
- mem_cache: MemCache,
258
- key: str,
259
- allow_output_mutation: bool,
260
- func_or_code: Callable[..., Any],
261
- hash_funcs: HashFuncsDict | None,
262
- ) -> Any:
263
- cache = mem_cache.cache
264
- if key in cache:
265
- entry = cache[key]
266
-
267
- if not allow_output_mutation:
268
- computed_output_hash = _get_output_hash(
269
- entry.value, func_or_code, hash_funcs
270
- )
271
- stored_output_hash = entry.hash
272
-
273
- if computed_output_hash != stored_output_hash:
274
- _LOGGER.debug("Cached object was mutated: %s", key)
275
- raise CachedObjectMutationError(entry.value, func_or_code)
276
-
277
- _LOGGER.debug("Memory cache HIT: %s", type(entry.value))
278
- return entry.value
279
-
280
- else:
281
- _LOGGER.debug("Memory cache MISS: %s", key)
282
- raise CacheKeyNotFoundError("Key not found in mem cache")
283
-
284
-
285
- def _write_to_mem_cache(
286
- mem_cache: MemCache,
287
- key: str,
288
- value: Any,
289
- allow_output_mutation: bool,
290
- func_or_code: Callable[..., Any],
291
- hash_funcs: HashFuncsDict | None,
292
- ) -> None:
293
- if allow_output_mutation:
294
- hash = None
295
- else:
296
- hash = _get_output_hash(value, func_or_code, hash_funcs)
297
-
298
- mem_cache.display_name = f"{func_or_code.__module__}.{func_or_code.__qualname__}"
299
- mem_cache.cache[key] = _CacheEntry(value=value, hash=hash)
300
-
301
-
302
- def _get_output_hash(
303
- value: Any, func_or_code: Callable[..., Any], hash_funcs: HashFuncsDict | None
304
- ) -> bytes:
305
- hasher = hashlib.new("md5", **HASHLIB_KWARGS)
306
- update_hash(
307
- value,
308
- hasher=hasher,
309
- hash_funcs=hash_funcs,
310
- hash_reason=HashReason.CACHING_FUNC_OUTPUT,
311
- hash_source=func_or_code,
312
- )
313
- return hasher.digest()
314
-
315
-
316
- def _read_from_disk_cache(key: str) -> Any:
317
- path = file_util.get_streamlit_file_path("cache", "%s.pickle" % key)
318
- try:
319
- with file_util.streamlit_read(path, binary=True) as input:
320
- entry = pickle.load(input)
321
- value = entry.value
322
- _LOGGER.debug("Disk cache HIT: %s", type(value))
323
- except util.Error as e:
324
- _LOGGER.error(e)
325
- raise CacheError("Unable to read from cache: %s" % e)
326
-
327
- except FileNotFoundError:
328
- raise CacheKeyNotFoundError("Key not found in disk cache")
329
- return value
330
-
331
-
332
- def _write_to_disk_cache(key: str, value: Any) -> None:
333
- path = file_util.get_streamlit_file_path("cache", "%s.pickle" % key)
334
-
335
- try:
336
- with file_util.streamlit_write(path, binary=True) as output:
337
- entry = _DiskCacheEntry(value=value)
338
- pickle.dump(entry, output, pickle.HIGHEST_PROTOCOL)
339
- except util.Error as e:
340
- _LOGGER.debug(e)
341
- # Clean up file so we don't leave zero byte files.
342
- try:
343
- os.remove(path)
344
- except (FileNotFoundError, OSError):
345
- # If we can't remove the file, it's not a big deal.
346
- pass
347
- raise CacheError("Unable to write to cache: %s" % e)
348
-
349
-
350
- def _read_from_cache(
351
- mem_cache: MemCache,
352
- key: str,
353
- persist: bool,
354
- allow_output_mutation: bool,
355
- func_or_code: Callable[..., Any],
356
- hash_funcs: HashFuncsDict | None = None,
357
- ) -> Any:
358
- """Read a value from the cache.
359
-
360
- Our goal is to read from memory if possible. If the data was mutated (hash
361
- changed), we show a warning. If reading from memory fails, we either read
362
- from disk or rerun the code.
363
- """
364
- try:
365
- return _read_from_mem_cache(
366
- mem_cache, key, allow_output_mutation, func_or_code, hash_funcs
367
- )
368
-
369
- except CachedObjectMutationError as e:
370
- handle_uncaught_app_exception(CachedObjectMutationWarning(e))
371
- return e.cached_value
372
-
373
- except CacheKeyNotFoundError as e:
374
- if persist:
375
- value = _read_from_disk_cache(key)
376
- _write_to_mem_cache(
377
- mem_cache, key, value, allow_output_mutation, func_or_code, hash_funcs
378
- )
379
- return value
380
- raise e
381
-
382
-
383
- @gather_metrics("_cache_object")
384
- def _write_to_cache(
385
- mem_cache: MemCache,
386
- key: str,
387
- value: Any,
388
- persist: bool,
389
- allow_output_mutation: bool,
390
- func_or_code: Callable[..., Any],
391
- hash_funcs: HashFuncsDict | None = None,
392
- ):
393
- _write_to_mem_cache(
394
- mem_cache, key, value, allow_output_mutation, func_or_code, hash_funcs
395
- )
396
- if persist:
397
- _write_to_disk_cache(key, value)
398
-
399
-
400
- F = TypeVar("F", bound=Callable[..., Any])
401
-
402
-
403
- @overload
404
- def cache(
405
- func: F,
406
- persist: bool = False,
407
- allow_output_mutation: bool = False,
408
- show_spinner: bool = True,
409
- suppress_st_warning: bool = False,
410
- hash_funcs: HashFuncsDict | None = None,
411
- max_entries: int | None = None,
412
- ttl: float | None = None,
413
- ) -> F:
414
- ...
415
-
416
-
417
- @overload
418
- def cache(
419
- func: None = None,
420
- persist: bool = False,
421
- allow_output_mutation: bool = False,
422
- show_spinner: bool = True,
423
- suppress_st_warning: bool = False,
424
- hash_funcs: HashFuncsDict | None = None,
425
- max_entries: int | None = None,
426
- ttl: float | None = None,
427
- ) -> Callable[[F], F]:
428
- ...
429
-
430
-
431
- def cache(
432
- func: F | None = None,
433
- persist: bool = False,
434
- allow_output_mutation: bool = False,
435
- show_spinner: bool = True,
436
- suppress_st_warning: bool = False,
437
- hash_funcs: HashFuncsDict | None = None,
438
- max_entries: int | None = None,
439
- ttl: float | None = None,
440
- ) -> Callable[[F], F] | F:
441
- """Function decorator to memoize function executions.
442
-
443
- Parameters
444
- ----------
445
- func : callable
446
- The function to cache. Streamlit hashes the function and dependent code.
447
-
448
- persist : bool
449
- Whether to persist the cache on disk.
450
-
451
- allow_output_mutation : bool
452
- Streamlit shows a warning when return values are mutated, as that
453
- can have unintended consequences. This is done by hashing the return value internally.
454
-
455
- If you know what you're doing and would like to override this warning, set this to True.
456
-
457
- show_spinner : bool
458
- Enable the spinner. Default is True to show a spinner when there is
459
- a cache miss.
460
-
461
- suppress_st_warning : bool
462
- Suppress warnings about calling Streamlit commands from within
463
- the cached function.
464
-
465
- hash_funcs : dict or None
466
- Mapping of types or fully qualified names to hash functions. This is used to override
467
- the behavior of the hasher inside Streamlit's caching mechanism: when the hasher
468
- encounters an object, it will first check to see if its type matches a key in this
469
- dict and, if so, will use the provided function to generate a hash for it. See below
470
- for an example of how this can be used.
471
-
472
- max_entries : int or None
473
- The maximum number of entries to keep in the cache, or None
474
- for an unbounded cache. (When a new entry is added to a full cache,
475
- the oldest cached entry will be removed.) The default is None.
476
-
477
- ttl : float or None
478
- The maximum number of seconds to keep an entry in the cache, or
479
- None if cache entries should not expire. The default is None.
480
-
481
- Example
482
- -------
483
- >>> import streamlit as st
484
- >>>
485
- >>> @st.cache
486
- ... def fetch_and_clean_data(url):
487
- ... # Fetch data from URL here, and then clean it up.
488
- ... return data
489
- ...
490
- >>> d1 = fetch_and_clean_data(DATA_URL_1)
491
- >>> # Actually executes the function, since this is the first time it was
492
- >>> # encountered.
493
- >>>
494
- >>> d2 = fetch_and_clean_data(DATA_URL_1)
495
- >>> # Does not execute the function. Instead, returns its previously computed
496
- >>> # value. This means that now the data in d1 is the same as in d2.
497
- >>>
498
- >>> d3 = fetch_and_clean_data(DATA_URL_2)
499
- >>> # This is a different URL, so the function executes.
500
-
501
- To set the ``persist`` parameter, use this command as follows:
502
-
503
- >>> @st.cache(persist=True)
504
- ... def fetch_and_clean_data(url):
505
- ... # Fetch data from URL here, and then clean it up.
506
- ... return data
507
-
508
- To disable hashing return values, set the ``allow_output_mutation`` parameter to ``True``:
509
-
510
- >>> @st.cache(allow_output_mutation=True)
511
- ... def fetch_and_clean_data(url):
512
- ... # Fetch data from URL here, and then clean it up.
513
- ... return data
514
-
515
-
516
- To override the default hashing behavior, pass a custom hash function.
517
- You can do that by mapping a type (e.g. ``MongoClient``) to a hash function (``id``) like this:
518
-
519
- >>> @st.cache(hash_funcs={MongoClient: id})
520
- ... def connect_to_database(url):
521
- ... return MongoClient(url)
522
-
523
- Alternatively, you can map the type's fully-qualified name
524
- (e.g. ``"pymongo.mongo_client.MongoClient"``) to the hash function instead:
525
-
526
- >>> @st.cache(hash_funcs={"pymongo.mongo_client.MongoClient": id})
527
- ... def connect_to_database(url):
528
- ... return MongoClient(url)
529
-
530
- """
531
- _LOGGER.debug("Entering st.cache: %s", func)
532
-
533
- # Support passing the params via function decorator, e.g.
534
- # @st.cache(persist=True, allow_output_mutation=True)
535
- if func is None:
536
-
537
- def wrapper(f: F) -> F:
538
- return cache(
539
- func=f,
540
- persist=persist,
541
- allow_output_mutation=allow_output_mutation,
542
- show_spinner=show_spinner,
543
- suppress_st_warning=suppress_st_warning,
544
- hash_funcs=hash_funcs,
545
- max_entries=max_entries,
546
- ttl=ttl,
547
- )
548
-
549
- return wrapper
550
- else:
551
- # To make mypy type narrow Optional[F] -> F
552
- non_optional_func = func
553
-
554
- cache_key = None
555
-
556
- @functools.wraps(non_optional_func)
557
- def wrapped_func(*args, **kwargs):
558
- """Wrapper function that only calls the underlying function on a cache miss.
559
-
560
- Cached objects are stored in the cache/ directory.
561
- """
562
-
563
- if not config.get_option("client.caching"):
564
- _LOGGER.debug("Purposefully skipping cache")
565
- return non_optional_func(*args, **kwargs)
566
-
567
- name = non_optional_func.__qualname__
568
-
569
- if len(args) == 0 and len(kwargs) == 0:
570
- message = "Running `%s()`." % name
571
- else:
572
- message = "Running `%s(...)`." % name
573
-
574
- def get_or_create_cached_value():
575
- nonlocal cache_key
576
- if cache_key is None:
577
- # Delay generating the cache key until the first call.
578
- # This way we can see values of globals, including functions
579
- # defined after this one.
580
- # If we generated the key earlier we would only hash those
581
- # globals by name, and miss changes in their code or value.
582
- cache_key = _hash_func(non_optional_func, hash_funcs)
583
-
584
- # First, get the cache that's attached to this function.
585
- # This cache's key is generated (above) from the function's code.
586
- mem_cache = _mem_caches.get_cache(cache_key, max_entries, ttl)
587
-
588
- # Next, calculate the key for the value we'll be searching for
589
- # within that cache. This key is generated from both the function's
590
- # code and the arguments that are passed into it. (Even though this
591
- # key is used to index into a per-function cache, it must be
592
- # globally unique, because it is *also* used for a global on-disk
593
- # cache that is *not* per-function.)
594
- value_hasher = hashlib.new("md5")
595
-
596
- if args:
597
- update_hash(
598
- args,
599
- hasher=value_hasher,
600
- hash_funcs=hash_funcs,
601
- hash_reason=HashReason.CACHING_FUNC_ARGS,
602
- hash_source=non_optional_func,
603
- )
604
-
605
- if kwargs:
606
- update_hash(
607
- kwargs,
608
- hasher=value_hasher,
609
- hash_funcs=hash_funcs,
610
- hash_reason=HashReason.CACHING_FUNC_ARGS,
611
- hash_source=non_optional_func,
612
- )
613
-
614
- value_key = value_hasher.hexdigest()
615
-
616
- # Avoid recomputing the body's hash by just appending the
617
- # previously-computed hash to the arg hash.
618
- value_key = "{}-{}".format(value_key, cache_key)
619
-
620
- _LOGGER.debug("Cache key: %s", value_key)
621
-
622
- try:
623
- return_value = _read_from_cache(
624
- mem_cache=mem_cache,
625
- key=value_key,
626
- persist=persist,
627
- allow_output_mutation=allow_output_mutation,
628
- func_or_code=non_optional_func,
629
- hash_funcs=hash_funcs,
630
- )
631
- _LOGGER.debug("Cache hit: %s", non_optional_func)
632
-
633
- except CacheKeyNotFoundError:
634
- _LOGGER.debug("Cache miss: %s", non_optional_func)
635
-
636
- with _calling_cached_function(non_optional_func):
637
- return_value = non_optional_func(*args, **kwargs)
638
-
639
- _write_to_cache(
640
- mem_cache=mem_cache,
641
- key=value_key,
642
- value=return_value,
643
- persist=persist,
644
- allow_output_mutation=allow_output_mutation,
645
- func_or_code=non_optional_func,
646
- hash_funcs=hash_funcs,
647
- )
648
-
649
- # st.cache is deprecated. We show a warning every time it's used.
650
- show_deprecation_warning(_make_deprecation_warning(return_value))
651
-
652
- return return_value
653
-
654
- if show_spinner:
655
- with spinner(message, _cache=True):
656
- return get_or_create_cached_value()
657
- else:
658
- return get_or_create_cached_value()
659
-
660
- # Make this a well-behaved decorator by preserving important function
661
- # attributes.
662
- try:
663
- wrapped_func.__dict__.update(non_optional_func.__dict__)
664
- except AttributeError:
665
- # For normal functions this should never happen, but if so it's not problematic.
666
- pass
667
-
668
- return cast(F, wrapped_func)
669
-
670
-
671
- def _hash_func(func: Callable[..., Any], hash_funcs: HashFuncsDict | None) -> str:
672
- # Create the unique key for a function's cache. The cache will be retrieved
673
- # from inside the wrapped function.
674
- #
675
- # A naive implementation would involve simply creating the cache object
676
- # right in the wrapper, which in a normal Python script would be executed
677
- # only once. But in Streamlit, we reload all modules related to a user's
678
- # app when the app is re-run, which means that - among other things - all
679
- # function decorators in the app will be re-run, and so any decorator-local
680
- # objects will be recreated.
681
- #
682
- # Furthermore, our caches can be destroyed and recreated (in response to
683
- # cache clearing, for example), which means that retrieving the function's
684
- # cache in the decorator (so that the wrapped function can save a lookup)
685
- # is incorrect: the cache itself may be recreated between
686
- # decorator-evaluation time and decorated-function-execution time. So we
687
- # must retrieve the cache object *and* perform the cached-value lookup
688
- # inside the decorated function.
689
- func_hasher = hashlib.new("md5")
690
-
691
- # Include the function's __module__ and __qualname__ strings in the hash.
692
- # This means that two identical functions in different modules
693
- # will not share a hash; it also means that two identical *nested*
694
- # functions in the same module will not share a hash.
695
- # We do not pass `hash_funcs` here, because we don't want our function's
696
- # name to get an unexpected hash.
697
- update_hash(
698
- (func.__module__, func.__qualname__),
699
- hasher=func_hasher,
700
- hash_funcs=None,
701
- hash_reason=HashReason.CACHING_FUNC_BODY,
702
- hash_source=func,
703
- )
704
-
705
- # Include the function's body in the hash. We *do* pass hash_funcs here,
706
- # because this step will be hashing any objects referenced in the function
707
- # body.
708
- update_hash(
709
- func,
710
- hasher=func_hasher,
711
- hash_funcs=hash_funcs,
712
- hash_reason=HashReason.CACHING_FUNC_BODY,
713
- hash_source=func,
714
- )
715
- cache_key = func_hasher.hexdigest()
716
- _LOGGER.debug(
717
- "mem_cache key for %s.%s: %s", func.__module__, func.__qualname__, cache_key
718
- )
719
- return cache_key
720
-
721
-
722
- def clear_cache() -> bool:
723
- """Clear the memoization cache.
724
-
725
- Returns
726
- -------
727
- boolean
728
- True if the disk cache was cleared. False otherwise (e.g. cache file
729
- doesn't exist on disk).
730
- """
731
- _clear_mem_cache()
732
- return _clear_disk_cache()
733
-
734
-
735
- def get_cache_path() -> str:
736
- return file_util.get_streamlit_file_path("cache")
737
-
738
-
739
- def _clear_disk_cache() -> bool:
740
- # TODO: Only delete disk cache for functions related to the user's current
741
- # script.
742
- cache_path = get_cache_path()
743
- if os.path.isdir(cache_path):
744
- shutil.rmtree(cache_path)
745
- return True
746
- return False
747
-
748
-
749
- def _clear_mem_cache() -> None:
750
- _mem_caches.clear()
751
-
752
-
753
- class CacheError(Exception):
754
- pass
755
-
756
-
757
- class CacheKeyNotFoundError(Exception):
758
- pass
759
-
760
-
761
- class CachedObjectMutationError(ValueError):
762
- # This is used internally, but never shown to the user.
763
- # Users see CachedObjectMutationWarning instead.
764
-
765
- def __init__(self, cached_value, func_or_code):
766
- self.cached_value = cached_value
767
- if inspect.iscode(func_or_code):
768
- self.cached_func_name = "a code block"
769
- else:
770
- self.cached_func_name = _get_cached_func_name_md(func_or_code)
771
-
772
- def __repr__(self) -> str:
773
- return util.repr_(self)
774
-
775
-
776
- class CachedObjectMutationWarning(StreamlitAPIWarning):
777
- def __init__(self, orig_exc):
778
- msg = self._get_message(orig_exc)
779
- super().__init__(msg)
780
-
781
- def _get_message(self, orig_exc):
782
- return (
783
- """
784
- Return value of %(func_name)s was mutated between runs.
785
-
786
- By default, Streamlit's cache should be treated as immutable, or it may behave
787
- in unexpected ways. You received this warning because Streamlit detected
788
- that an object returned by %(func_name)s was mutated outside of %(func_name)s.
789
-
790
- How to fix this:
791
- * If you did not mean to mutate that return value:
792
- - If possible, inspect your code to find and remove that mutation.
793
- - Otherwise, you could also clone the returned value so you can freely
794
- mutate it.
795
- * If you actually meant to mutate the return value and know the consequences of
796
- doing so, annotate the function with `@st.cache(allow_output_mutation=True)`.
797
-
798
- For more information and detailed solutions check out [our documentation.]
799
- (https://docs.streamlit.io/library/advanced-features/caching)
800
- """
801
- % {"func_name": orig_exc.cached_func_name}
802
- ).strip("\n")
803
-
804
-
805
- def _get_cached_func_name_md(func: Callable[..., Any]) -> str:
806
- """Get markdown representation of the function name."""
807
- if hasattr(func, "__name__"):
808
- return "`%s()`" % func.__name__
809
- else:
810
- return "a cached function"