diffract-core 0.2.1__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.
Files changed (193) hide show
  1. diffract/__init__.py +52 -0
  2. diffract/configs/fast_speed_without_disk.ini +48 -0
  3. diffract/configs/hybrid.ini +74 -0
  4. diffract/configs/sqlite.ini +62 -0
  5. diffract/containers.py +405 -0
  6. diffract/core/__init__.py +23 -0
  7. diffract/core/cache/__init__.py +24 -0
  8. diffract/core/cache/containers.py +93 -0
  9. diffract/core/cache/interface.py +117 -0
  10. diffract/core/cache/redis_manager.py +464 -0
  11. diffract/core/cache/simple_manager.py +478 -0
  12. diffract/core/compute/__init__.py +46 -0
  13. diffract/core/compute/config.py +48 -0
  14. diffract/core/compute/containers.py +81 -0
  15. diffract/core/compute/decorator.py +125 -0
  16. diffract/core/compute/exceptions.py +31 -0
  17. diffract/core/compute/execution/__init__.py +14 -0
  18. diffract/core/compute/execution/_types.py +70 -0
  19. diffract/core/compute/execution/aggregation.py +74 -0
  20. diffract/core/compute/execution/aggregation_runner.py +362 -0
  21. diffract/core/compute/execution/enums.py +38 -0
  22. diffract/core/compute/execution/executor.py +125 -0
  23. diffract/core/compute/execution/parameter_runner.py +125 -0
  24. diffract/core/compute/execution/restrictions.py +61 -0
  25. diffract/core/compute/execution/strategy.py +118 -0
  26. diffract/core/compute/execution/utils.py +112 -0
  27. diffract/core/compute/extensions/__init__.py +0 -0
  28. diffract/core/compute/extensions/power_law.py +1051 -0
  29. diffract/core/compute/extensions/rmt.py +150 -0
  30. diffract/core/compute/extensions/utils.py +36 -0
  31. diffract/core/compute/field_signature.py +183 -0
  32. diffract/core/compute/kernels/__init__.py +48 -0
  33. diffract/core/compute/kernels/alignment.py +106 -0
  34. diffract/core/compute/kernels/heavy_tailed.py +394 -0
  35. diffract/core/compute/kernels/marchenko_pastur.py +99 -0
  36. diffract/core/compute/kernels/mat_decomposition.py +101 -0
  37. diffract/core/compute/kernels/mat_properties.py +53 -0
  38. diffract/core/compute/kernels/model_quality.py +27 -0
  39. diffract/core/compute/kernels/norms.py +88 -0
  40. diffract/core/compute/kernels/ranks.py +35 -0
  41. diffract/core/compute/kernels/tracy_widom.py +36 -0
  42. diffract/core/compute/metadata.py +67 -0
  43. diffract/core/compute/registry.py +485 -0
  44. diffract/core/constants.py +147 -0
  45. diffract/core/data/__init__.py +32 -0
  46. diffract/core/data/interface.py +304 -0
  47. diffract/core/data/metadata/__init__.py +15 -0
  48. diffract/core/data/metadata/containers.py +60 -0
  49. diffract/core/data/metadata/interface.py +208 -0
  50. diffract/core/data/metadata/sqlite_index.py +604 -0
  51. diffract/core/data/nn/__init__.py +43 -0
  52. diffract/core/data/nn/aggregates/__init__.py +35 -0
  53. diffract/core/data/nn/aggregates/metadata.py +96 -0
  54. diffract/core/data/nn/aggregates/proxy.py +57 -0
  55. diffract/core/data/nn/aggregates/repository.py +87 -0
  56. diffract/core/data/nn/aggregates/view.py +307 -0
  57. diffract/core/data/nn/containers.py +86 -0
  58. diffract/core/data/nn/extractors/__init__.py +49 -0
  59. diffract/core/data/nn/extractors/base.py +300 -0
  60. diffract/core/data/nn/extractors/factory.py +234 -0
  61. diffract/core/data/nn/extractors/flax.py +112 -0
  62. diffract/core/data/nn/extractors/handlers/__init__.py +39 -0
  63. diffract/core/data/nn/extractors/handlers/base.py +110 -0
  64. diffract/core/data/nn/extractors/handlers/flax_handlers.py +71 -0
  65. diffract/core/data/nn/extractors/handlers/numpy_handlers.py +63 -0
  66. diffract/core/data/nn/extractors/handlers/onnx_handlers.py +67 -0
  67. diffract/core/data/nn/extractors/handlers/tensorflow_handlers.py +82 -0
  68. diffract/core/data/nn/extractors/handlers/torch_handlers.py +124 -0
  69. diffract/core/data/nn/extractors/interface.py +56 -0
  70. diffract/core/data/nn/extractors/numpy.py +94 -0
  71. diffract/core/data/nn/extractors/onnx.py +108 -0
  72. diffract/core/data/nn/extractors/tensorflow.py +99 -0
  73. diffract/core/data/nn/extractors/torch.py +204 -0
  74. diffract/core/data/nn/params/__init__.py +27 -0
  75. diffract/core/data/nn/params/interface.py +402 -0
  76. diffract/core/data/nn/params/metadata.py +110 -0
  77. diffract/core/data/nn/params/proxy.py +93 -0
  78. diffract/core/data/nn/params/repository.py +45 -0
  79. diffract/core/data/nn/params/schema.py +82 -0
  80. diffract/core/data/nn/params/view.py +393 -0
  81. diffract/core/data/proxy.py +246 -0
  82. diffract/core/data/repository.py +227 -0
  83. diffract/core/data/utils.py +33 -0
  84. diffract/core/data/view.py +389 -0
  85. diffract/core/export/__init__.py +27 -0
  86. diffract/core/export/containers.py +47 -0
  87. diffract/core/export/exporters.py +191 -0
  88. diffract/core/export/formatters/__init__.py +23 -0
  89. diffract/core/export/formatters/base.py +68 -0
  90. diffract/core/export/formatters/dict_formatter.py +38 -0
  91. diffract/core/export/formatters/json_formatter.py +58 -0
  92. diffract/core/export/formatters/list_formatter.py +41 -0
  93. diffract/core/export/formatters/pandas_formatter.py +86 -0
  94. diffract/core/export/formatters/polars_formatter.py +86 -0
  95. diffract/core/export/formatters/registry.py +84 -0
  96. diffract/core/export/interface.py +105 -0
  97. diffract/core/parallel/__init__.py +21 -0
  98. diffract/core/parallel/containers.py +57 -0
  99. diffract/core/parallel/execution.py +91 -0
  100. diffract/core/parallel/runtime.py +91 -0
  101. diffract/core/storage/__init__.py +35 -0
  102. diffract/core/storage/base_manager.py +330 -0
  103. diffract/core/storage/containers.py +122 -0
  104. diffract/core/storage/hdf5_manager.py +856 -0
  105. diffract/core/storage/hybrid_manager.py +218 -0
  106. diffract/core/storage/interface.py +222 -0
  107. diffract/core/storage/metadata.py +89 -0
  108. diffract/core/storage/ram_manager.py +159 -0
  109. diffract/core/storage/sqlite_manager.py +1062 -0
  110. diffract/core/storage/zarr_manager.py +992 -0
  111. diffract/core/utils/__init__.py +45 -0
  112. diffract/core/utils/build.py +46 -0
  113. diffract/core/utils/exceptions.py +11 -0
  114. diffract/core/utils/hashing.py +90 -0
  115. diffract/core/utils/imports.py +292 -0
  116. diffract/core/utils/math.py +15 -0
  117. diffract/py.typed +0 -0
  118. diffract/session/__init__.py +24 -0
  119. diffract/session/errors.py +17 -0
  120. diffract/session/field_cache.py +216 -0
  121. diffract/session/namespaces/__init__.py +15 -0
  122. diffract/session/namespaces/compute/__init__.py +219 -0
  123. diffract/session/namespaces/models/__init__.py +282 -0
  124. diffract/session/namespaces/models/parameters/__init__.py +133 -0
  125. diffract/session/namespaces/models/parameters/meta_patcher.py +167 -0
  126. diffract/session/namespaces/results/__init__.py +378 -0
  127. diffract/session/namespaces/results/eraser.py +129 -0
  128. diffract/session/namespaces/results/injester.py +249 -0
  129. diffract/session/namespaces/utils/__init__.py +141 -0
  130. diffract/session/namespaces/utils/merger.py +295 -0
  131. diffract/session/namespaces/viz/__init__.py +91 -0
  132. diffract/session/namespaces/viz/_utils.py +14 -0
  133. diffract/session/namespaces/viz/box.py +207 -0
  134. diffract/session/namespaces/viz/grid.py +160 -0
  135. diffract/session/namespaces/viz/heatmap.py +164 -0
  136. diffract/session/namespaces/viz/scatter.py +202 -0
  137. diffract/session/namespaces/viz/sparkline.py +270 -0
  138. diffract/session/namespaces/viz/violin.py +212 -0
  139. diffract/session/session.py +260 -0
  140. diffract/session/utils.py +81 -0
  141. diffract/viz/__init__.py +42 -0
  142. diffract/viz/data/__init__.py +34 -0
  143. diffract/viz/data/detection.py +57 -0
  144. diffract/viz/data/extraction.py +117 -0
  145. diffract/viz/data/filtering.py +57 -0
  146. diffract/viz/data/ordering.py +129 -0
  147. diffract/viz/data/provider.py +99 -0
  148. diffract/viz/data/types.py +98 -0
  149. diffract/viz/plots/__init__.py +37 -0
  150. diffract/viz/plots/base/__init__.py +20 -0
  151. diffract/viz/plots/base/axis.py +219 -0
  152. diffract/viz/plots/base/coloraxis.py +133 -0
  153. diffract/viz/plots/base/configurator.py +18 -0
  154. diffract/viz/plots/base/jitter.py +368 -0
  155. diffract/viz/plots/base/line.py +197 -0
  156. diffract/viz/plots/base/marker.py +278 -0
  157. diffract/viz/plots/base/overlay.py +14 -0
  158. diffract/viz/plots/base/plot.py +110 -0
  159. diffract/viz/plots/base/update.py +50 -0
  160. diffract/viz/plots/boxplot.py +283 -0
  161. diffract/viz/plots/cluster.py +374 -0
  162. diffract/viz/plots/heatmap.py +168 -0
  163. diffract/viz/plots/scatter.py +233 -0
  164. diffract/viz/plots/sparkline.py +405 -0
  165. diffract/viz/plots/subplots/__init__.py +32 -0
  166. diffract/viz/plots/subplots/coloraxis.py +339 -0
  167. diffract/viz/plots/subplots/factory.py +713 -0
  168. diffract/viz/plots/subplots/grid.py +214 -0
  169. diffract/viz/plots/subplots/layout.py +370 -0
  170. diffract/viz/plots/subplots/spec.py +39 -0
  171. diffract/viz/plots/violin.py +298 -0
  172. diffract/viz/renderer.py +513 -0
  173. diffract/viz/styling/__init__.py +78 -0
  174. diffract/viz/styling/palettes/__init__.py +20 -0
  175. diffract/viz/styling/palettes/bundle.py +16 -0
  176. diffract/viz/styling/palettes/color.py +83 -0
  177. diffract/viz/styling/palettes/dashes.py +27 -0
  178. diffract/viz/styling/palettes/symbols.py +42 -0
  179. diffract/viz/styling/resolvers/__init__.py +11 -0
  180. diffract/viz/styling/resolvers/categorical.py +68 -0
  181. diffract/viz/styling/resolvers/color.py +77 -0
  182. diffract/viz/styling/resolvers/numeric.py +108 -0
  183. diffract/viz/styling/sources.py +27 -0
  184. diffract/viz/styling/theme/__init__.py +29 -0
  185. diffract/viz/styling/theme/applier.py +114 -0
  186. diffract/viz/styling/theme/components.py +66 -0
  187. diffract/viz/styling/theme/presets.py +58 -0
  188. diffract/viz/styling/theme/theme.py +36 -0
  189. diffract_core-0.2.1.dist-info/METADATA +530 -0
  190. diffract_core-0.2.1.dist-info/RECORD +193 -0
  191. diffract_core-0.2.1.dist-info/WHEEL +4 -0
  192. diffract_core-0.2.1.dist-info/licenses/LICENSE +201 -0
  193. diffract_core-0.2.1.dist-info/licenses/NOTICE +2 -0
