streamlit-nightly 1.31.2.dev20240213__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 +8 -19
- 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/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/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/uploaded_file_manager.py +6 -5
- streamlit/runtime/websocket_session_manager.py +14 -14
- streamlit/source_util.py +13 -11
- streamlit/string_util.py +13 -9
- streamlit/temporary_directory.py +3 -1
- streamlit/testing/v1/element_tree.py +1 -2
- streamlit/type_util.py +21 -25
- streamlit/url_util.py +6 -4
- streamlit/user_info.py +8 -6
- streamlit/util.py +23 -37
- 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 +31 -31
- streamlit/web/server/server_util.py +4 -2
- streamlit/web/server/upload_file_request_handler.py +7 -8
- streamlit/web/server/websocket_headers.py +2 -2
- {streamlit_nightly-1.31.2.dev20240213.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/METADATA +1 -1
- {streamlit_nightly-1.31.2.dev20240213.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/RECORD +153 -153
- {streamlit_nightly-1.31.2.dev20240213.data → streamlit_nightly-1.31.2.dev20240214.data}/scripts/streamlit.cmd +0 -0
- {streamlit_nightly-1.31.2.dev20240213.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/WHEEL +0 -0
- {streamlit_nightly-1.31.2.dev20240213.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/entry_points.txt +0 -0
- {streamlit_nightly-1.31.2.dev20240213.dist-info → streamlit_nightly-1.31.2.dev20240214.dist-info}/top_level.txt +0 -0
@@ -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
|
@@ -55,7 +54,10 @@ from streamlit.web.server.server_util import DEVELOPMENT_PORT, make_url_path_reg
|
|
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,
|
@@ -184,7 +186,7 @@ def start_listening_tcp_socket(http_server: HTTPServer) -> None:
|
|
184
186
|
port = config.get_option("server.port")
|
185
187
|
|
186
188
|
if int(port) == DEVELOPMENT_PORT:
|
187
|
-
|
189
|
+
_LOGGER.warning(
|
188
190
|
"Port %s is reserved for internal development. "
|
189
191
|
"It is strongly recommended to select an alternative port "
|
190
192
|
"for `server.port`.",
|
@@ -195,13 +197,13 @@ def start_listening_tcp_socket(http_server: HTTPServer) -> None:
|
|
195
197
|
http_server.listen(port, address)
|
196
198
|
break # It worked! So let's break out of the loop.
|
197
199
|
|
198
|
-
except
|
200
|
+
except OSError as e:
|
199
201
|
if e.errno == errno.EADDRINUSE:
|
200
202
|
if server_port_is_manually_set():
|
201
|
-
|
203
|
+
_LOGGER.error("Port %s is already in use", port)
|
202
204
|
sys.exit(1)
|
203
205
|
else:
|
204
|
-
|
206
|
+
_LOGGER.debug(
|
205
207
|
"Port %s already in use, trying to use the next one.", port
|
206
208
|
)
|
207
209
|
port += 1
|
@@ -262,13 +264,13 @@ class Server:
|
|
262
264
|
When this returns, Streamlit is ready to accept new sessions.
|
263
265
|
"""
|
264
266
|
|
265
|
-
|
267
|
+
_LOGGER.debug("Starting server...")
|
266
268
|
|
267
269
|
app = self._create_app()
|
268
270
|
start_listening(app)
|
269
271
|
|
270
272
|
port = config.get_option("server.port")
|
271
|
-
|
273
|
+
_LOGGER.debug("Server started on port %s", port)
|
272
274
|
|
273
275
|
await self._runtime.start()
|
274
276
|
|
@@ -281,7 +283,7 @@ class Server:
|
|
281
283
|
"""Create our tornado web app."""
|
282
284
|
base = config.get_option("server.baseUrlPath")
|
283
285
|
|
284
|
-
routes:
|
286
|
+
routes: list[Any] = [
|
285
287
|
(
|
286
288
|
make_url_path_regex(base, STREAM_ENDPOINT),
|
287
289
|
BrowserWebSocketHandler,
|
@@ -354,10 +356,10 @@ class Server:
|
|
354
356
|
)
|
355
357
|
|
356
358
|
if config.get_option("global.developmentMode"):
|
357
|
-
|
359
|
+
_LOGGER.debug("Serving static content from the Node dev server")
|
358
360
|
else:
|
359
361
|
static_path = file_util.get_static_dir()
|
360
|
-
|
362
|
+
_LOGGER.debug("Serving static content from %s", static_path)
|
361
363
|
|
362
364
|
routes.extend(
|
363
365
|
[
|
@@ -367,14 +369,12 @@ class Server:
|
|
367
369
|
{
|
368
370
|
"path": "%s/" % static_path,
|
369
371
|
"default_filename": "index.html",
|
370
|
-
"get_pages": lambda:
|
371
|
-
[
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
]
|
377
|
-
),
|
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
|
+
},
|
378
378
|
},
|
379
379
|
),
|
380
380
|
(make_url_path_regex(base, trailing_slash=False), AddSlashHandler),
|
@@ -14,7 +14,9 @@
|
|
14
14
|
|
15
15
|
"""Server related utility functions"""
|
16
16
|
|
17
|
-
from
|
17
|
+
from __future__ import annotations
|
18
|
+
|
19
|
+
from typing import Final
|
18
20
|
from urllib.parse import urljoin
|
19
21
|
|
20
22
|
import tornado.web
|
@@ -67,7 +69,7 @@ def is_url_from_allowed_origins(url: str) -> bool:
|
|
67
69
|
return False
|
68
70
|
|
69
71
|
|
70
|
-
def _get_server_address_if_manually_set() ->
|
72
|
+
def _get_server_address_if_manually_set() -> str | None:
|
71
73
|
if config.is_manually_set("browser.serverAddress"):
|
72
74
|
return url_util.get_hostname(config.get_option("browser.serverAddress"))
|
73
75
|
return None
|
@@ -12,19 +12,18 @@
|
|
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
|
+
|
17
|
+
from typing import Any, Callable
|
16
18
|
|
17
19
|
import tornado.httputil
|
18
20
|
import tornado.web
|
19
21
|
|
20
22
|
from streamlit import config
|
21
|
-
from streamlit.logger import get_logger
|
22
23
|
from streamlit.runtime.memory_uploaded_file_manager import MemoryUploadedFileManager
|
23
|
-
from streamlit.runtime.uploaded_file_manager import
|
24
|
+
from streamlit.runtime.uploaded_file_manager import UploadedFileRec
|
24
25
|
from streamlit.web.server import routes, server_util
|
25
26
|
|
26
|
-
LOGGER = get_logger(__name__)
|
27
|
-
|
28
27
|
|
29
28
|
class UploadFileRequestHandler(tornado.web.RequestHandler):
|
30
29
|
"""Implements the POST /upload_file endpoint."""
|
@@ -84,8 +83,8 @@ class UploadFileRequestHandler(tornado.web.RequestHandler):
|
|
84
83
|
def put(self, **kwargs):
|
85
84
|
"""Receive an uploaded file and add it to our UploadedFileManager."""
|
86
85
|
|
87
|
-
args:
|
88
|
-
files:
|
86
|
+
args: dict[str, list[bytes]] = {}
|
87
|
+
files: dict[str, list[Any]] = {}
|
89
88
|
|
90
89
|
session_id = self.path_kwargs["session_id"]
|
91
90
|
file_id = self.path_kwargs["file_id"]
|
@@ -104,7 +103,7 @@ class UploadFileRequestHandler(tornado.web.RequestHandler):
|
|
104
103
|
self.send_error(400, reason=str(e))
|
105
104
|
return
|
106
105
|
|
107
|
-
uploaded_files:
|
106
|
+
uploaded_files: list[UploadedFileRec] = []
|
108
107
|
|
109
108
|
for _, flist in files.items():
|
110
109
|
for file in flist:
|
@@ -12,7 +12,7 @@
|
|
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
17
|
from streamlit import runtime
|
18
18
|
from streamlit.runtime.metrics_util import gather_metrics
|
@@ -21,7 +21,7 @@ from streamlit.web.server.browser_websocket_handler import BrowserWebSocketHandl
|
|
21
21
|
|
22
22
|
|
23
23
|
@gather_metrics("_get_websocket_headers")
|
24
|
-
def _get_websocket_headers() ->
|
24
|
+
def _get_websocket_headers() -> dict[str, str] | None:
|
25
25
|
"""Return a copy of the HTTP request headers for the current session's
|
26
26
|
WebSocket connection. If there's no active session, return None instead.
|
27
27
|
|