We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Caching bug
### Summary

In `cache_utils.py` there's the following code:
```python
try:
    source_code = inspect.getsource(func)
except OSError as e:
    _LOGGER.debug(
        "Failed to retrieve function's source code when building its key; falling back to bytecode. err={0}",
        e,
    )
    source_code = func.__code__
```

If the except code is triggered, the `source_code` variable is of `code` type, which is not hashable (at least in python 3.9), and causes an exception afterwards.

### Steps to reproduce

In my case it only happens when I bundle my app with pyinstaller, otherwise the except code is never triggered.
I'm not sure of another way to reproduce it.
A minimal reproducible case would probably be just calling a function wrapped with `st.singleton` after bundling with pyinstaller.

### Suggested solution

Simply replacing the last line with:
```python
source_code = func.__code__.co_code
```
makes the variable a byte-array representation of the function, which is hashbable.
See: https://docs.python.org/3/library/inspect.html

--- 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/caching/cache_utils.py`
Content:
```
1 # Copyright 2018-2021 Streamlit Inc.
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 """Common cache logic shared by st.memo and st.singleton."""
16 
17 import contextlib
18 import functools
19 import hashlib
20 import inspect
21 import threading
22 import types
23 from abc import abstractmethod
24 from typing import Callable, List, Iterator, Tuple, Optional, Any, Union
25 
26 import streamlit as st
27 from streamlit import util
28 from streamlit.caching.cache_errors import CacheKeyNotFoundError
29 from streamlit.logger import get_logger
30 from .cache_errors import (
31     CacheType,
32     CachedStFunctionWarning,
33     UnhashableParamError,
34     UnhashableTypeError,
35 )
36 from .hashing import update_hash
37 
38 _LOGGER = get_logger(__name__)
39 
40 
41 class Cache:
42     """Function cache interface. Caches persist across script runs."""
43 
44     @abstractmethod
45     def read_value(self, value_key: str) -> Any:
46         """Read a value from the cache.
47 
48         Raises
49         ------
50         CacheKeyNotFoundError
51             Raised if value_key is not in the cache.
52 
53         """
54         raise NotImplementedError
55 
56     @abstractmethod
57     def write_value(self, value_key: str, value: Any) -> None:
58         """Write a value to the cache, overwriting any existing value that
59         uses the value_key.
60         """
61         raise NotImplementedError
62 
63     @abstractmethod
64     def clear(self) -> None:
65         """Clear all values from this function cache."""
66         raise NotImplementedError
67 
68 
69 class CachedFunction:
70     """Encapsulates data for a cached function instance.
71 
72     CachedFunction instances are scoped to a single script run - they're not
73     persistent.
74     """
75 
76     def __init__(
77         self, func: types.FunctionType, show_spinner: bool, suppress_st_warning: bool
78     ):
79         self.func = func
80         self.show_spinner = show_spinner
81         self.suppress_st_warning = suppress_st_warning
82 
83     @property
84     def cache_type(self) -> CacheType:
85         raise NotImplementedError
86 
87     @property
88     def call_stack(self) -> "CachedFunctionCallStack":
89         raise NotImplementedError
90 
91     def get_function_cache(self, function_key: str) -> Cache:
92         """Get or create the function cache for the given key."""
93         raise NotImplementedError
94 
95 
96 def create_cache_wrapper(cached_func: CachedFunction) -> Callable[..., Any]:
97     """Create a wrapper for a CachedFunction. This implements the common
98     plumbing for both st.memo and st.singleton.
99     """
100     func = cached_func.func
101     function_key = _make_function_key(cached_func.cache_type, func)
102 
103     @functools.wraps(func)
104     def wrapper(*args, **kwargs):
105         """This function wrapper will only call the underlying function in
106         the case of a cache miss.
107         """
108 
109         # Retrieve the function's cache object. We must do this inside the
110         # wrapped function, because caches can be invalidated at any time.
111         cache = cached_func.get_function_cache(function_key)
112 
113         name = func.__qualname__
114 
115         if len(args) == 0 and len(kwargs) == 0:
116             message = f"Running `{name}()`."
117         else:
118             message = f"Running `{name}(...)`."
119 
120         def get_or_create_cached_value():
121             # Generate the key for the cached value. This is based on the
122             # arguments passed to the function.
123             value_key = _make_value_key(cached_func.cache_type, func, *args, **kwargs)
124 
125             try:
126                 return_value = cache.read_value(value_key)
127                 _LOGGER.debug("Cache hit: %s", func)
128 
129             except CacheKeyNotFoundError:
130                 _LOGGER.debug("Cache miss: %s", func)
131 
132                 with cached_func.call_stack.calling_cached_function(func):
133                     if cached_func.suppress_st_warning:
134                         with cached_func.call_stack.suppress_cached_st_function_warning():
135                             return_value = func(*args, **kwargs)
136                     else:
137                         return_value = func(*args, **kwargs)
138 
139                 cache.write_value(value_key, return_value)
140 
141             return return_value
142 
143         if cached_func.show_spinner:
144             with st.spinner(message):
145                 return get_or_create_cached_value()
146         else:
147             return get_or_create_cached_value()
148 
149     def clear():
150         """Clear the wrapped function's associated cache."""
151         cache = cached_func.get_function_cache(function_key)
152         cache.clear()
153 
154     # Mypy doesn't support declaring attributes of function objects,
155     # so we have to suppress a warning here. We can remove this suppression
156     # when this issue is resolved: https://github.com/python/mypy/issues/2087
157     wrapper.clear = clear  # type: ignore
158 
159     return wrapper
160 
161 
162 class CachedFunctionCallStack(threading.local):
163     """A utility for warning users when they call `st` commands inside
164     a cached function. Internally, this is just a counter that's incremented
165     when we enter a cache function, and decremented when we exit.
166 
167     Data is stored in a thread-local object, so it's safe to use an instance
168     of this class across multiple threads.
169     """
170 
171     def __init__(self, cache_type: CacheType):
172         self._cached_func_stack: List[types.FunctionType] = []
173         self._suppress_st_function_warning = 0
174         self._cache_type = cache_type
175 
176     def __repr__(self) -> str:
177         return util.repr_(self)
178 
179     @contextlib.contextmanager
180     def calling_cached_function(self, func: types.FunctionType) -> Iterator[None]:
181         self._cached_func_stack.append(func)
182         try:
183             yield
184         finally:
185             self._cached_func_stack.pop()
186 
187     @contextlib.contextmanager
188     def suppress_cached_st_function_warning(self) -> Iterator[None]:
189         self._suppress_st_function_warning += 1
190         try:
191             yield
192         finally:
193             self._suppress_st_function_warning -= 1
194             assert self._suppress_st_function_warning >= 0
195 
196     def maybe_show_cached_st_function_warning(
197         self, dg: "st.delta_generator.DeltaGenerator", st_func_name: str
198     ) -> None:
199         """If appropriate, warn about calling st.foo inside @memo.
200 
201         DeltaGenerator's @_with_element and @_widget wrappers use this to warn
202         the user when they're calling st.foo() from within a function that is
203         wrapped in @st.cache.
204 
205         Parameters
206         ----------
207         dg : DeltaGenerator
208             The DeltaGenerator to publish the warning to.
209 
210         st_func_name : str
211             The name of the Streamlit function that was called.
212 
213         """
214         if len(self._cached_func_stack) > 0 and self._suppress_st_function_warning <= 0:
215             cached_func = self._cached_func_stack[-1]
216             self._show_cached_st_function_warning(dg, st_func_name, cached_func)
217 
218     def _show_cached_st_function_warning(
219         self,
220         dg: "st.delta_generator.DeltaGenerator",
221         st_func_name: str,
222         cached_func: types.FunctionType,
223     ) -> None:
224         # Avoid infinite recursion by suppressing additional cached
225         # function warnings from within the cached function warning.
226         with self.suppress_cached_st_function_warning():
227             e = CachedStFunctionWarning(self._cache_type, st_func_name, cached_func)
228             dg.exception(e)
229 
230 
231 def _make_value_key(
232     cache_type: CacheType, func: types.FunctionType, *args, **kwargs
233 ) -> str:
234     """Create the key for a value within a cache.
235 
236     This key is generated from the function's arguments. All arguments
237     will be hashed, except for those named with a leading "_".
238 
239     Raises
240     ------
241     StreamlitAPIException
242         Raised (with a nicely-formatted explanation message) if we encounter
243         an un-hashable arg.
244     """
245 
246     # Create a (name, value) list of all *args and **kwargs passed to the
247     # function.
248     arg_pairs: List[Tuple[Optional[str], Any]] = []
249     for arg_idx in range(len(args)):
250         arg_name = _get_positional_arg_name(func, arg_idx)
251         arg_pairs.append((arg_name, args[arg_idx]))
252 
253     for kw_name, kw_val in kwargs.items():
254         # **kwargs ordering is preserved, per PEP 468
255         # https://www.python.org/dev/peps/pep-0468/, so this iteration is
256         # deterministic.
257         arg_pairs.append((kw_name, kw_val))
258 
259     # Create the hash from each arg value, except for those args whose name
260     # starts with "_". (Underscore-prefixed args are deliberately excluded from
261     # hashing.)
262     args_hasher = hashlib.new("md5")
263     for arg_name, arg_value in arg_pairs:
264         if arg_name is not None and arg_name.startswith("_"):
265             _LOGGER.debug("Not hashing %s because it starts with _", arg_name)
266             continue
267 
268         try:
269             update_hash(
270                 (arg_name, arg_value),
271                 hasher=args_hasher,
272                 cache_type=cache_type,
273             )
274         except UnhashableTypeError as exc:
275             raise UnhashableParamError(cache_type, func, arg_name, arg_value, exc)
276 
277     value_key = args_hasher.hexdigest()
278     _LOGGER.debug("Cache key: %s", value_key)
279 
280     return value_key
281 
282 
283 def _make_function_key(cache_type: CacheType, func: types.FunctionType) -> str:
284     """Create the unique key for a function's cache.
285 
286     A function's key is stable across reruns of the app, and changes when
287     the function's source code changes.
288     """
289     func_hasher = hashlib.new("md5")
290 
291     # Include the function's __module__ and __qualname__ strings in the hash.
292     # This means that two identical functions in different modules
293     # will not share a hash; it also means that two identical *nested*
294     # functions in the same module will not share a hash.
295     update_hash(
296         (func.__module__, func.__qualname__),
297         hasher=func_hasher,
298         cache_type=cache_type,
299     )
300 
301     # Include the function's source code in its hash. If the source code can't
302     # be retrieved, fall back to the function's bytecode instead.
303     source_code: Union[str, types.CodeType]
304     try:
305         source_code = inspect.getsource(func)
306     except OSError as e:
307         _LOGGER.debug(
308             "Failed to retrieve function's source code when building its key; falling back to bytecode. err={0}",
309             e,
310         )
311         source_code = func.__code__
312 
313     update_hash(
314         source_code,
315         hasher=func_hasher,
316         cache_type=cache_type,
317     )
318 
319     cache_key = func_hasher.hexdigest()
320     return cache_key
321 
322 
323 def _get_positional_arg_name(func: types.FunctionType, arg_index: int) -> Optional[str]:
324     """Return the name of a function's positional argument.
325 
326     If arg_index is out of range, or refers to a parameter that is not a
327     named positional argument (e.g. an *args, **kwargs, or keyword-only param),
328     return None instead.
329     """
330     if arg_index < 0:
331         return None
332 
333     params: List[inspect.Parameter] = list(inspect.signature(func).parameters.values())
334     if arg_index >= len(params):
335         return None
336 
337     if params[arg_index].kind in (
338         inspect.Parameter.POSITIONAL_OR_KEYWORD,
339         inspect.Parameter.POSITIONAL_ONLY,
340     ):
341         return params[arg_index].name
342 
343     return None
344
```
--- 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/caching/cache_utils.py b/lib/streamlit/caching/cache_utils.py
--- a/lib/streamlit/caching/cache_utils.py
+++ b/lib/streamlit/caching/cache_utils.py
@@ -300,7 +300,7 @@
 
     # Include the function's source code in its hash. If the source code can't
     # be retrieved, fall back to the function's bytecode instead.
-    source_code: Union[str, types.CodeType]
+    source_code: Union[str, bytes]
     try:
         source_code = inspect.getsource(func)
     except OSError as e:
@@ -308,7 +308,7 @@
             "Failed to retrieve function's source code when building its key; falling back to bytecode. err={0}",
             e,
         )
-        source_code = func.__code__
+        source_code = func.__code__.co_code
 
     update_hash(
         source_code,

{"golden_diff": "diff --git a/lib/streamlit/caching/cache_utils.py b/lib/streamlit/caching/cache_utils.py\n--- a/lib/streamlit/caching/cache_utils.py\n+++ b/lib/streamlit/caching/cache_utils.py\n@@ -300,7 +300,7 @@\n \n     # Include the function's source code in its hash. If the source code can't\n     # be retrieved, fall back to the function's bytecode instead.\n-    source_code: Union[str, types.CodeType]\n+    source_code: Union[str, bytes]\n     try:\n         source_code = inspect.getsource(func)\n     except OSError as e:\n@@ -308,7 +308,7 @@\n             \"Failed to retrieve function's source code when building its key; falling back to bytecode. err={0}\",\n             e,\n         )\n-        source_code = func.__code__\n+        source_code = func.__code__.co_code\n \n     update_hash(\n         source_code,\n", "issue": "Caching bug\n### Summary\r\n\r\nIn `cache_utils.py` there's the following code:\r\n```python\r\ntry:\r\n    source_code = inspect.getsource(func)\r\nexcept OSError as e:\r\n    _LOGGER.debug(\r\n        \"Failed to retrieve function's source code when building its key; falling back to bytecode. err={0}\",\r\n        e,\r\n    )\r\n    source_code = func.__code__\r\n```\r\n\r\nIf the except code is triggered, the `source_code` variable is of `code` type, which is not hashable (at least in python 3.9), and causes an exception afterwards.\r\n\r\n### Steps to reproduce\r\n\r\nIn my case it only happens when I bundle my app with pyinstaller, otherwise the except code is never triggered.\r\nI'm not sure of another way to reproduce it.\r\nA minimal reproducible case would probably be just calling a function wrapped with `st.singleton` after bundling with pyinstaller.\r\n\r\n### Suggested solution\r\n\r\nSimply replacing the last line with:\r\n```python\r\nsource_code = func.__code__.co_code\r\n```\r\nmakes the variable a byte-array representation of the function, which is hashbable.\r\nSee: https://docs.python.org/3/library/inspect.html\n", "before_files": [{"content": "# Copyright 2018-2021 Streamlit Inc.\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\"\"\"Common cache logic shared by st.memo and st.singleton.\"\"\"\n\nimport contextlib\nimport functools\nimport hashlib\nimport inspect\nimport threading\nimport types\nfrom abc import abstractmethod\nfrom typing import Callable, List, Iterator, Tuple, Optional, Any, Union\n\nimport streamlit as st\nfrom streamlit import util\nfrom streamlit.caching.cache_errors import CacheKeyNotFoundError\nfrom streamlit.logger import get_logger\nfrom .cache_errors import (\n    CacheType,\n    CachedStFunctionWarning,\n    UnhashableParamError,\n    UnhashableTypeError,\n)\nfrom .hashing import update_hash\n\n_LOGGER = get_logger(__name__)\n\n\nclass Cache:\n    \"\"\"Function cache interface. Caches persist across script runs.\"\"\"\n\n    @abstractmethod\n    def read_value(self, value_key: str) -> Any:\n        \"\"\"Read a value from the cache.\n\n        Raises\n        ------\n        CacheKeyNotFoundError\n            Raised if value_key is not in the cache.\n\n        \"\"\"\n        raise NotImplementedError\n\n    @abstractmethod\n    def write_value(self, value_key: str, value: Any) -> None:\n        \"\"\"Write a value to the cache, overwriting any existing value that\n        uses the value_key.\n        \"\"\"\n        raise NotImplementedError\n\n    @abstractmethod\n    def clear(self) -> None:\n        \"\"\"Clear all values from this function cache.\"\"\"\n        raise NotImplementedError\n\n\nclass CachedFunction:\n    \"\"\"Encapsulates data for a cached function instance.\n\n    CachedFunction instances are scoped to a single script run - they're not\n    persistent.\n    \"\"\"\n\n    def __init__(\n        self, func: types.FunctionType, show_spinner: bool, suppress_st_warning: bool\n    ):\n        self.func = func\n        self.show_spinner = show_spinner\n        self.suppress_st_warning = suppress_st_warning\n\n    @property\n    def cache_type(self) -> CacheType:\n        raise NotImplementedError\n\n    @property\n    def call_stack(self) -> \"CachedFunctionCallStack\":\n        raise NotImplementedError\n\n    def get_function_cache(self, function_key: str) -> Cache:\n        \"\"\"Get or create the function cache for the given key.\"\"\"\n        raise NotImplementedError\n\n\ndef create_cache_wrapper(cached_func: CachedFunction) -> Callable[..., Any]:\n    \"\"\"Create a wrapper for a CachedFunction. This implements the common\n    plumbing for both st.memo and st.singleton.\n    \"\"\"\n    func = cached_func.func\n    function_key = _make_function_key(cached_func.cache_type, func)\n\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs):\n        \"\"\"This function wrapper will only call the underlying function in\n        the case of a cache miss.\n        \"\"\"\n\n        # Retrieve the function's cache object. We must do this inside the\n        # wrapped function, because caches can be invalidated at any time.\n        cache = cached_func.get_function_cache(function_key)\n\n        name = func.__qualname__\n\n        if len(args) == 0 and len(kwargs) == 0:\n            message = f\"Running `{name}()`.\"\n        else:\n            message = f\"Running `{name}(...)`.\"\n\n        def get_or_create_cached_value():\n            # Generate the key for the cached value. This is based on the\n            # arguments passed to the function.\n            value_key = _make_value_key(cached_func.cache_type, func, *args, **kwargs)\n\n            try:\n                return_value = cache.read_value(value_key)\n                _LOGGER.debug(\"Cache hit: %s\", func)\n\n            except CacheKeyNotFoundError:\n                _LOGGER.debug(\"Cache miss: %s\", func)\n\n                with cached_func.call_stack.calling_cached_function(func):\n                    if cached_func.suppress_st_warning:\n                        with cached_func.call_stack.suppress_cached_st_function_warning():\n                            return_value = func(*args, **kwargs)\n                    else:\n                        return_value = func(*args, **kwargs)\n\n                cache.write_value(value_key, return_value)\n\n            return return_value\n\n        if cached_func.show_spinner:\n            with st.spinner(message):\n                return get_or_create_cached_value()\n        else:\n            return get_or_create_cached_value()\n\n    def clear():\n        \"\"\"Clear the wrapped function's associated cache.\"\"\"\n        cache = cached_func.get_function_cache(function_key)\n        cache.clear()\n\n    # Mypy doesn't support declaring attributes of function objects,\n    # so we have to suppress a warning here. We can remove this suppression\n    # when this issue is resolved: https://github.com/python/mypy/issues/2087\n    wrapper.clear = clear  # type: ignore\n\n    return wrapper\n\n\nclass CachedFunctionCallStack(threading.local):\n    \"\"\"A utility for warning users when they call `st` commands inside\n    a cached function. Internally, this is just a counter that's incremented\n    when we enter a cache function, and decremented when we exit.\n\n    Data is stored in a thread-local object, so it's safe to use an instance\n    of this class across multiple threads.\n    \"\"\"\n\n    def __init__(self, cache_type: CacheType):\n        self._cached_func_stack: List[types.FunctionType] = []\n        self._suppress_st_function_warning = 0\n        self._cache_type = cache_type\n\n    def __repr__(self) -> str:\n        return util.repr_(self)\n\n    @contextlib.contextmanager\n    def calling_cached_function(self, func: types.FunctionType) -> Iterator[None]:\n        self._cached_func_stack.append(func)\n        try:\n            yield\n        finally:\n            self._cached_func_stack.pop()\n\n    @contextlib.contextmanager\n    def suppress_cached_st_function_warning(self) -> Iterator[None]:\n        self._suppress_st_function_warning += 1\n        try:\n            yield\n        finally:\n            self._suppress_st_function_warning -= 1\n            assert self._suppress_st_function_warning >= 0\n\n    def maybe_show_cached_st_function_warning(\n        self, dg: \"st.delta_generator.DeltaGenerator\", st_func_name: str\n    ) -> None:\n        \"\"\"If appropriate, warn about calling st.foo inside @memo.\n\n        DeltaGenerator's @_with_element and @_widget wrappers use this to warn\n        the user when they're calling st.foo() from within a function that is\n        wrapped in @st.cache.\n\n        Parameters\n        ----------\n        dg : DeltaGenerator\n            The DeltaGenerator to publish the warning to.\n\n        st_func_name : str\n            The name of the Streamlit function that was called.\n\n        \"\"\"\n        if len(self._cached_func_stack) > 0 and self._suppress_st_function_warning <= 0:\n            cached_func = self._cached_func_stack[-1]\n            self._show_cached_st_function_warning(dg, st_func_name, cached_func)\n\n    def _show_cached_st_function_warning(\n        self,\n        dg: \"st.delta_generator.DeltaGenerator\",\n        st_func_name: str,\n        cached_func: types.FunctionType,\n    ) -> None:\n        # Avoid infinite recursion by suppressing additional cached\n        # function warnings from within the cached function warning.\n        with self.suppress_cached_st_function_warning():\n            e = CachedStFunctionWarning(self._cache_type, st_func_name, cached_func)\n            dg.exception(e)\n\n\ndef _make_value_key(\n    cache_type: CacheType, func: types.FunctionType, *args, **kwargs\n) -> str:\n    \"\"\"Create the key for a value within a cache.\n\n    This key is generated from the function's arguments. All arguments\n    will be hashed, except for those named with a leading \"_\".\n\n    Raises\n    ------\n    StreamlitAPIException\n        Raised (with a nicely-formatted explanation message) if we encounter\n        an un-hashable arg.\n    \"\"\"\n\n    # Create a (name, value) list of all *args and **kwargs passed to the\n    # function.\n    arg_pairs: List[Tuple[Optional[str], Any]] = []\n    for arg_idx in range(len(args)):\n        arg_name = _get_positional_arg_name(func, arg_idx)\n        arg_pairs.append((arg_name, args[arg_idx]))\n\n    for kw_name, kw_val in kwargs.items():\n        # **kwargs ordering is preserved, per PEP 468\n        # https://www.python.org/dev/peps/pep-0468/, so this iteration is\n        # deterministic.\n        arg_pairs.append((kw_name, kw_val))\n\n    # Create the hash from each arg value, except for those args whose name\n    # starts with \"_\". (Underscore-prefixed args are deliberately excluded from\n    # hashing.)\n    args_hasher = hashlib.new(\"md5\")\n    for arg_name, arg_value in arg_pairs:\n        if arg_name is not None and arg_name.startswith(\"_\"):\n            _LOGGER.debug(\"Not hashing %s because it starts with _\", arg_name)\n            continue\n\n        try:\n            update_hash(\n                (arg_name, arg_value),\n                hasher=args_hasher,\n                cache_type=cache_type,\n            )\n        except UnhashableTypeError as exc:\n            raise UnhashableParamError(cache_type, func, arg_name, arg_value, exc)\n\n    value_key = args_hasher.hexdigest()\n    _LOGGER.debug(\"Cache key: %s\", value_key)\n\n    return value_key\n\n\ndef _make_function_key(cache_type: CacheType, func: types.FunctionType) -> str:\n    \"\"\"Create the unique key for a function's cache.\n\n    A function's key is stable across reruns of the app, and changes when\n    the function's source code changes.\n    \"\"\"\n    func_hasher = hashlib.new(\"md5\")\n\n    # Include the function's __module__ and __qualname__ strings in the hash.\n    # This means that two identical functions in different modules\n    # will not share a hash; it also means that two identical *nested*\n    # functions in the same module will not share a hash.\n    update_hash(\n        (func.__module__, func.__qualname__),\n        hasher=func_hasher,\n        cache_type=cache_type,\n    )\n\n    # Include the function's source code in its hash. If the source code can't\n    # be retrieved, fall back to the function's bytecode instead.\n    source_code: Union[str, types.CodeType]\n    try:\n        source_code = inspect.getsource(func)\n    except OSError as e:\n        _LOGGER.debug(\n            \"Failed to retrieve function's source code when building its key; falling back to bytecode. err={0}\",\n            e,\n        )\n        source_code = func.__code__\n\n    update_hash(\n        source_code,\n        hasher=func_hasher,\n        cache_type=cache_type,\n    )\n\n    cache_key = func_hasher.hexdigest()\n    return cache_key\n\n\ndef _get_positional_arg_name(func: types.FunctionType, arg_index: int) -> Optional[str]:\n    \"\"\"Return the name of a function's positional argument.\n\n    If arg_index is out of range, or refers to a parameter that is not a\n    named positional argument (e.g. an *args, **kwargs, or keyword-only param),\n    return None instead.\n    \"\"\"\n    if arg_index < 0:\n        return None\n\n    params: List[inspect.Parameter] = list(inspect.signature(func).parameters.values())\n    if arg_index >= len(params):\n        return None\n\n    if params[arg_index].kind in (\n        inspect.Parameter.POSITIONAL_OR_KEYWORD,\n        inspect.Parameter.POSITIONAL_ONLY,\n    ):\n        return params[arg_index].name\n\n    return None\n", "path": "lib/streamlit/caching/cache_utils.py"}], "after_files": [{"content": "# Copyright 2018-2021 Streamlit Inc.\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\"\"\"Common cache logic shared by st.memo and st.singleton.\"\"\"\n\nimport contextlib\nimport functools\nimport hashlib\nimport inspect\nimport threading\nimport types\nfrom abc import abstractmethod\nfrom typing import Callable, List, Iterator, Tuple, Optional, Any, Union\n\nimport streamlit as st\nfrom streamlit import util\nfrom streamlit.caching.cache_errors import CacheKeyNotFoundError\nfrom streamlit.logger import get_logger\nfrom .cache_errors import (\n    CacheType,\n    CachedStFunctionWarning,\n    UnhashableParamError,\n    UnhashableTypeError,\n)\nfrom .hashing import update_hash\n\n_LOGGER = get_logger(__name__)\n\n\nclass Cache:\n    \"\"\"Function cache interface. Caches persist across script runs.\"\"\"\n\n    @abstractmethod\n    def read_value(self, value_key: str) -> Any:\n        \"\"\"Read a value from the cache.\n\n        Raises\n        ------\n        CacheKeyNotFoundError\n            Raised if value_key is not in the cache.\n\n        \"\"\"\n        raise NotImplementedError\n\n    @abstractmethod\n    def write_value(self, value_key: str, value: Any) -> None:\n        \"\"\"Write a value to the cache, overwriting any existing value that\n        uses the value_key.\n        \"\"\"\n        raise NotImplementedError\n\n    @abstractmethod\n    def clear(self) -> None:\n        \"\"\"Clear all values from this function cache.\"\"\"\n        raise NotImplementedError\n\n\nclass CachedFunction:\n    \"\"\"Encapsulates data for a cached function instance.\n\n    CachedFunction instances are scoped to a single script run - they're not\n    persistent.\n    \"\"\"\n\n    def __init__(\n        self, func: types.FunctionType, show_spinner: bool, suppress_st_warning: bool\n    ):\n        self.func = func\n        self.show_spinner = show_spinner\n        self.suppress_st_warning = suppress_st_warning\n\n    @property\n    def cache_type(self) -> CacheType:\n        raise NotImplementedError\n\n    @property\n    def call_stack(self) -> \"CachedFunctionCallStack\":\n        raise NotImplementedError\n\n    def get_function_cache(self, function_key: str) -> Cache:\n        \"\"\"Get or create the function cache for the given key.\"\"\"\n        raise NotImplementedError\n\n\ndef create_cache_wrapper(cached_func: CachedFunction) -> Callable[..., Any]:\n    \"\"\"Create a wrapper for a CachedFunction. This implements the common\n    plumbing for both st.memo and st.singleton.\n    \"\"\"\n    func = cached_func.func\n    function_key = _make_function_key(cached_func.cache_type, func)\n\n    @functools.wraps(func)\n    def wrapper(*args, **kwargs):\n        \"\"\"This function wrapper will only call the underlying function in\n        the case of a cache miss.\n        \"\"\"\n\n        # Retrieve the function's cache object. We must do this inside the\n        # wrapped function, because caches can be invalidated at any time.\n        cache = cached_func.get_function_cache(function_key)\n\n        name = func.__qualname__\n\n        if len(args) == 0 and len(kwargs) == 0:\n            message = f\"Running `{name}()`.\"\n        else:\n            message = f\"Running `{name}(...)`.\"\n\n        def get_or_create_cached_value():\n            # Generate the key for the cached value. This is based on the\n            # arguments passed to the function.\n            value_key = _make_value_key(cached_func.cache_type, func, *args, **kwargs)\n\n            try:\n                return_value = cache.read_value(value_key)\n                _LOGGER.debug(\"Cache hit: %s\", func)\n\n            except CacheKeyNotFoundError:\n                _LOGGER.debug(\"Cache miss: %s\", func)\n\n                with cached_func.call_stack.calling_cached_function(func):\n                    if cached_func.suppress_st_warning:\n                        with cached_func.call_stack.suppress_cached_st_function_warning():\n                            return_value = func(*args, **kwargs)\n                    else:\n                        return_value = func(*args, **kwargs)\n\n                cache.write_value(value_key, return_value)\n\n            return return_value\n\n        if cached_func.show_spinner:\n            with st.spinner(message):\n                return get_or_create_cached_value()\n        else:\n            return get_or_create_cached_value()\n\n    def clear():\n        \"\"\"Clear the wrapped function's associated cache.\"\"\"\n        cache = cached_func.get_function_cache(function_key)\n        cache.clear()\n\n    # Mypy doesn't support declaring attributes of function objects,\n    # so we have to suppress a warning here. We can remove this suppression\n    # when this issue is resolved: https://github.com/python/mypy/issues/2087\n    wrapper.clear = clear  # type: ignore\n\n    return wrapper\n\n\nclass CachedFunctionCallStack(threading.local):\n    \"\"\"A utility for warning users when they call `st` commands inside\n    a cached function. Internally, this is just a counter that's incremented\n    when we enter a cache function, and decremented when we exit.\n\n    Data is stored in a thread-local object, so it's safe to use an instance\n    of this class across multiple threads.\n    \"\"\"\n\n    def __init__(self, cache_type: CacheType):\n        self._cached_func_stack: List[types.FunctionType] = []\n        self._suppress_st_function_warning = 0\n        self._cache_type = cache_type\n\n    def __repr__(self) -> str:\n        return util.repr_(self)\n\n    @contextlib.contextmanager\n    def calling_cached_function(self, func: types.FunctionType) -> Iterator[None]:\n        self._cached_func_stack.append(func)\n        try:\n            yield\n        finally:\n            self._cached_func_stack.pop()\n\n    @contextlib.contextmanager\n    def suppress_cached_st_function_warning(self) -> Iterator[None]:\n        self._suppress_st_function_warning += 1\n        try:\n            yield\n        finally:\n            self._suppress_st_function_warning -= 1\n            assert self._suppress_st_function_warning >= 0\n\n    def maybe_show_cached_st_function_warning(\n        self, dg: \"st.delta_generator.DeltaGenerator\", st_func_name: str\n    ) -> None:\n        \"\"\"If appropriate, warn about calling st.foo inside @memo.\n\n        DeltaGenerator's @_with_element and @_widget wrappers use this to warn\n        the user when they're calling st.foo() from within a function that is\n        wrapped in @st.cache.\n\n        Parameters\n        ----------\n        dg : DeltaGenerator\n            The DeltaGenerator to publish the warning to.\n\n        st_func_name : str\n            The name of the Streamlit function that was called.\n\n        \"\"\"\n        if len(self._cached_func_stack) > 0 and self._suppress_st_function_warning <= 0:\n            cached_func = self._cached_func_stack[-1]\n            self._show_cached_st_function_warning(dg, st_func_name, cached_func)\n\n    def _show_cached_st_function_warning(\n        self,\n        dg: \"st.delta_generator.DeltaGenerator\",\n        st_func_name: str,\n        cached_func: types.FunctionType,\n    ) -> None:\n        # Avoid infinite recursion by suppressing additional cached\n        # function warnings from within the cached function warning.\n        with self.suppress_cached_st_function_warning():\n            e = CachedStFunctionWarning(self._cache_type, st_func_name, cached_func)\n            dg.exception(e)\n\n\ndef _make_value_key(\n    cache_type: CacheType, func: types.FunctionType, *args, **kwargs\n) -> str:\n    \"\"\"Create the key for a value within a cache.\n\n    This key is generated from the function's arguments. All arguments\n    will be hashed, except for those named with a leading \"_\".\n\n    Raises\n    ------\n    StreamlitAPIException\n        Raised (with a nicely-formatted explanation message) if we encounter\n        an un-hashable arg.\n    \"\"\"\n\n    # Create a (name, value) list of all *args and **kwargs passed to the\n    # function.\n    arg_pairs: List[Tuple[Optional[str], Any]] = []\n    for arg_idx in range(len(args)):\n        arg_name = _get_positional_arg_name(func, arg_idx)\n        arg_pairs.append((arg_name, args[arg_idx]))\n\n    for kw_name, kw_val in kwargs.items():\n        # **kwargs ordering is preserved, per PEP 468\n        # https://www.python.org/dev/peps/pep-0468/, so this iteration is\n        # deterministic.\n        arg_pairs.append((kw_name, kw_val))\n\n    # Create the hash from each arg value, except for those args whose name\n    # starts with \"_\". (Underscore-prefixed args are deliberately excluded from\n    # hashing.)\n    args_hasher = hashlib.new(\"md5\")\n    for arg_name, arg_value in arg_pairs:\n        if arg_name is not None and arg_name.startswith(\"_\"):\n            _LOGGER.debug(\"Not hashing %s because it starts with _\", arg_name)\n            continue\n\n        try:\n            update_hash(\n                (arg_name, arg_value),\n                hasher=args_hasher,\n                cache_type=cache_type,\n            )\n        except UnhashableTypeError as exc:\n            raise UnhashableParamError(cache_type, func, arg_name, arg_value, exc)\n\n    value_key = args_hasher.hexdigest()\n    _LOGGER.debug(\"Cache key: %s\", value_key)\n\n    return value_key\n\n\ndef _make_function_key(cache_type: CacheType, func: types.FunctionType) -> str:\n    \"\"\"Create the unique key for a function's cache.\n\n    A function's key is stable across reruns of the app, and changes when\n    the function's source code changes.\n    \"\"\"\n    func_hasher = hashlib.new(\"md5\")\n\n    # Include the function's __module__ and __qualname__ strings in the hash.\n    # This means that two identical functions in different modules\n    # will not share a hash; it also means that two identical *nested*\n    # functions in the same module will not share a hash.\n    update_hash(\n        (func.__module__, func.__qualname__),\n        hasher=func_hasher,\n        cache_type=cache_type,\n    )\n\n    # Include the function's source code in its hash. If the source code can't\n    # be retrieved, fall back to the function's bytecode instead.\n    source_code: Union[str, bytes]\n    try:\n        source_code = inspect.getsource(func)\n    except OSError as e:\n        _LOGGER.debug(\n            \"Failed to retrieve function's source code when building its key; falling back to bytecode. err={0}\",\n            e,\n        )\n        source_code = func.__code__.co_code\n\n    update_hash(\n        source_code,\n        hasher=func_hasher,\n        cache_type=cache_type,\n    )\n\n    cache_key = func_hasher.hexdigest()\n    return cache_key\n\n\ndef _get_positional_arg_name(func: types.FunctionType, arg_index: int) -> Optional[str]:\n    \"\"\"Return the name of a function's positional argument.\n\n    If arg_index is out of range, or refers to a parameter that is not a\n    named positional argument (e.g. an *args, **kwargs, or keyword-only param),\n    return None instead.\n    \"\"\"\n    if arg_index < 0:\n        return None\n\n    params: List[inspect.Parameter] = list(inspect.signature(func).parameters.values())\n    if arg_index >= len(params):\n        return None\n\n    if params[arg_index].kind in (\n        inspect.Parameter.POSITIONAL_OR_KEYWORD,\n        inspect.Parameter.POSITIONAL_ONLY,\n    ):\n        return params[arg_index].name\n\n    return None\n", "path": "lib/streamlit/caching/cache_utils.py"}]}