Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adapted to the latested streamlit version 1.23.1 #2

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
14 changes: 0 additions & 14 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,6 @@ jobs:
strategy:
matrix:
python-version: [3.7, 3.8, 3.9]
streamlit-version: [null]
include:
- python-version: 3.9
streamlit-version: 1.0.0
- python-version: 3.9
streamlit-version: 1.7.0
- python-version: 3.9
streamlit-version: 1.8.0

steps:
- uses: actions/checkout@v2
Expand All @@ -47,12 +39,6 @@ jobs:
shell: bash
run: timeout 10s poetry run pip --version || rm -rf .venv

- name: Install a specific version of Streamlit
if: ${{ matrix.streamlit-version }}
run: poetry add -D streamlit=="${STREAMLIT_VERSION}"
env:
STREAMLIT_VERSION: ${{ matrix.streamlit-version }}

- name: Install dependencies
run: poetry install

Expand Down
18 changes: 18 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Streamlit",
"type": "python",
"request": "launch",
"module": "streamlit",
"cwd": "${workspaceFolder}",
"args": ["run", "${workspaceFolder}/toy_example.py"],
"env": {
"PYTHONUNBUFFERED": "1",
"STREAMLIT_SERVER_PORT": "8501"
}
}
]
}

39 changes: 18 additions & 21 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
{
"editor.formatOnSave": true,
"editor.rulers": [
88
],
"python.formatting.provider": "black",
"python.linting.enabled": true,
"python.linting.flake8Enabled": true,
"python.linting.mypyEnabled": true,
"python.linting.pylintEnabled": false,
"python.pythonPath": ".venv/bin/python",
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
"editor.formatOnSave": true,
"editor.rulers": [88],
"python.formatting.provider": "none",
"python.linting.enabled": true,
"python.linting.flake8Enabled": true,
"python.linting.mypyEnabled": true,
"python.linting.pylintEnabled": false,
"python.pythonPath": ".venv/bin/python",
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
"cSpell.words": [
"streamlit"
],
}
"editor.defaultFormatter": "ms-python.black-formatter"
},
"python.testing.unittestEnabled": false,
"python.testing.nosetestsEnabled": false,
"python.testing.pytestEnabled": true,
"cSpell.words": ["streamlit"]
}
2,549 changes: 846 additions & 1,703 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
[tool.poetry]
name = "streamlit-sync"
version = "0.1.0"
version = "0.2.0"
description = ""
authors = ["Wauplin <[email protected]>"]
license = "MIT"
readme = "README.md"
# repository = "https://github.com/Wauplin"

[tool.poetry.dependencies]
python = "^3.7"
streamlit = "^1.0.0"
python = ">=3.7,!=3.9.7,<4.0"
diskcache = "^5.4.0"
streamlit = "^1.23.1"

[tool.poetry.dev-dependencies]
black = "^21.12b0"
Expand Down
152 changes: 136 additions & 16 deletions streamlit_sync/st_hack.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@
import re
from typing import Any, Iterable, Mapping, Optional, Tuple

