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,246 @@
|
|
|
1
|
+
"""Generic data proxy for lazy-loading storage-backed entities.
|
|
2
|
+
|
|
3
|
+
This module provides the base DataProxy class that can be specialized
|
|
4
|
+
for different data types (parameters, relations, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
11
|
+
|
|
12
|
+
from diffract.core.storage.interface import DEFAULT_TABLE
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from .interface import IDataRepository
|
|
16
|
+
|
|
17
|
+
TMetadata = TypeVar("TMetadata")
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(kw_only=True)
|
|
22
|
+
class DataProxy[TMetadata]:
|
|
23
|
+
"""Generic lazy-loading proxy for storage-backed data entities.
|
|
24
|
+
|
|
25
|
+
Provides efficient access to data through intelligent caching and
|
|
26
|
+
storage management. Entities are loaded on-demand and can be
|
|
27
|
+
prefetched for batch operations. The proxy pattern allows working
|
|
28
|
+
with large datasets without memory constraints.
|
|
29
|
+
|
|
30
|
+
Type Parameters:
|
|
31
|
+
TMetadata: The metadata type for this proxy (e.g., ParameterMetadata).
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> proxy = DataProxy.create_and_store(
|
|
35
|
+
... meta=metadata,
|
|
36
|
+
... repository=repository,
|
|
37
|
+
... )
|
|
38
|
+
>>> value = proxy.get_field("weights")
|
|
39
|
+
>>> proxy.set_field("computed_metric", computed_value)
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
meta: Immutable entity metadata.
|
|
43
|
+
_repository: Repository that owns storage/cache/metadata managers.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
meta: TMetadata
|
|
47
|
+
_repository: IDataRepository[TMetadata, DataProxy[TMetadata]] = field(repr=False)
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def get_table(cls) -> str:
|
|
51
|
+
"""Return the storage table name for this proxy type.
|
|
52
|
+
|
|
53
|
+
Override in subclasses to specify a different table.
|
|
54
|
+
"""
|
|
55
|
+
return DEFAULT_TABLE
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def create_and_store(
|
|
59
|
+
cls,
|
|
60
|
+
meta: TMetadata,
|
|
61
|
+
repository: IDataRepository[TMetadata, DataProxy[TMetadata]],
|
|
62
|
+
) -> DataProxy[TMetadata]:
|
|
63
|
+
"""Create proxy and store metadata in index.
|
|
64
|
+
|
|
65
|
+
Factory method that creates a new proxy and immediately
|
|
66
|
+
stores the metadata to the metadata index.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
meta: Entity metadata.
|
|
70
|
+
repository: Repository owning storage, cache, and metadata managers.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
New proxy with metadata stored.
|
|
74
|
+
"""
|
|
75
|
+
# Store full metadata in the index
|
|
76
|
+
repository.metadata_index.upsert(
|
|
77
|
+
cls.get_table(),
|
|
78
|
+
**meta.to_dict(),
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
proxy = cls(
|
|
82
|
+
meta=meta,
|
|
83
|
+
_repository=repository,
|
|
84
|
+
)
|
|
85
|
+
repository.append(proxy)
|
|
86
|
+
return proxy
|
|
87
|
+
|
|
88
|
+
def set_field(self, name: str, value: Any) -> None:
|
|
89
|
+
"""Store a named field for this entity.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
name: Field name to store.
|
|
93
|
+
value: Value to store (must be serializable).
|
|
94
|
+
"""
|
|
95
|
+
self._repository.storage_manager.set_field(
|
|
96
|
+
obj_uid=self.meta.uid,
|
|
97
|
+
field_name=name,
|
|
98
|
+
value=value,
|
|
99
|
+
table=self.get_table(),
|
|
100
|
+
)
|
|
101
|
+
self.prefetch_field(name, value)
|
|
102
|
+
|
|
103
|
+
def get_field(
|
|
104
|
+
self, name: str, default: T | None = None, auto_prefetch: bool = True
|
|
105
|
+
) -> Any | T:
|
|
106
|
+
"""Retrieve a named field for this entity.
|
|
107
|
+
|
|
108
|
+
Checks cache first for performance, falls back to storage if needed.
|
|
109
|
+
Optionally prefetches the field to cache for future access.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
name: Field name to retrieve.
|
|
113
|
+
default: Default value if field doesn't exist.
|
|
114
|
+
auto_prefetch: Whether to cache the value after loading.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Field value or default if not found.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
KeyError: If field doesn't exist and no default provided.
|
|
121
|
+
"""
|
|
122
|
+
cache = self._repository.cache_manager
|
|
123
|
+
if (cache is not None) and self.is_field_prefetched(name):
|
|
124
|
+
value = cache.get_field(obj_uid=self.meta.uid, field_name=name)
|
|
125
|
+
else:
|
|
126
|
+
try:
|
|
127
|
+
value = self._repository.storage_manager.get_field(
|
|
128
|
+
obj_uid=self.meta.uid, field_name=name, table=self.get_table()
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
except KeyError:
|
|
132
|
+
if default is not None:
|
|
133
|
+
value = default
|
|
134
|
+
else:
|
|
135
|
+
raise
|
|
136
|
+
|
|
137
|
+
if auto_prefetch:
|
|
138
|
+
self.prefetch_field(name, value)
|
|
139
|
+
|
|
140
|
+
return value
|
|
141
|
+
|
|
142
|
+
def has_field(self, name: str) -> bool:
|
|
143
|
+
"""Check if entity has a named field.
|
|
144
|
+
|
|
145
|
+
Checks both cache and storage for field existence.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
name: Field name to check.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
True if field exists, False otherwise.
|
|
152
|
+
"""
|
|
153
|
+
cache = self._repository.cache_manager
|
|
154
|
+
if (cache is not None) and (self.is_field_prefetched(name)):
|
|
155
|
+
return True
|
|
156
|
+
return self._repository.storage_manager.has_field(
|
|
157
|
+
obj_uid=self.meta.uid,
|
|
158
|
+
field_name=name,
|
|
159
|
+
table=self.get_table(),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def get_field_metadata(self, name: str) -> dict[str, Any] | None:
|
|
163
|
+
"""Return storage-level metadata for the given field if available."""
|
|
164
|
+
return self._repository.storage_manager.get_field_metadata(
|
|
165
|
+
obj_uid=self.meta.uid, field_name=name, table=self.get_table()
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def is_field_prefetched(self, name: str) -> bool:
|
|
169
|
+
"""Check if field is cached for fast access.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
name: Field name to check.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
True if field is in cache, False otherwise.
|
|
176
|
+
"""
|
|
177
|
+
cache = self._repository.cache_manager
|
|
178
|
+
if cache is None:
|
|
179
|
+
return True
|
|
180
|
+
return cache.has_field(obj_uid=self.meta.uid, field_name=name)
|
|
181
|
+
|
|
182
|
+
def prefetch_field(self, name: str, value: Any) -> None:
|
|
183
|
+
"""Cache a field value for fast access.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
name: Field name to cache.
|
|
187
|
+
value: Value to cache.
|
|
188
|
+
"""
|
|
189
|
+
cache = self._repository.cache_manager
|
|
190
|
+
if cache is not None:
|
|
191
|
+
cache.set_field(obj_uid=self.meta.uid, field_name=name, value=value)
|
|
192
|
+
|
|
193
|
+
def try_prefetch_field(self, name: str) -> bool:
|
|
194
|
+
"""Attempt to prefetch a field into cache.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
name: Field name to prefetch.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
True if field was prefetched or already cached, False if not found.
|
|
201
|
+
"""
|
|
202
|
+
if not self.is_field_prefetched(name):
|
|
203
|
+
try:
|
|
204
|
+
value = self._repository.storage_manager.get_field(
|
|
205
|
+
obj_uid=self.meta.uid, field_name=name, table=self.get_table()
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
except KeyError:
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
self.prefetch_field(name, value)
|
|
212
|
+
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
def erase_field(self, name: str) -> None:
|
|
216
|
+
"""Remove a field from storage and cache.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
name: Field name to remove.
|
|
220
|
+
"""
|
|
221
|
+
self._repository.storage_manager.erase_field(
|
|
222
|
+
obj_uid=self.meta.uid,
|
|
223
|
+
field_name=name,
|
|
224
|
+
table=self.get_table(),
|
|
225
|
+
)
|
|
226
|
+
cache = self._repository.cache_manager
|
|
227
|
+
if (cache is not None) and self.is_field_prefetched(name):
|
|
228
|
+
cache.erase_field(obj_uid=self.meta.uid, field_name=name)
|
|
229
|
+
|
|
230
|
+
def list_fields(self) -> list[str]:
|
|
231
|
+
"""List all field names for this entity.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
List of field names stored for this entity.
|
|
235
|
+
"""
|
|
236
|
+
return self._repository.storage_manager.list_fields(
|
|
237
|
+
obj_uid=self.meta.uid, table=self.get_table()
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def __hash__(self) -> int:
|
|
241
|
+
"""Return hash based on unique identifier.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Hash of the entity UID.
|
|
245
|
+
"""
|
|
246
|
+
return hash(self.meta.uid)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Generic data repository for managing storage, metadata index, and cache.
|
|
2
|
+
|
|
3
|
+
This module provides the base DataRepository class that can be specialized
|
|
4
|
+
for different data types (parameters, relations, etc.).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
from typing import TYPE_CHECKING, Any, ClassVar, Self, TypeVar
|
|
11
|
+
|
|
12
|
+
from diffract.core.parallel import ParallelContext
|
|
13
|
+
from diffract.core.storage.interface import DEFAULT_TABLE
|
|
14
|
+
|
|
15
|
+
from .view import DataView
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
import types
|
|
19
|
+
from collections.abc import Iterable, Iterator
|
|
20
|
+
|
|
21
|
+
from diffract.core.cache.interface import ICacheManager
|
|
22
|
+
from diffract.core.data.metadata.interface import IMetadataIndex
|
|
23
|
+
from diffract.core.storage.interface import IStorageManager
|
|
24
|
+
|
|
25
|
+
TMetadata = TypeVar("TMetadata")
|
|
26
|
+
TProxy = TypeVar("TProxy")
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DataRepository[TMetadata, TProxy]:
|
|
32
|
+
"""Generic repository owning storage/cache/metadata and managing entity membership.
|
|
33
|
+
|
|
34
|
+
Provides the infrastructure for persistent storage, metadata indexing, and
|
|
35
|
+
caching of metadata-bearing entities. Subclasses specify the metadata type,
|
|
36
|
+
proxy class, view class, storage table, and metadata schema.
|
|
37
|
+
|
|
38
|
+
Type Parameters:
|
|
39
|
+
TMetadata: The metadata type for entities in this repository.
|
|
40
|
+
TProxy: The proxy type for entities in this repository.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# Override in subclasses
|
|
44
|
+
METADATA_CLASS: type[TMetadata]
|
|
45
|
+
PROXY_CLASS: type[TProxy]
|
|
46
|
+
VIEW_CLASS: type[DataView[TMetadata, TProxy]]
|
|
47
|
+
TABLE: str = DEFAULT_TABLE
|
|
48
|
+
|
|
49
|
+
# Schema for MetadataIndex - override in subclasses
|
|
50
|
+
METADATA_COLUMNS: ClassVar[dict[str, type]] = {}
|
|
51
|
+
METADATA_INDEXES: ClassVar[list[str]] = []
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
storage_manager: IStorageManager,
|
|
56
|
+
metadata_index: IMetadataIndex,
|
|
57
|
+
cache_manager: ICacheManager | None = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
self._storage_manager = storage_manager
|
|
60
|
+
self._metadata_index = metadata_index
|
|
61
|
+
self._cache_manager = cache_manager
|
|
62
|
+
self._proxy_cache: dict[str, TProxy] = {}
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def storage_manager(self) -> IStorageManager:
|
|
66
|
+
"""Return the storage manager owned by this repository."""
|
|
67
|
+
return self._storage_manager
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def metadata_index(self) -> IMetadataIndex:
|
|
71
|
+
"""Return the metadata index owned by this repository."""
|
|
72
|
+
return self._metadata_index
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def cache_manager(self) -> ICacheManager | None:
|
|
76
|
+
"""Return the cache manager owned by this repository."""
|
|
77
|
+
return self._cache_manager
|
|
78
|
+
|
|
79
|
+
def __enter__(self) -> Self:
|
|
80
|
+
"""Enter batch write context for repository storage."""
|
|
81
|
+
self._storage_manager.__enter__()
|
|
82
|
+
self._metadata_index.__enter__()
|
|
83
|
+
return self
|
|
84
|
+
|
|
85
|
+
def __exit__(
|
|
86
|
+
self,
|
|
87
|
+
exc_type: type[BaseException] | None,
|
|
88
|
+
exc_val: BaseException | None,
|
|
89
|
+
exc_tb: types.TracebackType | None,
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Exit batch write context for repository storage."""
|
|
92
|
+
self._metadata_index.__exit__(exc_type, exc_val, exc_tb)
|
|
93
|
+
self._storage_manager.__exit__(exc_type, exc_val, exc_tb)
|
|
94
|
+
|
|
95
|
+
def __len__(self) -> int:
|
|
96
|
+
"""Return number of entities in the repository."""
|
|
97
|
+
return self._metadata_index.count(self.TABLE)
|
|
98
|
+
|
|
99
|
+
def __iter__(self) -> Iterator[TProxy]:
|
|
100
|
+
"""Iterate entity proxies in repository order."""
|
|
101
|
+
for uid in self._metadata_index.list_uids(self.TABLE):
|
|
102
|
+
yield self.get_proxy(uid)
|
|
103
|
+
|
|
104
|
+
def __getitem__(self, index: int) -> TProxy:
|
|
105
|
+
"""Return entity proxy by positional index."""
|
|
106
|
+
uids = self._metadata_index.list_uids(self.TABLE)
|
|
107
|
+
return self.get_proxy(uids[index])
|
|
108
|
+
|
|
109
|
+
def get_proxy(self, uid: str) -> TProxy:
|
|
110
|
+
"""Return entity proxy by uid with lazy loading."""
|
|
111
|
+
if uid in self._proxy_cache:
|
|
112
|
+
return self._proxy_cache[uid]
|
|
113
|
+
|
|
114
|
+
meta_dict = self._metadata_index.get(self.TABLE, uid)
|
|
115
|
+
if meta_dict is None:
|
|
116
|
+
raise KeyError(f"Entity with uid '{uid}' not found in {self.TABLE}")
|
|
117
|
+
|
|
118
|
+
meta = self.METADATA_CLASS.from_dict(meta_dict)
|
|
119
|
+
proxy = self.PROXY_CLASS(meta=meta, _repository=self)
|
|
120
|
+
self._proxy_cache[uid] = proxy
|
|
121
|
+
return proxy
|
|
122
|
+
|
|
123
|
+
def has_uid(self, uid: str) -> bool:
|
|
124
|
+
"""Check if entity with given uid exists."""
|
|
125
|
+
return self._metadata_index.get(self.TABLE, uid) is not None
|
|
126
|
+
|
|
127
|
+
def append(self, proxy: TProxy) -> None:
|
|
128
|
+
"""Add an entity proxy to repository (cache only, metadata already in index)."""
|
|
129
|
+
self._proxy_cache[proxy.meta.uid] = proxy
|
|
130
|
+
|
|
131
|
+
def extend(self, proxies: Iterable[TProxy]) -> None:
|
|
132
|
+
"""Add multiple entity proxies to repository cache."""
|
|
133
|
+
for proxy in proxies:
|
|
134
|
+
self.append(proxy)
|
|
135
|
+
|
|
136
|
+
def remove_by_uid(self, uid: str) -> None:
|
|
137
|
+
"""Remove an entity from repository by uid."""
|
|
138
|
+
self._proxy_cache.pop(uid, None)
|
|
139
|
+
self._metadata_index.delete(self.TABLE, uid)
|
|
140
|
+
self._storage_manager.erase_obj(uid, table=self.TABLE)
|
|
141
|
+
|
|
142
|
+
def clear(self, erase: bool = False) -> None:
|
|
143
|
+
"""Clear repository membership and optionally erase storage data.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
erase: If True, also erase underlying storage data and cache.
|
|
147
|
+
If False, only clear membership (metadata index and proxy cache).
|
|
148
|
+
"""
|
|
149
|
+
if erase:
|
|
150
|
+
if self._cache_manager is not None:
|
|
151
|
+
self._cache_manager.clear()
|
|
152
|
+
|
|
153
|
+
uids = self._metadata_index.list_uids(self.TABLE)
|
|
154
|
+
with self._storage_manager:
|
|
155
|
+
for uid in uids:
|
|
156
|
+
self._storage_manager.erase_obj(uid, table=self.TABLE)
|
|
157
|
+
|
|
158
|
+
self._metadata_index.clear(self.TABLE)
|
|
159
|
+
self._proxy_cache.clear()
|
|
160
|
+
|
|
161
|
+
def query(
|
|
162
|
+
self,
|
|
163
|
+
where: dict[str, Any] | None = None,
|
|
164
|
+
where_in: dict[str, list[Any]] | None = None,
|
|
165
|
+
where_like: dict[str, str] | None = None,
|
|
166
|
+
order_by: list[str] | None = None,
|
|
167
|
+
limit: int | None = None,
|
|
168
|
+
) -> list[str]:
|
|
169
|
+
"""Query UIDs from metadata index."""
|
|
170
|
+
return self._metadata_index.query(
|
|
171
|
+
self.TABLE,
|
|
172
|
+
where=where,
|
|
173
|
+
where_in=where_in,
|
|
174
|
+
where_like=where_like,
|
|
175
|
+
order_by=order_by,
|
|
176
|
+
limit=limit,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def list_uids(self) -> list[str]:
|
|
180
|
+
"""List all entity UIDs in the repository."""
|
|
181
|
+
return self._metadata_index.list_uids(self.TABLE)
|
|
182
|
+
|
|
183
|
+
def prefetch_proxies(
|
|
184
|
+
self,
|
|
185
|
+
uids: list[str],
|
|
186
|
+
parallel: ParallelContext | None = None,
|
|
187
|
+
) -> None:
|
|
188
|
+
"""Batch prefetch proxies into cache."""
|
|
189
|
+
uids_to_load = [uid for uid in uids if uid not in self._proxy_cache]
|
|
190
|
+
if not uids_to_load:
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
meta_batch = self._metadata_index.get_batch(self.TABLE, uids_to_load)
|
|
194
|
+
|
|
195
|
+
for uid, meta_dict in zip(uids_to_load, meta_batch, strict=True):
|
|
196
|
+
if meta_dict is not None:
|
|
197
|
+
meta = self.METADATA_CLASS.from_dict(meta_dict)
|
|
198
|
+
self._proxy_cache[uid] = self.PROXY_CLASS(meta=meta, _repository=self)
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def define_schema(cls, metadata_index: IMetadataIndex) -> None:
|
|
202
|
+
"""Define metadata schema in the index. Override in subclasses."""
|
|
203
|
+
metadata_index.define_table(
|
|
204
|
+
cls.TABLE,
|
|
205
|
+
columns=cls.METADATA_COLUMNS,
|
|
206
|
+
indexes=cls.METADATA_INDEXES,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def initialize(
|
|
211
|
+
cls,
|
|
212
|
+
storage_manager: IStorageManager,
|
|
213
|
+
metadata_index: IMetadataIndex,
|
|
214
|
+
cache_manager: ICacheManager | None = None,
|
|
215
|
+
parallel: ParallelContext | None = None,
|
|
216
|
+
) -> Self:
|
|
217
|
+
"""Initialize repository with schema definition."""
|
|
218
|
+
cls.define_schema(metadata_index)
|
|
219
|
+
return cls(
|
|
220
|
+
storage_manager=storage_manager,
|
|
221
|
+
metadata_index=metadata_index,
|
|
222
|
+
cache_manager=cache_manager,
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def create_view(self) -> DataView[TMetadata, TProxy]:
|
|
226
|
+
"""Create a view over all entities in the repository."""
|
|
227
|
+
return self.VIEW_CLASS(repository=self, uids=None)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Utility functions for the data module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Callable, Iterable
|
|
7
|
+
|
|
8
|
+
from diffract.core.constants import REGEX_PREFIX
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def build_matcher(patterns: Iterable[str]) -> Callable[[str], bool]:
|
|
12
|
+
"""Build a matcher function from exact strings and regex patterns.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
patterns: Strings to match. Prefix with REGEX_PREFIX ("re:") for regex.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Function that returns True if input matches any pattern.
|
|
19
|
+
"""
|
|
20
|
+
exact: set[str] = set()
|
|
21
|
+
compiled: list[re.Pattern[str]] = []
|
|
22
|
+
for pattern in patterns:
|
|
23
|
+
if pattern.startswith(REGEX_PREFIX):
|
|
24
|
+
compiled.append(re.compile(pattern.removeprefix(REGEX_PREFIX)))
|
|
25
|
+
else:
|
|
26
|
+
exact.add(pattern)
|
|
27
|
+
|
|
28
|
+
def _matches(value: str) -> bool:
|
|
29
|
+
if value in exact:
|
|
30
|
+
return True
|
|
31
|
+
return any(pattern.fullmatch(value) is not None for pattern in compiled)
|
|
32
|
+
|
|
33
|
+
return _matches
|