We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
streamlit.runtime.secrets.AttrDict no longer returns True for isinstance() against dict in streamlit 1.14.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

Hi.

With the latest streamlit version `streamlit.runtime.secrets.AttrDict` seems to inherit from `UserDict` instead of `dict` in previous version.

Many libraries I use check that their configuration is `isinstance` against `dict` and are now failing when I pass my config from st.secrets to them.

I can't see any documentation changes related to this and am not familiar with `UserDict`, do I have to go through my code and wrap all st.secrets access in a `dict()` conversion call now?

### Reproducible Code Example

```Python
import streamlit as st

print(isinstance(st.secrets["my_secret_dict"], dict)) # False on streamlit 1.14.0 - was True prior to that
```


### Steps To Reproduce

_No response_

### Expected Behavior

_No response_

### Current Behavior

_No response_

### Is this a regression?

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

### Debug info

- Streamlit version: 1.14.0
- Python version: 3.100
- Operating System: Linux
- Browser: Firefox
- Virtual environment: None


### 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/secrets.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 import os
16 import threading
17 from collections import UserDict
18 from typing import Any, ItemsView, Iterator, KeysView, Mapping, Optional, ValuesView
19 
20 import toml
21 from blinker import Signal
22 from typing_extensions import Final
23 
24 import streamlit as st
25 import streamlit.watcher.path_watcher
26 from streamlit.logger import get_logger
27 
28 _LOGGER = get_logger(__name__)
29 SECRETS_FILE_LOC = os.path.abspath(os.path.join(".", ".streamlit", "secrets.toml"))
30 
31 
32 def _missing_attr_error_message(attr_name: str) -> str:
33     return (
34         f'st.secrets has no attribute "{attr_name}". '
35         f"Did you forget to add it to secrets.toml or the app settings on Streamlit Cloud? "
36         f"More info: https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app/connect-to-data-sources/secrets-management"
37     )
38 
39 
40 def _missing_key_error_message(key: str) -> str:
41     return (
42         f'st.secrets has no key "{key}". '
43         f"Did you forget to add it to secrets.toml or the app settings on Streamlit Cloud? "
44         f"More info: https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app/connect-to-data-sources/secrets-management"
45     )
46 
47 
48 class AttrDict(UserDict):  # type: ignore[type-arg]
49     """
50     We use AttrDict to wrap up dictionary values from secrets
51     to provide dot access to nested secrets
52     """
53 
54     @staticmethod
55     def _maybe_wrap_in_attr_dict(value) -> Any:
56         if not isinstance(value, dict):
57             return value
58         else:
59             return AttrDict(**value)
60 
61     def __getattr__(self, attr_name: str) -> Any:
62         try:
63             value = super().__getitem__(attr_name)
64             return self._maybe_wrap_in_attr_dict(value)
65         except KeyError:
66             raise AttributeError(_missing_attr_error_message(attr_name))
67 
68     def __getitem__(self, key: str) -> Any:
69         try:
70             value = super().__getitem__(key)
71             return self._maybe_wrap_in_attr_dict(value)
72         except KeyError:
73             raise KeyError(_missing_key_error_message(key))
74 
75 
76 class Secrets(Mapping[str, Any]):
77     """A dict-like class that stores secrets.
78     Parses secrets.toml on-demand. Cannot be externally mutated.
79 
80     Safe to use from multiple threads.
81     """
82 
83     def __init__(self, file_path: str):
84         # Our secrets dict.
85         self._secrets: Optional[Mapping[str, Any]] = None
86         self._lock = threading.RLock()
87         self._file_watcher_installed = False
88         self._file_path = file_path
89         self._file_change_listener = Signal(
90             doc="Emitted when the `secrets.toml` file has been changed."
91         )
92 
93     def load_if_toml_exists(self) -> None:
94         """Load secrets.toml from disk if it exists. If it doesn't exist,
95         no exception will be raised. (If the file exists but is malformed,
96         an exception *will* be raised.)
97 
98         Thread-safe.
99         """
100         try:
101             self._parse(print_exceptions=False)
102         except FileNotFoundError:
103             # No secrets.toml file exists. That's fine.
104             pass
105 
106     def _reset(self) -> None:
107         """Clear the secrets dictionary and remove any secrets that were
108         added to os.environ.
109 
110         Thread-safe.
111         """
112         with self._lock:
113             if self._secrets is None:
114                 return
115 
116             for k, v in self._secrets.items():
117                 self._maybe_delete_environment_variable(k, v)
118             self._secrets = None
119 
120     def _parse(self, print_exceptions: bool) -> Mapping[str, Any]:
121         """Parse our secrets.toml file if it's not already parsed.
122         This function is safe to call from multiple threads.
123 
124         Parameters
125         ----------
126         print_exceptions : bool
127             If True, then exceptions will be printed with `st.error` before
128             being re-raised.
129 
130         Raises
131         ------
132         FileNotFoundError
133             Raised if secrets.toml doesn't exist.
134 
135         """
136         # Avoid taking a lock for the common case where secrets are already
137         # loaded.
138         secrets = self._secrets
139         if secrets is not None:
140             return secrets
141 
142         with self._lock:
143             if self._secrets is not None:
144                 return self._secrets
145 
146             try:
147                 with open(self._file_path, encoding="utf-8") as f:
148                     secrets_file_str = f.read()
149             except FileNotFoundError:
150                 if print_exceptions:
151                     st.error(f"Secrets file not found. Expected at: {self._file_path}")
152                 raise
153 
154             try:
155                 secrets = toml.loads(secrets_file_str)
156             except:
157                 if print_exceptions:
158                     st.error("Error parsing Secrets file.")
159                 raise
160 
161             for k, v in secrets.items():
162                 self._maybe_set_environment_variable(k, v)
163 
164             self._secrets = secrets
165             self._maybe_install_file_watcher()
166 
167             return self._secrets
168 
169     @staticmethod
170     def _maybe_set_environment_variable(k: Any, v: Any) -> None:
171         """Add the given key/value pair to os.environ if the value
172         is a string, int, or float."""
173         value_type = type(v)
174         if value_type in (str, int, float):
175             os.environ[k] = str(v)
176 
177     @staticmethod
178     def _maybe_delete_environment_variable(k: Any, v: Any) -> None:
179         """Remove the given key/value pair from os.environ if the value
180         is a string, int, or float."""
181         value_type = type(v)
182         if value_type in (str, int, float) and os.environ.get(k) == v:
183             del os.environ[k]
184 
185     def _maybe_install_file_watcher(self) -> None:
186         with self._lock:
187             if self._file_watcher_installed:
188                 return
189 
190             # We force our watcher_type to 'poll' because Streamlit Cloud
191             # stores `secrets.toml` in a virtual filesystem that is
192             # incompatible with watchdog.
193             streamlit.watcher.path_watcher.watch_file(
194                 self._file_path,
195                 self._on_secrets_file_changed,
196                 watcher_type="poll",
197             )
198 
199             # We set file_watcher_installed to True even if watch_file
200             # returns False to avoid repeatedly trying to install it.
201             self._file_watcher_installed = True
202 
203     def _on_secrets_file_changed(self, _) -> None:
204         with self._lock:
205             _LOGGER.debug("Secrets file %s changed, reloading", self._file_path)
206             self._reset()
207             self._parse(print_exceptions=True)
208 
209         # Emit a signal to notify receivers that the `secrets.toml` file
210         # has been changed.
211         self._file_change_listener.send()
212 
213     def __getattr__(self, key: str) -> Any:
214         """Return the value with the given key. If no such key
215         exists, raise an AttributeError.
216 
217         Thread-safe.
218         """
219         try:
220             value = self._parse(True)[key]
221             if not isinstance(value, dict):
222                 return value
223             else:
224                 return AttrDict(**value)
225         # We add FileNotFoundError since __getattr__ is expected to only raise
226         # AttributeError. Without handling FileNotFoundError, unittests.mocks
227         # fails during mock creation on Python3.9
228         except (KeyError, FileNotFoundError):
229             raise AttributeError(_missing_attr_error_message(key))
230 
231     def __getitem__(self, key: str) -> Any:
232         """Return the value with the given key. If no such key
233         exists, raise a KeyError.
234 
235         Thread-safe.
236         """
237         try:
238             value = self._parse(True)[key]
239             if not isinstance(value, dict):
240                 return value
241             else:
242                 return AttrDict(**value)
243         except KeyError:
244             raise KeyError(_missing_key_error_message(key))
245 
246     def __repr__(self) -> str:
247         """A string representation of the contents of the dict. Thread-safe."""
248         return repr(self._parse(True))
249 
250     def __len__(self) -> int:
251         """The number of entries in the dict. Thread-safe."""
252         return len(self._parse(True))
253 
254     def has_key(self, k: str) -> bool:
255         """True if the given key is in the dict. Thread-safe."""
256         return k in self._parse(True)
257 
258     def keys(self) -> KeysView[str]:
259         """A view of the keys in the dict. Thread-safe."""
260         return self._parse(True).keys()
261 
262     def values(self) -> ValuesView[Any]:
263         """A view of the values in the dict. Thread-safe."""
264         return self._parse(True).values()
265 
266     def items(self) -> ItemsView[str, Any]:
267         """A view of the key-value items in the dict. Thread-safe."""
268         return self._parse(True).items()
269 
270     def __contains__(self, key: Any) -> bool:
271         """True if the given key is in the dict. Thread-safe."""
272         return key in self._parse(True)
273 
274     def __iter__(self) -> Iterator[str]:
275         """An iterator over the keys in the dict. Thread-safe."""
276         return iter(self._parse(True))
277 
278 
279 secrets_singleton: Final = Secrets(SECRETS_FILE_LOC)
280
```
--- 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/secrets.py b/lib/streamlit/runtime/secrets.py
--- a/lib/streamlit/runtime/secrets.py
+++ b/lib/streamlit/runtime/secrets.py
@@ -14,8 +14,16 @@
 
 import os
 import threading
-from collections import UserDict
-from typing import Any, ItemsView, Iterator, KeysView, Mapping, Optional, ValuesView
+from typing import (
+    Any,
+    ItemsView,
+    Iterator,
+    KeysView,
+    Mapping,
+    NoReturn,
+    Optional,
+    ValuesView,
+)
 
 import toml
 from blinker import Signal
@@ -45,32 +53,50 @@
     )
 
 
-class AttrDict(UserDict):  # type: ignore[type-arg]
+class AttrDict(Mapping[str, Any]):
     """
     We use AttrDict to wrap up dictionary values from secrets
     to provide dot access to nested secrets
     """
 
+    def __init__(self, value):
+        self.__dict__["__nested_secrets__"] = dict(value)
+
     @staticmethod
     def _maybe_wrap_in_attr_dict(value) -> Any:
-        if not isinstance(value, dict):
+        if not isinstance(value, Mapping):
             return value
         else:
-            return AttrDict(**value)
+            return AttrDict(value)
 
-    def __getattr__(self, attr_name: str) -> Any:
+    def __len__(self) -> int:
+        return len(self.__nested_secrets__)
+
+    def __iter__(self) -> Iterator[str]:
+        return iter(self.__nested_secrets__)
+
+    def __getitem__(self, key: str) -> Any:
         try:
-            value = super().__getitem__(attr_name)
+            value = self.__nested_secrets__[key]
             return self._maybe_wrap_in_attr_dict(value)
         except KeyError:
-            raise AttributeError(_missing_attr_error_message(attr_name))
+            raise KeyError(_missing_key_error_message(key))
 
-    def __getitem__(self, key: str) -> Any:
+    def __getattr__(self, attr_name: str) -> Any:
         try:
-            value = super().__getitem__(key)
+            value = self.__nested_secrets__[attr_name]
             return self._maybe_wrap_in_attr_dict(value)
         except KeyError:
-            raise KeyError(_missing_key_error_message(key))
+            raise AttributeError(_missing_attr_error_message(attr_name))
+
+    def __repr__(self):
+        return repr(self.__nested_secrets__)
+
+    def __setitem__(self, key, value) -> NoReturn:
+        raise TypeError("Secrets does not support item assignment.")
+
+    def __setattr__(self, key, value) -> NoReturn:
+        raise TypeError("Secrets does not support attribute assignment.")
 
 
 class Secrets(Mapping[str, Any]):
@@ -218,10 +244,10 @@
         """
         try:
             value = self._parse(True)[key]
-            if not isinstance(value, dict):
+            if not isinstance(value, Mapping):
                 return value
             else:
-                return AttrDict(**value)
+                return AttrDict(value)
         # We add FileNotFoundError since __getattr__ is expected to only raise
         # AttributeError. Without handling FileNotFoundError, unittests.mocks
         # fails during mock creation on Python3.9
@@ -236,10 +262,10 @@
         """
         try:
             value = self._parse(True)[key]
-            if not isinstance(value, dict):
+            if not isinstance(value, Mapping):
                 return value
             else:
-                return AttrDict(**value)
+                return AttrDict(value)
         except KeyError:
             raise KeyError(_missing_key_error_message(key))

{"golden_diff": "diff --git a/lib/streamlit/runtime/secrets.py b/lib/streamlit/runtime/secrets.py\n--- a/lib/streamlit/runtime/secrets.py\n+++ b/lib/streamlit/runtime/secrets.py\n@@ -14,8 +14,16 @@\n \n import os\n import threading\n-from collections import UserDict\n-from typing import Any, ItemsView, Iterator, KeysView, Mapping, Optional, ValuesView\n+from typing import (\n+    Any,\n+    ItemsView,\n+    Iterator,\n+    KeysView,\n+    Mapping,\n+    NoReturn,\n+    Optional,\n+    ValuesView,\n+)\n \n import toml\n from blinker import Signal\n@@ -45,32 +53,50 @@\n     )\n \n \n-class AttrDict(UserDict):  # type: ignore[type-arg]\n+class AttrDict(Mapping[str, Any]):\n     \"\"\"\n     We use AttrDict to wrap up dictionary values from secrets\n     to provide dot access to nested secrets\n     \"\"\"\n \n+    def __init__(self, value):\n+        self.__dict__[\"__nested_secrets__\"] = dict(value)\n+\n     @staticmethod\n     def _maybe_wrap_in_attr_dict(value) -> Any:\n-        if not isinstance(value, dict):\n+        if not isinstance(value, Mapping):\n             return value\n         else:\n-            return AttrDict(**value)\n+            return AttrDict(value)\n \n-    def __getattr__(self, attr_name: str) -> Any:\n+    def __len__(self) -> int:\n+        return len(self.__nested_secrets__)\n+\n+    def __iter__(self) -> Iterator[str]:\n+        return iter(self.__nested_secrets__)\n+\n+    def __getitem__(self, key: str) -> Any:\n         try:\n-            value = super().__getitem__(attr_name)\n+            value = self.__nested_secrets__[key]\n             return self._maybe_wrap_in_attr_dict(value)\n         except KeyError:\n-            raise AttributeError(_missing_attr_error_message(attr_name))\n+            raise KeyError(_missing_key_error_message(key))\n \n-    def __getitem__(self, key: str) -> Any:\n+    def __getattr__(self, attr_name: str) -> Any:\n         try:\n-            value = super().__getitem__(key)\n+            value = self.__nested_secrets__[attr_name]\n             return self._maybe_wrap_in_attr_dict(value)\n         except KeyError:\n-            raise KeyError(_missing_key_error_message(key))\n+            raise AttributeError(_missing_attr_error_message(attr_name))\n+\n+    def __repr__(self):\n+        return repr(self.__nested_secrets__)\n+\n+    def __setitem__(self, key, value) -> NoReturn:\n+        raise TypeError(\"Secrets does not support item assignment.\")\n+\n+    def __setattr__(self, key, value) -> NoReturn:\n+        raise TypeError(\"Secrets does not support attribute assignment.\")\n \n \n class Secrets(Mapping[str, Any]):\n@@ -218,10 +244,10 @@\n         \"\"\"\n         try:\n             value = self._parse(True)[key]\n-            if not isinstance(value, dict):\n+            if not isinstance(value, Mapping):\n                 return value\n             else:\n-                return AttrDict(**value)\n+                return AttrDict(value)\n         # We add FileNotFoundError since __getattr__ is expected to only raise\n         # AttributeError. Without handling FileNotFoundError, unittests.mocks\n         # fails during mock creation on Python3.9\n@@ -236,10 +262,10 @@\n         \"\"\"\n         try:\n             value = self._parse(True)[key]\n-            if not isinstance(value, dict):\n+            if not isinstance(value, Mapping):\n                 return value\n             else:\n-                return AttrDict(**value)\n+                return AttrDict(value)\n         except KeyError:\n             raise KeyError(_missing_key_error_message(key))\n", "issue": "streamlit.runtime.secrets.AttrDict no longer returns True for isinstance() against dict in streamlit 1.14.0\n### Checklist\r\n\r\n- [X] I have searched the [existing issues](https://github.com/streamlit/streamlit/issues) for similar issues.\r\n- [X] I added a very descriptive title to this issue.\r\n- [X] I have provided sufficient information below to help reproduce this issue.\r\n\r\n### Summary\r\n\r\nHi.\r\n\r\nWith the latest streamlit version `streamlit.runtime.secrets.AttrDict` seems to inherit from `UserDict` instead of `dict` in previous version.\r\n\r\nMany libraries I use check that their configuration is `isinstance` against `dict` and are now failing when I pass my config from st.secrets to them.\r\n\r\nI can't see any documentation changes related to this and am not familiar with `UserDict`, do I have to go through my code and wrap all st.secrets access in a `dict()` conversion call now?\r\n\r\n### Reproducible Code Example\r\n\r\n```Python\r\nimport streamlit as st\r\n\r\nprint(isinstance(st.secrets[\"my_secret_dict\"], dict)) # False on streamlit 1.14.0 - was True prior to that\r\n```\r\n\r\n\r\n### Steps To Reproduce\r\n\r\n_No response_\r\n\r\n### Expected Behavior\r\n\r\n_No response_\r\n\r\n### Current Behavior\r\n\r\n_No response_\r\n\r\n### Is this a regression?\r\n\r\n- [X] Yes, this used to work in a previous version.\r\n\r\n### Debug info\r\n\r\n- Streamlit version: 1.14.0\r\n- Python version: 3.100\r\n- Operating System: Linux\r\n- Browser: Firefox\r\n- Virtual environment: None\r\n\r\n\r\n### Additional Information\r\n\r\n_No response_\r\n\r\n### Are you willing to submit a PR?\r\n\r\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\nimport os\nimport threading\nfrom collections import UserDict\nfrom typing import Any, ItemsView, Iterator, KeysView, Mapping, Optional, ValuesView\n\nimport toml\nfrom blinker import Signal\nfrom typing_extensions import Final\n\nimport streamlit as st\nimport streamlit.watcher.path_watcher\nfrom streamlit.logger import get_logger\n\n_LOGGER = get_logger(__name__)\nSECRETS_FILE_LOC = os.path.abspath(os.path.join(\".\", \".streamlit\", \"secrets.toml\"))\n\n\ndef _missing_attr_error_message(attr_name: str) -> str:\n    return (\n        f'st.secrets has no attribute \"{attr_name}\". '\n        f\"Did you forget to add it to secrets.toml or the app settings on Streamlit Cloud? \"\n        f\"More info: https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app/connect-to-data-sources/secrets-management\"\n    )\n\n\ndef _missing_key_error_message(key: str) -> str:\n    return (\n        f'st.secrets has no key \"{key}\". '\n        f\"Did you forget to add it to secrets.toml or the app settings on Streamlit Cloud? \"\n        f\"More info: https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app/connect-to-data-sources/secrets-management\"\n    )\n\n\nclass AttrDict(UserDict):  # type: ignore[type-arg]\n    \"\"\"\n    We use AttrDict to wrap up dictionary values from secrets\n    to provide dot access to nested secrets\n    \"\"\"\n\n    @staticmethod\n    def _maybe_wrap_in_attr_dict(value) -> Any:\n        if not isinstance(value, dict):\n            return value\n        else:\n            return AttrDict(**value)\n\n    def __getattr__(self, attr_name: str) -> Any:\n        try:\n            value = super().__getitem__(attr_name)\n            return self._maybe_wrap_in_attr_dict(value)\n        except KeyError:\n            raise AttributeError(_missing_attr_error_message(attr_name))\n\n    def __getitem__(self, key: str) -> Any:\n        try:\n            value = super().__getitem__(key)\n            return self._maybe_wrap_in_attr_dict(value)\n        except KeyError:\n            raise KeyError(_missing_key_error_message(key))\n\n\nclass Secrets(Mapping[str, Any]):\n    \"\"\"A dict-like class that stores secrets.\n    Parses secrets.toml on-demand. Cannot be externally mutated.\n\n    Safe to use from multiple threads.\n    \"\"\"\n\n    def __init__(self, file_path: str):\n        # Our secrets dict.\n        self._secrets: Optional[Mapping[str, Any]] = None\n        self._lock = threading.RLock()\n        self._file_watcher_installed = False\n        self._file_path = file_path\n        self._file_change_listener = Signal(\n            doc=\"Emitted when the `secrets.toml` file has been changed.\"\n        )\n\n    def load_if_toml_exists(self) -> None:\n        \"\"\"Load secrets.toml from disk if it exists. If it doesn't exist,\n        no exception will be raised. (If the file exists but is malformed,\n        an exception *will* be raised.)\n\n        Thread-safe.\n        \"\"\"\n        try:\n            self._parse(print_exceptions=False)\n        except FileNotFoundError:\n            # No secrets.toml file exists. That's fine.\n            pass\n\n    def _reset(self) -> None:\n        \"\"\"Clear the secrets dictionary and remove any secrets that were\n        added to os.environ.\n\n        Thread-safe.\n        \"\"\"\n        with self._lock:\n            if self._secrets is None:\n                return\n\n            for k, v in self._secrets.items():\n                self._maybe_delete_environment_variable(k, v)\n            self._secrets = None\n\n    def _parse(self, print_exceptions: bool) -> Mapping[str, Any]:\n        \"\"\"Parse our secrets.toml file if it's not already parsed.\n        This function is safe to call from multiple threads.\n\n        Parameters\n        ----------\n        print_exceptions : bool\n            If True, then exceptions will be printed with `st.error` before\n            being re-raised.\n\n        Raises\n        ------\n        FileNotFoundError\n            Raised if secrets.toml doesn't exist.\n\n        \"\"\"\n        # Avoid taking a lock for the common case where secrets are already\n        # loaded.\n        secrets = self._secrets\n        if secrets is not None:\n            return secrets\n\n        with self._lock:\n            if self._secrets is not None:\n                return self._secrets\n\n            try:\n                with open(self._file_path, encoding=\"utf-8\") as f:\n                    secrets_file_str = f.read()\n            except FileNotFoundError:\n                if print_exceptions:\n                    st.error(f\"Secrets file not found. Expected at: {self._file_path}\")\n                raise\n\n            try:\n                secrets = toml.loads(secrets_file_str)\n            except:\n                if print_exceptions:\n                    st.error(\"Error parsing Secrets file.\")\n                raise\n\n            for k, v in secrets.items():\n                self._maybe_set_environment_variable(k, v)\n\n            self._secrets = secrets\n            self._maybe_install_file_watcher()\n\n            return self._secrets\n\n    @staticmethod\n    def _maybe_set_environment_variable(k: Any, v: Any) -> None:\n        \"\"\"Add the given key/value pair to os.environ if the value\n        is a string, int, or float.\"\"\"\n        value_type = type(v)\n        if value_type in (str, int, float):\n            os.environ[k] = str(v)\n\n    @staticmethod\n    def _maybe_delete_environment_variable(k: Any, v: Any) -> None:\n        \"\"\"Remove the given key/value pair from os.environ if the value\n        is a string, int, or float.\"\"\"\n        value_type = type(v)\n        if value_type in (str, int, float) and os.environ.get(k) == v:\n            del os.environ[k]\n\n    def _maybe_install_file_watcher(self) -> None:\n        with self._lock:\n            if self._file_watcher_installed:\n                return\n\n            # We force our watcher_type to 'poll' because Streamlit Cloud\n            # stores `secrets.toml` in a virtual filesystem that is\n            # incompatible with watchdog.\n            streamlit.watcher.path_watcher.watch_file(\n                self._file_path,\n                self._on_secrets_file_changed,\n                watcher_type=\"poll\",\n            )\n\n            # We set file_watcher_installed to True even if watch_file\n            # returns False to avoid repeatedly trying to install it.\n            self._file_watcher_installed = True\n\n    def _on_secrets_file_changed(self, _) -> None:\n        with self._lock:\n            _LOGGER.debug(\"Secrets file %s changed, reloading\", self._file_path)\n            self._reset()\n            self._parse(print_exceptions=True)\n\n        # Emit a signal to notify receivers that the `secrets.toml` file\n        # has been changed.\n        self._file_change_listener.send()\n\n    def __getattr__(self, key: str) -> Any:\n        \"\"\"Return the value with the given key. If no such key\n        exists, raise an AttributeError.\n\n        Thread-safe.\n        \"\"\"\n        try:\n            value = self._parse(True)[key]\n            if not isinstance(value, dict):\n                return value\n            else:\n                return AttrDict(**value)\n        # We add FileNotFoundError since __getattr__ is expected to only raise\n        # AttributeError. Without handling FileNotFoundError, unittests.mocks\n        # fails during mock creation on Python3.9\n        except (KeyError, FileNotFoundError):\n            raise AttributeError(_missing_attr_error_message(key))\n\n    def __getitem__(self, key: str) -> Any:\n        \"\"\"Return the value with the given key. If no such key\n        exists, raise a KeyError.\n\n        Thread-safe.\n        \"\"\"\n        try:\n            value = self._parse(True)[key]\n            if not isinstance(value, dict):\n                return value\n            else:\n                return AttrDict(**value)\n        except KeyError:\n            raise KeyError(_missing_key_error_message(key))\n\n    def __repr__(self) -> str:\n        \"\"\"A string representation of the contents of the dict. Thread-safe.\"\"\"\n        return repr(self._parse(True))\n\n    def __len__(self) -> int:\n        \"\"\"The number of entries in the dict. Thread-safe.\"\"\"\n        return len(self._parse(True))\n\n    def has_key(self, k: str) -> bool:\n        \"\"\"True if the given key is in the dict. Thread-safe.\"\"\"\n        return k in self._parse(True)\n\n    def keys(self) -> KeysView[str]:\n        \"\"\"A view of the keys in the dict. Thread-safe.\"\"\"\n        return self._parse(True).keys()\n\n    def values(self) -> ValuesView[Any]:\n        \"\"\"A view of the values in the dict. Thread-safe.\"\"\"\n        return self._parse(True).values()\n\n    def items(self) -> ItemsView[str, Any]:\n        \"\"\"A view of the key-value items in the dict. Thread-safe.\"\"\"\n        return self._parse(True).items()\n\n    def __contains__(self, key: Any) -> bool:\n        \"\"\"True if the given key is in the dict. Thread-safe.\"\"\"\n        return key in self._parse(True)\n\n    def __iter__(self) -> Iterator[str]:\n        \"\"\"An iterator over the keys in the dict. Thread-safe.\"\"\"\n        return iter(self._parse(True))\n\n\nsecrets_singleton: Final = Secrets(SECRETS_FILE_LOC)\n", "path": "lib/streamlit/runtime/secrets.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\nimport os\nimport threading\nfrom typing import (\n    Any,\n    ItemsView,\n    Iterator,\n    KeysView,\n    Mapping,\n    NoReturn,\n    Optional,\n    ValuesView,\n)\n\nimport toml\nfrom blinker import Signal\nfrom typing_extensions import Final\n\nimport streamlit as st\nimport streamlit.watcher.path_watcher\nfrom streamlit.logger import get_logger\n\n_LOGGER = get_logger(__name__)\nSECRETS_FILE_LOC = os.path.abspath(os.path.join(\".\", \".streamlit\", \"secrets.toml\"))\n\n\ndef _missing_attr_error_message(attr_name: str) -> str:\n    return (\n        f'st.secrets has no attribute \"{attr_name}\". '\n        f\"Did you forget to add it to secrets.toml or the app settings on Streamlit Cloud? \"\n        f\"More info: https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app/connect-to-data-sources/secrets-management\"\n    )\n\n\ndef _missing_key_error_message(key: str) -> str:\n    return (\n        f'st.secrets has no key \"{key}\". '\n        f\"Did you forget to add it to secrets.toml or the app settings on Streamlit Cloud? \"\n        f\"More info: https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app/connect-to-data-sources/secrets-management\"\n    )\n\n\nclass AttrDict(Mapping[str, Any]):\n    \"\"\"\n    We use AttrDict to wrap up dictionary values from secrets\n    to provide dot access to nested secrets\n    \"\"\"\n\n    def __init__(self, value):\n        self.__dict__[\"__nested_secrets__\"] = dict(value)\n\n    @staticmethod\n    def _maybe_wrap_in_attr_dict(value) -> Any:\n        if not isinstance(value, Mapping):\n            return value\n        else:\n            return AttrDict(value)\n\n    def __len__(self) -> int:\n        return len(self.__nested_secrets__)\n\n    def __iter__(self) -> Iterator[str]:\n        return iter(self.__nested_secrets__)\n\n    def __getitem__(self, key: str) -> Any:\n        try:\n            value = self.__nested_secrets__[key]\n            return self._maybe_wrap_in_attr_dict(value)\n        except KeyError:\n            raise KeyError(_missing_key_error_message(key))\n\n    def __getattr__(self, attr_name: str) -> Any:\n        try:\n            value = self.__nested_secrets__[attr_name]\n            return self._maybe_wrap_in_attr_dict(value)\n        except KeyError:\n            raise AttributeError(_missing_attr_error_message(attr_name))\n\n    def __repr__(self):\n        return repr(self.__nested_secrets__)\n\n    def __setitem__(self, key, value) -> NoReturn:\n        raise TypeError(\"Secrets does not support item assignment.\")\n\n    def __setattr__(self, key, value) -> NoReturn:\n        raise TypeError(\"Secrets does not support attribute assignment.\")\n\n\nclass Secrets(Mapping[str, Any]):\n    \"\"\"A dict-like class that stores secrets.\n    Parses secrets.toml on-demand. Cannot be externally mutated.\n\n    Safe to use from multiple threads.\n    \"\"\"\n\n    def __init__(self, file_path: str):\n        # Our secrets dict.\n        self._secrets: Optional[Mapping[str, Any]] = None\n        self._lock = threading.RLock()\n        self._file_watcher_installed = False\n        self._file_path = file_path\n        self._file_change_listener = Signal(\n            doc=\"Emitted when the `secrets.toml` file has been changed.\"\n        )\n\n    def load_if_toml_exists(self) -> None:\n        \"\"\"Load secrets.toml from disk if it exists. If it doesn't exist,\n        no exception will be raised. (If the file exists but is malformed,\n        an exception *will* be raised.)\n\n        Thread-safe.\n        \"\"\"\n        try:\n            self._parse(print_exceptions=False)\n        except FileNotFoundError:\n            # No secrets.toml file exists. That's fine.\n            pass\n\n    def _reset(self) -> None:\n        \"\"\"Clear the secrets dictionary and remove any secrets that were\n        added to os.environ.\n\n        Thread-safe.\n        \"\"\"\n        with self._lock:\n            if self._secrets is None:\n                return\n\n            for k, v in self._secrets.items():\n                self._maybe_delete_environment_variable(k, v)\n            self._secrets = None\n\n    def _parse(self, print_exceptions: bool) -> Mapping[str, Any]:\n        \"\"\"Parse our secrets.toml file if it's not already parsed.\n        This function is safe to call from multiple threads.\n\n        Parameters\n        ----------\n        print_exceptions : bool\n            If True, then exceptions will be printed with `st.error` before\n            being re-raised.\n\n        Raises\n        ------\n        FileNotFoundError\n            Raised if secrets.toml doesn't exist.\n\n        \"\"\"\n        # Avoid taking a lock for the common case where secrets are already\n        # loaded.\n        secrets = self._secrets\n        if secrets is not None:\n            return secrets\n\n        with self._lock:\n            if self._secrets is not None:\n                return self._secrets\n\n            try:\n                with open(self._file_path, encoding=\"utf-8\") as f:\n                    secrets_file_str = f.read()\n            except FileNotFoundError:\n                if print_exceptions:\n                    st.error(f\"Secrets file not found. Expected at: {self._file_path}\")\n                raise\n\n            try:\n                secrets = toml.loads(secrets_file_str)\n            except:\n                if print_exceptions:\n                    st.error(\"Error parsing Secrets file.\")\n                raise\n\n            for k, v in secrets.items():\n                self._maybe_set_environment_variable(k, v)\n\n            self._secrets = secrets\n            self._maybe_install_file_watcher()\n\n            return self._secrets\n\n    @staticmethod\n    def _maybe_set_environment_variable(k: Any, v: Any) -> None:\n        \"\"\"Add the given key/value pair to os.environ if the value\n        is a string, int, or float.\"\"\"\n        value_type = type(v)\n        if value_type in (str, int, float):\n            os.environ[k] = str(v)\n\n    @staticmethod\n    def _maybe_delete_environment_variable(k: Any, v: Any) -> None:\n        \"\"\"Remove the given key/value pair from os.environ if the value\n        is a string, int, or float.\"\"\"\n        value_type = type(v)\n        if value_type in (str, int, float) and os.environ.get(k) == v:\n            del os.environ[k]\n\n    def _maybe_install_file_watcher(self) -> None:\n        with self._lock:\n            if self._file_watcher_installed:\n                return\n\n            # We force our watcher_type to 'poll' because Streamlit Cloud\n            # stores `secrets.toml` in a virtual filesystem that is\n            # incompatible with watchdog.\n            streamlit.watcher.path_watcher.watch_file(\n                self._file_path,\n                self._on_secrets_file_changed,\n                watcher_type=\"poll\",\n            )\n\n            # We set file_watcher_installed to True even if watch_file\n            # returns False to avoid repeatedly trying to install it.\n            self._file_watcher_installed = True\n\n    def _on_secrets_file_changed(self, _) -> None:\n        with self._lock:\n            _LOGGER.debug(\"Secrets file %s changed, reloading\", self._file_path)\n            self._reset()\n            self._parse(print_exceptions=True)\n\n        # Emit a signal to notify receivers that the `secrets.toml` file\n        # has been changed.\n        self._file_change_listener.send()\n\n    def __getattr__(self, key: str) -> Any:\n        \"\"\"Return the value with the given key. If no such key\n        exists, raise an AttributeError.\n\n        Thread-safe.\n        \"\"\"\n        try:\n            value = self._parse(True)[key]\n            if not isinstance(value, Mapping):\n                return value\n            else:\n                return AttrDict(value)\n        # We add FileNotFoundError since __getattr__ is expected to only raise\n        # AttributeError. Without handling FileNotFoundError, unittests.mocks\n        # fails during mock creation on Python3.9\n        except (KeyError, FileNotFoundError):\n            raise AttributeError(_missing_attr_error_message(key))\n\n    def __getitem__(self, key: str) -> Any:\n        \"\"\"Return the value with the given key. If no such key\n        exists, raise a KeyError.\n\n        Thread-safe.\n        \"\"\"\n        try:\n            value = self._parse(True)[key]\n            if not isinstance(value, Mapping):\n                return value\n            else:\n                return AttrDict(value)\n        except KeyError:\n            raise KeyError(_missing_key_error_message(key))\n\n    def __repr__(self) -> str:\n        \"\"\"A string representation of the contents of the dict. Thread-safe.\"\"\"\n        return repr(self._parse(True))\n\n    def __len__(self) -> int:\n        \"\"\"The number of entries in the dict. Thread-safe.\"\"\"\n        return len(self._parse(True))\n\n    def has_key(self, k: str) -> bool:\n        \"\"\"True if the given key is in the dict. Thread-safe.\"\"\"\n        return k in self._parse(True)\n\n    def keys(self) -> KeysView[str]:\n        \"\"\"A view of the keys in the dict. Thread-safe.\"\"\"\n        return self._parse(True).keys()\n\n    def values(self) -> ValuesView[Any]:\n        \"\"\"A view of the values in the dict. Thread-safe.\"\"\"\n        return self._parse(True).values()\n\n    def items(self) -> ItemsView[str, Any]:\n        \"\"\"A view of the key-value items in the dict. Thread-safe.\"\"\"\n        return self._parse(True).items()\n\n    def __contains__(self, key: Any) -> bool:\n        \"\"\"True if the given key is in the dict. Thread-safe.\"\"\"\n        return key in self._parse(True)\n\n    def __iter__(self) -> Iterator[str]:\n        \"\"\"An iterator over the keys in the dict. Thread-safe.\"\"\"\n        return iter(self._parse(True))\n\n\nsecrets_singleton: Final = Secrets(SECRETS_FILE_LOC)\n", "path": "lib/streamlit/runtime/secrets.py"}]}