from streamlit.server.server import Server
from streamlit.state.session_state import (
GENERATED_WIDGET_KEY_PREFIX,
from streamlit.runtime.state.common import GENERATED_WIDGET_ID_PREFIX
from streamlit.runtime.state.session_state import (
STREAMLIT_INTERNAL_KEY_PREFIX,
SessionState,
)
from streamlit.web.server import Server

from .exceptions import StreamlitSyncException

try:
from streamlit.state.auto_session_state import get_session_state
from streamlit.runtime.state import get_session_state
except ImportError:
# streamlit < 1.7
from streamlit.state.session_state import get_session_state

try:
from streamlit.scriptrunner import get_script_run_ctx
from streamlit.runtime.scriptrunner.script_run_context import get_script_run_ctx
except ImportError:
try:
# streamlit < 1.7
Expand All @@ -30,21 +30,44 @@
# streamlit < 1.4
from streamlit.report_thread import get_report_ctx as get_script_run_ctx

from streamlit.runtime import get_instance as get_runtime_instance

try:
from streamlit.state.session_state import _is_keyed_widget_id
from streamlit.runtime.state.session_state import is_keyed_widget_id
except ImportError:
from streamlit.state.session_state import is_keyed_widget_id as _is_keyed_widget_id
from streamlit.state.session_state import is_keyed_widget_id as is_keyed_widget_id


from streamlit.elements.file_uploader import FileUploaderMixin
from typing import List, Sequence, Union, cast, overload
from streamlit.runtime.state import (
WidgetArgs,
WidgetCallback,
WidgetKwargs,
register_widget,
)
from streamlit import config
from streamlit.type_util import Key, LabelVisibility, maybe_raise_label_warnings, to_key
from streamlit.runtime.scriptrunner import ScriptRunContext
from streamlit.runtime.uploaded_file_manager import UploadedFile

from streamlit.proto.FileUploader_pb2 import FileUploader as FileUploaderProto
from streamlit.elements.utils import (
check_callback_rules,
check_session_state_rules,
get_label_visibility_proto_value,
)
from streamlit.elements.form import current_form_id
from textwrap import dedent

_WIDGET_ID_REGEX = re.compile(
re.escape(GENERATED_WIDGET_KEY_PREFIX) + r"-[0-9a-f]{32}-(?P<user_key>.*)"
re.escape(GENERATED_WIDGET_ID_PREFIX) + r"-[0-9a-f]{32}-(?P<user_key>.*)"
)


def widget_id_to_user_key(widget_id: str) -> str:
"""Return user key if widget is a keyed-widget, else the widget id itself."""
if _is_keyed_widget_id(widget_id):
if is_keyed_widget_id(widget_id):
match = _WIDGET_ID_REGEX.match(widget_id)
if match is None:
# If broken, look at implementation in
Expand Down Expand Up @@ -89,15 +112,11 @@ def _always_set_frontend_value_if_changed(
if initial_register_widget is not None:

def _patched_register_widget(
self: Any, metadata: Any, widget_id: str, user_key: Optional[str]
self: Any, metadata: Any, user_key: Optional[str]
) -> Tuple[Any, bool]:
assert initial_register_widget is not None
widget_value, _ = initial_register_widget(
self, metadata, widget_id, user_key
)
return widget_value, _always_set_frontend_value_if_changed(
self, widget_id, user_key
)
widget_value = initial_register_widget(self, metadata, user_key)
return widget_value

SessionState.register_widget = _patched_register_widget

Expand Down Expand Up @@ -149,3 +168,104 @@ def del_internal_values(keys: Iterable[str]) -> None:
internal_state = get_session_state()
for key in keys:
del internal_state[widget_id_to_user_key(key)]


SomeUploadedFiles = Optional[Union[UploadedFile, List[UploadedFile]]]
TYPE_PAIRS = [
(".jpg", ".jpeg"),
(".mpg", ".mpeg"),
(".mp4", ".mpeg4"),
(".tif", ".tiff"),
(".htm", ".html"),
]


def _patch_file_uploader(
self,
label: str,
type: Optional[Union[str, Sequence[str]]] = None,
accept_multiple_files: bool = False,
key: Optional[Key] = None,
help: Optional[str] = None,
on_change: Optional[WidgetCallback] = None,
args: Optional[WidgetArgs] = None,
kwargs: Optional[WidgetKwargs] = None,
*, # keyword-only arguments:
label_visibility: LabelVisibility = "visible",
disabled: bool = False,
ctx: Optional[ScriptRunContext] = None,
) -> SomeUploadedFiles:
key = to_key(key)
check_callback_rules(self.dg, on_change)
check_session_state_rules(default_value=None, key=key, writes_allowed=True)
maybe_raise_label_warnings(label, label_visibility)

if type:
if isinstance(type, str):
type = [type]

# May need a regex or a library to validate file types are valid
# extensions.
type = [
file_type if file_type[0] == "." else f".{file_type}" for file_type in type
]

type = [t.lower() for t in type]

for x, y in TYPE_PAIRS:
if x in type and y not in type:
type.append(y)
if y in type and x not in type:
type.append(x)

file_uploader_proto = FileUploaderProto()
file_uploader_proto.label = label
file_uploader_proto.type[:] = type if type is not None else []
file_uploader_proto.max_upload_size_mb = config.get_option("server.maxUploadSize")
file_uploader_proto.multiple_files = accept_multiple_files
file_uploader_proto.form_id = current_form_id(self.dg)
if help is not None:
file_uploader_proto.help = dedent(help)

serde = FileUploaderSerde(accept_multiple_files)

# FileUploader's widget value is a list of file IDs
# representing the current set of files that this uploader should
# know about.
widget_state = register_widget(
"file_uploader",
file_uploader_proto,
user_key=key,
on_change_handler=on_change,
args=args,
kwargs=kwargs,
deserializer=serde.deserialize,
serializer=serde.serialize,
ctx=ctx,
)

# This needs to be done after register_widget because we don't want
# the following proto fields to affect a widget's ID.
file_uploader_proto.disabled = disabled
file_uploader_proto.label_visibility.value = get_label_visibility_proto_value(
label_visibility
)

file_uploader_state = serde.serialize(widget_state.value)
uploaded_file_info = file_uploader_state.uploaded_file_info
if ctx is not None and len(uploaded_file_info) != 0:
newest_file_id = file_uploader_state.max_file_id
active_file_ids = [f.id for f in uploaded_file_info]

ctx.uploaded_file_mgr.remove_orphaned_files(
session_id=ctx.session_id,
widget_id=file_uploader_proto.id,
newest_file_id=newest_file_id,
active_file_ids=active_file_ids,
)

self.dg._enqueue("file_uploader", file_uploader_proto)
return widget_state.value


FileUploaderMixin._file_uploader = _patch_file_uploader
Loading