openms-insight 0.1.2__py3-none-any.whl → 0.1.4__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 +11 -7
- openms_insight/components/__init__.py +2 -2
- openms_insight/components/heatmap.py +433 -228
- openms_insight/components/lineplot.py +377 -82
- openms_insight/components/sequenceview.py +677 -213
- openms_insight/components/table.py +86 -58
- openms_insight/core/__init__.py +2 -2
- openms_insight/core/base.py +122 -54
- openms_insight/core/registry.py +6 -5
- openms_insight/core/state.py +33 -31
- openms_insight/core/subprocess_preprocess.py +1 -3
- openms_insight/js-component/dist/assets/index.css +1 -1
- openms_insight/js-component/dist/assets/index.js +105 -105
- openms_insight/preprocessing/__init__.py +5 -6
- openms_insight/preprocessing/compression.py +123 -67
- openms_insight/preprocessing/filtering.py +39 -13
- openms_insight/rendering/__init__.py +1 -1
- openms_insight/rendering/bridge.py +192 -42
- {openms_insight-0.1.2.dist-info → openms_insight-0.1.4.dist-info}/METADATA +163 -20
- openms_insight-0.1.4.dist-info/RECORD +28 -0
- openms_insight-0.1.2.dist-info/RECORD +0 -28
- {openms_insight-0.1.2.dist-info → openms_insight-0.1.4.dist-info}/WHEEL +0 -0
- {openms_insight-0.1.2.dist-info → openms_insight-0.1.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -6,7 +6,7 @@ import polars as pl
|
|
|
6
6
|
|
|
7
7
|
from ..core.base import BaseComponent
|
|
8
8
|
from ..core.registry import register_component
|
|
9
|
-
from ..preprocessing.filtering import
|
|
9
|
+
from ..preprocessing.filtering import filter_and_collect_cached
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@register_component("table")
|
|
@@ -57,14 +57,14 @@ class Table(BaseComponent):
|
|
|
57
57
|
regenerate_cache: bool = False,
|
|
58
58
|
column_definitions: Optional[List[Dict[str, Any]]] = None,
|
|
59
59
|
title: Optional[str] = None,
|
|
60
|
-
index_field: str =
|
|
60
|
+
index_field: str = "id",
|
|
61
61
|
go_to_fields: Optional[List[str]] = None,
|
|
62
|
-
layout: str =
|
|
62
|
+
layout: str = "fitDataFill",
|
|
63
63
|
default_row: int = 0,
|
|
64
64
|
initial_sort: Optional[List[Dict[str, Any]]] = None,
|
|
65
65
|
pagination: bool = True,
|
|
66
66
|
page_size: int = 100,
|
|
67
|
-
**kwargs
|
|
67
|
+
**kwargs,
|
|
68
68
|
):
|
|
69
69
|
"""
|
|
70
70
|
Initialize the Table component.
|
|
@@ -138,7 +138,7 @@ class Table(BaseComponent):
|
|
|
138
138
|
initial_sort=initial_sort,
|
|
139
139
|
pagination=pagination,
|
|
140
140
|
page_size=page_size,
|
|
141
|
-
**kwargs
|
|
141
|
+
**kwargs,
|
|
142
142
|
)
|
|
143
143
|
|
|
144
144
|
def _get_cache_config(self) -> Dict[str, Any]:
|
|
@@ -149,10 +149,29 @@ class Table(BaseComponent):
|
|
|
149
149
|
Dict of config values that affect preprocessing
|
|
150
150
|
"""
|
|
151
151
|
return {
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
"column_definitions": self._column_definitions,
|
|
153
|
+
"index_field": self._index_field,
|
|
154
|
+
"title": self._title,
|
|
155
|
+
"go_to_fields": self._go_to_fields,
|
|
156
|
+
"layout": self._layout,
|
|
157
|
+
"default_row": self._default_row,
|
|
158
|
+
"initial_sort": self._initial_sort,
|
|
159
|
+
"pagination": self._pagination,
|
|
160
|
+
"page_size": self._page_size,
|
|
154
161
|
}
|
|
155
162
|
|
|
163
|
+
def _restore_cache_config(self, config: Dict[str, Any]) -> None:
|
|
164
|
+
"""Restore component-specific configuration from cached config."""
|
|
165
|
+
self._column_definitions = config.get("column_definitions")
|
|
166
|
+
self._index_field = config.get("index_field", "id")
|
|
167
|
+
self._title = config.get("title")
|
|
168
|
+
self._go_to_fields = config.get("go_to_fields")
|
|
169
|
+
self._layout = config.get("layout", "fitDataFill")
|
|
170
|
+
self._default_row = config.get("default_row", 0)
|
|
171
|
+
self._initial_sort = config.get("initial_sort")
|
|
172
|
+
self._pagination = config.get("pagination", True)
|
|
173
|
+
self._page_size = config.get("page_size", 100)
|
|
174
|
+
|
|
156
175
|
def _get_row_group_size(self) -> int:
|
|
157
176
|
"""
|
|
158
177
|
Get optimal row group size for parquet writing.
|
|
@@ -195,31 +214,40 @@ class Table(BaseComponent):
|
|
|
195
214
|
self._column_definitions = []
|
|
196
215
|
for name, dtype in zip(schema.names(), schema.dtypes()):
|
|
197
216
|
col_def: Dict[str, Any] = {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
217
|
+
"field": name,
|
|
218
|
+
"title": name.replace("_", " ").title(),
|
|
219
|
+
"headerTooltip": True,
|
|
201
220
|
}
|
|
202
221
|
# Set sorter based on data type
|
|
203
|
-
if dtype in (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
222
|
+
if dtype in (
|
|
223
|
+
pl.Int8,
|
|
224
|
+
pl.Int16,
|
|
225
|
+
pl.Int32,
|
|
226
|
+
pl.Int64,
|
|
227
|
+
pl.UInt8,
|
|
228
|
+
pl.UInt16,
|
|
229
|
+
pl.UInt32,
|
|
230
|
+
pl.UInt64,
|
|
231
|
+
pl.Float32,
|
|
232
|
+
pl.Float64,
|
|
233
|
+
):
|
|
234
|
+
col_def["sorter"] = "number"
|
|
235
|
+
col_def["hozAlign"] = "right"
|
|
208
236
|
elif dtype == pl.Boolean:
|
|
209
|
-
col_def[
|
|
237
|
+
col_def["sorter"] = "boolean"
|
|
210
238
|
elif dtype in (pl.Date, pl.Datetime, pl.Time):
|
|
211
|
-
col_def[
|
|
239
|
+
col_def["sorter"] = "date"
|
|
212
240
|
else:
|
|
213
|
-
col_def[
|
|
241
|
+
col_def["sorter"] = "string"
|
|
214
242
|
|
|
215
243
|
self._column_definitions.append(col_def)
|
|
216
244
|
|
|
217
245
|
# Store column definitions in preprocessed data for serialization
|
|
218
|
-
self._preprocessed_data[
|
|
246
|
+
self._preprocessed_data["column_definitions"] = self._column_definitions
|
|
219
247
|
|
|
220
248
|
# Store LazyFrame for streaming to disk (filter happens at render time)
|
|
221
249
|
# Base class will use sink_parquet() to stream without full materialization
|
|
222
|
-
self._preprocessed_data[
|
|
250
|
+
self._preprocessed_data["data"] = data # Keep lazy
|
|
223
251
|
|
|
224
252
|
def _get_columns_to_select(self) -> Optional[List[str]]:
|
|
225
253
|
"""Get list of columns needed for this table."""
|
|
@@ -227,9 +255,9 @@ class Table(BaseComponent):
|
|
|
227
255
|
return None
|
|
228
256
|
|
|
229
257
|
columns_to_select = [
|
|
230
|
-
col_def[
|
|
258
|
+
col_def["field"]
|
|
231
259
|
for col_def in self._column_definitions
|
|
232
|
-
if
|
|
260
|
+
if "field" in col_def
|
|
233
261
|
]
|
|
234
262
|
# Always include index field for row identification
|
|
235
263
|
if self._index_field and self._index_field not in columns_to_select:
|
|
@@ -249,11 +277,11 @@ class Table(BaseComponent):
|
|
|
249
277
|
|
|
250
278
|
def _get_vue_component_name(self) -> str:
|
|
251
279
|
"""Return the Vue component name."""
|
|
252
|
-
return
|
|
280
|
+
return "TabulatorTable"
|
|
253
281
|
|
|
254
282
|
def _get_data_key(self) -> str:
|
|
255
283
|
"""Return the key used to send primary data to Vue."""
|
|
256
|
-
return
|
|
284
|
+
return "tableData"
|
|
257
285
|
|
|
258
286
|
def _prepare_vue_data(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
259
287
|
"""
|
|
@@ -272,7 +300,7 @@ class Table(BaseComponent):
|
|
|
272
300
|
columns = self._get_columns_to_select()
|
|
273
301
|
|
|
274
302
|
# Get cached data (DataFrame or LazyFrame)
|
|
275
|
-
data = self._preprocessed_data.get(
|
|
303
|
+
data = self._preprocessed_data.get("data")
|
|
276
304
|
if data is None:
|
|
277
305
|
# Fallback to raw data if available
|
|
278
306
|
data = self._raw_data
|
|
@@ -290,7 +318,7 @@ class Table(BaseComponent):
|
|
|
290
318
|
filter_defaults=self._filter_defaults,
|
|
291
319
|
)
|
|
292
320
|
|
|
293
|
-
return {
|
|
321
|
+
return {"tableData": df_pandas, "_hash": data_hash}
|
|
294
322
|
|
|
295
323
|
def _get_component_args(self) -> Dict[str, Any]:
|
|
296
324
|
"""
|
|
@@ -302,29 +330,29 @@ class Table(BaseComponent):
|
|
|
302
330
|
# Get column definitions (may have been loaded from cache)
|
|
303
331
|
column_defs = self._column_definitions
|
|
304
332
|
if column_defs is None:
|
|
305
|
-
column_defs = self._preprocessed_data.get(
|
|
333
|
+
column_defs = self._preprocessed_data.get("column_definitions", [])
|
|
306
334
|
|
|
307
335
|
args: Dict[str, Any] = {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
336
|
+
"componentType": self._get_vue_component_name(),
|
|
337
|
+
"columnDefinitions": column_defs,
|
|
338
|
+
"tableIndexField": self._index_field,
|
|
339
|
+
"tableLayoutParam": self._layout,
|
|
340
|
+
"defaultRow": self._default_row,
|
|
313
341
|
# Pass interactivity so Vue knows which identifier to update on row click
|
|
314
|
-
|
|
342
|
+
"interactivity": self._interactivity,
|
|
315
343
|
# Pagination settings
|
|
316
|
-
|
|
317
|
-
|
|
344
|
+
"pagination": self._pagination,
|
|
345
|
+
"pageSize": self._page_size,
|
|
318
346
|
}
|
|
319
347
|
|
|
320
348
|
if self._title:
|
|
321
|
-
args[
|
|
349
|
+
args["title"] = self._title
|
|
322
350
|
|
|
323
351
|
if self._go_to_fields:
|
|
324
|
-
args[
|
|
352
|
+
args["goToFields"] = self._go_to_fields
|
|
325
353
|
|
|
326
354
|
if self._initial_sort:
|
|
327
|
-
args[
|
|
355
|
+
args["initialSort"] = self._initial_sort
|
|
328
356
|
|
|
329
357
|
# Add any extra config options
|
|
330
358
|
args.update(self._config)
|
|
@@ -335,8 +363,8 @@ class Table(BaseComponent):
|
|
|
335
363
|
self,
|
|
336
364
|
field: str,
|
|
337
365
|
formatter: str,
|
|
338
|
-
formatter_params: Optional[Dict[str, Any]] = None
|
|
339
|
-
) ->
|
|
366
|
+
formatter_params: Optional[Dict[str, Any]] = None,
|
|
367
|
+
) -> "Table":
|
|
340
368
|
"""
|
|
341
369
|
Add or update a column formatter.
|
|
342
370
|
|
|
@@ -349,10 +377,10 @@ class Table(BaseComponent):
|
|
|
349
377
|
Self for method chaining
|
|
350
378
|
"""
|
|
351
379
|
for col_def in self._column_definitions or []:
|
|
352
|
-
if col_def.get(
|
|
353
|
-
col_def[
|
|
380
|
+
if col_def.get("field") == field:
|
|
381
|
+
col_def["formatter"] = formatter
|
|
354
382
|
if formatter_params:
|
|
355
|
-
col_def[
|
|
383
|
+
col_def["formatterParams"] = formatter_params
|
|
356
384
|
break
|
|
357
385
|
return self
|
|
358
386
|
|
|
@@ -360,10 +388,10 @@ class Table(BaseComponent):
|
|
|
360
388
|
self,
|
|
361
389
|
field: str,
|
|
362
390
|
precision: int = 2,
|
|
363
|
-
symbol: str =
|
|
364
|
-
thousand: str =
|
|
365
|
-
decimal: str =
|
|
366
|
-
) ->
|
|
391
|
+
symbol: str = "",
|
|
392
|
+
thousand: str = ",",
|
|
393
|
+
decimal: str = ".",
|
|
394
|
+
) -> "Table":
|
|
367
395
|
"""
|
|
368
396
|
Format a column as currency/money.
|
|
369
397
|
|
|
@@ -379,13 +407,13 @@ class Table(BaseComponent):
|
|
|
379
407
|
"""
|
|
380
408
|
return self.with_column_formatter(
|
|
381
409
|
field,
|
|
382
|
-
|
|
410
|
+
"money",
|
|
383
411
|
{
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
412
|
+
"precision": precision,
|
|
413
|
+
"symbol": symbol,
|
|
414
|
+
"thousand": thousand,
|
|
415
|
+
"decimal": decimal,
|
|
416
|
+
},
|
|
389
417
|
)
|
|
390
418
|
|
|
391
419
|
def with_progress_bar(
|
|
@@ -393,8 +421,8 @@ class Table(BaseComponent):
|
|
|
393
421
|
field: str,
|
|
394
422
|
min_val: float = 0,
|
|
395
423
|
max_val: float = 100,
|
|
396
|
-
color: Optional[str] = None
|
|
397
|
-
) ->
|
|
424
|
+
color: Optional[str] = None,
|
|
425
|
+
) -> "Table":
|
|
398
426
|
"""
|
|
399
427
|
Format a column as a progress bar.
|
|
400
428
|
|
|
@@ -407,7 +435,7 @@ class Table(BaseComponent):
|
|
|
407
435
|
Returns:
|
|
408
436
|
Self for method chaining
|
|
409
437
|
"""
|
|
410
|
-
params: Dict[str, Any] = {
|
|
438
|
+
params: Dict[str, Any] = {"min": min_val, "max": max_val}
|
|
411
439
|
if color:
|
|
412
|
-
params[
|
|
413
|
-
return self.with_column_formatter(field,
|
|
440
|
+
params["color"] = color
|
|
441
|
+
return self.with_column_formatter(field, "progress", params)
|
openms_insight/core/__init__.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Core infrastructure for openms_insight."""
|
|
2
2
|
|
|
3
3
|
from .base import BaseComponent
|
|
4
|
-
from .state import StateManager
|
|
5
|
-
from .registry import register_component, get_component_class
|
|
6
4
|
from .cache import CacheMissError
|
|
5
|
+
from .registry import get_component_class, register_component
|
|
6
|
+
from .state import StateManager
|
|
7
7
|
|
|
8
8
|
__all__ = [
|
|
9
9
|
"BaseComponent",
|
openms_insight/core/base.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Base component class for all visualization components."""
|
|
2
2
|
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
from datetime import datetime
|
|
5
3
|
import hashlib
|
|
6
4
|
import json
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from datetime import datetime
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Any, Dict, List, Optional
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
9
|
+
|
|
9
10
|
import polars as pl
|
|
10
11
|
|
|
11
12
|
from .cache import CacheMissError, get_cache_dir
|
|
@@ -50,15 +51,24 @@ class BaseComponent(ABC):
|
|
|
50
51
|
interactivity: Optional[Dict[str, str]] = None,
|
|
51
52
|
cache_path: str = ".",
|
|
52
53
|
regenerate_cache: bool = False,
|
|
53
|
-
**kwargs
|
|
54
|
+
**kwargs,
|
|
54
55
|
):
|
|
55
56
|
"""
|
|
56
57
|
Initialize the component.
|
|
57
58
|
|
|
59
|
+
Components can be created in two modes:
|
|
60
|
+
|
|
61
|
+
1. **Creation mode** (data provided): Creates cache with specified config.
|
|
62
|
+
All configuration (filters, interactivity, component-specific) is stored.
|
|
63
|
+
|
|
64
|
+
2. **Reconstruction mode** (no data): Loads everything from cache.
|
|
65
|
+
Only cache_id and cache_path are needed. All configuration is restored
|
|
66
|
+
from the cached manifest. Any other parameters passed are ignored.
|
|
67
|
+
|
|
58
68
|
Args:
|
|
59
69
|
cache_id: Unique identifier for this component's cache (MANDATORY).
|
|
60
70
|
Creates a folder {cache_path}/{cache_id}/ for cached data.
|
|
61
|
-
data: Polars LazyFrame with source data.
|
|
71
|
+
data: Polars LazyFrame with source data. Required for creation mode.
|
|
62
72
|
data_path: Path to parquet file with source data. Preferred over
|
|
63
73
|
data= for large datasets as preprocessing runs in a subprocess
|
|
64
74
|
to ensure memory is released after cache creation.
|
|
@@ -84,23 +94,51 @@ class BaseComponent(ABC):
|
|
|
84
94
|
|
|
85
95
|
self._cache_id = cache_id
|
|
86
96
|
self._cache_dir = get_cache_dir(cache_path, cache_id)
|
|
87
|
-
self._filters = filters or {}
|
|
88
|
-
self._filter_defaults = filter_defaults or {}
|
|
89
|
-
self._interactivity = interactivity or {}
|
|
90
97
|
self._preprocessed_data: Dict[str, Any] = {}
|
|
91
|
-
self._config = kwargs
|
|
92
98
|
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
# Determine mode: reconstruction (no data) or creation (data provided)
|
|
100
|
+
has_data = data is not None or data_path is not None
|
|
101
|
+
|
|
102
|
+
# Check if any configuration arguments were explicitly provided
|
|
103
|
+
# Note: We only check filters/interactivity/filter_defaults because component-
|
|
104
|
+
# specific kwargs always have default values passed by subclasses
|
|
105
|
+
has_config = (
|
|
106
|
+
filters is not None
|
|
107
|
+
or filter_defaults is not None
|
|
108
|
+
or interactivity is not None
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if not has_data and not regenerate_cache:
|
|
112
|
+
# Reconstruction mode - only cache_id and cache_path allowed
|
|
113
|
+
if has_config:
|
|
96
114
|
raise CacheMissError(
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
"Configuration arguments (filters, interactivity, filter_defaults) "
|
|
116
|
+
"require data= or data_path= to be provided. "
|
|
117
|
+
"For reconstruction from cache, use only cache_id and cache_path."
|
|
99
118
|
)
|
|
119
|
+
if not self._cache_exists():
|
|
120
|
+
raise CacheMissError(
|
|
121
|
+
f"Cache not found at '{self._cache_dir}'. "
|
|
122
|
+
f"Provide data= or data_path= to create the cache."
|
|
123
|
+
)
|
|
124
|
+
self._raw_data = None
|
|
125
|
+
self._load_from_cache()
|
|
126
|
+
else:
|
|
127
|
+
# Creation mode - use provided config
|
|
128
|
+
if not has_data:
|
|
129
|
+
raise CacheMissError(
|
|
130
|
+
"regenerate_cache=True requires data= or data_path= to be provided."
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
self._filters = filters or {}
|
|
134
|
+
self._filter_defaults = filter_defaults or {}
|
|
135
|
+
self._interactivity = interactivity or {}
|
|
136
|
+
self._config = kwargs
|
|
100
137
|
|
|
101
138
|
if data_path is not None:
|
|
102
139
|
# Subprocess preprocessing - memory released after cache creation
|
|
103
140
|
from .subprocess_preprocess import preprocess_component
|
|
141
|
+
|
|
104
142
|
preprocess_component(
|
|
105
143
|
type(self),
|
|
106
144
|
data_path=data_path,
|
|
@@ -109,20 +147,16 @@ class BaseComponent(ABC):
|
|
|
109
147
|
filters=filters,
|
|
110
148
|
filter_defaults=filter_defaults,
|
|
111
149
|
interactivity=interactivity,
|
|
112
|
-
**kwargs
|
|
150
|
+
**kwargs,
|
|
113
151
|
)
|
|
114
152
|
self._raw_data = None
|
|
115
153
|
self._load_from_cache()
|
|
116
154
|
else:
|
|
117
|
-
# In-process preprocessing
|
|
155
|
+
# In-process preprocessing
|
|
118
156
|
self._raw_data = data
|
|
119
157
|
self._validate_mappings()
|
|
120
158
|
self._preprocess()
|
|
121
159
|
self._save_to_cache()
|
|
122
|
-
else:
|
|
123
|
-
# Load from valid cache
|
|
124
|
-
self._raw_data = None
|
|
125
|
-
self._load_from_cache()
|
|
126
160
|
|
|
127
161
|
def _validate_mappings(self) -> None:
|
|
128
162
|
"""Validate that filter and interactivity columns exist in the data schema."""
|
|
@@ -163,7 +197,7 @@ class BaseComponent(ABC):
|
|
|
163
197
|
config_dict = {
|
|
164
198
|
"filters": self._filters,
|
|
165
199
|
"interactivity": self._interactivity,
|
|
166
|
-
**self._get_cache_config()
|
|
200
|
+
**self._get_cache_config(),
|
|
167
201
|
}
|
|
168
202
|
config_str = json.dumps(config_dict, sort_keys=True, default=str)
|
|
169
203
|
return hashlib.sha256(config_str.encode()).hexdigest()
|
|
@@ -176,15 +210,17 @@ class BaseComponent(ABC):
|
|
|
176
210
|
"""Get path to preprocessed data directory."""
|
|
177
211
|
return self._cache_dir / "preprocessed"
|
|
178
212
|
|
|
179
|
-
def
|
|
213
|
+
def _cache_exists(self) -> bool:
|
|
180
214
|
"""
|
|
181
|
-
Check if cache
|
|
215
|
+
Check if a valid cache exists that can be loaded.
|
|
182
216
|
|
|
183
|
-
Cache
|
|
184
|
-
1. manifest.json exists
|
|
217
|
+
Cache exists when:
|
|
218
|
+
1. manifest.json exists and is readable
|
|
185
219
|
2. version matches current CACHE_VERSION
|
|
186
220
|
3. component_type matches
|
|
187
|
-
|
|
221
|
+
|
|
222
|
+
Note: This does NOT check config hash. In reconstruction mode,
|
|
223
|
+
all configuration is restored from the cache manifest.
|
|
188
224
|
"""
|
|
189
225
|
manifest_path = self._get_manifest_path()
|
|
190
226
|
if not manifest_path.exists():
|
|
@@ -204,24 +240,32 @@ class BaseComponent(ABC):
|
|
|
204
240
|
if manifest.get("component_type") != self._component_type:
|
|
205
241
|
return False
|
|
206
242
|
|
|
207
|
-
# Check config hash
|
|
208
|
-
current_hash = self._compute_config_hash()
|
|
209
|
-
if manifest.get("config_hash") != current_hash:
|
|
210
|
-
return False
|
|
211
|
-
|
|
212
243
|
return True
|
|
213
244
|
|
|
214
245
|
def _load_from_cache(self) -> None:
|
|
215
|
-
"""Load preprocessed data from cache.
|
|
246
|
+
"""Load all configuration and preprocessed data from cache.
|
|
247
|
+
|
|
248
|
+
Restores:
|
|
249
|
+
- filters mapping
|
|
250
|
+
- filter_defaults mapping
|
|
251
|
+
- interactivity mapping
|
|
252
|
+
- Component-specific configuration via _restore_cache_config()
|
|
253
|
+
- All preprocessed data files
|
|
254
|
+
"""
|
|
216
255
|
manifest_path = self._get_manifest_path()
|
|
217
256
|
preprocessed_dir = self._get_preprocessed_dir()
|
|
218
257
|
|
|
219
258
|
with open(manifest_path) as f:
|
|
220
259
|
manifest = json.load(f)
|
|
221
260
|
|
|
222
|
-
#
|
|
261
|
+
# Restore filters, filter_defaults, and interactivity from manifest
|
|
223
262
|
self._filters = manifest.get("filters", {})
|
|
263
|
+
self._filter_defaults = manifest.get("filter_defaults", {})
|
|
224
264
|
self._interactivity = manifest.get("interactivity", {})
|
|
265
|
+
self._config = manifest.get("config", {})
|
|
266
|
+
|
|
267
|
+
# Restore component-specific configuration
|
|
268
|
+
self._restore_cache_config(manifest.get("config", {}))
|
|
225
269
|
|
|
226
270
|
# Load preprocessed data files
|
|
227
271
|
data_files = manifest.get("data_files", {})
|
|
@@ -235,9 +279,25 @@ class BaseComponent(ABC):
|
|
|
235
279
|
for key, value in data_values.items():
|
|
236
280
|
self._preprocessed_data[key] = value
|
|
237
281
|
|
|
282
|
+
@abstractmethod
|
|
283
|
+
def _restore_cache_config(self, config: Dict[str, Any]) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Restore component-specific configuration from cached config dict.
|
|
286
|
+
|
|
287
|
+
Called during reconstruction mode to restore all component attributes
|
|
288
|
+
that were stored in the manifest's config section.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
config: The config dict from manifest (result of _get_cache_config())
|
|
292
|
+
"""
|
|
293
|
+
pass
|
|
294
|
+
|
|
238
295
|
def _save_to_cache(self) -> None:
|
|
239
296
|
"""Save preprocessed data to cache."""
|
|
240
|
-
from ..preprocessing.filtering import
|
|
297
|
+
from ..preprocessing.filtering import (
|
|
298
|
+
optimize_for_transfer,
|
|
299
|
+
optimize_for_transfer_lazy,
|
|
300
|
+
)
|
|
241
301
|
|
|
242
302
|
# Create directories
|
|
243
303
|
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
|
@@ -252,11 +312,15 @@ class BaseComponent(ABC):
|
|
|
252
312
|
"config_hash": self._compute_config_hash(),
|
|
253
313
|
"config": self._get_cache_config(),
|
|
254
314
|
"filters": self._filters,
|
|
315
|
+
"filter_defaults": self._filter_defaults,
|
|
255
316
|
"interactivity": self._interactivity,
|
|
256
317
|
"data_files": {},
|
|
257
318
|
"data_values": {},
|
|
258
319
|
}
|
|
259
320
|
|
|
321
|
+
# Check if files were already saved during preprocessing (e.g., cascading)
|
|
322
|
+
files_already_saved = self._preprocessed_data.pop("_files_already_saved", False)
|
|
323
|
+
|
|
260
324
|
# Save preprocessed data with type optimization for efficient transfer
|
|
261
325
|
# Float64→Float32 reduces Arrow payload size
|
|
262
326
|
# Int64→Int32 (when safe) avoids BigInt overhead in JavaScript
|
|
@@ -264,18 +328,28 @@ class BaseComponent(ABC):
|
|
|
264
328
|
if isinstance(value, pl.LazyFrame):
|
|
265
329
|
filename = f"{key}.parquet"
|
|
266
330
|
filepath = preprocessed_dir / filename
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
331
|
+
|
|
332
|
+
if files_already_saved and filepath.exists():
|
|
333
|
+
# File was saved during preprocessing (cascading) - just register it
|
|
334
|
+
manifest["data_files"][key] = filename
|
|
335
|
+
else:
|
|
336
|
+
# Apply streaming-safe optimization (Float64→Float32 only)
|
|
337
|
+
# Int64 bounds checking would require collect(), breaking streaming
|
|
338
|
+
value = optimize_for_transfer_lazy(value)
|
|
339
|
+
value.sink_parquet(filepath, compression="zstd")
|
|
340
|
+
manifest["data_files"][key] = filename
|
|
272
341
|
elif isinstance(value, pl.DataFrame):
|
|
273
342
|
filename = f"{key}.parquet"
|
|
274
343
|
filepath = preprocessed_dir / filename
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
344
|
+
|
|
345
|
+
if files_already_saved and filepath.exists():
|
|
346
|
+
# File was saved during preprocessing - just register it
|
|
347
|
+
manifest["data_files"][key] = filename
|
|
348
|
+
else:
|
|
349
|
+
# Full optimization including Int64→Int32 with bounds checking
|
|
350
|
+
value = optimize_for_transfer(value)
|
|
351
|
+
value.write_parquet(filepath, compression="zstd")
|
|
352
|
+
manifest["data_files"][key] = filename
|
|
279
353
|
elif self._is_json_serializable(value):
|
|
280
354
|
manifest["data_values"][key] = value
|
|
281
355
|
|
|
@@ -332,10 +406,7 @@ class BaseComponent(ABC):
|
|
|
332
406
|
pass
|
|
333
407
|
|
|
334
408
|
@abstractmethod
|
|
335
|
-
def _prepare_vue_data(
|
|
336
|
-
self,
|
|
337
|
-
state: Dict[str, Any]
|
|
338
|
-
) -> Dict[str, Any]:
|
|
409
|
+
def _prepare_vue_data(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
339
410
|
"""
|
|
340
411
|
Prepare data payload for Vue component.
|
|
341
412
|
|
|
@@ -404,8 +475,8 @@ class BaseComponent(ABC):
|
|
|
404
475
|
def __call__(
|
|
405
476
|
self,
|
|
406
477
|
key: Optional[str] = None,
|
|
407
|
-
state_manager: Optional[
|
|
408
|
-
height: Optional[int] = None
|
|
478
|
+
state_manager: Optional["StateManager"] = None,
|
|
479
|
+
height: Optional[int] = None,
|
|
409
480
|
) -> Any:
|
|
410
481
|
"""
|
|
411
482
|
Render the component in Streamlit.
|
|
@@ -419,17 +490,14 @@ class BaseComponent(ABC):
|
|
|
419
490
|
Returns:
|
|
420
491
|
The value returned by the Vue component (usually selection state)
|
|
421
492
|
"""
|
|
422
|
-
from .state import get_default_state_manager
|
|
423
493
|
from ..rendering.bridge import render_component
|
|
494
|
+
from .state import get_default_state_manager
|
|
424
495
|
|
|
425
496
|
if state_manager is None:
|
|
426
497
|
state_manager = get_default_state_manager()
|
|
427
498
|
|
|
428
499
|
return render_component(
|
|
429
|
-
component=self,
|
|
430
|
-
state_manager=state_manager,
|
|
431
|
-
key=key,
|
|
432
|
-
height=height
|
|
500
|
+
component=self, state_manager=state_manager, key=key, height=height
|
|
433
501
|
)
|
|
434
502
|
|
|
435
503
|
def __repr__(self) -> str:
|
openms_insight/core/registry.py
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"""Component type registry for serialization and deserialization."""
|
|
2
2
|
|
|
3
|
-
from typing import Dict, Type
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, Type
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
6
|
from .base import BaseComponent
|
|
7
7
|
|
|
8
8
|
# Global registry mapping component type names to their classes
|
|
9
|
-
_COMPONENT_REGISTRY: Dict[str, Type[
|
|
9
|
+
_COMPONENT_REGISTRY: Dict[str, Type["BaseComponent"]] = {}
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def register_component(name: str):
|
|
@@ -24,7 +24,8 @@ def register_component(name: str):
|
|
|
24
24
|
class Table(BaseComponent):
|
|
25
25
|
...
|
|
26
26
|
"""
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
def decorator(cls: Type["BaseComponent"]) -> Type["BaseComponent"]:
|
|
28
29
|
if name in _COMPONENT_REGISTRY:
|
|
29
30
|
raise ValueError(
|
|
30
31
|
f"Component type '{name}' is already registered to "
|
|
@@ -37,7 +38,7 @@ def register_component(name: str):
|
|
|
37
38
|
return decorator
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
def get_component_class(name: str) -> Type[
|
|
41
|
+
def get_component_class(name: str) -> Type["BaseComponent"]:
|
|
41
42
|
"""
|
|
42
43
|
Get a component class by its registered name.
|
|
43
44
|
|
|
@@ -59,7 +60,7 @@ def get_component_class(name: str) -> Type['BaseComponent']:
|
|
|
59
60
|
return _COMPONENT_REGISTRY[name]
|
|
60
61
|
|
|
61
62
|
|
|
62
|
-
def list_registered_components() -> Dict[str, Type[
|
|
63
|
+
def list_registered_components() -> Dict[str, Type["BaseComponent"]]:
|
|
63
64
|
"""
|
|
64
65
|
Get all registered component types.
|
|
65
66
|
|