sdmxlib 0.27.1__tar.gz → 0.27.2__tar.gz
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.
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/PKG-INFO +1 -1
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/pyproject.toml +1 -1
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/local/_registry.py +161 -15
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/README.md +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/client.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/federated.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/filters.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/policy.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/providers.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/query.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/registry.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/api/session.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/catalog.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/data_store.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_csv/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_csv/reader.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_csv/writer.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_json/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_json/metadata.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_json/reader.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_json/writer.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml21/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml21/namespaces.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml21/reader.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml21/writer.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml30/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml30/metadata.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml30/namespaces.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml30/reader.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/formats/sdmx_ml30/writer.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/local/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/local/_data_store.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/annotations.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/base.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/binding.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/category.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/code_query.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/codelist.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/collections.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/concept.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/constraint.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/convert.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/dataflow.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/dataset.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/datastructure.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/expr.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/hierarchy.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/hierarchy_query.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/in_memory_registry.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/istring.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/mapping.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/message.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/metadataflow.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/metadataset.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/metadatastructure.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/organisation.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/provision.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/ref.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/registry.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/representation.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/urn.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/model/validation.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/polars.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/py.typed +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/rest.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/sql.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/storage/__init__.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/storage/lazy.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/storage/readers.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/storage/schema.py +0 -0
- {sdmxlib-0.27.1 → sdmxlib-0.27.2}/src/sdmxlib/storage/writers.py +0 -0
|
@@ -30,6 +30,7 @@ stashes the URI; the DuckDB connection is opened by ``open()`` /
|
|
|
30
30
|
local.add(...)
|
|
31
31
|
"""
|
|
32
32
|
|
|
33
|
+
from collections import OrderedDict
|
|
33
34
|
from collections.abc import Iterable
|
|
34
35
|
from pathlib import Path
|
|
35
36
|
from typing import Any, Self
|
|
@@ -57,6 +58,90 @@ _ITEM_TO_SCHEME: dict[str, tuple[str, str]] = {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
|
|
61
|
+
DEFAULT_RESOLVE_CACHE_SIZE = 256
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _LRUDict(OrderedDict[str, Any]):
|
|
65
|
+
"""Bounded LRU mapping used as the registry-level resolver cache.
|
|
66
|
+
|
|
67
|
+
Subclasses :class:`OrderedDict` so it can be passed wherever the
|
|
68
|
+
resolver expects ``dict[str, Any]``. Inserts evict least-recently-used
|
|
69
|
+
entries past ``maxsize``; reads via :meth:`get` move keys to the end.
|
|
70
|
+
``maxsize == 0`` disables caching — inserts are silently dropped, so
|
|
71
|
+
the structure stays empty.
|
|
72
|
+
|
|
73
|
+
Tracks ``hits`` / ``misses`` for :meth:`Registry.resolve_cache_info`;
|
|
74
|
+
``__contains__`` is treated as internal control flow (the resolver
|
|
75
|
+
uses it for dedup) and is not counted.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, maxsize: int) -> None:
|
|
79
|
+
super().__init__()
|
|
80
|
+
if maxsize < 0:
|
|
81
|
+
msg = f"resolve_cache_size must be >= 0, got {maxsize}"
|
|
82
|
+
raise ValueError(msg)
|
|
83
|
+
self.maxsize = maxsize
|
|
84
|
+
self.hits = 0
|
|
85
|
+
self.misses = 0
|
|
86
|
+
|
|
87
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
88
|
+
if self.maxsize == 0:
|
|
89
|
+
return
|
|
90
|
+
if key in self:
|
|
91
|
+
self.move_to_end(key)
|
|
92
|
+
super().__setitem__(key, value)
|
|
93
|
+
while len(self) > self.maxsize:
|
|
94
|
+
self.popitem(last=False)
|
|
95
|
+
|
|
96
|
+
def get(self, key: str, default: Any = None) -> Any: # type: ignore[override]
|
|
97
|
+
if super().__contains__(key):
|
|
98
|
+
self.hits += 1
|
|
99
|
+
self.move_to_end(key)
|
|
100
|
+
return super().__getitem__(key)
|
|
101
|
+
self.misses += 1
|
|
102
|
+
return default
|
|
103
|
+
|
|
104
|
+
def reset_stats(self) -> None:
|
|
105
|
+
self.hits = 0
|
|
106
|
+
self.misses = 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class _CallScopedMemo(dict[str, Any]):
|
|
110
|
+
"""Per-call resolver memo layered over a persistent LRU cache.
|
|
111
|
+
|
|
112
|
+
Two reasons for the overlay rather than passing the LRU directly:
|
|
113
|
+
|
|
114
|
+
1. **Correctness under tight cache sizes (incl. ``maxsize=0``).** The
|
|
115
|
+
resolver writes URN→target during one phase and reads them back in
|
|
116
|
+
a later phase of the same call. If those writes went straight to
|
|
117
|
+
the LRU, an entry could be evicted before the read — leaving Refs
|
|
118
|
+
unresolved for callers using a small (or disabled) cache. The
|
|
119
|
+
overlay holds in-flight entries in an unbounded per-call dict so
|
|
120
|
+
single-call resolution is independent of cache size.
|
|
121
|
+
2. **Promote-on-success.** Entries written during the call enter the
|
|
122
|
+
LRU only at the end of the call, via :meth:`promote`. That keeps
|
|
123
|
+
partial-failure semantics clean: if a call raises midway, the LRU
|
|
124
|
+
is unchanged.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def __init__(self, lru: _LRUDict) -> None:
|
|
128
|
+
super().__init__()
|
|
129
|
+
self._lru = lru
|
|
130
|
+
|
|
131
|
+
def __contains__(self, key: object) -> bool:
|
|
132
|
+
return super().__contains__(key) or self._lru.__contains__(key)
|
|
133
|
+
|
|
134
|
+
def get(self, key: str, default: Any = None) -> Any: # type: ignore[override]
|
|
135
|
+
if super().__contains__(key):
|
|
136
|
+
return super().__getitem__(key)
|
|
137
|
+
return self._lru.get(key, default)
|
|
138
|
+
|
|
139
|
+
def promote(self) -> None:
|
|
140
|
+
"""Copy this call's resolved entries into the persistent LRU."""
|
|
141
|
+
for k, v in self.items():
|
|
142
|
+
self._lru[k] = v
|
|
143
|
+
|
|
144
|
+
|
|
60
145
|
class Registry:
|
|
61
146
|
"""DuckDB-backed persistent SDMX artefact store.
|
|
62
147
|
|
|
@@ -86,12 +171,22 @@ class Registry:
|
|
|
86
171
|
uri: str | Path,
|
|
87
172
|
*,
|
|
88
173
|
source_registry: str | None = None,
|
|
174
|
+
resolve_cache_size: int = DEFAULT_RESOLVE_CACHE_SIZE,
|
|
89
175
|
) -> None:
|
|
176
|
+
"""Open or wrap a DuckDB-backed registry.
|
|
177
|
+
|
|
178
|
+
``resolve_cache_size`` bounds the LRU of resolved Ref targets that
|
|
179
|
+
:meth:`get` / :meth:`get_all` / :meth:`to_registry` share across
|
|
180
|
+
calls. Pass ``0`` to disable caching (matches pre-v0.27.2 behaviour
|
|
181
|
+
— one full read per Ref). The cache is cleared on every mutation
|
|
182
|
+
(``add`` / ``drop`` / ``replace``) for invalidation correctness.
|
|
183
|
+
"""
|
|
90
184
|
self._uri = Path(uri) if isinstance(uri, str) else uri
|
|
91
185
|
self._source_registry = source_registry
|
|
92
186
|
self._conn: duckdb.DuckDBPyConnection | None = None
|
|
93
187
|
self._cursor: duckdb.DuckDBPyConnection | None = None
|
|
94
188
|
self._owns_connection = True
|
|
189
|
+
self._resolve_cache: _LRUDict = _LRUDict(resolve_cache_size)
|
|
95
190
|
|
|
96
191
|
@property
|
|
97
192
|
def uri(self) -> Path:
|
|
@@ -137,6 +232,7 @@ class Registry:
|
|
|
137
232
|
conn: duckdb.DuckDBPyConnection,
|
|
138
233
|
*,
|
|
139
234
|
source_registry: str | None = None,
|
|
235
|
+
resolve_cache_size: int = DEFAULT_RESOLVE_CACHE_SIZE,
|
|
140
236
|
) -> Self:
|
|
141
237
|
"""Wrap an existing DuckDB connection.
|
|
142
238
|
|
|
@@ -144,6 +240,9 @@ class Registry:
|
|
|
144
240
|
Registry leaves the connection open. Use this when the connection
|
|
145
241
|
is shared with another component (e.g. a co-located DuckLake catalog)
|
|
146
242
|
that owns its lifecycle.
|
|
243
|
+
|
|
244
|
+
``resolve_cache_size`` configures the resolver LRU; see
|
|
245
|
+
:meth:`__init__`.
|
|
147
246
|
"""
|
|
148
247
|
instance = cls.__new__(cls)
|
|
149
248
|
instance._uri = Path(":connection:") # noqa: SLF001 — alternate constructor sets private state
|
|
@@ -151,6 +250,7 @@ class Registry:
|
|
|
151
250
|
instance._conn = conn # noqa: SLF001
|
|
152
251
|
instance._cursor = None # noqa: SLF001
|
|
153
252
|
instance._owns_connection = False # noqa: SLF001
|
|
253
|
+
instance._resolve_cache = _LRUDict(resolve_cache_size) # noqa: SLF001
|
|
154
254
|
instance._initialise_schema() # noqa: SLF001
|
|
155
255
|
return instance
|
|
156
256
|
|
|
@@ -209,9 +309,14 @@ class Registry:
|
|
|
209
309
|
|
|
210
310
|
``source_registry`` overrides the registry-level default for this
|
|
211
311
|
call. ``None`` (default) means "use the registry-level default."
|
|
312
|
+
|
|
313
|
+
Clears the resolver cache: any cached resolved target may now be
|
|
314
|
+
stale (e.g. a freshly-added Codelist replaces the one a previous
|
|
315
|
+
``get`` had cached).
|
|
212
316
|
"""
|
|
213
317
|
tag = source_registry if source_registry is not None else self._source_registry
|
|
214
318
|
writers.put_artefact(self._c, artefact, source_registry=tag)
|
|
319
|
+
self._resolve_cache.clear()
|
|
215
320
|
|
|
216
321
|
def add_all(
|
|
217
322
|
self,
|
|
@@ -256,22 +361,25 @@ class Registry:
|
|
|
256
361
|
if result is None:
|
|
257
362
|
return None
|
|
258
363
|
if not isinstance(result, (LazyCodelist, LazyHierarchy)):
|
|
259
|
-
self.
|
|
364
|
+
memo = _CallScopedMemo(self._resolve_cache)
|
|
365
|
+
self._resolve_refs(result, memo=memo)
|
|
366
|
+
memo.promote()
|
|
260
367
|
return result # type: ignore[return-value]
|
|
261
368
|
|
|
262
369
|
def get_all[T](self, artefact_type: "type[T]") -> "list[T]":
|
|
263
370
|
"""All artefacts of the given Python type.
|
|
264
371
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
372
|
+
Resolution shares one call-scoped memo across the batch and is
|
|
373
|
+
backed by the registry's persistent ``_resolve_cache``, so any
|
|
374
|
+
parent scheme referenced by multiple artefacts is loaded once,
|
|
375
|
+
not once per artefact.
|
|
268
376
|
"""
|
|
269
377
|
results = readers.get_all(self._c, artefact_type)
|
|
270
|
-
memo
|
|
271
|
-
walked: set[int] = set()
|
|
378
|
+
memo = _CallScopedMemo(self._resolve_cache)
|
|
272
379
|
for r in results:
|
|
273
380
|
if not isinstance(r, (LazyCodelist, LazyHierarchy)):
|
|
274
|
-
self._resolve_refs(r, memo=memo
|
|
381
|
+
self._resolve_refs(r, memo=memo)
|
|
382
|
+
memo.promote()
|
|
275
383
|
return results # type: ignore[return-value]
|
|
276
384
|
|
|
277
385
|
# ── Ref resolution ─────────────────────────────────────────────────
|
|
@@ -498,14 +606,51 @@ class Registry:
|
|
|
498
606
|
# ── Mutations ─────────────────────────────────────────────────────────
|
|
499
607
|
|
|
500
608
|
def drop(self, urn: "SdmxUrn[Any]") -> bool:
|
|
501
|
-
"""Remove an artefact by URN. Returns True if it existed.
|
|
502
|
-
|
|
609
|
+
"""Remove an artefact by URN. Returns True if it existed.
|
|
610
|
+
|
|
611
|
+
Clears the resolver cache (cached resolved targets may reference
|
|
612
|
+
the dropped artefact).
|
|
613
|
+
"""
|
|
614
|
+
result = writers.drop_artefact(self._c, str(urn))
|
|
615
|
+
self._resolve_cache.clear()
|
|
616
|
+
return result
|
|
503
617
|
|
|
504
618
|
def replace(self, old_urn: "SdmxUrn[Any]", new: Any) -> None:
|
|
505
|
-
"""Remove the old artefact and add the new one.
|
|
619
|
+
"""Remove the old artefact and add the new one.
|
|
620
|
+
|
|
621
|
+
Each leg clears the resolver cache via :meth:`add` / :meth:`drop`;
|
|
622
|
+
the net effect is a single fully-cleared cache at the end.
|
|
623
|
+
"""
|
|
506
624
|
writers.drop_artefact(self._c, str(old_urn))
|
|
507
625
|
self.add(new)
|
|
508
626
|
|
|
627
|
+
# ── Resolver cache ────────────────────────────────────────────────────
|
|
628
|
+
|
|
629
|
+
def clear_resolve_cache(self) -> None:
|
|
630
|
+
"""Purge the resolver cache and reset hit/miss counters.
|
|
631
|
+
|
|
632
|
+
Useful for explicit invalidation hooks (e.g. a webhook telling the
|
|
633
|
+
service that upstream metadata changed without going through this
|
|
634
|
+
registry's mutation API). Cache also clears automatically on every
|
|
635
|
+
``add`` / ``drop`` / ``replace``.
|
|
636
|
+
"""
|
|
637
|
+
self._resolve_cache.clear()
|
|
638
|
+
self._resolve_cache.reset_stats()
|
|
639
|
+
|
|
640
|
+
def resolve_cache_info(self) -> dict[str, int]:
|
|
641
|
+
"""Return resolver cache stats: size, max_size, hits, misses.
|
|
642
|
+
|
|
643
|
+
Intended for ops dashboards and integration tests asserting cache
|
|
644
|
+
behaviour. ``size == 0`` after construction or any mutation; rises
|
|
645
|
+
as ``get`` / ``get_all`` populate it, capped at ``max_size``.
|
|
646
|
+
"""
|
|
647
|
+
return {
|
|
648
|
+
"size": len(self._resolve_cache),
|
|
649
|
+
"max_size": self._resolve_cache.maxsize,
|
|
650
|
+
"hits": self._resolve_cache.hits,
|
|
651
|
+
"misses": self._resolve_cache.misses,
|
|
652
|
+
}
|
|
653
|
+
|
|
509
654
|
# ── Direct SQL helpers ────────────────────────────────────────────────
|
|
510
655
|
|
|
511
656
|
def code_map(self, codelist_urn: "str | SdmxUrn[Any]", *, lang: str) -> dict[str, str]:
|
|
@@ -536,17 +681,18 @@ class Registry:
|
|
|
536
681
|
def to_registry(self) -> InMemoryRegistry:
|
|
537
682
|
"""Load everything into an in-memory Registry with full Ref interning.
|
|
538
683
|
|
|
539
|
-
|
|
540
|
-
is read at most once even when many artefacts
|
|
684
|
+
Resolution uses the registry's persistent ``_resolve_cache`` so each
|
|
685
|
+
parent scheme is read at most once even when many artefacts
|
|
686
|
+
reference it.
|
|
541
687
|
"""
|
|
542
688
|
reg = InMemoryRegistry()
|
|
543
|
-
memo
|
|
544
|
-
walked: set[int] = set()
|
|
689
|
+
memo = _CallScopedMemo(self._resolve_cache)
|
|
545
690
|
rows = self._c.execute("SELECT urn, artefact_type FROM artefact").fetchall()
|
|
546
691
|
for urn_str, _artefact_type in rows:
|
|
547
692
|
obj = readers.get_artefact(self._c, urn_str, eager=True)
|
|
548
693
|
if obj is None:
|
|
549
694
|
continue
|
|
550
|
-
self._resolve_refs(obj, memo=memo
|
|
695
|
+
self._resolve_refs(obj, memo=memo)
|
|
551
696
|
reg.add(obj)
|
|
697
|
+
memo.promote()
|
|
552
698
|
return reg
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|