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.
- diffract/__init__.py +52 -0
- diffract/configs/fast_speed_without_disk.ini +48 -0
- diffract/configs/hybrid.ini +74 -0
- diffract/configs/sqlite.ini +62 -0
- diffract/containers.py +405 -0
- diffract/core/__init__.py +23 -0
- diffract/core/cache/__init__.py +24 -0
- diffract/core/cache/containers.py +93 -0
- diffract/core/cache/interface.py +117 -0
- diffract/core/cache/redis_manager.py +464 -0
- diffract/core/cache/simple_manager.py +478 -0
- diffract/core/compute/__init__.py +46 -0
- diffract/core/compute/config.py +48 -0
- diffract/core/compute/containers.py +81 -0
- diffract/core/compute/decorator.py +125 -0
- diffract/core/compute/exceptions.py +31 -0
- diffract/core/compute/execution/__init__.py +14 -0
- diffract/core/compute/execution/_types.py +70 -0
- diffract/core/compute/execution/aggregation.py +74 -0
- diffract/core/compute/execution/aggregation_runner.py +362 -0
- diffract/core/compute/execution/enums.py +38 -0
- diffract/core/compute/execution/executor.py +125 -0
- diffract/core/compute/execution/parameter_runner.py +125 -0
- diffract/core/compute/execution/restrictions.py +61 -0
- diffract/core/compute/execution/strategy.py +118 -0
- diffract/core/compute/execution/utils.py +112 -0
- diffract/core/compute/extensions/__init__.py +0 -0
- diffract/core/compute/extensions/power_law.py +1051 -0
- diffract/core/compute/extensions/rmt.py +150 -0
- diffract/core/compute/extensions/utils.py +36 -0
- diffract/core/compute/field_signature.py +183 -0
- diffract/core/compute/kernels/__init__.py +48 -0
- diffract/core/compute/kernels/alignment.py +106 -0
- diffract/core/compute/kernels/heavy_tailed.py +394 -0
- diffract/core/compute/kernels/marchenko_pastur.py +99 -0
- diffract/core/compute/kernels/mat_decomposition.py +101 -0
- diffract/core/compute/kernels/mat_properties.py +53 -0
- diffract/core/compute/kernels/model_quality.py +27 -0
- diffract/core/compute/kernels/norms.py +88 -0
- diffract/core/compute/kernels/ranks.py +35 -0
- diffract/core/compute/kernels/tracy_widom.py +36 -0
- diffract/core/compute/metadata.py +67 -0
- diffract/core/compute/registry.py +485 -0
- diffract/core/constants.py +147 -0
- diffract/core/data/__init__.py +32 -0
- diffract/core/data/interface.py +304 -0
- diffract/core/data/metadata/__init__.py +15 -0
- diffract/core/data/metadata/containers.py +60 -0
- diffract/core/data/metadata/interface.py +208 -0
- diffract/core/data/metadata/sqlite_index.py +604 -0
- diffract/core/data/nn/__init__.py +43 -0
- diffract/core/data/nn/aggregates/__init__.py +35 -0
- diffract/core/data/nn/aggregates/metadata.py +96 -0
- diffract/core/data/nn/aggregates/proxy.py +57 -0
- diffract/core/data/nn/aggregates/repository.py +87 -0
- diffract/core/data/nn/aggregates/view.py +307 -0
- diffract/core/data/nn/containers.py +86 -0
- diffract/core/data/nn/extractors/__init__.py +49 -0
- diffract/core/data/nn/extractors/base.py +300 -0
- diffract/core/data/nn/extractors/factory.py +234 -0
- diffract/core/data/nn/extractors/flax.py +112 -0
- diffract/core/data/nn/extractors/handlers/__init__.py +39 -0
- diffract/core/data/nn/extractors/handlers/base.py +110 -0
- diffract/core/data/nn/extractors/handlers/flax_handlers.py +71 -0
- diffract/core/data/nn/extractors/handlers/numpy_handlers.py +63 -0
- diffract/core/data/nn/extractors/handlers/onnx_handlers.py +67 -0
- diffract/core/data/nn/extractors/handlers/tensorflow_handlers.py +82 -0
- diffract/core/data/nn/extractors/handlers/torch_handlers.py +124 -0
- diffract/core/data/nn/extractors/interface.py +56 -0
- diffract/core/data/nn/extractors/numpy.py +94 -0
- diffract/core/data/nn/extractors/onnx.py +108 -0
- diffract/core/data/nn/extractors/tensorflow.py +99 -0
- diffract/core/data/nn/extractors/torch.py +204 -0
- diffract/core/data/nn/params/__init__.py +27 -0
- diffract/core/data/nn/params/interface.py +402 -0
- diffract/core/data/nn/params/metadata.py +110 -0
- diffract/core/data/nn/params/proxy.py +93 -0
- diffract/core/data/nn/params/repository.py +45 -0
- diffract/core/data/nn/params/schema.py +82 -0
- diffract/core/data/nn/params/view.py +393 -0
- diffract/core/data/proxy.py +246 -0
- diffract/core/data/repository.py +227 -0
- diffract/core/data/utils.py +33 -0
- diffract/core/data/view.py +389 -0
- diffract/core/export/__init__.py +27 -0
- diffract/core/export/containers.py +47 -0
- diffract/core/export/exporters.py +191 -0
- diffract/core/export/formatters/__init__.py +23 -0
- diffract/core/export/formatters/base.py +68 -0
- diffract/core/export/formatters/dict_formatter.py +38 -0
- diffract/core/export/formatters/json_formatter.py +58 -0
- diffract/core/export/formatters/list_formatter.py +41 -0
- diffract/core/export/formatters/pandas_formatter.py +86 -0
- diffract/core/export/formatters/polars_formatter.py +86 -0
- diffract/core/export/formatters/registry.py +84 -0
- diffract/core/export/interface.py +105 -0
- diffract/core/parallel/__init__.py +21 -0
- diffract/core/parallel/containers.py +57 -0
- diffract/core/parallel/execution.py +91 -0
- diffract/core/parallel/runtime.py +91 -0
- diffract/core/storage/__init__.py +35 -0
- diffract/core/storage/base_manager.py +330 -0
- diffract/core/storage/containers.py +122 -0
- diffract/core/storage/hdf5_manager.py +856 -0
- diffract/core/storage/hybrid_manager.py +218 -0
- diffract/core/storage/interface.py +222 -0
- diffract/core/storage/metadata.py +89 -0
- diffract/core/storage/ram_manager.py +159 -0
- diffract/core/storage/sqlite_manager.py +1062 -0
- diffract/core/storage/zarr_manager.py +992 -0
- diffract/core/utils/__init__.py +45 -0
- diffract/core/utils/build.py +46 -0
- diffract/core/utils/exceptions.py +11 -0
- diffract/core/utils/hashing.py +90 -0
- diffract/core/utils/imports.py +292 -0
- diffract/core/utils/math.py +15 -0
- diffract/py.typed +0 -0
- diffract/session/__init__.py +24 -0
- diffract/session/errors.py +17 -0
- diffract/session/field_cache.py +216 -0
- diffract/session/namespaces/__init__.py +15 -0
- diffract/session/namespaces/compute/__init__.py +219 -0
- diffract/session/namespaces/models/__init__.py +282 -0
- diffract/session/namespaces/models/parameters/__init__.py +133 -0
- diffract/session/namespaces/models/parameters/meta_patcher.py +167 -0
- diffract/session/namespaces/results/__init__.py +378 -0
- diffract/session/namespaces/results/eraser.py +129 -0
- diffract/session/namespaces/results/injester.py +249 -0
- diffract/session/namespaces/utils/__init__.py +141 -0
- diffract/session/namespaces/utils/merger.py +295 -0
- diffract/session/namespaces/viz/__init__.py +91 -0
- diffract/session/namespaces/viz/_utils.py +14 -0
- diffract/session/namespaces/viz/box.py +207 -0
- diffract/session/namespaces/viz/grid.py +160 -0
- diffract/session/namespaces/viz/heatmap.py +164 -0
- diffract/session/namespaces/viz/scatter.py +202 -0
- diffract/session/namespaces/viz/sparkline.py +270 -0
- diffract/session/namespaces/viz/violin.py +212 -0
- diffract/session/session.py +260 -0
- diffract/session/utils.py +81 -0
- diffract/viz/__init__.py +42 -0
- diffract/viz/data/__init__.py +34 -0
- diffract/viz/data/detection.py +57 -0
- diffract/viz/data/extraction.py +117 -0
- diffract/viz/data/filtering.py +57 -0
- diffract/viz/data/ordering.py +129 -0
- diffract/viz/data/provider.py +99 -0
- diffract/viz/data/types.py +98 -0
- diffract/viz/plots/__init__.py +37 -0
- diffract/viz/plots/base/__init__.py +20 -0
- diffract/viz/plots/base/axis.py +219 -0
- diffract/viz/plots/base/coloraxis.py +133 -0
- diffract/viz/plots/base/configurator.py +18 -0
- diffract/viz/plots/base/jitter.py +368 -0
- diffract/viz/plots/base/line.py +197 -0
- diffract/viz/plots/base/marker.py +278 -0
- diffract/viz/plots/base/overlay.py +14 -0
- diffract/viz/plots/base/plot.py +110 -0
- diffract/viz/plots/base/update.py +50 -0
- diffract/viz/plots/boxplot.py +283 -0
- diffract/viz/plots/cluster.py +374 -0
- diffract/viz/plots/heatmap.py +168 -0
- diffract/viz/plots/scatter.py +233 -0
- diffract/viz/plots/sparkline.py +405 -0
- diffract/viz/plots/subplots/__init__.py +32 -0
- diffract/viz/plots/subplots/coloraxis.py +339 -0
- diffract/viz/plots/subplots/factory.py +713 -0
- diffract/viz/plots/subplots/grid.py +214 -0
- diffract/viz/plots/subplots/layout.py +370 -0
- diffract/viz/plots/subplots/spec.py +39 -0
- diffract/viz/plots/violin.py +298 -0
- diffract/viz/renderer.py +513 -0
- diffract/viz/styling/__init__.py +78 -0
- diffract/viz/styling/palettes/__init__.py +20 -0
- diffract/viz/styling/palettes/bundle.py +16 -0
- diffract/viz/styling/palettes/color.py +83 -0
- diffract/viz/styling/palettes/dashes.py +27 -0
- diffract/viz/styling/palettes/symbols.py +42 -0
- diffract/viz/styling/resolvers/__init__.py +11 -0
- diffract/viz/styling/resolvers/categorical.py +68 -0
- diffract/viz/styling/resolvers/color.py +77 -0
- diffract/viz/styling/resolvers/numeric.py +108 -0
- diffract/viz/styling/sources.py +27 -0
- diffract/viz/styling/theme/__init__.py +29 -0
- diffract/viz/styling/theme/applier.py +114 -0
- diffract/viz/styling/theme/components.py +66 -0
- diffract/viz/styling/theme/presets.py +58 -0
- diffract/viz/styling/theme/theme.py +36 -0
- diffract_core-0.2.1.dist-info/METADATA +530 -0
- diffract_core-0.2.1.dist-info/RECORD +193 -0
- diffract_core-0.2.1.dist-info/WHEEL +4 -0
- diffract_core-0.2.1.dist-info/licenses/LICENSE +201 -0
- 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
|
+
]
|