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,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