forked from Wauplin/streamlit-sync
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathst_hack.py
149 lines (112 loc) · 5.53 KB
/
st_hack.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
"""Module that contains all calls to internal APIs of Streamlit.
It is most likely that this module will break in future updates of Streamlit.
"""
import re
from typing import Any, Iterable, Mapping, Optional, Tuple
from streamlit.web.server import Server
from streamlit.runtime.state.session_state import (
STREAMLIT_INTERNAL_KEY_PREFIX,
SessionState,
)
from streamlit.runtime.state.common import GENERATED_WIDGET_ID_PREFIX
from .exceptions import StreamlitSyncException
try:
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.runtime.scriptrunner.script_run_context import get_script_run_ctx
except ImportError:
try:
# streamlit < 1.7
from streamlit.script_run_context import get_script_run_ctx
except ImportError:
# 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.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
_WIDGET_ID_REGEX = re.compile(
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):
match = _WIDGET_ID_REGEX.match(widget_id)
if match is None:
# If broken, look at implementation in
# - https://github.com/streamlit/streamlit/blob/develop/lib/streamlit/state/widgets.py#L258 # noqa: E501
# - https://github.com/streamlit/streamlit/blob/d07ffac8927e1a35b34684b55222854b3dd5a9a7/lib/streamlit/state/widgets.py#L258 # noqa: E501
raise StreamlitSyncException(
"Broken streamlit-sync library: Streamlit has most likely "
"changed the widget id generation."
)
return match["user_key"]
return widget_id
# Monkeypatch SessionState
if not getattr(SessionState, "_is_patched_by_streamlit_sync", False):
def _always_set_frontend_value_if_changed(
self: SessionState, widget_id: str, user_key: Optional[str]
) -> bool:
"""Keep `widget_state` and `session_state` in sync when a widget is registered.
By default if a value has changed, the frontend widget will be updated only if
it's a user-keyed-widget. I guess this is done because user-keyed-widget can
be manually updated from st.session_state but not the "implicitly-keyed"
widgets.
In our case, we want to update the frontend for any value change.
See:
- Streamlit >= 1.8
- https://github.com/streamlit/streamlit/blob/develop/lib/streamlit/state/session_state.py#L599 # noqa: E501
- https://github.com/streamlit/streamlit/blob/d07ffac8927e1a35b34684b55222854b3dd5a9a7/lib/streamlit/state/session_state.py#L599 # noqa: E501
- Streamlit <= 1.7
- https://github.com/streamlit/streamlit/blob/1.7.0/lib/streamlit/state/session_state.py#L596 # noqa: E501
- https://github.com/streamlit/streamlit/blob/a3f1cef8e23a97188710b71c4cf927f4783f58c5/lib/streamlit/state/session_state.py#L596 # noqa: E501
"""
return self.is_new_state_value(user_key or widget_id)
# For Streamlit >= 1.8
initial_register_widget = getattr(SessionState, "register_widget", None)
if initial_register_widget is not None:
def _patched_register_widget(
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, user_key)
return widget_value
SessionState.register_widget = _patched_register_widget
# For streamlit < 1.8
SessionState.should_set_frontend_state_value = _always_set_frontend_value_if_changed
# Patch only once
SessionState._is_patched_by_streamlit_sync = True
def get_session_id() -> str:
"""Return current session id."""
ctx = get_script_run_ctx()
assert ctx is not None # TODO: confirm this is true
return ctx.session_id
def is_trigger_value(key: str, internal_session_state: SessionState) -> bool:
"""Return True if widget is a of type "trigger_value" (e.g. a button).
We don't want to propagate the effect of the button to avoid performing twice the
action.
"""
widget_metadata = internal_session_state._new_widget_state.widget_metadata
if key in widget_metadata:
return widget_metadata[key].value_type == "trigger_value"
return False
def is_form_submitter_value(key: str) -> bool:
"""Check if the widget key refers to a submit button from a form.
In this case, value is not synced.
Example: 'FormSubmitter:my_form-Submit'
"""
return "FormSubmitter" in key and "-Submit" in key
def set_internal_values(mapping: Mapping[str, Any]) -> None:
"""Set values to the streamlit internal session state."""
internal_state = get_session_state()
for key, value in mapping.items():
internal_state[widget_id_to_user_key(key)] = value
def del_internal_values(keys: Iterable[str]) -> None:
"""Delete values from the streamlit internal session state."""
internal_state = get_session_state()
for key in keys:
del internal_state[widget_id_to_user_key(key)]