cultcache-py 0.2.0__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.
- cultcache_py/__init__.py +29 -0
- cultcache_py/backing_store.py +79 -0
- cultcache_py/cache.py +383 -0
- cultcache_py/documents.py +303 -0
- cultcache_py/interop.py +102 -0
- cultcache_py/py.typed +1 -0
- cultcache_py/stores.py +355 -0
- cultcache_py-0.2.0.dist-info/METADATA +185 -0
- cultcache_py-0.2.0.dist-info/RECORD +12 -0
- cultcache_py-0.2.0.dist-info/WHEEL +5 -0
- cultcache_py-0.2.0.dist-info/entry_points.txt +2 -0
- cultcache_py-0.2.0.dist-info/top_level.txt +1 -0
cultcache_py/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .backing_store import BackingStore, CultCacheEnvelope
|
|
2
|
+
from .backing_store import CultCacheSchemaCatalogEntry, CultCacheSchemaCatalogMember
|
|
3
|
+
from .cache import CultCache, CultCacheBuilder
|
|
4
|
+
from .documents import (
|
|
5
|
+
DatabaseEntryField,
|
|
6
|
+
DocumentDefinition,
|
|
7
|
+
database_entry_field,
|
|
8
|
+
define_database_entry_type,
|
|
9
|
+
define_document_registry,
|
|
10
|
+
define_document_type,
|
|
11
|
+
)
|
|
12
|
+
from .stores import JsonLinesBackingStore, SingleFileMessagePackBackingStore
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"BackingStore",
|
|
16
|
+
"CultCache",
|
|
17
|
+
"CultCacheBuilder",
|
|
18
|
+
"CultCacheEnvelope",
|
|
19
|
+
"CultCacheSchemaCatalogEntry",
|
|
20
|
+
"CultCacheSchemaCatalogMember",
|
|
21
|
+
"DatabaseEntryField",
|
|
22
|
+
"DocumentDefinition",
|
|
23
|
+
"JsonLinesBackingStore",
|
|
24
|
+
"SingleFileMessagePackBackingStore",
|
|
25
|
+
"database_entry_field",
|
|
26
|
+
"define_database_entry_type",
|
|
27
|
+
"define_document_registry",
|
|
28
|
+
"define_document_type",
|
|
29
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class CultCacheSchemaCatalogMember:
|
|
10
|
+
slot: int
|
|
11
|
+
member_name: str
|
|
12
|
+
type_name: str
|
|
13
|
+
is_reference: bool = False
|
|
14
|
+
is_many: bool = False
|
|
15
|
+
target_schema_name: str | None = None
|
|
16
|
+
is_name: bool = False
|
|
17
|
+
index_alias: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class CultCacheSchemaCatalogEntry:
|
|
22
|
+
schema_id: str
|
|
23
|
+
schema_name: str
|
|
24
|
+
schema_version: str
|
|
25
|
+
content_hash: str
|
|
26
|
+
canonical_schema_json: str
|
|
27
|
+
compatible_schema_ids: tuple[str, ...] = field(default_factory=tuple)
|
|
28
|
+
members: tuple[CultCacheSchemaCatalogMember, ...] = field(default_factory=tuple)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class CultCacheEnvelope:
|
|
33
|
+
key: str
|
|
34
|
+
type: str
|
|
35
|
+
payload: bytes
|
|
36
|
+
stored_at: str
|
|
37
|
+
schema_id: str | None = None
|
|
38
|
+
catalog_entry: CultCacheSchemaCatalogEntry | None = None
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def create(
|
|
42
|
+
cls,
|
|
43
|
+
*,
|
|
44
|
+
key: str,
|
|
45
|
+
type: str,
|
|
46
|
+
payload: bytes,
|
|
47
|
+
schema_id: str | None = None,
|
|
48
|
+
catalog_entry: CultCacheSchemaCatalogEntry | None = None,
|
|
49
|
+
) -> "CultCacheEnvelope":
|
|
50
|
+
return cls(
|
|
51
|
+
key=key,
|
|
52
|
+
type=type,
|
|
53
|
+
payload=payload,
|
|
54
|
+
stored_at=datetime.now(timezone.utc).isoformat(),
|
|
55
|
+
schema_id=schema_id,
|
|
56
|
+
catalog_entry=catalog_entry,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BackingStore(Protocol):
|
|
61
|
+
def pull_all(self) -> list[CultCacheEnvelope]:
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
def push(self, envelope: CultCacheEnvelope) -> None:
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
def delete(self, type: str, key: str) -> None:
|
|
68
|
+
...
|
|
69
|
+
|
|
70
|
+
def push_all(self, envelopes: list[CultCacheEnvelope]) -> None:
|
|
71
|
+
existing = {
|
|
72
|
+
(envelope.type, envelope.key): envelope for envelope in self.pull_all()
|
|
73
|
+
}
|
|
74
|
+
for envelope in envelopes:
|
|
75
|
+
existing[(envelope.type, envelope.key)] = envelope
|
|
76
|
+
self._replace_all(list(existing.values()))
|
|
77
|
+
|
|
78
|
+
def _replace_all(self, envelopes: list[CultCacheEnvelope]) -> None:
|
|
79
|
+
...
|
cultcache_py/cache.py
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
from .backing_store import BackingStore, CultCacheEnvelope
|
|
7
|
+
from .documents import DocumentDefinition, extract_value
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
GLOBAL_KEY = "__global__"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CultCacheError(RuntimeError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class _State:
|
|
19
|
+
documents: dict[str, DocumentDefinition[Any]] = field(default_factory=dict)
|
|
20
|
+
documents_by_schema_name: dict[str, DocumentDefinition[Any]] = field(default_factory=dict)
|
|
21
|
+
values: dict[str, dict[str, Any]] = field(default_factory=dict)
|
|
22
|
+
envelopes: dict[str, dict[str, CultCacheEnvelope]] = field(default_factory=dict)
|
|
23
|
+
stores_by_type: dict[str, list[BackingStore]] = field(default_factory=dict)
|
|
24
|
+
generic_stores: list[BackingStore] = field(default_factory=list)
|
|
25
|
+
name_extractors: dict[str, str | Any] = field(default_factory=dict)
|
|
26
|
+
index_extractors: dict[str, dict[str, str | Any]] = field(default_factory=dict)
|
|
27
|
+
names: dict[tuple[str, str], str] = field(default_factory=dict)
|
|
28
|
+
indexes: dict[tuple[str, str, str], str] = field(default_factory=dict)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CultCacheBuilder:
|
|
32
|
+
def __init__(self) -> None:
|
|
33
|
+
self._cache = CultCache()
|
|
34
|
+
|
|
35
|
+
def register_document_type(self, document: DocumentDefinition[Any]) -> "CultCacheBuilder":
|
|
36
|
+
self._cache.register_document_type(document)
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
def register_registry(self, documents: list[DocumentDefinition[Any]] | tuple[DocumentDefinition[Any], ...]) -> "CultCacheBuilder":
|
|
40
|
+
self._cache.register_registry(documents)
|
|
41
|
+
return self
|
|
42
|
+
|
|
43
|
+
def register_name_lookup(self, document: DocumentDefinition[Any], extractor: str | Any) -> "CultCacheBuilder":
|
|
44
|
+
self._cache.register_name_lookup(document, extractor)
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
def register_index(self, document: DocumentDefinition[Any], index: str, extractor: str | Any) -> "CultCacheBuilder":
|
|
48
|
+
self._cache.register_index(document, index, extractor)
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def add_backing_store(self, store: BackingStore, types: list[str] | tuple[str, ...] | set[str]) -> "CultCacheBuilder":
|
|
52
|
+
self._cache.add_backing_store(store, types)
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def add_generic_store(self, store: BackingStore) -> "CultCacheBuilder":
|
|
56
|
+
self._cache.add_generic_store(store)
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def build(self) -> "CultCache":
|
|
60
|
+
return self._cache
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CultCache:
|
|
64
|
+
GLOBAL_KEY = GLOBAL_KEY
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def builder(cls) -> CultCacheBuilder:
|
|
68
|
+
return CultCacheBuilder()
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
self._state = _State()
|
|
72
|
+
|
|
73
|
+
def register_document_type(self, document: DocumentDefinition[Any]) -> None:
|
|
74
|
+
if document.type in self._state.documents:
|
|
75
|
+
raise CultCacheError(f"Document type already registered: {document.type}")
|
|
76
|
+
schema_name = document.catalog_entry().schema_name
|
|
77
|
+
if schema_name in self._state.documents_by_schema_name:
|
|
78
|
+
raise CultCacheError(f"Document schema name already registered: {schema_name}")
|
|
79
|
+
self._state.documents[document.type] = document
|
|
80
|
+
self._state.documents_by_schema_name[schema_name] = document
|
|
81
|
+
if document.name is not None:
|
|
82
|
+
self.register_name_lookup(document, document.name)
|
|
83
|
+
for index, extractor in document.indexes.items():
|
|
84
|
+
self.register_index(document, index, extractor)
|
|
85
|
+
|
|
86
|
+
def register_registry(self, documents: list[DocumentDefinition[Any]] | tuple[DocumentDefinition[Any], ...]) -> None:
|
|
87
|
+
for document in documents:
|
|
88
|
+
self.register_document_type(document)
|
|
89
|
+
|
|
90
|
+
def register_name_lookup(self, document: DocumentDefinition[Any], extractor: str | Any) -> None:
|
|
91
|
+
self._assert_registered(document)
|
|
92
|
+
self._state.name_extractors[document.type] = extractor
|
|
93
|
+
self._rebuild_indexes()
|
|
94
|
+
|
|
95
|
+
def register_index(self, document: DocumentDefinition[Any], index: str, extractor: str | Any) -> None:
|
|
96
|
+
self._assert_registered(document)
|
|
97
|
+
self._state.index_extractors.setdefault(document.type, {})[index] = extractor
|
|
98
|
+
self._rebuild_indexes()
|
|
99
|
+
|
|
100
|
+
def add_backing_store(self, store: BackingStore, types: list[str] | tuple[str, ...] | set[str]) -> None:
|
|
101
|
+
for type in types:
|
|
102
|
+
self._state.stores_by_type.setdefault(type, []).append(store)
|
|
103
|
+
|
|
104
|
+
def add_generic_store(self, store: BackingStore) -> None:
|
|
105
|
+
self._state.generic_stores.append(store)
|
|
106
|
+
|
|
107
|
+
def pull_all_backing_stores(self) -> None:
|
|
108
|
+
self._state.values.clear()
|
|
109
|
+
self._state.envelopes.clear()
|
|
110
|
+
seen_globals: set[str] = set()
|
|
111
|
+
for store in [*self._all_specific_stores(), *self._state.generic_stores]:
|
|
112
|
+
for envelope in store.pull_all():
|
|
113
|
+
document = self._resolve_document_for_envelope(envelope)
|
|
114
|
+
if document is None:
|
|
115
|
+
raise CultCacheError(f"Unknown persisted document type: {envelope.type}")
|
|
116
|
+
if envelope.type != document.type:
|
|
117
|
+
envelope = CultCacheEnvelope(
|
|
118
|
+
key=envelope.key,
|
|
119
|
+
type=document.type,
|
|
120
|
+
payload=envelope.payload,
|
|
121
|
+
stored_at=envelope.stored_at,
|
|
122
|
+
schema_id=envelope.schema_id,
|
|
123
|
+
catalog_entry=envelope.catalog_entry,
|
|
124
|
+
)
|
|
125
|
+
if document.global_document:
|
|
126
|
+
if envelope.type in seen_globals and envelope.key == GLOBAL_KEY:
|
|
127
|
+
raise CultCacheError(f"Duplicate global document for type: {envelope.type}")
|
|
128
|
+
seen_globals.add(envelope.type)
|
|
129
|
+
value = document.decode_payload(envelope.payload)
|
|
130
|
+
self._state.values.setdefault(envelope.type, {})[envelope.key] = value
|
|
131
|
+
self._state.envelopes.setdefault(envelope.type, {})[envelope.key] = envelope
|
|
132
|
+
self._rebuild_indexes()
|
|
133
|
+
|
|
134
|
+
def get(self, document: DocumentDefinition[T], key: str) -> T | None:
|
|
135
|
+
self._assert_registered(document)
|
|
136
|
+
values = self._state.values.get(document.type)
|
|
137
|
+
return None if values is None else values.get(key)
|
|
138
|
+
|
|
139
|
+
def get_required(self, document: DocumentDefinition[T], key: str) -> T:
|
|
140
|
+
value = self.get(document, key)
|
|
141
|
+
if value is None:
|
|
142
|
+
raise CultCacheError(f"Missing {document.type}:{key}")
|
|
143
|
+
return value
|
|
144
|
+
|
|
145
|
+
def get_all(self, document: DocumentDefinition[T]) -> list[T]:
|
|
146
|
+
self._assert_registered(document)
|
|
147
|
+
return list(self._state.values.get(document.type, {}).values())
|
|
148
|
+
|
|
149
|
+
def get_envelope(self, document: DocumentDefinition[Any], key: str) -> CultCacheEnvelope | None:
|
|
150
|
+
self._assert_registered(document)
|
|
151
|
+
envelopes = self._state.envelopes.get(document.type)
|
|
152
|
+
return None if envelopes is None else envelopes.get(key)
|
|
153
|
+
|
|
154
|
+
def get_required_envelope(self, document: DocumentDefinition[Any], key: str) -> CultCacheEnvelope:
|
|
155
|
+
envelope = self.get_envelope(document, key)
|
|
156
|
+
if envelope is None:
|
|
157
|
+
raise CultCacheError(f"Missing envelope {document.type}:{key}")
|
|
158
|
+
return envelope
|
|
159
|
+
|
|
160
|
+
def get_global(self, document: DocumentDefinition[T]) -> T | None:
|
|
161
|
+
self._assert_global(document)
|
|
162
|
+
return self.get(document, GLOBAL_KEY)
|
|
163
|
+
|
|
164
|
+
def get_required_global(self, document: DocumentDefinition[T]) -> T:
|
|
165
|
+
self._assert_global(document)
|
|
166
|
+
return self.get_required(document, GLOBAL_KEY)
|
|
167
|
+
|
|
168
|
+
def get_key_by_name(self, document: DocumentDefinition[Any], name: str) -> str | None:
|
|
169
|
+
self._assert_registered(document)
|
|
170
|
+
return self._state.names.get((document.type, name))
|
|
171
|
+
|
|
172
|
+
def get_by_name(self, document: DocumentDefinition[T], name: str) -> T | None:
|
|
173
|
+
key = self.get_key_by_name(document, name)
|
|
174
|
+
return None if key is None else self.get(document, key)
|
|
175
|
+
|
|
176
|
+
def get_key_by_index(self, document: DocumentDefinition[Any], index: str, value: str) -> str | None:
|
|
177
|
+
self._assert_registered(document)
|
|
178
|
+
return self._state.indexes.get((document.type, index, value))
|
|
179
|
+
|
|
180
|
+
def get_by_index(self, document: DocumentDefinition[T], index: str, value: str) -> T | None:
|
|
181
|
+
key = self.get_key_by_index(document, index, value)
|
|
182
|
+
return None if key is None else self.get(document, key)
|
|
183
|
+
|
|
184
|
+
def put(self, document: DocumentDefinition[T], key: str, value: T) -> None:
|
|
185
|
+
self._assert_registered(document)
|
|
186
|
+
if document.global_document and key != GLOBAL_KEY:
|
|
187
|
+
raise CultCacheError(f"Global document {document.type} must use key {GLOBAL_KEY}")
|
|
188
|
+
values = self._state.values.setdefault(document.type, {})
|
|
189
|
+
envelopes = self._state.envelopes.setdefault(document.type, {})
|
|
190
|
+
old_value = values.get(key)
|
|
191
|
+
catalog_entry = document.catalog_entry()
|
|
192
|
+
envelope = CultCacheEnvelope.create(
|
|
193
|
+
key=key,
|
|
194
|
+
type=document.type,
|
|
195
|
+
payload=document.encode_payload(value),
|
|
196
|
+
schema_id=catalog_entry.schema_id,
|
|
197
|
+
catalog_entry=catalog_entry,
|
|
198
|
+
)
|
|
199
|
+
stores = self._stores_for_type(document.type)
|
|
200
|
+
for store in stores:
|
|
201
|
+
store.push(envelope)
|
|
202
|
+
if old_value is not None:
|
|
203
|
+
self._remove_value_indexes(document.type, key, old_value)
|
|
204
|
+
values[key] = value
|
|
205
|
+
envelopes[key] = envelope
|
|
206
|
+
self._add_value_indexes(document.type, key, value)
|
|
207
|
+
|
|
208
|
+
def put_envelope(self, document: DocumentDefinition[T], envelope: CultCacheEnvelope) -> T:
|
|
209
|
+
self._assert_registered(document)
|
|
210
|
+
if envelope.type != document.type:
|
|
211
|
+
raise CultCacheError(
|
|
212
|
+
f"Envelope type {envelope.type} does not match document type {document.type}"
|
|
213
|
+
)
|
|
214
|
+
values = self._state.values.setdefault(document.type, {})
|
|
215
|
+
envelopes = self._state.envelopes.setdefault(document.type, {})
|
|
216
|
+
old_value = values.get(envelope.key)
|
|
217
|
+
value = document.decode_payload(envelope.payload)
|
|
218
|
+
for store in self._stores_for_type(document.type):
|
|
219
|
+
store.push(envelope)
|
|
220
|
+
if old_value is not None:
|
|
221
|
+
self._remove_value_indexes(document.type, envelope.key, old_value)
|
|
222
|
+
values[envelope.key] = value
|
|
223
|
+
envelopes[envelope.key] = envelope
|
|
224
|
+
self._add_value_indexes(document.type, envelope.key, value)
|
|
225
|
+
return value
|
|
226
|
+
|
|
227
|
+
def put_envelopes(self, document: DocumentDefinition[T], envelopes: list[CultCacheEnvelope]) -> list[T]:
|
|
228
|
+
self._assert_registered(document)
|
|
229
|
+
values: list[T] = []
|
|
230
|
+
for envelope in envelopes:
|
|
231
|
+
if envelope.type != document.type:
|
|
232
|
+
raise CultCacheError(
|
|
233
|
+
f"Envelope type {envelope.type} does not match document type {document.type}"
|
|
234
|
+
)
|
|
235
|
+
if document.global_document and envelope.key != GLOBAL_KEY:
|
|
236
|
+
raise CultCacheError(f"Global document {document.type} must use key {GLOBAL_KEY}")
|
|
237
|
+
values.append(document.decode_payload(envelope.payload))
|
|
238
|
+
|
|
239
|
+
stores = self._stores_for_type(document.type)
|
|
240
|
+
for store in stores:
|
|
241
|
+
store.push_all(envelopes)
|
|
242
|
+
values_by_key = self._state.values.setdefault(document.type, {})
|
|
243
|
+
envelopes_by_key = self._state.envelopes.setdefault(document.type, {})
|
|
244
|
+
for envelope, value in zip(envelopes, values):
|
|
245
|
+
old_value = values_by_key.get(envelope.key)
|
|
246
|
+
if old_value is not None:
|
|
247
|
+
self._remove_value_indexes(document.type, envelope.key, old_value)
|
|
248
|
+
values_by_key[envelope.key] = value
|
|
249
|
+
envelopes_by_key[envelope.key] = envelope
|
|
250
|
+
self._add_value_indexes(document.type, envelope.key, value)
|
|
251
|
+
return values
|
|
252
|
+
|
|
253
|
+
def put_global(self, document: DocumentDefinition[T], value: T) -> None:
|
|
254
|
+
self._assert_global(document)
|
|
255
|
+
self.put(document, GLOBAL_KEY, value)
|
|
256
|
+
|
|
257
|
+
def update(self, document: DocumentDefinition[T], key: str, updater: Any) -> T:
|
|
258
|
+
current = self.get_required(document, key)
|
|
259
|
+
updated = updater(current)
|
|
260
|
+
self.put(document, key, updated)
|
|
261
|
+
return updated
|
|
262
|
+
|
|
263
|
+
def update_global(self, document: DocumentDefinition[T], updater: Any) -> T:
|
|
264
|
+
self._assert_global(document)
|
|
265
|
+
return self.update(document, GLOBAL_KEY, updater)
|
|
266
|
+
|
|
267
|
+
def delete(self, document: DocumentDefinition[Any], key: str) -> None:
|
|
268
|
+
self._assert_registered(document)
|
|
269
|
+
for store in self._stores_for_type(document.type):
|
|
270
|
+
store.delete(document.type, key)
|
|
271
|
+
values = self._state.values.get(document.type)
|
|
272
|
+
old_value = None if values is None else values.pop(key, None)
|
|
273
|
+
if values == {}:
|
|
274
|
+
self._state.values.pop(document.type, None)
|
|
275
|
+
envelopes = self._state.envelopes.get(document.type)
|
|
276
|
+
if envelopes is not None:
|
|
277
|
+
envelopes.pop(key, None)
|
|
278
|
+
if envelopes == {}:
|
|
279
|
+
self._state.envelopes.pop(document.type, None)
|
|
280
|
+
if old_value is not None:
|
|
281
|
+
self._remove_value_indexes(document.type, key, old_value)
|
|
282
|
+
|
|
283
|
+
def delete_global(self, document: DocumentDefinition[Any]) -> None:
|
|
284
|
+
self._assert_global(document)
|
|
285
|
+
self.delete(document, GLOBAL_KEY)
|
|
286
|
+
|
|
287
|
+
def snapshot(self) -> dict[str, dict[str, Any]]:
|
|
288
|
+
out: dict[str, dict[str, Any]] = {}
|
|
289
|
+
for type, values in self._state.values.items():
|
|
290
|
+
out[type] = dict(values)
|
|
291
|
+
return out
|
|
292
|
+
|
|
293
|
+
def snapshot_envelopes(self) -> list[CultCacheEnvelope]:
|
|
294
|
+
return [
|
|
295
|
+
envelope
|
|
296
|
+
for envelopes in self._state.envelopes.values()
|
|
297
|
+
for envelope in envelopes.values()
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
def _stores_for_type(self, type: str) -> list[BackingStore]:
|
|
301
|
+
specific = self._state.stores_by_type.get(type)
|
|
302
|
+
return specific if specific else self._state.generic_stores
|
|
303
|
+
|
|
304
|
+
def _all_specific_stores(self) -> list[BackingStore]:
|
|
305
|
+
stores: list[BackingStore] = []
|
|
306
|
+
seen: set[int] = set()
|
|
307
|
+
for routed in self._state.stores_by_type.values():
|
|
308
|
+
for store in routed:
|
|
309
|
+
marker = id(store)
|
|
310
|
+
if marker not in seen:
|
|
311
|
+
stores.append(store)
|
|
312
|
+
seen.add(marker)
|
|
313
|
+
return stores
|
|
314
|
+
|
|
315
|
+
def _rebuild_indexes(self) -> None:
|
|
316
|
+
self._state.names.clear()
|
|
317
|
+
self._state.indexes.clear()
|
|
318
|
+
for type, values in self._state.values.items():
|
|
319
|
+
for key, value in values.items():
|
|
320
|
+
self._add_value_indexes(type, key, value)
|
|
321
|
+
|
|
322
|
+
def _add_value_indexes(self, type: str, key: str, value: Any) -> None:
|
|
323
|
+
name_extractor = self._state.name_extractors.get(type)
|
|
324
|
+
if name_extractor is not None:
|
|
325
|
+
name = extract_value(value, name_extractor)
|
|
326
|
+
if name is not None:
|
|
327
|
+
self._state.names[(type, str(name))] = key
|
|
328
|
+
for index, extractor in self._state.index_extractors.get(type, {}).items():
|
|
329
|
+
index_value = extract_value(value, extractor)
|
|
330
|
+
if index_value is not None:
|
|
331
|
+
self._state.indexes[(type, index, str(index_value))] = key
|
|
332
|
+
|
|
333
|
+
def _remove_value_indexes(self, type: str, key: str, value: Any) -> None:
|
|
334
|
+
name_extractor = self._state.name_extractors.get(type)
|
|
335
|
+
if name_extractor is not None:
|
|
336
|
+
name = extract_value(value, name_extractor)
|
|
337
|
+
if name is not None:
|
|
338
|
+
self._remove_name_index(type, key, str(name))
|
|
339
|
+
for index, extractor in self._state.index_extractors.get(type, {}).items():
|
|
340
|
+
index_value = extract_value(value, extractor)
|
|
341
|
+
if index_value is not None:
|
|
342
|
+
self._remove_secondary_index(type, key, index, str(index_value))
|
|
343
|
+
|
|
344
|
+
def _remove_name_index(self, type: str, key: str, name: str) -> None:
|
|
345
|
+
lookup_key = (type, name)
|
|
346
|
+
if self._state.names.get(lookup_key) != key:
|
|
347
|
+
return
|
|
348
|
+
self._state.names.pop(lookup_key, None)
|
|
349
|
+
name_extractor = self._state.name_extractors.get(type)
|
|
350
|
+
if name_extractor is None:
|
|
351
|
+
return
|
|
352
|
+
for candidate_key, candidate in self._state.values.get(type, {}).items():
|
|
353
|
+
candidate_name = extract_value(candidate, name_extractor)
|
|
354
|
+
if candidate_key != key and candidate_name is not None and str(candidate_name) == name:
|
|
355
|
+
self._state.names[lookup_key] = candidate_key
|
|
356
|
+
|
|
357
|
+
def _remove_secondary_index(self, type: str, key: str, index: str, value: str) -> None:
|
|
358
|
+
lookup_key = (type, index, value)
|
|
359
|
+
if self._state.indexes.get(lookup_key) != key:
|
|
360
|
+
return
|
|
361
|
+
self._state.indexes.pop(lookup_key, None)
|
|
362
|
+
extractor = self._state.index_extractors.get(type, {}).get(index)
|
|
363
|
+
if extractor is None:
|
|
364
|
+
return
|
|
365
|
+
for candidate_key, candidate in self._state.values.get(type, {}).items():
|
|
366
|
+
candidate_value = extract_value(candidate, extractor)
|
|
367
|
+
if candidate_key != key and candidate_value is not None and str(candidate_value) == value:
|
|
368
|
+
self._state.indexes[lookup_key] = candidate_key
|
|
369
|
+
|
|
370
|
+
def _assert_registered(self, document: DocumentDefinition[Any]) -> None:
|
|
371
|
+
if self._state.documents.get(document.type) is not document:
|
|
372
|
+
raise CultCacheError(f"Document type is not registered on this cache: {document.type}")
|
|
373
|
+
|
|
374
|
+
def _assert_global(self, document: DocumentDefinition[Any]) -> None:
|
|
375
|
+
self._assert_registered(document)
|
|
376
|
+
if not document.global_document:
|
|
377
|
+
raise CultCacheError(f"Document type is not global: {document.type}")
|
|
378
|
+
|
|
379
|
+
def _resolve_document_for_envelope(self, envelope: CultCacheEnvelope) -> DocumentDefinition[Any] | None:
|
|
380
|
+
document = self._state.documents.get(envelope.type)
|
|
381
|
+
if document is not None:
|
|
382
|
+
return document
|
|
383
|
+
return self._state.documents_by_schema_name.get(envelope.type)
|