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.
@@ -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 compute_dataframe_hash, filter_and_collect_cached
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 = 'id',
60
+ index_field: str = "id",
61
61
  go_to_fields: Optional[List[str]] = None,
62
- layout: str = 'fitDataFill',
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
- 'column_definitions': self._column_definitions,
153
- 'index_field': self._index_field,
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
- 'field': name,
199
- 'title': name.replace('_', ' ').title(),
200
- 'headerTooltip': True,
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 (pl.Int8, pl.Int16, pl.Int32, pl.Int64,
204
- pl.UInt8, pl.UInt16, pl.UInt32, pl.UInt64,
205
- pl.Float32, pl.Float64):
206
- col_def['sorter'] = 'number'
207
- col_def['hozAlign'] = 'right'
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['sorter'] = 'boolean'
237
+ col_def["sorter"] = "boolean"
210
238
  elif dtype in (pl.Date, pl.Datetime, pl.Time):
211
- col_def['sorter'] = 'date'
239
+ col_def["sorter"] = "date"
212
240
  else:
213
- col_def['sorter'] = 'string'
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['column_definitions'] = self._column_definitions
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['data'] = data # Keep lazy
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['field']
258
+ col_def["field"]
231
259
  for col_def in self._column_definitions
232
- if 'field' in col_def
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 'TabulatorTable'
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 'tableData'
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('data')
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 {'tableData': df_pandas, '_hash': data_hash}
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('column_definitions', [])
333
+ column_defs = self._preprocessed_data.get("column_definitions", [])
306
334
 
307
335
  args: Dict[str, Any] = {
308
- 'componentType': self._get_vue_component_name(),
309
- 'columnDefinitions': column_defs,
310
- 'tableIndexField': self._index_field,
311
- 'tableLayoutParam': self._layout,
312
- 'defaultRow': self._default_row,
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
- 'interactivity': self._interactivity,
342
+ "interactivity": self._interactivity,
315
343
  # Pagination settings
316
- 'pagination': self._pagination,
317
- 'pageSize': self._page_size,
344
+ "pagination": self._pagination,
345
+ "pageSize": self._page_size,
318
346
  }
319
347
 
320
348
  if self._title:
321
- args['title'] = self._title
349
+ args["title"] = self._title
322
350
 
323
351
  if self._go_to_fields:
324
- args['goToFields'] = self._go_to_fields
352
+ args["goToFields"] = self._go_to_fields
325
353
 
326
354
  if self._initial_sort:
327
- args['initialSort'] = self._initial_sort
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
- ) -> 'Table':
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('field') == field:
353
- col_def['formatter'] = formatter
380
+ if col_def.get("field") == field:
381
+ col_def["formatter"] = formatter
354
382
  if formatter_params:
355
- col_def['formatterParams'] = formatter_params
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
- ) -> 'Table':
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
- 'money',
410
+ "money",
383
411
  {
384
- 'precision': precision,
385
- 'symbol': symbol,
386
- 'thousand': thousand,
387
- 'decimal': decimal,
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
- ) -> 'Table':
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] = {'min': min_val, 'max': max_val}
438
+ params: Dict[str, Any] = {"min": min_val, "max": max_val}
411
439
  if color:
412
- params['color'] = color
413
- return self.with_column_formatter(field, 'progress', params)
440
+ params["color"] = color
441
+ return self.with_column_formatter(field, "progress", params)
@@ -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",
@@ -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, TYPE_CHECKING
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
- CACHE_VERSION = 2
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. Optional if cache exists.
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
- # Check if we should load from cache or preprocess
93
- if regenerate_cache or not self._is_cache_valid():
94
- if data is None and data_path is None:
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
- f"Cache not found at '{self._cache_dir}' and no data provided. "
97
- f"Either provide data=, data_path=, or ensure cache exists."
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 (backward compatible)
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 _is_cache_valid(self) -> bool:
213
+ def _cache_exists(self) -> bool:
179
214
  """
180
- Check if cache is valid and can be loaded.
215
+ Check if a valid cache exists that can be loaded.
181
216
 
182
- Cache is valid when:
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
- 4. config_hash matches current config
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
- # Load filters and interactivity from manifest
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 - stream LazyFrames directly to disk
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
- # Stream directly to disk without full materialization
263
- value.sink_parquet(filepath, compression='zstd')
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
- value.write_parquet(filepath, compression='zstd')
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['StateManager'] = None,
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:
@@ -1,12 +1,12 @@
1
1
  """Component type registry for serialization and deserialization."""
2
2
 
3
- from typing import Dict, Type, TYPE_CHECKING
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['BaseComponent']] = {}
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
- def decorator(cls: Type['BaseComponent']) -> Type['BaseComponent']:
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['BaseComponent']:
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['BaseComponent']]:
63
+ def list_registered_components() -> Dict[str, Type["BaseComponent"]]:
63
64
  """
64
65
  Get all registered component types.
65
66