@@ -0,0 +1,604 @@
1
+ """SQLite-based metadata index implementation.
2
+
3
+ This module provides a SQLite implementation of the metadata index
4
+ with optimized querying and large-filter handling.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ import sqlite3
12
+ import threading
13
+ from contextlib import closing, suppress
14
+ from pathlib import Path
15
+ from typing import TYPE_CHECKING, Any, Self
16
+
17
+ if TYPE_CHECKING:
18
+ import types
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # Mapping from Python types to SQLite types
23
+ _TYPE_MAP: dict[type, str] = {
24
+ str: "TEXT",
25
+ int: "INTEGER",
26
+ float: "REAL",
27
+ bool: "INTEGER",
28
+ }
29
+
30
+ _DEFAULT_MAX_SQL_VARIABLES = 999
31
+ _SQL_VARIABLE_SAFETY_MARGIN = 32
32
+ _DEFAULT_IN_TEMP_TABLE_THRESHOLD = 512
33
+
34
+
35
+ class SQLiteMetadataIndex:
36
+ """SQLite-based metadata index with optimized querying.
37
+
38
+ Provides structured metadata storage with SQL-based filtering.
39
+ Uses a single connection with serialized writes and supports
40
+ batch operations through context manager.
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ path: str,
46
+ *,
47
+ timeout: float = 5.0,
48
+ cache_size_kb: int = 32000,
49
+ **kwargs: Any,
50
+ ) -> None:
51
+ """Initialize SQLite metadata index.
52
+
53
+ Args:
54
+ path: Path to SQLite database file.
55
+ timeout: Database operation timeout in seconds.
56
+ cache_size_kb: Cache size in kilobytes.
57
+ **kwargs: Optional tuning args. Supports:
58
+ - in_temp_table_threshold: switch to temp-table IN strategy
59
+ when value list reaches this size.
60
+ """
61
+ self._path = path
62
+ self._timeout = timeout
63
+ self._cache_size_kb = cache_size_kb
64
+ self._conn: sqlite3.Connection | None = None
65
+ self._lock = threading.RLock()
66
+ self._context_depth = 0
67
+ self._tables: dict[str, dict[str, type]] = {}
68
+ self._max_sql_variables = _DEFAULT_MAX_SQL_VARIABLES
69
+ self._temp_table_counter = 0
70
+
71
+ raw_threshold = kwargs.get(
72
+ "in_temp_table_threshold", _DEFAULT_IN_TEMP_TABLE_THRESHOLD
73
+ )
74
+ try:
75
+ threshold = int(raw_threshold)
76
+ except (TypeError, ValueError):
77
+ threshold = _DEFAULT_IN_TEMP_TABLE_THRESHOLD
78
+ self._in_temp_table_threshold = max(1, threshold)
79
+
80
+ # Ensure parent directory exists (skip for in-memory databases)
81
+ if ":memory:" not in path:
82
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
83
+ self._close_in_destructor_only = False
84
+ else:
85
+ self._close_in_destructor_only = True
86
+ self._in_destructor = False
87
+
88
+ def connect(self) -> None:
89
+ """Connect to database and apply pragmas."""
90
+ if self._conn is not None:
91
+ return
92
+
93
+ self._conn = sqlite3.connect(
94
+ self._path if ":memory:" not in self._path else ":memory:",
95
+ check_same_thread=False,
96
+ timeout=self._timeout,
97
+ isolation_level=None,
98
+ )
99
+
100
+ with closing(self._conn.cursor()) as cur:
101
+ cur.execute("PRAGMA journal_mode=WAL")
102
+ cur.execute("PRAGMA synchronous=NORMAL")
103
+ cur.execute(f"PRAGMA cache_size=-{self._cache_size_kb}")
104
+ cur.execute("PRAGMA temp_store=MEMORY")
105
+ self._max_sql_variables = self._detect_max_sql_variables()
106
+
107
+ def close(self) -> None:
108
+ """Close database connection."""
109
+ if self._close_in_destructor_only and not self._in_destructor:
110
+ return
111
+
112
+ if self._conn is not None:
113
+ with suppress(sqlite3.Error):
114
+ self._conn.close()
115
+ self._conn = None
116
+
117
+ def __enter__(self) -> Self:
118
+ """Enter batch operation context."""
119
+ self._lock.acquire()
120
+ if self._context_depth == 0:
121
+ self._ensure_connected()
122
+ self._conn.execute("BEGIN IMMEDIATE")
123
+ self._context_depth += 1
124
+ return self
125
+
126
+ def __exit__(
127
+ self,
128
+ exc_type: type[BaseException] | None,
129
+ exc_val: BaseException | None,
130
+ exc_tb: types.TracebackType | None,
131
+ ) -> None:
132
+ """Exit batch context and commit/rollback."""
133
+ try:
134
+ self._context_depth -= 1
135
+ if self._context_depth == 0:
136
+ if exc_type is None:
137
+ self._conn.execute("COMMIT")
138
+ else:
139
+ self._conn.execute("ROLLBACK")
140
+ finally:
141
+ self._lock.release()
142
+
143
+ def _ensure_connected(self) -> None:
144
+ """Ensure database connection is established."""
145
+ if self._conn is None:
146
+ self.connect()
147
+
148
+ def _detect_max_sql_variables(self) -> int:
149
+ """Detect SQLite's max number of bound variables."""
150
+ self._ensure_connected()
151
+ try:
152
+ with closing(self._conn.cursor()) as cur:
153
+ options = cur.execute("PRAGMA compile_options").fetchall()
154
+ except sqlite3.Error:
155
+ return _DEFAULT_MAX_SQL_VARIABLES
156
+
157
+ prefix = "MAX_VARIABLE_NUMBER="
158
+ for (option,) in options:
159
+ if not isinstance(option, str) or not option.startswith(prefix):
160
+ continue
161
+ with suppress(ValueError):
162
+ return max(_DEFAULT_MAX_SQL_VARIABLES, int(option[len(prefix) :]))
163
+
164
+ return _DEFAULT_MAX_SQL_VARIABLES
165
+
166
+ def _inline_param_limit(self, *, existing_params: int = 0) -> int:
167
+ """Return safe max number of inline SQL variables."""
168
+ return max(
169
+ 1,
170
+ self._max_sql_variables - _SQL_VARIABLE_SAFETY_MARGIN - existing_params,
171
+ )
172
+
173
+ def _can_inline_values(
174
+ self,
175
+ value_count: int,
176
+ *,
177
+ existing_params: int = 0,
178
+ ) -> bool:
179
+ """Return True when values can be safely inlined in an IN clause."""
180
+ if value_count <= 0:
181
+ return True
182
+ if value_count >= self._in_temp_table_threshold:
183
+ return False
184
+ return value_count <= self._inline_param_limit(existing_params=existing_params)
185
+
186
+ def _next_temp_table_name(self, prefix: str) -> str:
187
+ """Generate a unique temp table name."""
188
+ safe_prefix = "".join(ch if ch.isalnum() else "_" for ch in prefix)
189
+ self._temp_table_counter += 1
190
+ return f"tmp_{safe_prefix}_{threading.get_ident()}_{self._temp_table_counter}"
191
+
192
+ def _deduplicate_values(self, values: list[Any]) -> list[Any]:
193
+ """Best-effort deduplication preserving order."""
194
+ try:
195
+ return list(dict.fromkeys(values))
196
+ except TypeError:
197
+ return values
198
+
199
+ def _create_temp_values_table(
200
+ self,
201
+ cur: sqlite3.Cursor,
202
+ values: list[Any],
203
+ *,
204
+ prefix: str,
205
+ ) -> str:
206
+ """Create and populate a temp table with a single value column."""
207
+ table_name = self._next_temp_table_name(prefix)
208
+ index_name = f"{table_name}_value_idx"
209
+ cur.execute(f"CREATE TEMP TABLE {table_name} (value)")
210
+ deduplicated = self._deduplicate_values(values)
211
+ cur.executemany(
212
+ f"INSERT INTO {table_name} (value) VALUES (?)",
213
+ ((value,) for value in deduplicated),
214
+ )
215
+ cur.execute(f"CREATE INDEX {index_name} ON {table_name}(value)")
216
+ return table_name
217
+
218
+ def _create_temp_sequence_table(
219
+ self,
220
+ cur: sqlite3.Cursor,
221
+ values: list[Any],
222
+ *,
223
+ prefix: str,
224
+ ) -> str:
225
+ """Create and populate a temp table preserving input order."""
226
+ table_name = self._next_temp_table_name(prefix)
227
+ index_name = f"{table_name}_value_idx"
228
+ cur.execute(f"CREATE TEMP TABLE {table_name} (seq INTEGER PRIMARY KEY, value)")
229
+ cur.executemany(
230
+ f"INSERT INTO {table_name} (seq, value) VALUES (?, ?)",
231
+ ((i, value) for i, value in enumerate(values)),
232
+ )
233
+ cur.execute(f"CREATE INDEX {index_name} ON {table_name}(value)")
234
+ return table_name
235
+
236
+ def _drop_temp_table(self, cur: sqlite3.Cursor, table_name: str) -> None:
237
+ """Drop a temp table, suppressing cleanup errors."""
238
+ with suppress(sqlite3.Error):
239
+ cur.execute(f"DROP TABLE IF EXISTS {table_name}")
240
+
241
+ def _table_exists(self, table: str) -> bool:
242
+ """Check if table exists."""
243
+ self._ensure_connected()
244
+ with closing(self._conn.cursor()) as cur:
245
+ cur.execute(
246
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?",
247
+ (table,),
248
+ )
249
+ return cur.fetchone() is not None
250
+
251
+ def define_table(
252
+ self,
253
+ table: str,
254
+ columns: dict[str, type],
255
+ indexes: list[str] | None = None,
256
+ ) -> None:
257
+ """Define a table schema with typed columns."""
258
+ self._ensure_connected()
259
+
260
+ # Build column definitions
261
+ col_defs = ["uid TEXT PRIMARY KEY"]
262
+ for col_name, col_type in columns.items():
263
+ sql_type = _TYPE_MAP.get(col_type, "TEXT")
264
+ col_defs.append(f"{col_name} {sql_type}")
265
+
266
+ # Add json_data column for extensibility
267
+ col_defs.append("json_data TEXT")
268
+
269
+ create_sql = f"CREATE TABLE IF NOT EXISTS {table} ({', '.join(col_defs)})"
270
+
271
+ with self._lock, closing(self._conn.cursor()) as cur:
272
+ cur.execute(create_sql)
273
+
274
+ # Create indexes
275
+ if indexes:
276
+ for col in indexes:
277
+ idx_name = f"idx_{table}_{col}_uid"
278
+ cur.execute(
279
+ f"CREATE INDEX IF NOT EXISTS {idx_name} ON {table}({col}, uid)"
280
+ )
281
+
282
+ self._tables[table] = columns
283
+
284
+ def insert(self, table: str, uid: str, **fields: Any) -> None:
285
+ """Insert a new record."""
286
+ self._ensure_connected()
287
+
288
+ columns = self._tables.get(table, {})
289
+ col_names = ["uid"]
290
+ col_values: list[Any] = [uid]
291
+ json_extra: dict[str, Any] = {}
292
+
293
+ for key, value in fields.items():
294
+ if key in columns:
295
+ col_names.append(key)
296
+ col_values.append(self._serialize_value(value))
297
+ else:
298
+ json_extra[key] = value
299
+
300
+ col_names.append("json_data")
301
+ col_values.append(json.dumps(json_extra) if json_extra else None)
302
+
303
+ placeholders = ", ".join("?" * len(col_names))
304
+ sql = f"INSERT INTO {table} ({', '.join(col_names)}) VALUES ({placeholders})"
305
+
306
+ with self._lock, closing(self._conn.cursor()) as cur:
307
+ cur.execute(sql, col_values)
308
+
309
+ def update(self, table: str, uid: str, **fields: Any) -> None:
310
+ """Update an existing record."""
311
+ self._ensure_connected()
312
+
313
+ columns = self._tables.get(table, {})
314
+ set_parts: list[str] = []
315
+ values: list[Any] = []
316
+
317
+ for key, value in fields.items():
318
+ if key in columns:
319
+ set_parts.append(f"{key} = ?")
320
+ values.append(self._serialize_value(value))
321
+
322
+ if not set_parts:
323
+ return
324
+
325
+ values.append(uid)
326
+ sql = f"UPDATE {table} SET {', '.join(set_parts)} WHERE uid = ?"
327
+
328
+ with self._lock, closing(self._conn.cursor()) as cur:
329
+ cur.execute(sql, values)
330
+
331
+ def upsert(self, table: str, uid: str, **fields: Any) -> None:
332
+ """Insert or update a record."""
333
+ self._ensure_connected()
334
+
335
+ columns = self._tables.get(table, {})
336
+ col_names = ["uid"]
337
+ col_values: list[Any] = [uid]
338
+ json_extra: dict[str, Any] = {}
339
+
340
+ for key, value in fields.items():
341
+ if key in columns:
342
+ col_names.append(key)
343
+ col_values.append(self._serialize_value(value))
344
+ else:
345
+ json_extra[key] = value
346
+
347
+ col_names.append("json_data")
348
+ col_values.append(json.dumps(json_extra) if json_extra else None)
349
+
350
+ placeholders = ", ".join("?" * len(col_names))
351
+ sql = (
352
+ f"INSERT OR REPLACE INTO {table} "
353
+ f"({', '.join(col_names)}) VALUES ({placeholders})"
354
+ )
355
+
356
+ with self._lock, closing(self._conn.cursor()) as cur:
357
+ cur.execute(sql, col_values)
358
+
359
+ def get(self, table: str, uid: str) -> dict[str, Any] | None:
360
+ """Get a single record by uid."""
361
+ self._ensure_connected()
362
+
363
+ with closing(self._conn.cursor()) as cur:
364
+ cur.execute(f"SELECT * FROM {table} WHERE uid = ?", (uid,))
365
+ row = cur.fetchone()
366
+ if row is None:
367
+ return None
368
+ return self._row_to_dict(cur.description, row)
369
+
370
+ def get_batch(self, table: str, uids: list[str]) -> list[dict[str, Any] | None]:
371
+ """Get multiple records by uids."""
372
+ if not uids:
373
+ return []
374
+
375
+ self._ensure_connected()
376
+
377
+ with self._lock, closing(self._conn.cursor()) as cur:
378
+ if self._can_inline_values(len(uids)):
379
+ placeholders = ", ".join("?" * len(uids))
380
+ sql = f"SELECT * FROM {table} WHERE uid IN ({placeholders})"
381
+ cur.execute(sql, uids)
382
+ rows = cur.fetchall()
383
+
384
+ # Build uid -> row mapping
385
+ uid_to_row: dict[str, dict[str, Any]] = {}
386
+ for row in rows:
387
+ row_dict = self._row_to_dict(cur.description, row)
388
+ uid_to_row[row_dict["uid"]] = row_dict
389
+
390
+ # Return in same order as input uids
391
+ return [uid_to_row.get(uid) for uid in uids]
392
+
393
+ tmp_table = self._create_temp_sequence_table(
394
+ cur, uids, prefix=f"{table}_get_batch"
395
+ )
396
+ try:
397
+ cur.execute(
398
+ f"SELECT f.seq, t.* FROM {tmp_table} AS f "
399
+ f"LEFT JOIN {table} AS t ON t.uid = f.value "
400
+ "ORDER BY f.seq"
401
+ )
402
+ rows = cur.fetchall()
403
+ description = cur.description
404
+
405
+ results: list[dict[str, Any] | None] = [None] * len(uids)
406
+ for row in rows:
407
+ seq = int(row[0])
408
+ if row[1] is None:
409
+ continue
410
+ results[seq] = self._row_to_dict(description[1:], row[1:])
411
+
412
+ return results
413
+ finally:
414
+ self._drop_temp_table(cur, tmp_table)
415
+
416
+ def query(
417
+ self,
418
+ table: str,
419
+ where: dict[str, Any] | None = None,
420
+ where_in: dict[str, list[Any]] | None = None,
421
+ where_like: dict[str, str] | None = None,
422
+ order_by: list[str] | None = None,
423
+ limit: int | None = None,
424
+ ) -> list[str]:
425
+ """Query UIDs matching criteria."""
426
+ self._ensure_connected()
427
+
428
+ conditions: list[str] = []
429
+ params: list[Any] = []
430
+ temp_where_in: dict[str, list[Any]] = {}
431
+ table_alias = "base"
432
+
433
+ if where:
434
+ for col, val in where.items():
435
+ conditions.append(f"{table_alias}.{col} = ?")
436
+ params.append(self._serialize_value(val))
437
+
438
+ if where_like:
439
+ for col, pattern in where_like.items():
440
+ conditions.append(f"{table_alias}.{col} LIKE ?")
441
+ params.append(pattern)
442
+
443
+ if where_in:
444
+ for col, raw_values in where_in.items():
445
+ if len(raw_values) == 0:
446
+ # SQL semantics: x IN () should always produce no rows.
447
+ return []
448
+
449
+ values = [self._serialize_value(v) for v in raw_values]
450
+ if self._can_inline_values(len(values), existing_params=len(params)):
451
+ placeholders = ", ".join("?" * len(values))
452
+ conditions.append(f"{table_alias}.{col} IN ({placeholders})")
453
+ params.extend(values)
454
+ else:
455
+ temp_where_in[col] = values
456
+
457
+ with self._lock, closing(self._conn.cursor()) as cur:
458
+ temp_tables: dict[str, str] = {}
459
+ try:
460
+ for col, values in temp_where_in.items():
461
+ temp_tables[col] = self._create_temp_values_table(
462
+ cur, values, prefix=f"{table}_{col}_in"
463
+ )
464
+
465
+ from_clause = f"{table} AS {table_alias}"
466
+ uid_temp = temp_tables.get("uid")
467
+ if uid_temp is not None:
468
+ from_clause += (
469
+ f" JOIN {uid_temp} AS uid_filter "
470
+ f"ON uid_filter.value = {table_alias}.uid"
471
+ )
472
+
473
+ for col, tmp_table in temp_tables.items():
474
+ if col == "uid":
475
+ continue
476
+ conditions.append(
477
+ f"{table_alias}.{col} IN (SELECT value FROM {tmp_table})"
478
+ )
479
+
480
+ sql = f"SELECT {table_alias}.uid FROM {from_clause}"
481
+ if conditions:
482
+ sql += f" WHERE {' AND '.join(conditions)}"
483
+ if order_by:
484
+ sql += f" ORDER BY {', '.join(order_by)}"
485
+ if limit is not None:
486
+ sql += f" LIMIT {limit}"
487
+
488
+ cur.execute(sql, params)
489
+ return [row[0] for row in cur.fetchall()]
490
+ finally:
491
+ for tmp_table in temp_tables.values():
492
+ self._drop_temp_table(cur, tmp_table)
493
+
494
+ def delete(self, table: str, uid: str) -> None:
495
+ """Delete a record by uid."""
496
+ self._ensure_connected()
497
+
498
+ with self._lock, closing(self._conn.cursor()) as cur:
499
+ cur.execute(f"DELETE FROM {table} WHERE uid = ?", (uid,))
500
+
501
+ def delete_batch(self, table: str, uids: list[str]) -> None:
502
+ """Delete multiple records by uids."""
503
+ if not uids:
504
+ return
505
+
506
+ self._ensure_connected()
507
+
508
+ with self._lock, closing(self._conn.cursor()) as cur:
509
+ if self._can_inline_values(len(uids)):
510
+ placeholders = ", ".join("?" * len(uids))
511
+ sql = f"DELETE FROM {table} WHERE uid IN ({placeholders})"
512
+ cur.execute(sql, uids)
513
+ return
514
+
515
+ serialized = [self._serialize_value(uid) for uid in uids]
516
+ tmp_table = self._create_temp_values_table(
517
+ cur, serialized, prefix=f"{table}_delete_batch"
518
+ )
519
+ try:
520
+ cur.execute(
521
+ f"DELETE FROM {table} WHERE uid IN (SELECT value FROM {tmp_table})"
522
+ )
523
+ finally:
524
+ self._drop_temp_table(cur, tmp_table)
525
+
526
+ def count(self, table: str, where: dict[str, Any] | None = None) -> int:
527
+ """Count records in table."""
528
+ self._ensure_connected()
529
+
530
+ conditions: list[str] = []
531
+ params: list[Any] = []
532
+
533
+ if where:
534
+ for col, val in where.items():
535
+ conditions.append(f"{col} = ?")
536
+ params.append(self._serialize_value(val))
537
+
538
+ sql = f"SELECT COUNT(*) FROM {table}"
539
+ if conditions:
540
+ sql += f" WHERE {' AND '.join(conditions)}"
541
+
542
+ with closing(self._conn.cursor()) as cur:
543
+ cur.execute(sql, params)
544
+ return cur.fetchone()[0]
545
+
546
+ def distinct(self, table: str, column: str) -> list[Any]:
547
+ """Get distinct values for a column."""
548
+ self._ensure_connected()
549
+
550
+ with closing(self._conn.cursor()) as cur:
551
+ cur.execute(f"SELECT DISTINCT {column} FROM {table}")
552
+ return [row[0] for row in cur.fetchall()]
553
+
554
+ def list_uids(self, table: str) -> list[str]:
555
+ """List all UIDs in a table."""
556
+ self._ensure_connected()
557
+
558
+ with closing(self._conn.cursor()) as cur:
559
+ cur.execute(f"SELECT uid FROM {table}")
560
+ return [row[0] for row in cur.fetchall()]
561
+
562
+ def clear(self, table: str | None = None) -> None:
563
+ """Clear data from index."""
564
+ self._ensure_connected()
565
+
566
+ with self._lock, closing(self._conn.cursor()) as cur:
567
+ if table is None:
568
+ # Get all tables and delete from each
569
+ cur.execute("SELECT name FROM sqlite_master WHERE type='table'")
570
+ tables = [row[0] for row in cur.fetchall()]
571
+ for t in tables:
572
+ if not t.startswith("sqlite_"):
573
+ cur.execute(f"DELETE FROM {t}")
574
+ else:
575
+ cur.execute(f"DELETE FROM {table}")
576
+
577
+ def _serialize_value(self, value: Any) -> Any:
578
+ """Serialize a value for storage."""
579
+ if isinstance(value, bool):
580
+ return 1 if value else 0
581
+ return value
582
+
583
+ def _row_to_dict(
584
+ self, description: tuple[tuple[str, ...], ...], row: tuple[Any, ...]
585
+ ) -> dict[str, Any]:
586
+ """Convert a database row to dictionary."""
587
+ result: dict[str, Any] = {}
588
+ for i, col_info in enumerate(description):
589
+ col_name = col_info[0]
590
+ value = row[i]
591
+
592
+ if col_name == "json_data" and value:
593
+ # Merge json_data into result
594
+ result.update(json.loads(value))
595
+ elif col_name != "json_data":
596
+ result[col_name] = value
597
+
598
+ return result
599
+
600
+ def __del__(self) -> None:
601
+ """Cleanup on deletion."""
602
+ self._in_destructor = True
603
+ with suppress(Exception):
604
+ self.close()
@@ -0,0 +1,43 @@
1
+ """Model parameter extraction and management module.
2
+
3
+ This module provides comprehensive tools for extracting, organizing, and managing
4
+ neural network model parameters across different deep learning frameworks.
5
+ It supports efficient parameter storage, lazy loading, and flexible filtering
6
+ capabilities for large-scale model analysis.
7
+
8
+ Features:
9
+ - Framework-agnostic parameter extraction
10
+ - Hierarchical parameter organization with metadata
11
+ - Lazy loading and intelligent caching for memory efficiency
12
+ - Rich filtering by name, type, model ID, and fields
13
+ - Batch operations for parameter prefetching and field management
14
+ - Persistent storage with compression and deduplication
15
+
16
+ Core Components:
17
+ - ParameterDataProxy: Individual parameter with lazy loading
18
+ - ParameterCollection: Mutable collection with filtering capabilities
19
+ - ParameterMetadata: Parameter metadata and type information
20
+ - IParameterView: Protocol for parameter view implementations
21
+
22
+ Example:
23
+ >>> # Prefer working via Session which owns the parameter repository.
24
+ >>> # Views (IParameterView) are the primary interface for batch operations.
25
+ """
26
+
27
+ from .containers import ModelParametersContainer
28
+ from .params import (
29
+ IParameterView,
30
+ ParameterDataProxy,
31
+ ParameterMetadata,
32
+ ParameterType,
33
+ ParameterView,
34
+ )
35
+
36
+ __all__ = [
37
+ "IParameterView",
38
+ "ModelParametersContainer",
39
+ "ParameterDataProxy",
40
+ "ParameterMetadata",
41
+ "ParameterType",
42
+ "ParameterView",
43
+ ]
@@ -0,0 +1,35 @@
1
+ """Aggregates module for managing aggregated data between models/parameters.
2
+
3
+ This module provides infrastructure for storing and managing aggregate data
4
+ (computed aggregations between parameters/models) separately from parameters.
5
+
6
+ Key Components:
7
+ - AggregateMetadata: Metadata for aggregate entities
8
+ - AggregateProxy: Proxy for accessing aggregate data
9
+ - AggregateView: View for batch operations on aggregates
10
+ - AggregateRepository: Repository managing aggregate storage
11
+
12
+ Example:
13
+ >>> metadata = AggregateMetadata(
14
+ ... field_name="l_overlap",
15
+ ... context_models=("model_a", "model_b"),
16
+ ... context_params=("layer.weight", "layer.weight"),
17
+ ... )
18
+ >>> proxy = AggregateProxy.create_and_store(
19
+ ... meta=metadata,
20
+ ... repository=repository,
21
+ ... )
22
+ >>> proxy.set_field("value", computed_overlap)
23
+ """
24
+
25
+ from .metadata import AggregateMetadata
26
+ from .proxy import AggregateProxy
27
+ from .repository import AggregateRepository
28
+ from .view import AggregateView
29
+
30
+ __all__ = [
31
+ "AggregateMetadata",
32
+ "AggregateProxy",
33
+ "AggregateRepository",
34
+ "AggregateView",
35
+ ]