openms-insight 0.1.0__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.
- openms_insight/__init__.py +32 -0
- openms_insight/components/__init__.py +11 -0
- openms_insight/components/heatmap.py +823 -0
- openms_insight/components/lineplot.py +492 -0
- openms_insight/components/sequenceview.py +384 -0
- openms_insight/components/table.py +400 -0
- openms_insight/core/__init__.py +14 -0
- openms_insight/core/base.py +413 -0
- openms_insight/core/cache.py +39 -0
- openms_insight/core/registry.py +82 -0
- openms_insight/core/state.py +215 -0
- openms_insight/js-component/dist/assets/index.css +5 -0
- openms_insight/js-component/dist/assets/index.js +4220 -0
- openms_insight/js-component/dist/assets/materialdesignicons-webfont.eot +0 -0
- openms_insight/js-component/dist/assets/materialdesignicons-webfont.ttf +0 -0
- openms_insight/js-component/dist/assets/materialdesignicons-webfont.woff +0 -0
- openms_insight/js-component/dist/assets/materialdesignicons-webfont.woff2 +0 -0
- openms_insight/js-component/dist/index.html +14 -0
- openms_insight/preprocessing/__init__.py +22 -0
- openms_insight/preprocessing/compression.py +338 -0
- openms_insight/preprocessing/filtering.py +316 -0
- openms_insight/rendering/__init__.py +8 -0
- openms_insight/rendering/bridge.py +312 -0
- openms_insight-0.1.0.dist-info/METADATA +256 -0
- openms_insight-0.1.0.dist-info/RECORD +27 -0
- openms_insight-0.1.0.dist-info/WHEEL +4 -0
- openms_insight-0.1.0.dist-info/licenses/LICENSE +29 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""State management for cross-component selection synchronization."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
# Module-level default state manager
|
|
7
|
+
_default_state_manager: Optional['StateManager'] = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_default_state_manager() -> 'StateManager':
|
|
11
|
+
"""
|
|
12
|
+
Get or create the default shared StateManager.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
The default StateManager instance
|
|
16
|
+
"""
|
|
17
|
+
global _default_state_manager
|
|
18
|
+
if _default_state_manager is None:
|
|
19
|
+
_default_state_manager = StateManager()
|
|
20
|
+
return _default_state_manager
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def reset_default_state_manager() -> None:
|
|
24
|
+
"""Reset the default state manager (useful for testing)."""
|
|
25
|
+
global _default_state_manager
|
|
26
|
+
_default_state_manager = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StateManager:
|
|
30
|
+
"""
|
|
31
|
+
Manages selection state across components with conflict resolution.
|
|
32
|
+
|
|
33
|
+
Features:
|
|
34
|
+
- Counter-based conflict resolution to handle concurrent updates
|
|
35
|
+
- Session ID for multi-tab/session safety
|
|
36
|
+
- Streamlit session_state integration
|
|
37
|
+
- Support for arbitrary selection identifiers
|
|
38
|
+
|
|
39
|
+
The StateManager maintains a dict of selections keyed by identifier names.
|
|
40
|
+
When a component updates a selection, all components sharing that identifier
|
|
41
|
+
will see the new value on the next render.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, session_key: str = "svc_state"):
|
|
45
|
+
"""
|
|
46
|
+
Initialize the StateManager.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
session_key: Key to use in Streamlit session_state for storing
|
|
50
|
+
state. Use different keys for independent component groups.
|
|
51
|
+
"""
|
|
52
|
+
self._session_key = session_key
|
|
53
|
+
self._ensure_session_state()
|
|
54
|
+
|
|
55
|
+
def _ensure_session_state(self) -> None:
|
|
56
|
+
"""Ensure session state is initialized."""
|
|
57
|
+
import streamlit as st
|
|
58
|
+
|
|
59
|
+
if self._session_key not in st.session_state:
|
|
60
|
+
st.session_state[self._session_key] = {
|
|
61
|
+
'counter': 0,
|
|
62
|
+
'id': float(np.random.random()),
|
|
63
|
+
'selections': {},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def _state(self) -> Dict[str, Any]:
|
|
68
|
+
"""Get the internal state dict from session_state."""
|
|
69
|
+
import streamlit as st
|
|
70
|
+
self._ensure_session_state()
|
|
71
|
+
return st.session_state[self._session_key]
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def session_id(self) -> float:
|
|
75
|
+
"""Get the unique session ID."""
|
|
76
|
+
return self._state['id']
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def counter(self) -> int:
|
|
80
|
+
"""Get the current state counter."""
|
|
81
|
+
return self._state['counter']
|
|
82
|
+
|
|
83
|
+
def get_selection(self, identifier: str) -> Any:
|
|
84
|
+
"""
|
|
85
|
+
Get current selection value for an identifier.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
identifier: The selection identifier name
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The current selection value, or None if not set
|
|
92
|
+
"""
|
|
93
|
+
return self._state['selections'].get(identifier)
|
|
94
|
+
|
|
95
|
+
def set_selection(self, identifier: str, value: Any) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
Set selection value for an identifier.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
identifier: The selection identifier name
|
|
101
|
+
value: The value to set
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
True if the value changed, False otherwise
|
|
105
|
+
"""
|
|
106
|
+
current = self._state['selections'].get(identifier)
|
|
107
|
+
if current == value:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
self._state['selections'][identifier] = value
|
|
111
|
+
self._state['counter'] += 1
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
def clear_selection(self, identifier: str) -> bool:
|
|
115
|
+
"""
|
|
116
|
+
Clear selection for an identifier.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
identifier: The selection identifier name
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if a selection was cleared, False if it wasn't set
|
|
123
|
+
"""
|
|
124
|
+
if identifier in self._state['selections']:
|
|
125
|
+
del self._state['selections'][identifier]
|
|
126
|
+
self._state['counter'] += 1
|
|
127
|
+
return True
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
def get_all_selections(self) -> Dict[str, Any]:
|
|
131
|
+
"""
|
|
132
|
+
Get all current selections.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Dict mapping identifiers to their selected values
|
|
136
|
+
"""
|
|
137
|
+
return self._state['selections'].copy()
|
|
138
|
+
|
|
139
|
+
def get_state_for_vue(self) -> Dict[str, Any]:
|
|
140
|
+
"""
|
|
141
|
+
Get state dict formatted for sending to Vue component.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dict with counter, id, and all selections as top-level keys
|
|
145
|
+
"""
|
|
146
|
+
state = {
|
|
147
|
+
'counter': self._state['counter'],
|
|
148
|
+
'id': self._state['id'],
|
|
149
|
+
}
|
|
150
|
+
state.update(self._state['selections'])
|
|
151
|
+
return state
|
|
152
|
+
|
|
153
|
+
def update_from_vue(self, vue_state: Dict[str, Any]) -> bool:
|
|
154
|
+
"""
|
|
155
|
+
Update state from Vue component return value.
|
|
156
|
+
|
|
157
|
+
Uses counter-based conflict resolution: only accepts updates from
|
|
158
|
+
Vue if its counter is >= our counter (prevents stale updates).
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
vue_state: State dict returned by Vue component
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if state was modified, False otherwise
|
|
165
|
+
"""
|
|
166
|
+
if vue_state is None:
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
# Verify same session (prevents cross-tab interference)
|
|
170
|
+
if vue_state.get('id') != self._state['id']:
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
# Extract metadata
|
|
174
|
+
vue_counter = vue_state.pop('counter', 0)
|
|
175
|
+
vue_state.pop('id', None)
|
|
176
|
+
|
|
177
|
+
# Filter out internal keys (starting with _)
|
|
178
|
+
vue_state = {k: v for k, v in vue_state.items() if not k.startswith('_')}
|
|
179
|
+
|
|
180
|
+
modified = False
|
|
181
|
+
|
|
182
|
+
# Always accept previously undefined keys (but skip None/undefined values)
|
|
183
|
+
for key, value in vue_state.items():
|
|
184
|
+
if key not in self._state['selections']:
|
|
185
|
+
# Only add if value is not None (undefined in Vue = no selection)
|
|
186
|
+
if value is not None:
|
|
187
|
+
self._state['selections'][key] = value
|
|
188
|
+
modified = True
|
|
189
|
+
|
|
190
|
+
# Only accept conflicting updates if Vue has newer state
|
|
191
|
+
if vue_counter >= self._state['counter']:
|
|
192
|
+
for key, value in vue_state.items():
|
|
193
|
+
if key in self._state['selections']:
|
|
194
|
+
if self._state['selections'][key] != value:
|
|
195
|
+
self._state['selections'][key] = value
|
|
196
|
+
modified = True
|
|
197
|
+
|
|
198
|
+
if modified:
|
|
199
|
+
# Set counter to be at least vue_counter + 1 to reject future stale updates
|
|
200
|
+
# from other Vue components that haven't received the latest state yet
|
|
201
|
+
self._state['counter'] = max(self._state['counter'] + 1, vue_counter + 1)
|
|
202
|
+
|
|
203
|
+
return modified
|
|
204
|
+
|
|
205
|
+
def clear(self) -> None:
|
|
206
|
+
"""Clear all selections and reset counter."""
|
|
207
|
+
self._state['selections'] = {}
|
|
208
|
+
self._state['counter'] = 0
|
|
209
|
+
|
|
210
|
+
def __repr__(self) -> str:
|
|
211
|
+
return (
|
|
212
|
+
f"StateManager(session_key='{self._session_key}', "
|
|
213
|
+
f"counter={self.counter}, "
|
|
214
|
+
f"selections={self.get_all_selections()})"
|
|
215
|
+
)
|