streamlit-nightly 1.31.2.dev20240212__py2.py3-none-any.whl → 1.31.2.dev20240214__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit/case_converters.py +9 -4
- streamlit/cli_util.py +2 -0
- streamlit/code_util.py +5 -2
- streamlit/color_util.py +2 -0
- streamlit/column_config.py +2 -0
- streamlit/commands/execution_control.py +4 -2
- streamlit/commands/experimental_query_params.py +7 -4
- streamlit/commands/page_config.py +11 -9
- streamlit/components/v1/components.py +23 -16
- streamlit/config.py +3 -5
- streamlit/config_option.py +12 -11
- streamlit/connections/base_connection.py +4 -2
- streamlit/connections/snowflake_connection.py +4 -4
- streamlit/connections/snowpark_connection.py +3 -3
- streamlit/connections/sql_connection.py +6 -6
- streamlit/connections/util.py +8 -5
- streamlit/constants.py +2 -0
- streamlit/cursor.py +16 -14
- streamlit/delta_generator.py +10 -13
- streamlit/deprecation_util.py +4 -3
- streamlit/echo.py +5 -3
- streamlit/elements/alert.py +16 -14
- streamlit/elements/altair_utils.py +8 -6
- streamlit/elements/arrow.py +4 -4
- streamlit/elements/arrow_altair.py +24 -34
- streamlit/elements/arrow_vega_lite.py +9 -14
- streamlit/elements/balloons.py +4 -2
- streamlit/elements/bokeh_chart.py +7 -7
- streamlit/elements/code.py +6 -4
- streamlit/elements/deck_gl_json_chart.py +8 -8
- streamlit/elements/doc_string.py +5 -9
- streamlit/elements/empty.py +4 -2
- streamlit/elements/exception.py +10 -10
- streamlit/elements/form.py +1 -3
- streamlit/elements/graphviz_chart.py +5 -6
- streamlit/elements/heading.py +16 -14
- streamlit/elements/iframe.py +14 -12
- streamlit/elements/image.py +8 -8
- streamlit/elements/json.py +6 -4
- streamlit/elements/layouts.py +12 -10
- streamlit/elements/lib/column_config_utils.py +2 -2
- streamlit/elements/lib/column_types.py +23 -23
- streamlit/elements/lib/dicttools.py +10 -6
- streamlit/elements/lib/mutable_status_container.py +7 -7
- streamlit/elements/lib/pandas_styler_utils.py +6 -6
- streamlit/elements/lib/streamlit_plotly_theme.py +2 -0
- streamlit/elements/map.py +11 -22
- streamlit/elements/markdown.py +16 -14
- streamlit/elements/media.py +16 -16
- streamlit/elements/metric.py +9 -7
- streamlit/elements/plotly_chart.py +5 -5
- streamlit/elements/progress.py +6 -6
- streamlit/elements/pyplot.py +10 -13
- streamlit/elements/snow.py +4 -2
- streamlit/elements/spinner.py +2 -0
- streamlit/elements/text.py +7 -5
- streamlit/elements/toast.py +6 -4
- streamlit/elements/utils.py +15 -28
- streamlit/elements/widgets/button.py +39 -39
- streamlit/elements/widgets/camera_input.py +21 -17
- streamlit/elements/widgets/chat.py +6 -7
- streamlit/elements/widgets/checkbox.py +21 -19
- streamlit/elements/widgets/color_picker.py +18 -16
- streamlit/elements/widgets/data_editor.py +7 -7
- streamlit/elements/widgets/file_uploader.py +59 -55
- streamlit/elements/widgets/multiselect.py +33 -42
- streamlit/elements/widgets/number_input.py +10 -5
- streamlit/elements/widgets/radio.py +1 -1
- streamlit/elements/widgets/select_slider.py +25 -34
- streamlit/elements/widgets/selectbox.py +1 -1
- streamlit/elements/widgets/slider.py +28 -36
- streamlit/elements/widgets/text_widgets.py +6 -6
- streamlit/elements/widgets/time_widgets.py +13 -13
- streamlit/elements/write.py +21 -29
- streamlit/env_util.py +5 -3
- streamlit/error_util.py +7 -3
- streamlit/errors.py +3 -1
- streamlit/external/langchain/streamlit_callback_handler.py +26 -24
- streamlit/file_util.py +18 -14
- streamlit/folder_black_list.py +3 -1
- streamlit/git_util.py +5 -3
- streamlit/js_number.py +10 -13
- streamlit/logger.py +5 -5
- streamlit/net_util.py +14 -11
- streamlit/platform.py +2 -0
- streamlit/runtime/__init__.py +2 -0
- streamlit/runtime/app_session.py +42 -42
- streamlit/runtime/caching/__init__.py +4 -4
- streamlit/runtime/caching/cache_data_api.py +3 -3
- streamlit/runtime/caching/cache_errors.py +5 -3
- streamlit/runtime/caching/cache_type.py +2 -0
- streamlit/runtime/caching/cache_utils.py +2 -4
- streamlit/runtime/caching/cached_message_replay.py +12 -5
- streamlit/runtime/caching/hashing.py +29 -21
- streamlit/runtime/caching/storage/cache_storage_protocol.py +1 -2
- streamlit/runtime/caching/storage/local_disk_cache_storage.py +6 -5
- streamlit/runtime/connection_factory.py +8 -8
- streamlit/runtime/forward_msg_cache.py +20 -18
- streamlit/runtime/forward_msg_queue.py +8 -9
- streamlit/runtime/legacy_caching/caching.py +32 -42
- streamlit/runtime/legacy_caching/hashing.py +29 -25
- streamlit/runtime/media_file_manager.py +16 -14
- streamlit/runtime/media_file_storage.py +8 -8
- streamlit/runtime/memory_media_file_storage.py +12 -14
- streamlit/runtime/memory_session_storage.py +4 -3
- streamlit/runtime/memory_uploaded_file_manager.py +9 -10
- streamlit/runtime/metrics_util.py +20 -20
- streamlit/runtime/runtime.py +25 -27
- streamlit/runtime/runtime_util.py +5 -3
- streamlit/runtime/script_data.py +2 -0
- streamlit/runtime/scriptrunner/magic.py +17 -11
- streamlit/runtime/scriptrunner/magic_funcs.py +2 -0
- streamlit/runtime/scriptrunner/script_requests.py +6 -4
- streamlit/runtime/scriptrunner/script_run_context.py +17 -17
- streamlit/runtime/scriptrunner/script_runner.py +7 -5
- streamlit/runtime/secrets.py +4 -6
- streamlit/runtime/session_manager.py +14 -14
- streamlit/runtime/state/common.py +5 -4
- streamlit/runtime/state/query_params.py +8 -6
- streamlit/runtime/state/query_params_proxy.py +7 -5
- streamlit/runtime/state/safe_session_state.py +7 -5
- streamlit/runtime/state/session_state.py +3 -4
- streamlit/runtime/state/session_state_proxy.py +5 -5
- streamlit/runtime/state/widgets.py +20 -18
- streamlit/runtime/stats.py +13 -15
- streamlit/runtime/uploaded_file_manager.py +6 -5
- streamlit/runtime/websocket_session_manager.py +14 -14
- streamlit/source_util.py +13 -11
- streamlit/static/asset-manifest.json +13 -13
- streamlit/static/index.html +1 -1
- streamlit/static/static/css/2411.8b8f33d6.chunk.css +1 -0
- streamlit/static/static/css/43.e3b876c5.chunk.css +1 -0
- streamlit/static/static/css/6692.65519639.chunk.css +1 -0
- streamlit/static/static/js/{3075.76725a14.chunk.js → 2411.714d213e.chunk.js} +2 -2
- streamlit/static/static/js/4185.21ca0590.chunk.js +1 -0
- streamlit/static/static/js/43.36939bb1.chunk.js +1 -0
- streamlit/static/static/js/{5117.6a701db1.chunk.js → 5117.04bfe5d3.chunk.js} +1 -1
- streamlit/static/static/js/{5791.30b01ee8.chunk.js → 5791.c5138157.chunk.js} +1 -1
- streamlit/static/static/js/656.8c998bc8.chunk.js +2 -0
- streamlit/static/static/js/{6692.6ac4ea6f.chunk.js → 6692.6496cbc2.chunk.js} +1 -1
- streamlit/static/static/js/7142.400eefdd.chunk.js +1 -0
- streamlit/static/static/js/main.2737c0f9.js +2 -0
- streamlit/static/static/js/{main.043d802e.js.LICENSE.txt → main.2737c0f9.js.LICENSE.txt} +23 -25
- streamlit/string_util.py +13 -9
- streamlit/temporary_directory.py +3 -1
- streamlit/testing/v1/element_tree.py +1 -2
- streamlit/testing/v1/util.py +7 -3
- streamlit/type_util.py +30 -25
- streamlit/url_util.py +6 -4
- streamlit/user_info.py +8 -6
- streamlit/util.py +23 -37
- streamlit/version.py +16 -9
- streamlit/watcher/event_based_path_watcher.py +10 -10
- streamlit/watcher/local_sources_watcher.py +15 -13
- streamlit/watcher/path_watcher.py +0 -3
- streamlit/watcher/polling_path_watcher.py +9 -8
- streamlit/watcher/util.py +3 -2
- streamlit/web/cache_storage_manager_config.py +2 -0
- streamlit/web/server/app_static_file_handler.py +6 -5
- streamlit/web/server/browser_websocket_handler.py +10 -8
- streamlit/web/server/component_request_handler.py +7 -4
- streamlit/web/server/media_file_handler.py +5 -4
- streamlit/web/server/routes.py +6 -3
- streamlit/web/server/server.py +41 -34
- streamlit/web/server/server_util.py +8 -3
- streamlit/web/server/stats_request_handler.py +14 -5
- streamlit/web/server/upload_file_request_handler.py +7 -8
- streamlit/web/server/websocket_headers.py +2 -2
- {streamlit_nightly-1.31.2.dev20240212.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.31.2.dev20240212.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/RECORD +176 -176
- streamlit/static/static/css/3075.81b3d18f.chunk.css +0 -1
- streamlit/static/static/css/43.c24b25fa.chunk.css +0 -1
- streamlit/static/static/css/6692.bb444a79.chunk.css +0 -1
- streamlit/static/static/js/1215.baf3721f.chunk.js +0 -2
- streamlit/static/static/js/4185.90e929dc.chunk.js +0 -1
- streamlit/static/static/js/43.8ca4bc8a.chunk.js +0 -1
- streamlit/static/static/js/7142.a359ed63.chunk.js +0 -1
- streamlit/static/static/js/main.043d802e.js +0 -2
- /streamlit/static/static/js/{3075.76725a14.chunk.js.LICENSE.txt → 2411.714d213e.chunk.js.LICENSE.txt} +0 -0
- /streamlit/static/static/js/{1215.baf3721f.chunk.js.LICENSE.txt → 656.8c998bc8.chunk.js.LICENSE.txt} +0 -0
- {streamlit_nightly-1.31.2.dev20240212.data → streamlit_nightly-1.31.2.dev20240214.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.31.2.dev20240212.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.31.2.dev20240212.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.31.2.dev20240212.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/top_level.txt +0 -0
@@ -39,7 +39,7 @@ from __future__ import annotations
|
|
39
39
|
|
40
40
|
import os
|
41
41
|
import threading
|
42
|
-
from typing import Callable,
|
42
|
+
from typing import Callable, Final, cast
|
43
43
|
|
44
44
|
from blinker import ANY, Signal
|
45
45
|
from watchdog import events
|
@@ -109,13 +109,13 @@ class EventBasedPathWatcher:
|
|
109
109
|
path_watcher.stop_watching_path(self._path, self._on_changed)
|
110
110
|
|
111
111
|
|
112
|
-
class _MultiPathWatcher
|
112
|
+
class _MultiPathWatcher:
|
113
113
|
"""Watches multiple paths."""
|
114
114
|
|
115
|
-
_singleton:
|
115
|
+
_singleton: _MultiPathWatcher | None = None
|
116
116
|
|
117
117
|
@classmethod
|
118
|
-
def get_singleton(cls) ->
|
118
|
+
def get_singleton(cls) -> _MultiPathWatcher:
|
119
119
|
"""Return the singleton _MultiPathWatcher object.
|
120
120
|
|
121
121
|
Instantiates one if necessary.
|
@@ -127,18 +127,18 @@ class _MultiPathWatcher(object):
|
|
127
127
|
return cast("_MultiPathWatcher", _MultiPathWatcher._singleton)
|
128
128
|
|
129
129
|
# Don't allow constructor to be called more than once.
|
130
|
-
def __new__(cls) ->
|
130
|
+
def __new__(cls) -> _MultiPathWatcher:
|
131
131
|
"""Constructor."""
|
132
132
|
if _MultiPathWatcher._singleton is not None:
|
133
133
|
raise RuntimeError("Use .get_singleton() instead")
|
134
|
-
return super(
|
134
|
+
return super().__new__(cls)
|
135
135
|
|
136
136
|
def __init__(self) -> None:
|
137
137
|
"""Constructor."""
|
138
138
|
_MultiPathWatcher._singleton = self
|
139
139
|
|
140
140
|
# Map of folder_to_watch -> _FolderEventHandler.
|
141
|
-
self._folder_handlers:
|
141
|
+
self._folder_handlers: dict[str, _FolderEventHandler] = {}
|
142
142
|
|
143
143
|
# Used for mutation of _folder_handlers dict
|
144
144
|
self._lock = threading.Lock()
|
@@ -218,7 +218,7 @@ class _MultiPathWatcher(object):
|
|
218
218
|
self._observer.join(timeout=5)
|
219
219
|
|
220
220
|
|
221
|
-
class WatchedPath
|
221
|
+
class WatchedPath:
|
222
222
|
"""Emits notifications when a single path is modified."""
|
223
223
|
|
224
224
|
def __init__(
|
@@ -254,8 +254,8 @@ class _FolderEventHandler(events.FileSystemEventHandler):
|
|
254
254
|
"""
|
255
255
|
|
256
256
|
def __init__(self) -> None:
|
257
|
-
super(
|
258
|
-
self._watched_paths:
|
257
|
+
super().__init__()
|
258
|
+
self._watched_paths: dict[str, WatchedPath] = {}
|
259
259
|
self._lock = threading.Lock() # for watched_paths mutations
|
260
260
|
self.watch: ObservedWatch | None = None
|
261
261
|
|
@@ -12,11 +12,13 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import collections
|
16
18
|
import os
|
17
19
|
import sys
|
18
20
|
import types
|
19
|
-
from typing import Callable,
|
21
|
+
from typing import Callable, Final
|
20
22
|
|
21
23
|
from streamlit import config, file_util
|
22
24
|
from streamlit.folder_black_list import FolderBlackList
|
@@ -27,7 +29,7 @@ from streamlit.watcher.path_watcher import (
|
|
27
29
|
get_default_path_watcher_class,
|
28
30
|
)
|
29
31
|
|
30
|
-
|
32
|
+
_LOGGER: Final = get_logger(__name__)
|
31
33
|
|
32
34
|
WatchedModule = collections.namedtuple("WatchedModule", ["watcher", "module_name"])
|
33
35
|
|
@@ -40,23 +42,23 @@ class LocalSourcesWatcher:
|
|
40
42
|
def __init__(self, main_script_path: str):
|
41
43
|
self._main_script_path = os.path.abspath(main_script_path)
|
42
44
|
self._script_folder = os.path.dirname(self._main_script_path)
|
43
|
-
self._on_file_changed:
|
45
|
+
self._on_file_changed: list[Callable[[str], None]] = []
|
44
46
|
self._is_closed = False
|
45
|
-
self._cached_sys_modules:
|
47
|
+
self._cached_sys_modules: set[str] = set()
|
46
48
|
|
47
49
|
# Blacklist for folders that should not be watched
|
48
50
|
self._folder_black_list = FolderBlackList(
|
49
51
|
config.get_option("server.folderWatchBlacklist")
|
50
52
|
)
|
51
53
|
|
52
|
-
self._watched_modules:
|
53
|
-
self._watched_pages:
|
54
|
+
self._watched_modules: dict[str, WatchedModule] = {}
|
55
|
+
self._watched_pages: set[str] = set()
|
54
56
|
|
55
57
|
self.update_watched_pages()
|
56
58
|
|
57
59
|
def update_watched_pages(self) -> None:
|
58
60
|
old_watched_pages = self._watched_pages
|
59
|
-
new_pages_paths:
|
61
|
+
new_pages_paths: set[str] = set()
|
60
62
|
|
61
63
|
for page_info in get_pages(self._main_script_path).values():
|
62
64
|
new_pages_paths.add(page_info["script_path"])
|
@@ -77,7 +79,7 @@ class LocalSourcesWatcher:
|
|
77
79
|
|
78
80
|
def on_file_changed(self, filepath):
|
79
81
|
if filepath not in self._watched_modules:
|
80
|
-
|
82
|
+
_LOGGER.error("Received event for non-watched file: %s", filepath)
|
81
83
|
return
|
82
84
|
|
83
85
|
# Workaround:
|
@@ -159,17 +161,17 @@ class LocalSourcesWatcher:
|
|
159
161
|
self._cached_sys_modules = set(sys.modules)
|
160
162
|
self._register_necessary_watchers(modules_paths)
|
161
163
|
|
162
|
-
def _register_necessary_watchers(self, module_paths:
|
164
|
+
def _register_necessary_watchers(self, module_paths: dict[str, set[str]]) -> None:
|
163
165
|
for name, paths in module_paths.items():
|
164
166
|
for path in paths:
|
165
167
|
if self._file_should_be_watched(path):
|
166
168
|
self._register_watcher(path, name)
|
167
169
|
|
168
|
-
def _exclude_blacklisted_paths(self, paths:
|
170
|
+
def _exclude_blacklisted_paths(self, paths: set[str]) -> set[str]:
|
169
171
|
return {p for p in paths if not self._folder_black_list.is_blacklisted(p)}
|
170
172
|
|
171
173
|
|
172
|
-
def get_module_paths(module: types.ModuleType) ->
|
174
|
+
def get_module_paths(module: types.ModuleType) -> set[str]:
|
173
175
|
paths_extractors = [
|
174
176
|
# https://docs.python.org/3/reference/datamodel.html
|
175
177
|
# __file__ is the pathname of the file from which the module was loaded
|
@@ -202,7 +204,7 @@ def get_module_paths(module: types.ModuleType) -> Set[str]:
|
|
202
204
|
# Some modules might not have __file__ or __spec__ attributes.
|
203
205
|
pass
|
204
206
|
except Exception as e:
|
205
|
-
|
207
|
+
_LOGGER.warning(f"Examining the path of {module.__name__} raised: {e}")
|
206
208
|
|
207
209
|
all_paths.update(
|
208
210
|
[os.path.abspath(str(p)) for p in potential_paths if _is_valid_path(p)]
|
@@ -210,5 +212,5 @@ def get_module_paths(module: types.ModuleType) -> Set[str]:
|
|
210
212
|
return all_paths
|
211
213
|
|
212
214
|
|
213
|
-
def _is_valid_path(path:
|
215
|
+
def _is_valid_path(path: str | None) -> bool:
|
214
216
|
return isinstance(path, str) and (os.path.isfile(path) or os.path.isdir(path))
|
@@ -18,11 +18,8 @@ from typing import Callable, Type, Union
|
|
18
18
|
|
19
19
|
import streamlit.watcher
|
20
20
|
from streamlit import cli_util, config, env_util
|
21
|
-
from streamlit.logger import get_logger
|
22
21
|
from streamlit.watcher.polling_path_watcher import PollingPathWatcher
|
23
22
|
|
24
|
-
LOGGER = get_logger(__name__)
|
25
|
-
|
26
23
|
|
27
24
|
# local_sources_watcher.py caches the return value of
|
28
25
|
# get_default_path_watcher_class(), so it needs to differentiate between the
|
@@ -14,19 +14,20 @@
|
|
14
14
|
|
15
15
|
"""A class that watches a given path via polling."""
|
16
16
|
|
17
|
+
from __future__ import annotations
|
18
|
+
|
17
19
|
import time
|
18
20
|
from concurrent.futures import ThreadPoolExecutor
|
19
|
-
from typing import Callable,
|
21
|
+
from typing import Callable, Final
|
20
22
|
|
21
23
|
from streamlit.logger import get_logger
|
22
24
|
from streamlit.util import repr_
|
23
25
|
from streamlit.watcher import util
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
+
_LOGGER: Final = get_logger(__name__)
|
27
28
|
|
28
|
-
_MAX_WORKERS = 4
|
29
|
-
_POLLING_PERIOD_SECS = 0.2
|
29
|
+
_MAX_WORKERS: Final = 4
|
30
|
+
_POLLING_PERIOD_SECS: Final = 0.2
|
30
31
|
|
31
32
|
|
32
33
|
class PollingPathWatcher:
|
@@ -41,14 +42,14 @@ class PollingPathWatcher:
|
|
41
42
|
This is a no-op, and exists for interface parity with
|
42
43
|
EventBasedPathWatcher.
|
43
44
|
"""
|
44
|
-
|
45
|
+
_LOGGER.debug("Watcher closed")
|
45
46
|
|
46
47
|
def __init__(
|
47
48
|
self,
|
48
49
|
path: str,
|
49
50
|
on_changed: Callable[[str], None],
|
50
51
|
*, # keyword-only arguments:
|
51
|
-
glob_pattern:
|
52
|
+
glob_pattern: str | None = None,
|
52
53
|
allow_nonexistent: bool = False,
|
53
54
|
) -> None:
|
54
55
|
"""Constructor.
|
@@ -113,7 +114,7 @@ class PollingPathWatcher:
|
|
113
114
|
|
114
115
|
self._md5 = md5
|
115
116
|
|
116
|
-
|
117
|
+
_LOGGER.debug("Change detected: %s", self._path)
|
117
118
|
self._on_changed(self._path)
|
118
119
|
|
119
120
|
self._schedule()
|
streamlit/watcher/util.py
CHANGED
@@ -18,11 +18,12 @@ These are functions that only make sense within the watcher. In particular,
|
|
18
18
|
functions that use streamlit.config can go here to avoid a dependency cycle.
|
19
19
|
"""
|
20
20
|
|
21
|
+
from __future__ import annotations
|
22
|
+
|
21
23
|
import hashlib
|
22
24
|
import os
|
23
25
|
import time
|
24
26
|
from pathlib import Path
|
25
|
-
from typing import Optional
|
26
27
|
|
27
28
|
from streamlit.util import HASHLIB_KWARGS
|
28
29
|
|
@@ -36,7 +37,7 @@ _RETRY_WAIT_SECS = 0.1
|
|
36
37
|
def calc_md5_with_blocking_retries(
|
37
38
|
path: str,
|
38
39
|
*, # keyword-only arguments:
|
39
|
-
glob_pattern:
|
40
|
+
glob_pattern: str | None = None,
|
40
41
|
allow_nonexistent: bool = False,
|
41
42
|
) -> str:
|
42
43
|
"""Calculate the MD5 checksum of a given path.
|
@@ -12,6 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
from streamlit.runtime.caching.storage import CacheStorageManager
|
16
18
|
from streamlit.runtime.caching.storage.local_disk_cache_storage import (
|
17
19
|
LocalDiskCacheStorageManager,
|
@@ -12,17 +12,18 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import mimetypes
|
16
18
|
import os
|
17
19
|
from pathlib import Path
|
18
|
-
from typing import
|
20
|
+
from typing import Final
|
19
21
|
|
20
22
|
import tornado.web
|
21
23
|
|
22
24
|
from streamlit.logger import get_logger
|
23
25
|
|
24
|
-
_LOGGER = get_logger(__name__)
|
25
|
-
|
26
|
+
_LOGGER: Final = get_logger(__name__)
|
26
27
|
|
27
28
|
# We agreed on these limitations for the initial release of static file sharing,
|
28
29
|
# based on security concerns from the SiS and Community Cloud teams
|
@@ -34,11 +35,11 @@ SAFE_APP_STATIC_FILE_EXTENSIONS = (".jpg", ".jpeg", ".png", ".gif", ".webp")
|
|
34
35
|
|
35
36
|
|
36
37
|
class AppStaticFileHandler(tornado.web.StaticFileHandler):
|
37
|
-
def initialize(self, path: str, default_filename:
|
38
|
+
def initialize(self, path: str, default_filename: str | None = None) -> None:
|
38
39
|
super().initialize(path, default_filename)
|
39
40
|
mimetypes.add_type("image/webp", ".webp")
|
40
41
|
|
41
|
-
def validate_absolute_path(self, root: str, absolute_path: str) ->
|
42
|
+
def validate_absolute_path(self, root: str, absolute_path: str) -> str | None:
|
42
43
|
full_path = os.path.realpath(absolute_path)
|
43
44
|
|
44
45
|
if os.path.isdir(full_path):
|
@@ -12,10 +12,12 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import base64
|
16
18
|
import binascii
|
17
19
|
import json
|
18
|
-
from typing import Any, Awaitable,
|
20
|
+
from typing import Any, Awaitable, Final
|
19
21
|
|
20
22
|
import tornado.concurrent
|
21
23
|
import tornado.locks
|
@@ -23,7 +25,6 @@ import tornado.netutil
|
|
23
25
|
import tornado.web
|
24
26
|
import tornado.websocket
|
25
27
|
from tornado.websocket import WebSocketHandler
|
26
|
-
from typing_extensions import Final
|
27
28
|
|
28
29
|
from streamlit import config
|
29
30
|
from streamlit.logger import get_logger
|
@@ -41,7 +42,7 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
41
42
|
|
42
43
|
def initialize(self, runtime: Runtime) -> None:
|
43
44
|
self._runtime = runtime
|
44
|
-
self._session_id:
|
45
|
+
self._session_id: str | None = None
|
45
46
|
# The XSRF cookie is normally set when xsrf_form_html is used, but in a
|
46
47
|
# pure-Javascript application that does not use any regular forms we just
|
47
48
|
# need to read the self.xsrf_token manually to set the cookie as a side
|
@@ -61,7 +62,7 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
61
62
|
except tornado.websocket.WebSocketClosedError as e:
|
62
63
|
raise SessionClientDisconnectedError from e
|
63
64
|
|
64
|
-
def select_subprotocol(self, subprotocols:
|
65
|
+
def select_subprotocol(self, subprotocols: list[str]) -> str | None:
|
65
66
|
"""Return the first subprotocol in the given list.
|
66
67
|
|
67
68
|
This method is used by Tornado to select a protocol when the
|
@@ -87,7 +88,7 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
87
88
|
|
88
89
|
return None
|
89
90
|
|
90
|
-
def open(self, *args, **kwargs) ->
|
91
|
+
def open(self, *args, **kwargs) -> Awaitable[None] | None:
|
91
92
|
# Extract user info from the X-Streamlit-User header
|
92
93
|
is_public_cloud_app = False
|
93
94
|
|
@@ -100,7 +101,8 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
100
101
|
except (KeyError, binascii.Error, json.decoder.JSONDecodeError):
|
101
102
|
email = "test@example.com"
|
102
103
|
|
103
|
-
user_info:
|
104
|
+
user_info: dict[str, str | None] = dict()
|
105
|
+
|
104
106
|
if is_public_cloud_app:
|
105
107
|
user_info["email"] = None
|
106
108
|
else:
|
@@ -135,7 +137,7 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
135
137
|
self._runtime.disconnect_session(self._session_id)
|
136
138
|
self._session_id = None
|
137
139
|
|
138
|
-
def get_compression_options(self) ->
|
140
|
+
def get_compression_options(self) -> dict[Any, Any] | None:
|
139
141
|
"""Enable WebSocket compression.
|
140
142
|
|
141
143
|
Returning an empty dict enables websocket compression. Returning
|
@@ -147,7 +149,7 @@ class BrowserWebSocketHandler(WebSocketHandler, SessionClient):
|
|
147
149
|
return {}
|
148
150
|
return None
|
149
151
|
|
150
|
-
def on_message(self, payload:
|
152
|
+
def on_message(self, payload: str | bytes) -> None:
|
151
153
|
if not self._session_id:
|
152
154
|
return
|
153
155
|
|
@@ -12,8 +12,11 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import mimetypes
|
16
18
|
import os
|
19
|
+
from typing import Final
|
17
20
|
|
18
21
|
import tornado.web
|
19
22
|
|
@@ -21,7 +24,7 @@ import streamlit.web.server.routes
|
|
21
24
|
from streamlit.components.v1.components import ComponentRegistry
|
22
25
|
from streamlit.logger import get_logger
|
23
26
|
|
24
|
-
_LOGGER = get_logger(__name__)
|
27
|
+
_LOGGER: Final = get_logger(__name__)
|
25
28
|
|
26
29
|
|
27
30
|
class ComponentRequestHandler(tornado.web.RequestHandler):
|
@@ -63,7 +66,7 @@ class ComponentRequestHandler(tornado.web.RequestHandler):
|
|
63
66
|
|
64
67
|
self.set_extra_headers(path)
|
65
68
|
|
66
|
-
def set_extra_headers(self, path) -> None:
|
69
|
+
def set_extra_headers(self, path: str) -> None:
|
67
70
|
"""Disable cache for HTML files.
|
68
71
|
|
69
72
|
Other assets like JS and CSS are suffixed with their hash, so they can
|
@@ -86,7 +89,7 @@ class ComponentRequestHandler(tornado.web.RequestHandler):
|
|
86
89
|
self.finish()
|
87
90
|
|
88
91
|
@staticmethod
|
89
|
-
def get_content_type(abspath) -> str:
|
92
|
+
def get_content_type(abspath: str) -> str:
|
90
93
|
"""Returns the ``Content-Type`` header to be used for this request.
|
91
94
|
From tornado.web.StaticFileHandler.
|
92
95
|
"""
|
@@ -108,4 +111,4 @@ class ComponentRequestHandler(tornado.web.RequestHandler):
|
|
108
111
|
@staticmethod
|
109
112
|
def get_url(file_id: str) -> str:
|
110
113
|
"""Return the URL for a component file with the given ID."""
|
111
|
-
return "components/{}"
|
114
|
+
return f"components/{file_id}"
|
@@ -12,7 +12,8 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
-
from
|
15
|
+
from __future__ import annotations
|
16
|
+
|
16
17
|
from urllib.parse import quote
|
17
18
|
|
18
19
|
import tornado.web
|
@@ -67,11 +68,11 @@ class MediaFileHandler(tornado.web.StaticFileHandler):
|
|
67
68
|
# Check that the value can be encoded in latin1. Latin1 is
|
68
69
|
# the default encoding for headers.
|
69
70
|
filename.encode("latin1")
|
70
|
-
file_expr = 'filename="{}"'
|
71
|
+
file_expr = f'filename="{filename}"'
|
71
72
|
except UnicodeEncodeError:
|
72
73
|
# RFC5987 syntax.
|
73
74
|
# See: https://datatracker.ietf.org/doc/html/rfc5987
|
74
|
-
file_expr = "filename*=utf-8''{
|
75
|
+
file_expr = f"filename*=utf-8''{quote(filename)}"
|
75
76
|
|
76
77
|
self.set_header("Content-Disposition", f"attachment; {file_expr}")
|
77
78
|
|
@@ -112,7 +113,7 @@ class MediaFileHandler(tornado.web.StaticFileHandler):
|
|
112
113
|
|
113
114
|
@classmethod
|
114
115
|
def get_content(
|
115
|
-
cls, abspath: str, start:
|
116
|
+
cls, abspath: str, start: int | None = None, end: int | None = None
|
116
117
|
):
|
117
118
|
_LOGGER.debug("MediaFileHandler: GET %s", abspath)
|
118
119
|
|
streamlit/web/server/routes.py
CHANGED
@@ -12,7 +12,10 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import os
|
18
|
+
from typing import Final
|
16
19
|
|
17
20
|
import tornado.web
|
18
21
|
|
@@ -21,10 +24,10 @@ from streamlit.logger import get_logger
|
|
21
24
|
from streamlit.runtime.runtime_util import serialize_forward_msg
|
22
25
|
from streamlit.web.server.server_util import emit_endpoint_deprecation_notice
|
23
26
|
|
24
|
-
_LOGGER = get_logger(__name__)
|
27
|
+
_LOGGER: Final = get_logger(__name__)
|
25
28
|
|
26
29
|
|
27
|
-
def allow_cross_origin_requests():
|
30
|
+
def allow_cross_origin_requests() -> bool:
|
28
31
|
"""True if cross-origin requests are allowed.
|
29
32
|
|
30
33
|
We only allow cross-origin requests when CORS protection has been disabled
|
@@ -43,7 +46,7 @@ class StaticFileHandler(tornado.web.StaticFileHandler):
|
|
43
46
|
|
44
47
|
super().initialize(path=path, default_filename=default_filename)
|
45
48
|
|
46
|
-
def set_extra_headers(self, path):
|
49
|
+
def set_extra_headers(self, path: str) -> None:
|
47
50
|
"""Disable cache for HTML files.
|
48
51
|
|
49
52
|
Other assets like JS and CSS are suffixed with their hash, so they can
|
streamlit/web/server/server.py
CHANGED
@@ -12,14 +12,14 @@
|
|
12
12
|
# See the License for the specific language governing permissions and
|
13
13
|
# limitations under the License.
|
14
14
|
|
15
|
+
from __future__ import annotations
|
16
|
+
|
15
17
|
import errno
|
16
18
|
import logging
|
17
19
|
import os
|
18
|
-
import socket
|
19
|
-
import ssl
|
20
20
|
import sys
|
21
21
|
from pathlib import Path
|
22
|
-
from typing import Any, Awaitable,
|
22
|
+
from typing import TYPE_CHECKING, Any, Awaitable, Final
|
23
23
|
|
24
24
|
import tornado.concurrent
|
25
25
|
import tornado.locks
|
@@ -27,7 +27,6 @@ import tornado.netutil
|
|
27
27
|
import tornado.web
|
28
28
|
import tornado.websocket
|
29
29
|
from tornado.httpserver import HTTPServer
|
30
|
-
from typing_extensions import Final
|
31
30
|
|
32
31
|
from streamlit import cli_util, config, file_util, source_util, util
|
33
32
|
from streamlit.components.v1.components import ComponentRegistry
|
@@ -51,11 +50,14 @@ from streamlit.web.server.routes import (
|
|
51
50
|
MessageCacheHandler,
|
52
51
|
StaticFileHandler,
|
53
52
|
)
|
54
|
-
from streamlit.web.server.server_util import make_url_path_regex
|
53
|
+
from streamlit.web.server.server_util import DEVELOPMENT_PORT, make_url_path_regex
|
55
54
|
from streamlit.web.server.stats_request_handler import StatsRequestHandler
|
56
55
|
from streamlit.web.server.upload_file_request_handler import UploadFileRequestHandler
|
57
56
|
|
58
|
-
|
57
|
+
if TYPE_CHECKING:
|
58
|
+
from ssl import SSLContext
|
59
|
+
|
60
|
+
_LOGGER: Final = get_logger(__name__)
|
59
61
|
|
60
62
|
TORNADO_SETTINGS = {
|
61
63
|
# Gzip HTTP responses.
|
@@ -74,11 +76,11 @@ TORNADO_SETTINGS = {
|
|
74
76
|
|
75
77
|
# When server.port is not available it will look for the next available port
|
76
78
|
# up to MAX_PORT_SEARCH_RETRIES.
|
77
|
-
MAX_PORT_SEARCH_RETRIES = 100
|
79
|
+
MAX_PORT_SEARCH_RETRIES: Final = 100
|
78
80
|
|
79
81
|
# When server.address starts with this prefix, the server will bind
|
80
82
|
# to an unix socket.
|
81
|
-
UNIX_SOCKET_PREFIX = "unix://"
|
83
|
+
UNIX_SOCKET_PREFIX: Final = "unix://"
|
82
84
|
|
83
85
|
MEDIA_ENDPOINT: Final = "/media"
|
84
86
|
UPLOAD_FILE_ENDPOINT: Final = "/_stcore/upload_file"
|
@@ -128,11 +130,9 @@ def start_listening(app: tornado.web.Application) -> None:
|
|
128
130
|
start_listening_tcp_socket(http_server)
|
129
131
|
|
130
132
|
|
131
|
-
def _get_ssl_options(
|
132
|
-
cert_file: Optional[str], key_file: Optional[str]
|
133
|
-
) -> Union[ssl.SSLContext, None]:
|
133
|
+
def _get_ssl_options(cert_file: str | None, key_file: str | None) -> SSLContext | None:
|
134
134
|
if bool(cert_file) != bool(key_file):
|
135
|
-
|
135
|
+
_LOGGER.error(
|
136
136
|
"Options 'server.sslCertFile' and 'server.sslKeyFile' must "
|
137
137
|
"be set together. Set missing options or delete existing options."
|
138
138
|
)
|
@@ -142,12 +142,14 @@ def _get_ssl_options(
|
|
142
142
|
# sufficiently user-friendly
|
143
143
|
# FileNotFoundError: [Errno 2] No such file or directory
|
144
144
|
if not Path(cert_file).exists():
|
145
|
-
|
145
|
+
_LOGGER.error("Cert file '%s' does not exist.", cert_file)
|
146
146
|
sys.exit(1)
|
147
147
|
if not Path(key_file).exists():
|
148
|
-
|
148
|
+
_LOGGER.error("Key file '%s' does not exist.", key_file)
|
149
149
|
sys.exit(1)
|
150
150
|
|
151
|
+
import ssl
|
152
|
+
|
151
153
|
ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
152
154
|
# When the SSL certificate fails to load, an exception is raised as below,
|
153
155
|
# but it is not sufficiently user-friendly.
|
@@ -155,7 +157,7 @@ def _get_ssl_options(
|
|
155
157
|
try:
|
156
158
|
ssl_ctx.load_cert_chain(cert_file, key_file)
|
157
159
|
except ssl.SSLError:
|
158
|
-
|
160
|
+
_LOGGER.error(
|
159
161
|
"Failed to load SSL certificate. Make sure "
|
160
162
|
"cert file '%s' and key file '%s' are correct.",
|
161
163
|
cert_file,
|
@@ -183,23 +185,30 @@ def start_listening_tcp_socket(http_server: HTTPServer) -> None:
|
|
183
185
|
address = config.get_option("server.address")
|
184
186
|
port = config.get_option("server.port")
|
185
187
|
|
188
|
+
if int(port) == DEVELOPMENT_PORT:
|
189
|
+
_LOGGER.warning(
|
190
|
+
"Port %s is reserved for internal development. "
|
191
|
+
"It is strongly recommended to select an alternative port "
|
192
|
+
"for `server.port`.",
|
193
|
+
DEVELOPMENT_PORT,
|
194
|
+
)
|
195
|
+
|
186
196
|
try:
|
187
197
|
http_server.listen(port, address)
|
188
198
|
break # It worked! So let's break out of the loop.
|
189
199
|
|
190
|
-
except
|
200
|
+
except OSError as e:
|
191
201
|
if e.errno == errno.EADDRINUSE:
|
192
202
|
if server_port_is_manually_set():
|
193
|
-
|
203
|
+
_LOGGER.error("Port %s is already in use", port)
|
194
204
|
sys.exit(1)
|
195
205
|
else:
|
196
|
-
|
206
|
+
_LOGGER.debug(
|
197
207
|
"Port %s already in use, trying to use the next one.", port
|
198
208
|
)
|
199
209
|
port += 1
|
200
|
-
#
|
201
|
-
|
202
|
-
if port == 3000:
|
210
|
+
# Don't use the development port here:
|
211
|
+
if port == DEVELOPMENT_PORT:
|
203
212
|
port += 1
|
204
213
|
|
205
214
|
config.set_option(
|
@@ -255,13 +264,13 @@ class Server:
|
|
255
264
|
When this returns, Streamlit is ready to accept new sessions.
|
256
265
|
"""
|
257
266
|
|
258
|
-
|
267
|
+
_LOGGER.debug("Starting server...")
|
259
268
|
|
260
269
|
app = self._create_app()
|
261
270
|
start_listening(app)
|
262
271
|
|
263
272
|
port = config.get_option("server.port")
|
264
|
-
|
273
|
+
_LOGGER.debug("Server started on port %s", port)
|
265
274
|
|
266
275
|
await self._runtime.start()
|
267
276
|
|
@@ -274,7 +283,7 @@ class Server:
|
|
274
283
|
"""Create our tornado web app."""
|
275
284
|
base = config.get_option("server.baseUrlPath")
|
276
285
|
|
277
|
-
routes:
|
286
|
+
routes: list[Any] = [
|
278
287
|
(
|
279
288
|
make_url_path_regex(base, STREAM_ENDPOINT),
|
280
289
|
BrowserWebSocketHandler,
|
@@ -347,10 +356,10 @@ class Server:
|
|
347
356
|
)
|
348
357
|
|
349
358
|
if config.get_option("global.developmentMode"):
|
350
|
-
|
359
|
+
_LOGGER.debug("Serving static content from the Node dev server")
|
351
360
|
else:
|
352
361
|
static_path = file_util.get_static_dir()
|
353
|
-
|
362
|
+
_LOGGER.debug("Serving static content from %s", static_path)
|
354
363
|
|
355
364
|
routes.extend(
|
356
365
|
[
|
@@ -360,14 +369,12 @@ class Server:
|
|
360
369
|
{
|
361
370
|
"path": "%s/" % static_path,
|
362
371
|
"default_filename": "index.html",
|
363
|
-
"get_pages": lambda:
|
364
|
-
[
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
]
|
370
|
-
),
|
372
|
+
"get_pages": lambda: {
|
373
|
+
page_info["page_name"]
|
374
|
+
for page_info in source_util.get_pages(
|
375
|
+
self.main_script_path
|
376
|
+
).values()
|
377
|
+
},
|
371
378
|
},
|
372
379
|
),
|
373
380
|
(make_url_path_regex(base, trailing_slash=False), AddSlashHandler),
|