We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Bug in clearing cache of cached function via .clear() in streamlit==1.20.0
### Checklist

- [X] I have searched the [existing issues](https://github.com/streamlit/streamlit/issues) for similar issues.
- [X] I added a very descriptive title to this issue.
- [X] I have provided sufficient information below to help reproduce this issue.

### Summary

In streamlit==1.20.0 I am unable to clear cache of a cached functions via `.clear()` (this was possible in streamlit 1.19.0).

I suspect this is due to changes made in latest streamlit version to `streamlit\runtime\caching\`, and in particular to `storage` module: now, indeed, even if `st.cache_data` is used without any custom argument - and, then, with `persist=None` - streamlit try to clear also local cache, and searching for the local cache folder eventually raise the error `FileNotFoundError: [Errno 2] No such file or directory` / `FileNotFoundError: [WinError 3] The system cannot find the path specified`

### Reproducible Code Example

```Python
import streamlit as st
import time

@st.cache_data
def dummy():
    st.write('Hello from dummy function! I will wait for 5 seconds...')
    time.sleep(5)
    st.write('Done!')

left, right = st.columns(2)
with left:
    if st.button('Run dummy function'):
        dummy()
with right:
    if st.button('Clear cache'):
        dummy.clear()
```


### Steps To Reproduce

1. `pip install streamlit==1.20.0`
2. Copy above snippet to `app.py`
3. `streamlit run app.py`
4. Click a first time on "Run dummy function" button (-> see cache spinner)
5. Click a second time on "Run dummy function" (-> confirm that function has been cached)
6. Click on "Clear cache" button (-> `FileNotFoundError` should be raised)

### Expected Behavior

As in previous streamlit 1.19.0 version, cache can be cleared without searching for local cache if `persist = None | False`

### Current Behavior

Cannot properly clear cache due to `FileNotFoundError: [WinError 3] The system cannot find the path specified: 'C:\\Users\\XXXXXXXXX\\.streamlit\\cache'` (on local Windows machine) or `FileNotFoundError: [Errno 2] No such file or directory: '/root/.streamlit/cache'` (on remote containerized execution)

Full traceback:

```
FileNotFoundError: [WinError 3] The system cannot find the path specified: 'C:\\Users\\XXXXXXXXX\\.streamlit\\cache'

File "C:\Users\XXXXXXXXX\Miniconda3\envs\hc\lib\site-packages\streamlit\runtime\scriptrunner\script_runner.py", line 565, in _run_script
    exec(code, module.__dict__)
File "XXXXXXXXX\app.py", line 16, in <module>
    dummy.clear()
File "C:\Users\XXXXXXXXX\Miniconda3\envs\hc\lib\site-packages\streamlit\runtime\caching\cache_utils.py", line 331, in clear
    cache.clear()
File "C:\Users\XXXXXXXXX\Miniconda3\envs\hc\lib\site-packages\streamlit\runtime\caching\cache_utils.py", line 137, in clear
    self._clear()
File "C:\Users\XXXXXXXXX\Miniconda3\envs\hc\lib\site-packages\streamlit\runtime\caching\cache_data_api.py", line 659, in _clear
    self.storage.clear()
File "C:\Users\XXXXXXXXX\Miniconda3\envs\hc\lib\site-packages\streamlit\runtime\caching\storage\in_memory_cache_storage_wrapper.py", line 107, in clear
    self._persist_storage.clear()
File "C:\Users\XXXXXXXXX\Miniconda3\envs\hc\lib\site-packages\streamlit\runtime\caching\storage\local_disk_cache_storage.py", line 174, in clear
    for file_name in os.listdir(cache_dir):
```

### Is this a regression?

- [X] Yes, this used to work in a previous version.

### Debug info

- Streamlit version: 1.20.0
- Python version: 3.10.8
- Operating System: Windows 10 Enterprise v22H2 build 19045.2604
- Browser: Brave
- Virtual environment: miniconda environment


### Additional Information

_No response_

### Are you willing to submit a PR?

- [ ] Yes, I am willing to submit a PR!

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py`
Content:
```
1 # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
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 """Declares the LocalDiskCacheStorageManager class, which is used
16 to create LocalDiskCacheStorage instances wrapped by InMemoryCacheStorageWrapper,
17 InMemoryCacheStorageWrapper wrapper allows to have first layer of in-memory cache,
18 before accessing to LocalDiskCacheStorage itself.
19 
20 Declares the LocalDiskCacheStorage class, which is used to store cached
21 values on disk.
22 
23 How these classes work together
24 -------------------------------
25 
26 - LocalDiskCacheStorageManager : each instance of this is able
27 to create LocalDiskCacheStorage instances wrapped by InMemoryCacheStorageWrapper,
28 and to clear data from cache storage folder. It is also LocalDiskCacheStorageManager
29 responsibility to check if the context is valid for the storage, and to log warning
30 if the context is not valid.
31 
32 - LocalDiskCacheStorage : each instance of this is able to get, set, delete, and clear
33 entries from disk for a single `@st.cache_data` decorated function if `persist="disk"`
34 is used in CacheStorageContext.
35 
36 
37     ┌───────────────────────────────┐
38     │  LocalDiskCacheStorageManager │
39     │                               │
40     │     - clear_all               │
41     │     - check_context           │
42     │                               │
43     └──┬────────────────────────────┘
44        │
45        │                ┌──────────────────────────────┐
46        │                │                              │
47        │ create(context)│  InMemoryCacheStorageWrapper │
48        └────────────────►                              │
49                         │    ┌─────────────────────┐   │
50                         │    │                     │   │
51                         │    │   LocalDiskStorage  │   │
52                         │    │                     │   │
53                         │    └─────────────────────┘   │
54                         │                              │
55                         └──────────────────────────────┘
56 
57 """
58 
59 from __future__ import annotations
60 
61 import math
62 import os
63 import shutil
64 
65 from streamlit import util
66 from streamlit.file_util import get_streamlit_file_path, streamlit_read, streamlit_write
67 from streamlit.logger import get_logger
68 from streamlit.runtime.caching.storage.cache_storage_protocol import (
69     CacheStorage,
70     CacheStorageContext,
71     CacheStorageError,
72     CacheStorageKeyNotFoundError,
73     CacheStorageManager,
74 )
75 from streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper import (
76     InMemoryCacheStorageWrapper,
77 )
78 
79 # Streamlit directory where persisted @st.cache_data objects live.
80 # (This is the same directory that @st.cache persisted objects live.
81 # But @st.cache_data uses a different extension, so they don't overlap.)
82 _CACHE_DIR_NAME = "cache"
83 
84 # The extension for our persisted @st.cache_data objects.
85 # (`@st.cache_data` was originally called `@st.memo`)
86 _CACHED_FILE_EXTENSION = "memo"
87 
88 _LOGGER = get_logger(__name__)
89 
90 
91 class LocalDiskCacheStorageManager(CacheStorageManager):
92     def create(self, context: CacheStorageContext) -> CacheStorage:
93         """Creates a new cache storage instance wrapped with in-memory cache layer"""
94         persist_storage = LocalDiskCacheStorage(context)
95         return InMemoryCacheStorageWrapper(
96             persist_storage=persist_storage, context=context
97         )
98 
99     def clear_all(self) -> None:
100         cache_path = get_cache_folder_path()
101         if os.path.isdir(cache_path):
102             shutil.rmtree(cache_path)
103 
104     def check_context(self, context: CacheStorageContext) -> None:
105         if (
106             context.persist == "disk"
107             and context.ttl_seconds is not None
108             and not math.isinf(context.ttl_seconds)
109         ):
110             _LOGGER.warning(
111                 f"The cached function '{context.function_display_name}' has a TTL "
112                 "that will be ignored. Persistent cached functions currently don't "
113                 "support TTL."
114             )
115 
116 
117 class LocalDiskCacheStorage(CacheStorage):
118     """Cache storage that persists data to disk
119     This is the default cache persistence layer for `@st.cache_data`
120     """
121 
122     def __init__(self, context: CacheStorageContext):
123         self.function_key = context.function_key
124         self.persist = context.persist
125         self._ttl_seconds = context.ttl_seconds
126         self._max_entries = context.max_entries
127 
128     @property
129     def ttl_seconds(self) -> float:
130         return self._ttl_seconds if self._ttl_seconds is not None else math.inf
131 
132     @property
133     def max_entries(self) -> float:
134         return float(self._max_entries) if self._max_entries is not None else math.inf
135 
136     def get(self, key: str) -> bytes:
137         """
138         Returns the stored value for the key if persisted,
139         raise CacheStorageKeyNotFoundError if not found, or not configured
140         with persist="disk"
141         """
142         if self.persist == "disk":
143             path = self._get_cache_file_path(key)
144             try:
145                 with streamlit_read(path, binary=True) as input:
146                     value = input.read()
147                     _LOGGER.debug("Disk cache HIT: %s", key)
148                     return bytes(value)
149             except FileNotFoundError:
150                 raise CacheStorageKeyNotFoundError("Key not found in disk cache")
151             except Exception as ex:
152                 _LOGGER.error(ex)
153                 raise CacheStorageError("Unable to read from cache") from ex
154         else:
155             raise CacheStorageKeyNotFoundError(
156                 f"Local disk cache storage is disabled (persist={self.persist})"
157             )
158 
159     def set(self, key: str, value: bytes) -> None:
160         """Sets the value for a given key"""
161         if self.persist == "disk":
162             path = self._get_cache_file_path(key)
163             try:
164                 with streamlit_write(path, binary=True) as output:
165                     output.write(value)
166             except util.Error as e:
167                 _LOGGER.debug(e)
168                 # Clean up file so we don't leave zero byte files.
169                 try:
170                     os.remove(path)
171                 except (FileNotFoundError, IOError, OSError):
172                     # If we can't remove the file, it's not a big deal.
173                     pass
174                 raise CacheStorageError("Unable to write to cache") from e
175 
176     def delete(self, key: str) -> None:
177         """Delete a cache file from disk. If the file does not exist on disk,
178         return silently. If another exception occurs, log it. Does not throw.
179         """
180         if self.persist == "disk":
181             path = self._get_cache_file_path(key)
182             try:
183                 os.remove(path)
184             except FileNotFoundError:
185                 # The file is already removed.
186                 pass
187             except Exception as ex:
188                 _LOGGER.exception(
189                     "Unable to remove a file from the disk cache", exc_info=ex
190                 )
191 
192     def clear(self) -> None:
193         """Delete all keys for the current storage"""
194         cache_dir = get_cache_folder_path()
195 
196         for file_name in os.listdir(cache_dir):
197             if self._is_cache_file(file_name):
198                 os.remove(os.path.join(cache_dir, file_name))
199 
200     def close(self) -> None:
201         """Dummy implementation of close, we don't need to actually "close" anything"""
202 
203     def _get_cache_file_path(self, value_key: str) -> str:
204         """Return the path of the disk cache file for the given value."""
205         cache_dir = get_cache_folder_path()
206         return os.path.join(
207             cache_dir, f"{self.function_key}-{value_key}.{_CACHED_FILE_EXTENSION}"
208         )
209 
210     def _is_cache_file(self, fname: str) -> bool:
211         """Return true if the given file name is a cache file for this storage."""
212         return fname.startswith(f"{self.function_key}-") and fname.endswith(
213             f".{_CACHED_FILE_EXTENSION}"
214         )
215 
216 
217 def get_cache_folder_path() -> str:
218     return get_streamlit_file_path(_CACHE_DIR_NAME)
219
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py b/lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py
--- a/lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py
+++ b/lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py
@@ -193,9 +193,13 @@
         """Delete all keys for the current storage"""
         cache_dir = get_cache_folder_path()
 
-        for file_name in os.listdir(cache_dir):
-            if self._is_cache_file(file_name):
-                os.remove(os.path.join(cache_dir, file_name))
+        if os.path.isdir(cache_dir):
+            # We try to remove all files in the cache directory that start with
+            # the function key, whether `clear` called for `self.persist`
+            # storage or not, to avoid leaving orphaned files in the cache directory.
+            for file_name in os.listdir(cache_dir):
+                if self._is_cache_file(file_name):
+                    os.remove(os.path.join(cache_dir, file_name))
 
     def close(self) -> None:
         """Dummy implementation of close, we don't need to actually "close" anything"""

{"golden_diff": "diff --git a/lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py b/lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py\n--- a/lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py\n+++ b/lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py\n@@ -193,9 +193,13 @@\n         \"\"\"Delete all keys for the current storage\"\"\"\n         cache_dir = get_cache_folder_path()\n \n-        for file_name in os.listdir(cache_dir):\n-            if self._is_cache_file(file_name):\n-                os.remove(os.path.join(cache_dir, file_name))\n+        if os.path.isdir(cache_dir):\n+            # We try to remove all files in the cache directory that start with\n+            # the function key, whether `clear` called for `self.persist`\n+            # storage or not, to avoid leaving orphaned files in the cache directory.\n+            for file_name in os.listdir(cache_dir):\n+                if self._is_cache_file(file_name):\n+                    os.remove(os.path.join(cache_dir, file_name))\n \n     def close(self) -> None:\n         \"\"\"Dummy implementation of close, we don't need to actually \"close\" anything\"\"\"\n", "issue": "Bug in clearing cache of cached function via .clear() in streamlit==1.20.0\n### Checklist\n\n- [X] I have searched the [existing issues](https://github.com/streamlit/streamlit/issues) for similar issues.\n- [X] I added a very descriptive title to this issue.\n- [X] I have provided sufficient information below to help reproduce this issue.\n\n### Summary\n\nIn streamlit==1.20.0 I am unable to clear cache of a cached functions via `.clear()` (this was possible in streamlit 1.19.0).\r\n\r\nI suspect this is due to changes made in latest streamlit version to `streamlit\\runtime\\caching\\`, and in particular to `storage` module: now, indeed, even if `st.cache_data` is used without any custom argument - and, then, with `persist=None` - streamlit try to clear also local cache, and searching for the local cache folder eventually raise the error `FileNotFoundError: [Errno 2] No such file or directory` / `FileNotFoundError: [WinError 3] The system cannot find the path specified`\n\n### Reproducible Code Example\n\n```Python\nimport streamlit as st\r\nimport time\r\n\r\n@st.cache_data\r\ndef dummy():\r\n    st.write('Hello from dummy function! I will wait for 5 seconds...')\r\n    time.sleep(5)\r\n    st.write('Done!')\r\n\r\nleft, right = st.columns(2)\r\nwith left:\r\n    if st.button('Run dummy function'):\r\n        dummy()\r\nwith right:\r\n    if st.button('Clear cache'):\r\n        dummy.clear()\n```\n\n\n### Steps To Reproduce\n\n1. `pip install streamlit==1.20.0`\r\n2. Copy above snippet to `app.py`\r\n3. `streamlit run app.py`\r\n4. Click a first time on \"Run dummy function\" button (-> see cache spinner)\r\n5. Click a second time on \"Run dummy function\" (-> confirm that function has been cached)\r\n6. Click on \"Clear cache\" button (-> `FileNotFoundError` should be raised)\n\n### Expected Behavior\n\nAs in previous streamlit 1.19.0 version, cache can be cleared without searching for local cache if `persist = None | False`\n\n### Current Behavior\n\nCannot properly clear cache due to `FileNotFoundError: [WinError 3] The system cannot find the path specified: 'C:\\\\Users\\\\XXXXXXXXX\\\\.streamlit\\\\cache'` (on local Windows machine) or `FileNotFoundError: [Errno 2] No such file or directory: '/root/.streamlit/cache'` (on remote containerized execution)\r\n\r\nFull traceback:\r\n\r\n```\r\nFileNotFoundError: [WinError 3] The system cannot find the path specified: 'C:\\\\Users\\\\XXXXXXXXX\\\\.streamlit\\\\cache'\r\n\r\nFile \"C:\\Users\\XXXXXXXXX\\Miniconda3\\envs\\hc\\lib\\site-packages\\streamlit\\runtime\\scriptrunner\\script_runner.py\", line 565, in _run_script\r\n    exec(code, module.__dict__)\r\nFile \"XXXXXXXXX\\app.py\", line 16, in <module>\r\n    dummy.clear()\r\nFile \"C:\\Users\\XXXXXXXXX\\Miniconda3\\envs\\hc\\lib\\site-packages\\streamlit\\runtime\\caching\\cache_utils.py\", line 331, in clear\r\n    cache.clear()\r\nFile \"C:\\Users\\XXXXXXXXX\\Miniconda3\\envs\\hc\\lib\\site-packages\\streamlit\\runtime\\caching\\cache_utils.py\", line 137, in clear\r\n    self._clear()\r\nFile \"C:\\Users\\XXXXXXXXX\\Miniconda3\\envs\\hc\\lib\\site-packages\\streamlit\\runtime\\caching\\cache_data_api.py\", line 659, in _clear\r\n    self.storage.clear()\r\nFile \"C:\\Users\\XXXXXXXXX\\Miniconda3\\envs\\hc\\lib\\site-packages\\streamlit\\runtime\\caching\\storage\\in_memory_cache_storage_wrapper.py\", line 107, in clear\r\n    self._persist_storage.clear()\r\nFile \"C:\\Users\\XXXXXXXXX\\Miniconda3\\envs\\hc\\lib\\site-packages\\streamlit\\runtime\\caching\\storage\\local_disk_cache_storage.py\", line 174, in clear\r\n    for file_name in os.listdir(cache_dir):\r\n```\n\n### Is this a regression?\n\n- [X] Yes, this used to work in a previous version.\n\n### Debug info\n\n- Streamlit version: 1.20.0\r\n- Python version: 3.10.8\r\n- Operating System: Windows 10 Enterprise v22H2 build 19045.2604\r\n- Browser: Brave\r\n- Virtual environment: miniconda environment\r\n\n\n### Additional Information\n\n_No response_\n\n### Are you willing to submit a PR?\n\n- [ ] Yes, I am willing to submit a PR!\n", "before_files": [{"content": "# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Declares the LocalDiskCacheStorageManager class, which is used\nto create LocalDiskCacheStorage instances wrapped by InMemoryCacheStorageWrapper,\nInMemoryCacheStorageWrapper wrapper allows to have first layer of in-memory cache,\nbefore accessing to LocalDiskCacheStorage itself.\n\nDeclares the LocalDiskCacheStorage class, which is used to store cached\nvalues on disk.\n\nHow these classes work together\n-------------------------------\n\n- LocalDiskCacheStorageManager : each instance of this is able\nto create LocalDiskCacheStorage instances wrapped by InMemoryCacheStorageWrapper,\nand to clear data from cache storage folder. It is also LocalDiskCacheStorageManager\nresponsibility to check if the context is valid for the storage, and to log warning\nif the context is not valid.\n\n- LocalDiskCacheStorage : each instance of this is able to get, set, delete, and clear\nentries from disk for a single `@st.cache_data` decorated function if `persist=\"disk\"`\nis used in CacheStorageContext.\n\n\n    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n    \u2502  LocalDiskCacheStorageManager \u2502\n    \u2502                               \u2502\n    \u2502     - clear_all               \u2502\n    \u2502     - check_context           \u2502\n    \u2502                               \u2502\n    \u2514\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n       \u2502\n       \u2502                \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n       \u2502                \u2502                              \u2502\n       \u2502 create(context)\u2502  InMemoryCacheStorageWrapper \u2502\n       \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba                              \u2502\n                        \u2502    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502\n                        \u2502    \u2502                     \u2502   \u2502\n                        \u2502    \u2502   LocalDiskStorage  \u2502   \u2502\n                        \u2502    \u2502                     \u2502   \u2502\n                        \u2502    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502\n                        \u2502                              \u2502\n                        \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport math\nimport os\nimport shutil\n\nfrom streamlit import util\nfrom streamlit.file_util import get_streamlit_file_path, streamlit_read, streamlit_write\nfrom streamlit.logger import get_logger\nfrom streamlit.runtime.caching.storage.cache_storage_protocol import (\n    CacheStorage,\n    CacheStorageContext,\n    CacheStorageError,\n    CacheStorageKeyNotFoundError,\n    CacheStorageManager,\n)\nfrom streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper import (\n    InMemoryCacheStorageWrapper,\n)\n\n# Streamlit directory where persisted @st.cache_data objects live.\n# (This is the same directory that @st.cache persisted objects live.\n# But @st.cache_data uses a different extension, so they don't overlap.)\n_CACHE_DIR_NAME = \"cache\"\n\n# The extension for our persisted @st.cache_data objects.\n# (`@st.cache_data` was originally called `@st.memo`)\n_CACHED_FILE_EXTENSION = \"memo\"\n\n_LOGGER = get_logger(__name__)\n\n\nclass LocalDiskCacheStorageManager(CacheStorageManager):\n    def create(self, context: CacheStorageContext) -> CacheStorage:\n        \"\"\"Creates a new cache storage instance wrapped with in-memory cache layer\"\"\"\n        persist_storage = LocalDiskCacheStorage(context)\n        return InMemoryCacheStorageWrapper(\n            persist_storage=persist_storage, context=context\n        )\n\n    def clear_all(self) -> None:\n        cache_path = get_cache_folder_path()\n        if os.path.isdir(cache_path):\n            shutil.rmtree(cache_path)\n\n    def check_context(self, context: CacheStorageContext) -> None:\n        if (\n            context.persist == \"disk\"\n            and context.ttl_seconds is not None\n            and not math.isinf(context.ttl_seconds)\n        ):\n            _LOGGER.warning(\n                f\"The cached function '{context.function_display_name}' has a TTL \"\n                \"that will be ignored. Persistent cached functions currently don't \"\n                \"support TTL.\"\n            )\n\n\nclass LocalDiskCacheStorage(CacheStorage):\n    \"\"\"Cache storage that persists data to disk\n    This is the default cache persistence layer for `@st.cache_data`\n    \"\"\"\n\n    def __init__(self, context: CacheStorageContext):\n        self.function_key = context.function_key\n        self.persist = context.persist\n        self._ttl_seconds = context.ttl_seconds\n        self._max_entries = context.max_entries\n\n    @property\n    def ttl_seconds(self) -> float:\n        return self._ttl_seconds if self._ttl_seconds is not None else math.inf\n\n    @property\n    def max_entries(self) -> float:\n        return float(self._max_entries) if self._max_entries is not None else math.inf\n\n    def get(self, key: str) -> bytes:\n        \"\"\"\n        Returns the stored value for the key if persisted,\n        raise CacheStorageKeyNotFoundError if not found, or not configured\n        with persist=\"disk\"\n        \"\"\"\n        if self.persist == \"disk\":\n            path = self._get_cache_file_path(key)\n            try:\n                with streamlit_read(path, binary=True) as input:\n                    value = input.read()\n                    _LOGGER.debug(\"Disk cache HIT: %s\", key)\n                    return bytes(value)\n            except FileNotFoundError:\n                raise CacheStorageKeyNotFoundError(\"Key not found in disk cache\")\n            except Exception as ex:\n                _LOGGER.error(ex)\n                raise CacheStorageError(\"Unable to read from cache\") from ex\n        else:\n            raise CacheStorageKeyNotFoundError(\n                f\"Local disk cache storage is disabled (persist={self.persist})\"\n            )\n\n    def set(self, key: str, value: bytes) -> None:\n        \"\"\"Sets the value for a given key\"\"\"\n        if self.persist == \"disk\":\n            path = self._get_cache_file_path(key)\n            try:\n                with streamlit_write(path, binary=True) as output:\n                    output.write(value)\n            except util.Error as e:\n                _LOGGER.debug(e)\n                # Clean up file so we don't leave zero byte files.\n                try:\n                    os.remove(path)\n                except (FileNotFoundError, IOError, OSError):\n                    # If we can't remove the file, it's not a big deal.\n                    pass\n                raise CacheStorageError(\"Unable to write to cache\") from e\n\n    def delete(self, key: str) -> None:\n        \"\"\"Delete a cache file from disk. If the file does not exist on disk,\n        return silently. If another exception occurs, log it. Does not throw.\n        \"\"\"\n        if self.persist == \"disk\":\n            path = self._get_cache_file_path(key)\n            try:\n                os.remove(path)\n            except FileNotFoundError:\n                # The file is already removed.\n                pass\n            except Exception as ex:\n                _LOGGER.exception(\n                    \"Unable to remove a file from the disk cache\", exc_info=ex\n                )\n\n    def clear(self) -> None:\n        \"\"\"Delete all keys for the current storage\"\"\"\n        cache_dir = get_cache_folder_path()\n\n        for file_name in os.listdir(cache_dir):\n            if self._is_cache_file(file_name):\n                os.remove(os.path.join(cache_dir, file_name))\n\n    def close(self) -> None:\n        \"\"\"Dummy implementation of close, we don't need to actually \"close\" anything\"\"\"\n\n    def _get_cache_file_path(self, value_key: str) -> str:\n        \"\"\"Return the path of the disk cache file for the given value.\"\"\"\n        cache_dir = get_cache_folder_path()\n        return os.path.join(\n            cache_dir, f\"{self.function_key}-{value_key}.{_CACHED_FILE_EXTENSION}\"\n        )\n\n    def _is_cache_file(self, fname: str) -> bool:\n        \"\"\"Return true if the given file name is a cache file for this storage.\"\"\"\n        return fname.startswith(f\"{self.function_key}-\") and fname.endswith(\n            f\".{_CACHED_FILE_EXTENSION}\"\n        )\n\n\ndef get_cache_folder_path() -> str:\n    return get_streamlit_file_path(_CACHE_DIR_NAME)\n", "path": "lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py"}], "after_files": [{"content": "# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Declares the LocalDiskCacheStorageManager class, which is used\nto create LocalDiskCacheStorage instances wrapped by InMemoryCacheStorageWrapper,\nInMemoryCacheStorageWrapper wrapper allows to have first layer of in-memory cache,\nbefore accessing to LocalDiskCacheStorage itself.\n\nDeclares the LocalDiskCacheStorage class, which is used to store cached\nvalues on disk.\n\nHow these classes work together\n-------------------------------\n\n- LocalDiskCacheStorageManager : each instance of this is able\nto create LocalDiskCacheStorage instances wrapped by InMemoryCacheStorageWrapper,\nand to clear data from cache storage folder. It is also LocalDiskCacheStorageManager\nresponsibility to check if the context is valid for the storage, and to log warning\nif the context is not valid.\n\n- LocalDiskCacheStorage : each instance of this is able to get, set, delete, and clear\nentries from disk for a single `@st.cache_data` decorated function if `persist=\"disk\"`\nis used in CacheStorageContext.\n\n\n    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n    \u2502  LocalDiskCacheStorageManager \u2502\n    \u2502                               \u2502\n    \u2502     - clear_all               \u2502\n    \u2502     - check_context           \u2502\n    \u2502                               \u2502\n    \u2514\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n       \u2502\n       \u2502                \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n       \u2502                \u2502                              \u2502\n       \u2502 create(context)\u2502  InMemoryCacheStorageWrapper \u2502\n       \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25ba                              \u2502\n                        \u2502    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502\n                        \u2502    \u2502                     \u2502   \u2502\n                        \u2502    \u2502   LocalDiskStorage  \u2502   \u2502\n                        \u2502    \u2502                     \u2502   \u2502\n                        \u2502    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502\n                        \u2502                              \u2502\n                        \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n\"\"\"\n\nfrom __future__ import annotations\n\nimport math\nimport os\nimport shutil\n\nfrom streamlit import util\nfrom streamlit.file_util import get_streamlit_file_path, streamlit_read, streamlit_write\nfrom streamlit.logger import get_logger\nfrom streamlit.runtime.caching.storage.cache_storage_protocol import (\n    CacheStorage,\n    CacheStorageContext,\n    CacheStorageError,\n    CacheStorageKeyNotFoundError,\n    CacheStorageManager,\n)\nfrom streamlit.runtime.caching.storage.in_memory_cache_storage_wrapper import (\n    InMemoryCacheStorageWrapper,\n)\n\n# Streamlit directory where persisted @st.cache_data objects live.\n# (This is the same directory that @st.cache persisted objects live.\n# But @st.cache_data uses a different extension, so they don't overlap.)\n_CACHE_DIR_NAME = \"cache\"\n\n# The extension for our persisted @st.cache_data objects.\n# (`@st.cache_data` was originally called `@st.memo`)\n_CACHED_FILE_EXTENSION = \"memo\"\n\n_LOGGER = get_logger(__name__)\n\n\nclass LocalDiskCacheStorageManager(CacheStorageManager):\n    def create(self, context: CacheStorageContext) -> CacheStorage:\n        \"\"\"Creates a new cache storage instance wrapped with in-memory cache layer\"\"\"\n        persist_storage = LocalDiskCacheStorage(context)\n        return InMemoryCacheStorageWrapper(\n            persist_storage=persist_storage, context=context\n        )\n\n    def clear_all(self) -> None:\n        cache_path = get_cache_folder_path()\n        if os.path.isdir(cache_path):\n            shutil.rmtree(cache_path)\n\n    def check_context(self, context: CacheStorageContext) -> None:\n        if (\n            context.persist == \"disk\"\n            and context.ttl_seconds is not None\n            and not math.isinf(context.ttl_seconds)\n        ):\n            _LOGGER.warning(\n                f\"The cached function '{context.function_display_name}' has a TTL \"\n                \"that will be ignored. Persistent cached functions currently don't \"\n                \"support TTL.\"\n            )\n\n\nclass LocalDiskCacheStorage(CacheStorage):\n    \"\"\"Cache storage that persists data to disk\n    This is the default cache persistence layer for `@st.cache_data`\n    \"\"\"\n\n    def __init__(self, context: CacheStorageContext):\n        self.function_key = context.function_key\n        self.persist = context.persist\n        self._ttl_seconds = context.ttl_seconds\n        self._max_entries = context.max_entries\n\n    @property\n    def ttl_seconds(self) -> float:\n        return self._ttl_seconds if self._ttl_seconds is not None else math.inf\n\n    @property\n    def max_entries(self) -> float:\n        return float(self._max_entries) if self._max_entries is not None else math.inf\n\n    def get(self, key: str) -> bytes:\n        \"\"\"\n        Returns the stored value for the key if persisted,\n        raise CacheStorageKeyNotFoundError if not found, or not configured\n        with persist=\"disk\"\n        \"\"\"\n        if self.persist == \"disk\":\n            path = self._get_cache_file_path(key)\n            try:\n                with streamlit_read(path, binary=True) as input:\n                    value = input.read()\n                    _LOGGER.debug(\"Disk cache HIT: %s\", key)\n                    return bytes(value)\n            except FileNotFoundError:\n                raise CacheStorageKeyNotFoundError(\"Key not found in disk cache\")\n            except Exception as ex:\n                _LOGGER.error(ex)\n                raise CacheStorageError(\"Unable to read from cache\") from ex\n        else:\n            raise CacheStorageKeyNotFoundError(\n                f\"Local disk cache storage is disabled (persist={self.persist})\"\n            )\n\n    def set(self, key: str, value: bytes) -> None:\n        \"\"\"Sets the value for a given key\"\"\"\n        if self.persist == \"disk\":\n            path = self._get_cache_file_path(key)\n            try:\n                with streamlit_write(path, binary=True) as output:\n                    output.write(value)\n            except util.Error as e:\n                _LOGGER.debug(e)\n                # Clean up file so we don't leave zero byte files.\n                try:\n                    os.remove(path)\n                except (FileNotFoundError, IOError, OSError):\n                    # If we can't remove the file, it's not a big deal.\n                    pass\n                raise CacheStorageError(\"Unable to write to cache\") from e\n\n    def delete(self, key: str) -> None:\n        \"\"\"Delete a cache file from disk. If the file does not exist on disk,\n        return silently. If another exception occurs, log it. Does not throw.\n        \"\"\"\n        if self.persist == \"disk\":\n            path = self._get_cache_file_path(key)\n            try:\n                os.remove(path)\n            except FileNotFoundError:\n                # The file is already removed.\n                pass\n            except Exception as ex:\n                _LOGGER.exception(\n                    \"Unable to remove a file from the disk cache\", exc_info=ex\n                )\n\n    def clear(self) -> None:\n        \"\"\"Delete all keys for the current storage\"\"\"\n        cache_dir = get_cache_folder_path()\n\n        if os.path.isdir(cache_dir):\n            # We try to remove all files in the cache directory that start with\n            # the function key, whether `clear` called for `self.persist`\n            # storage or not, to avoid leaving orphaned files in the cache directory.\n            for file_name in os.listdir(cache_dir):\n                if self._is_cache_file(file_name):\n                    os.remove(os.path.join(cache_dir, file_name))\n\n    def close(self) -> None:\n        \"\"\"Dummy implementation of close, we don't need to actually \"close\" anything\"\"\"\n\n    def _get_cache_file_path(self, value_key: str) -> str:\n        \"\"\"Return the path of the disk cache file for the given value.\"\"\"\n        cache_dir = get_cache_folder_path()\n        return os.path.join(\n            cache_dir, f\"{self.function_key}-{value_key}.{_CACHED_FILE_EXTENSION}\"\n        )\n\n    def _is_cache_file(self, fname: str) -> bool:\n        \"\"\"Return true if the given file name is a cache file for this storage.\"\"\"\n        return fname.startswith(f\"{self.function_key}-\") and fname.endswith(\n            f\".{_CACHED_FILE_EXTENSION}\"\n        )\n\n\ndef get_cache_folder_path() -> str:\n    return get_streamlit_file_path(_CACHE_DIR_NAME)\n", "path": "lib/streamlit/runtime/caching/storage/local_disk_cache_storage.py"}]}