openms-insight 0.1.1__py3-none-any.whl → 0.1.3__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 +192 -102
- 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 +113 -49
- 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 +113 -113
- openms_insight/preprocessing/__init__.py +5 -6
- openms_insight/preprocessing/compression.py +68 -66
- openms_insight/preprocessing/filtering.py +119 -9
- openms_insight/rendering/__init__.py +1 -1
- openms_insight/rendering/bridge.py +192 -42
- {openms_insight-0.1.1.dist-info → openms_insight-0.1.3.dist-info}/METADATA +163 -20
- openms_insight-0.1.3.dist-info/RECORD +28 -0
- openms_insight-0.1.1.dist-info/RECORD +0 -28
- {openms_insight-0.1.1.dist-info → openms_insight-0.1.3.dist-info}/WHEEL +0 -0
- {openms_insight-0.1.1.dist-info → openms_insight-0.1.3.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
|
|
@@ -15,7 +16,8 @@ if TYPE_CHECKING:
|
|
|
15
16
|
|
|
16
17
|
# Cache format version - increment when cache structure changes
|
|
17
18
|
# Version 2: Added sorting by filter columns + smaller row groups for predicate pushdown
|
|
18
|
-
|
|
19
|
+
# Version 3: Downcast numeric types (Int64→Int32, Float64→Float32) for efficient transfer
|
|
20
|
+
CACHE_VERSION = 3
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
class BaseComponent(ABC):
|
|
@@ -49,15 +51,24 @@ class BaseComponent(ABC):
|
|
|
49
51
|
interactivity: Optional[Dict[str, str]] = None,
|
|
50
52
|
cache_path: str = ".",
|
|
51
53
|
regenerate_cache: bool = False,
|
|
52
|
-
**kwargs
|
|
54
|
+
**kwargs,
|
|
53
55
|
):
|
|
54
56
|
"""
|
|
55
57
|
Initialize the component.
|
|
56
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
|
+
|
|
57
68
|
Args:
|
|
58
69
|
cache_id: Unique identifier for this component's cache (MANDATORY).
|
|
59
70
|
Creates a folder {cache_path}/{cache_id}/ for cached data.
|
|
60
|
-
data: Polars LazyFrame with source data.
|
|
71
|
+
data: Polars LazyFrame with source data. Required for creation mode.
|
|
61
72
|
data_path: Path to parquet file with source data. Preferred over
|
|
62
73
|
data= for large datasets as preprocessing runs in a subprocess
|
|
63
74
|
to ensure memory is released after cache creation.
|
|
@@ -83,23 +94,51 @@ class BaseComponent(ABC):
|
|
|
83
94
|
|
|
84
95
|
self._cache_id = cache_id
|
|
85
96
|
self._cache_dir = get_cache_dir(cache_path, cache_id)
|
|
86
|
-
self._filters = filters or {}
|
|
87
|
-
self._filter_defaults = filter_defaults or {}
|
|
88
|
-
self._interactivity = interactivity or {}
|
|
89
97
|
self._preprocessed_data: Dict[str, Any] = {}
|
|
90
|
-
self._config = kwargs
|
|
91
98
|
|
|
92
|
-
#
|
|
93
|
-
|
|
94
|
-
|
|
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:
|
|
95
114
|
raise CacheMissError(
|
|
96
|
-
|
|
97
|
-
|
|
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."
|
|
98
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
|
|
99
137
|
|
|
100
138
|
if data_path is not None:
|
|
101
139
|
# Subprocess preprocessing - memory released after cache creation
|
|
102
140
|
from .subprocess_preprocess import preprocess_component
|
|
141
|
+
|
|
103
142
|
preprocess_component(
|
|
104
143
|
type(self),
|
|
105
144
|
data_path=data_path,
|
|
@@ -108,20 +147,16 @@ class BaseComponent(ABC):
|
|
|
108
147
|
filters=filters,
|
|
109
148
|
filter_defaults=filter_defaults,
|
|
110
149
|
interactivity=interactivity,
|
|
111
|
-
**kwargs
|
|
150
|
+
**kwargs,
|
|
112
151
|
)
|
|
113
152
|
self._raw_data = None
|
|
114
153
|
self._load_from_cache()
|
|
115
154
|
else:
|
|
116
|
-
# In-process preprocessing
|
|
155
|
+
# In-process preprocessing
|
|
117
156
|
self._raw_data = data
|
|
118
157
|
self._validate_mappings()
|
|
119
158
|
self._preprocess()
|
|
120
159
|
self._save_to_cache()
|
|
121
|
-
else:
|
|
122
|
-
# Load from valid cache
|
|
123
|
-
self._raw_data = None
|
|
124
|
-
self._load_from_cache()
|
|
125
160
|
|
|
126
161
|
def _validate_mappings(self) -> None:
|
|
127
162
|
"""Validate that filter and interactivity columns exist in the data schema."""
|
|
@@ -162,7 +197,7 @@ class BaseComponent(ABC):
|
|
|
162
197
|
config_dict = {
|
|
163
198
|
"filters": self._filters,
|
|
164
199
|
"interactivity": self._interactivity,
|
|
165
|
-
**self._get_cache_config()
|
|
200
|
+
**self._get_cache_config(),
|
|
166
201
|
}
|
|
167
202
|
config_str = json.dumps(config_dict, sort_keys=True, default=str)
|
|
168
203
|
return hashlib.sha256(config_str.encode()).hexdigest()
|
|
@@ -175,15 +210,17 @@ class BaseComponent(ABC):
|
|
|
175
210
|
"""Get path to preprocessed data directory."""
|
|
176
211
|
return self._cache_dir / "preprocessed"
|
|
177
212
|
|
|
178
|
-
def
|
|
213
|
+
def _cache_exists(self) -> bool:
|
|
179
214
|
"""
|
|
180
|
-
Check if cache
|
|
215
|
+
Check if a valid cache exists that can be loaded.
|
|
181
216
|
|
|
182
|
-
Cache
|
|
183
|
-
1. manifest.json exists
|
|
217
|
+
Cache exists when:
|
|
218
|
+
1. manifest.json exists and is readable
|
|
184
219
|
2. version matches current CACHE_VERSION
|
|
185
220
|
3. component_type matches
|
|
186
|
-
|
|
221
|
+
|
|
222
|
+
Note: This does NOT check config hash. In reconstruction mode,
|
|
223
|
+
all configuration is restored from the cache manifest.
|
|
187
224
|
"""
|
|
188
225
|
manifest_path = self._get_manifest_path()
|
|
189
226
|
if not manifest_path.exists():
|
|
@@ -203,24 +240,32 @@ class BaseComponent(ABC):
|
|
|
203
240
|
if manifest.get("component_type") != self._component_type:
|
|
204
241
|
return False
|
|
205
242
|
|
|
206
|
-
# Check config hash
|
|
207
|
-
current_hash = self._compute_config_hash()
|
|
208
|
-
if manifest.get("config_hash") != current_hash:
|
|
209
|
-
return False
|
|
210
|
-
|
|
211
243
|
return True
|
|
212
244
|
|
|
213
245
|
def _load_from_cache(self) -> None:
|
|
214
|
-
"""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
|
+
"""
|
|
215
255
|
manifest_path = self._get_manifest_path()
|
|
216
256
|
preprocessed_dir = self._get_preprocessed_dir()
|
|
217
257
|
|
|
218
258
|
with open(manifest_path) as f:
|
|
219
259
|
manifest = json.load(f)
|
|
220
260
|
|
|
221
|
-
#
|
|
261
|
+
# Restore filters, filter_defaults, and interactivity from manifest
|
|
222
262
|
self._filters = manifest.get("filters", {})
|
|
263
|
+
self._filter_defaults = manifest.get("filter_defaults", {})
|
|
223
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", {}))
|
|
224
269
|
|
|
225
270
|
# Load preprocessed data files
|
|
226
271
|
data_files = manifest.get("data_files", {})
|
|
@@ -234,8 +279,26 @@ class BaseComponent(ABC):
|
|
|
234
279
|
for key, value in data_values.items():
|
|
235
280
|
self._preprocessed_data[key] = value
|
|
236
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
|
+
|
|
237
295
|
def _save_to_cache(self) -> None:
|
|
238
296
|
"""Save preprocessed data to cache."""
|
|
297
|
+
from ..preprocessing.filtering import (
|
|
298
|
+
optimize_for_transfer,
|
|
299
|
+
optimize_for_transfer_lazy,
|
|
300
|
+
)
|
|
301
|
+
|
|
239
302
|
# Create directories
|
|
240
303
|
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
|
241
304
|
preprocessed_dir = self._get_preprocessed_dir()
|
|
@@ -249,23 +312,30 @@ class BaseComponent(ABC):
|
|
|
249
312
|
"config_hash": self._compute_config_hash(),
|
|
250
313
|
"config": self._get_cache_config(),
|
|
251
314
|
"filters": self._filters,
|
|
315
|
+
"filter_defaults": self._filter_defaults,
|
|
252
316
|
"interactivity": self._interactivity,
|
|
253
317
|
"data_files": {},
|
|
254
318
|
"data_values": {},
|
|
255
319
|
}
|
|
256
320
|
|
|
257
|
-
# Save preprocessed data
|
|
321
|
+
# Save preprocessed data with type optimization for efficient transfer
|
|
322
|
+
# Float64→Float32 reduces Arrow payload size
|
|
323
|
+
# Int64→Int32 (when safe) avoids BigInt overhead in JavaScript
|
|
258
324
|
for key, value in self._preprocessed_data.items():
|
|
259
325
|
if isinstance(value, pl.LazyFrame):
|
|
260
326
|
filename = f"{key}.parquet"
|
|
261
327
|
filepath = preprocessed_dir / filename
|
|
262
|
-
#
|
|
263
|
-
|
|
328
|
+
# Apply streaming-safe optimization (Float64→Float32 only)
|
|
329
|
+
# Int64 bounds checking would require collect(), breaking streaming
|
|
330
|
+
value = optimize_for_transfer_lazy(value)
|
|
331
|
+
value.sink_parquet(filepath, compression="zstd")
|
|
264
332
|
manifest["data_files"][key] = filename
|
|
265
333
|
elif isinstance(value, pl.DataFrame):
|
|
266
334
|
filename = f"{key}.parquet"
|
|
267
335
|
filepath = preprocessed_dir / filename
|
|
268
|
-
|
|
336
|
+
# Full optimization including Int64→Int32 with bounds checking
|
|
337
|
+
value = optimize_for_transfer(value)
|
|
338
|
+
value.write_parquet(filepath, compression="zstd")
|
|
269
339
|
manifest["data_files"][key] = filename
|
|
270
340
|
elif self._is_json_serializable(value):
|
|
271
341
|
manifest["data_values"][key] = value
|
|
@@ -323,10 +393,7 @@ class BaseComponent(ABC):
|
|
|
323
393
|
pass
|
|
324
394
|
|
|
325
395
|
@abstractmethod
|
|
326
|
-
def _prepare_vue_data(
|
|
327
|
-
self,
|
|
328
|
-
state: Dict[str, Any]
|
|
329
|
-
) -> Dict[str, Any]:
|
|
396
|
+
def _prepare_vue_data(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
330
397
|
"""
|
|
331
398
|
Prepare data payload for Vue component.
|
|
332
399
|
|
|
@@ -395,8 +462,8 @@ class BaseComponent(ABC):
|
|
|
395
462
|
def __call__(
|
|
396
463
|
self,
|
|
397
464
|
key: Optional[str] = None,
|
|
398
|
-
state_manager: Optional[
|
|
399
|
-
height: Optional[int] = None
|
|
465
|
+
state_manager: Optional["StateManager"] = None,
|
|
466
|
+
height: Optional[int] = None,
|
|
400
467
|
) -> Any:
|
|
401
468
|
"""
|
|
402
469
|
Render the component in Streamlit.
|
|
@@ -410,17 +477,14 @@ class BaseComponent(ABC):
|
|
|
410
477
|
Returns:
|
|
411
478
|
The value returned by the Vue component (usually selection state)
|
|
412
479
|
"""
|
|
413
|
-
from .state import get_default_state_manager
|
|
414
480
|
from ..rendering.bridge import render_component
|
|
481
|
+
from .state import get_default_state_manager
|
|
415
482
|
|
|
416
483
|
if state_manager is None:
|
|
417
484
|
state_manager = get_default_state_manager()
|
|
418
485
|
|
|
419
486
|
return render_component(
|
|
420
|
-
component=self,
|
|
421
|
-
state_manager=state_manager,
|
|
422
|
-
key=key,
|
|
423
|
-
height=height
|
|
487
|
+
component=self, state_manager=state_manager, key=key, height=height
|
|
424
488
|
)
|
|
425
489
|
|
|
426
490
|
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
|
|