From 29c24350de282fe8dff7db6ebf32d911362f5aa7 Mon Sep 17 00:00:00 2001 From: "Yuichiro Tachibana (Tsuchiya)" Date: Mon, 15 Aug 2022 12:47:29 +0900 Subject: [PATCH] Upgrade streamlit as a dev dep to 1.12.0 and fix imports (#1005) * Upgrade streamlit as a dev dep to 1.12.0 and fix imports * Update CHANGELOG.md * Fix to use packaging.version for version comparison * Fix import * Fix import * Add a test matrix item * Fix initialization of SessionInfo in a test --- .github/workflows/main.yml | 7 +++- CHANGELOG.md | 3 ++ poetry.lock | 28 ++++++------- pyproject.toml | 1 + streamlit_webrtc/eventloop.py | 9 ++++- streamlit_webrtc/relay.py | 4 +- streamlit_webrtc/server.py | 67 ++++++++++++++++++++++++++++++++ streamlit_webrtc/session_info.py | 28 ++++++++----- streamlit_webrtc/shutdown.py | 16 +++++--- tests/session_info_test.py | 10 +++-- 10 files changed, 131 insertions(+), 42 deletions(-) create mode 100644 streamlit_webrtc/server.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 463811d6..a4664a85 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,12 +29,15 @@ jobs: streamlit-version: 1.0.0 - python-version: 3.9 streamlit-version: 1.4.0 - # Test with streamlit <1.6.0 and >=1.6.0. See https://github.com/whitphx/streamlit-webrtc/issues/709 + # Test with streamlit >=1.6.0. See https://github.com/whitphx/streamlit-webrtc/issues/709 - python-version: 3.9 streamlit-version: 1.6.0 - # Test with streamlit <1.8.0 and >=1.8.0. See https://github.com/whitphx/streamlit-webrtc/issues/759 + # Test with streamlit >=1.8.0. See https://github.com/whitphx/streamlit-webrtc/issues/759 - python-version: 3.9 streamlit-version: 1.8.0 + # Test with streamlit >=1.12.0. See https://github.com/whitphx/streamlit-webrtc/issues/1004 + - python-version: 3.9 + streamlit-version: 1.12.0 steps: - uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 0de857df..5cc1a0bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Fix +- Escape-hatch to access the running Streamlit server object for the new web server design with streamlit>=1.12.0, #1005. + ## [0.42.0] - 2022-07-02 ### Fix diff --git a/poetry.lock b/poetry.lock index a6328e0c..9137f599 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1397,37 +1397,36 @@ python-versions = ">=3.6" [[package]] name = "streamlit" -version = "1.10.0" +version = "1.12.0" description = "The fastest way to build data apps in Python" category = "main" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] altair = ">=3.2.0" -attrs = "*" -blinker = "*" +blinker = ">=1.0.0" cachetools = ">=4.0" click = ">=7.0" gitpython = "!=3.1.19" importlib-metadata = ">=1.4" numpy = "*" -packaging = "*" +packaging = ">=14.1" pandas = ">=0.21.0" pillow = ">=6.2.0" protobuf = ">=3.12,<4" -pyarrow = "*" +pyarrow = ">=4.0" pydeck = ">=0.1.dev5" pympler = ">=0.9" python-dateutil = "*" -requests = "*" -rich = "*" +requests = ">=2.4" +rich = ">=10.11.0" semver = "*" toml = "*" tornado = ">=5.0" -typing-extensions = "*" -tzlocal = "*" -validators = "*" +typing-extensions = ">=3.10.0.0" +tzlocal = ">=1.1" +validators = ">=0.2" watchdog = {version = "*", markers = "platform_system != \"Darwin\""} [[package]] @@ -1636,7 +1635,7 @@ testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest- [metadata] lock-version = "1.1" python-versions = ">=3.7,<3.11" -content-hash = "e6a2fa435a1ca544c133e6e041036ebe2acd345d493f995a3a03ed12a35ac863" +content-hash = "a772203afed34b957fafb7c076c42e08c15480d31cae6d893ee68051b3b05361" [metadata.files] aioice = [ @@ -2810,10 +2809,7 @@ soupsieve = [ {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, ] -streamlit = [ - {file = "streamlit-1.10.0-py2.py3-none-any.whl", hash = "sha256:4febd37144a62177e583d6970ab50f351a2bfd4eb112cf19e38c25b12246a4db"}, - {file = "streamlit-1.10.0.tar.gz", hash = "sha256:9479d623dd4bec1fc7bd27e85d5012672351210e640889022ee7b4631c4efb2c"}, -] +streamlit = [] streamlit-server-state = [ {file = "streamlit-server-state-0.12.2.tar.gz", hash = "sha256:ce3af2ff7475aa3b607d7b179523ef60817f6f5ca96d1b13f275db3afa4e7cba"}, {file = "streamlit_server_state-0.12.2-py3-none-any.whl", hash = "sha256:cf85dfa538a0ee6f46e503b768f520e0ffb715cdd5151765af5620901bbe11d7"}, diff --git a/pyproject.toml b/pyproject.toml index 65522ba1..ed958153 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ python = ">=3.7,<3.11" streamlit = ">=0.84.1" aiortc = "^1.1.2" typing_extensions = { version = ">=3.7.4,<5.0.0", python = "<3.8" } +packaging = ">=20.0" [tool.poetry.dev-dependencies] black = "^21.12b0" diff --git a/streamlit_webrtc/eventloop.py b/streamlit_webrtc/eventloop.py index d586420b..99b232ad 100644 --- a/streamlit_webrtc/eventloop.py +++ b/streamlit_webrtc/eventloop.py @@ -2,12 +2,17 @@ import contextlib from typing import Union -from streamlit.server.server import Server from tornado.platform.asyncio import BaseAsyncIOLoop +from .server import _is_modern_architecture, get_current_server + def get_server_event_loop() -> asyncio.AbstractEventLoop: - current_server = Server.get_current() + current_server = get_current_server() + + if _is_modern_architecture(): + return current_server._eventloop + ioloop = current_server._ioloop # `ioloop` is expected to be of type `BaseAsyncIOLoop`, diff --git a/streamlit_webrtc/relay.py b/streamlit_webrtc/relay.py index b15b0719..dd9f6160 100644 --- a/streamlit_webrtc/relay.py +++ b/streamlit_webrtc/relay.py @@ -1,13 +1,13 @@ from aiortc.contrib.media import MediaRelay -from streamlit.server.server import Server from .eventloop import get_server_event_loop, loop_context +from .server import get_current_server _SERVER_GLOBAL_RELAY_ATTR_NAME_ = "streamlit-webrtc-global-relay" def get_global_relay() -> MediaRelay: - server = Server.get_current() + server = get_current_server() if hasattr(server, _SERVER_GLOBAL_RELAY_ATTR_NAME_): return getattr(server, _SERVER_GLOBAL_RELAY_ATTR_NAME_) diff --git a/streamlit_webrtc/server.py b/streamlit_webrtc/server.py new file mode 100644 index 00000000..8eeaef7f --- /dev/null +++ b/streamlit_webrtc/server.py @@ -0,0 +1,67 @@ +import logging + +import streamlit as st +from packaging import version + +logger = logging.getLogger(__name__) + +_server = None + +VERSION_1_12_0 = version.parse("1.12.0") + + +def _is_modern_architecture() -> bool: + """Returns if the imported streamlit package version is >=1.12.0. + It is because since that version, streamlit has changed its internal architecture + making `web` and `runtime` submodules to which some files have been moved + decoupling the web server-related files and the core runtime, + e.g. https://github.com/streamlit/streamlit/pull/4956. + + During this a huge refactoring, `Server._singleton` and + its accessor `Server.get_current()` have been removed + (https://github.com/streamlit/streamlit/pull/4966) + that we have been using as a server-wide global object, + so we have to change the way to access it. + """ + return version.parse(st.__version__) >= VERSION_1_12_0 + + +def get_current_server(): + global _server + if _server: + return _server + + if _is_modern_architecture(): + logger.debug( + "The running Streamlit version is gte 1.12.0. " + "Try to get the server instance" + ) + + import gc + + from streamlit.web.server.server import Server + + servers = [obj for obj in gc.get_objects() if isinstance(obj, Server)] + + if len(servers) == 0: + raise RuntimeError("Unexpectedly no server exists") + if len(servers) > 1: + logger.warning( + "Unexpectedly multiple server instances exist. Use the first one." + ) + + _server = servers[0] + else: + logger.debug( + "The running Streamlit version is less than 1.12.0. " + "Call Server.get_current()" + ) + try: + from streamlit.web.server.server import Server + except ModuleNotFoundError: + # streamlit < 1.12.0 + from streamlit.server.server import Server + + _server = Server.get_current() + + return _server diff --git a/streamlit_webrtc/session_info.py b/streamlit_webrtc/session_info.py index 6a41fc48..028da833 100644 --- a/streamlit_webrtc/session_info.py +++ b/streamlit_webrtc/session_info.py @@ -1,20 +1,28 @@ from typing import Optional -from streamlit.server.server import SessionInfo +try: + from streamlit.web.server.server import SessionInfo +except ModuleNotFoundError: + # streamlit < 1.12.0 + from streamlit.server.server import SessionInfo # type: ignore try: - from streamlit.scriptrunner import get_script_run_ctx + from streamlit.runtime.scriptrunner import get_script_run_ctx except ModuleNotFoundError: - # streamlit < 1.8 + # streamlit < 1.12.0 try: - from streamlit.script_run_context import get_script_run_ctx # type: ignore + from streamlit.scriptrunner import get_script_run_ctx # type: ignore except ModuleNotFoundError: - # streamlit < 1.4 - from streamlit.report_thread import ( # type: ignore - get_report_ctx as get_script_run_ctx, - ) + # streamlit < 1.8 + try: + from streamlit.script_run_context import get_script_run_ctx # type: ignore + except ModuleNotFoundError: + # streamlit < 1.4 + from streamlit.report_thread import ( # type: ignore + get_report_ctx as get_script_run_ctx, + ) -from streamlit.server.server import Server +from .server import get_current_server # Ref: https://gist.github.com/tvst/036da038ab3e999a64497f42de966a92 @@ -28,7 +36,7 @@ def get_session_id() -> str: def get_this_session_info() -> Optional[SessionInfo]: - current_server = Server.get_current() + current_server = get_current_server() # The original implementation of SessionState (https://gist.github.com/tvst/036da038ab3e999a64497f42de966a92) has a problem # noqa: E501 # as referred to in https://gist.github.com/tvst/036da038ab3e999a64497f42de966a92#gistcomment-3484515, # noqa: E501 diff --git a/streamlit_webrtc/shutdown.py b/streamlit_webrtc/shutdown.py index b45dec9d..9b734093 100644 --- a/streamlit_webrtc/shutdown.py +++ b/streamlit_webrtc/shutdown.py @@ -4,13 +4,17 @@ from typing import Callable, Union try: - from streamlit.app_session import AppSession, AppSessionState + from streamlit.runtime.app_session import AppSession, AppSessionState except ModuleNotFoundError: - # streamlit < 1.4 - from streamlit.report_session import ( # type: ignore - ReportSession as AppSession, - ReportSessionState as AppSessionState, - ) + # streamlit < 1.12.0 + try: + from streamlit.app_session import AppSession, AppSessionState # type: ignore + except ModuleNotFoundError: + # streamlit < 1.4 + from streamlit.report_session import ( # type: ignore + ReportSession as AppSession, + ReportSessionState as AppSessionState, + ) from .session_info import get_this_session_info diff --git a/tests/session_info_test.py b/tests/session_info_test.py index 3c128e71..50598153 100644 --- a/tests/session_info_test.py +++ b/tests/session_info_test.py @@ -1,10 +1,12 @@ from unittest.mock import Mock -from streamlit.server.server import SessionInfo - -from streamlit_webrtc.session_info import get_script_run_count +from streamlit_webrtc.server import _is_modern_architecture +from streamlit_webrtc.session_info import SessionInfo, get_script_run_count def test_get_script_run_count(): - session_info = SessionInfo(ws=Mock(), session=Mock()) + if _is_modern_architecture(): + session_info = SessionInfo(client=Mock(), session=Mock()) + else: + session_info = SessionInfo(ws=Mock(), session=Mock()) assert get_script_run_count(session_info) == 0