nl2data-semantic-catalog-postgres 0.1.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.
- nl2data_semantic_catalog_postgres/__init__.py +18 -0
- nl2data_semantic_catalog_postgres/client.py +139 -0
- nl2data_semantic_catalog_postgres/config.py +136 -0
- nl2data_semantic_catalog_postgres/envelope.py +327 -0
- nl2data_semantic_catalog_postgres/errors.py +188 -0
- nl2data_semantic_catalog_postgres/fake_postgres/__init__.py +53 -0
- nl2data_semantic_catalog_postgres/fake_postgres/driver.py +209 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_audit.py +243 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_drafts.py +84 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_maintenance.py +263 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_publications.py +249 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_schema.py +23 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_snapshots.py +197 -0
- nl2data_semantic_catalog_postgres/fake_postgres/handlers_versions.py +330 -0
- nl2data_semantic_catalog_postgres/fake_postgres/keys.py +99 -0
- nl2data_semantic_catalog_postgres/fake_postgres/pool.py +152 -0
- nl2data_semantic_catalog_postgres/fake_postgres/registry.py +144 -0
- nl2data_semantic_catalog_postgres/maintenance.py +235 -0
- nl2data_semantic_catalog_postgres/py.typed +0 -0
- nl2data_semantic_catalog_postgres/repositories/__init__.py +26 -0
- nl2data_semantic_catalog_postgres/repositories/activation.py +849 -0
- nl2data_semantic_catalog_postgres/repositories/audit_evidence.py +221 -0
- nl2data_semantic_catalog_postgres/repositories/drafts.py +156 -0
- nl2data_semantic_catalog_postgres/repositories/evidence.py +415 -0
- nl2data_semantic_catalog_postgres/repositories/publications.py +486 -0
- nl2data_semantic_catalog_postgres/repositories/snapshots.py +372 -0
- nl2data_semantic_catalog_postgres/schema.py +337 -0
- nl2data_semantic_catalog_postgres/sql.py +520 -0
- nl2data_semantic_catalog_postgres/store.py +716 -0
- nl2data_semantic_catalog_postgres/unit_of_work.py +589 -0
- nl2data_semantic_catalog_postgres-0.1.0.dist-info/METADATA +76 -0
- nl2data_semantic_catalog_postgres-0.1.0.dist-info/RECORD +34 -0
- nl2data_semantic_catalog_postgres-0.1.0.dist-info/WHEEL +5 -0
- nl2data_semantic_catalog_postgres-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Assembly-draft statement handlers for the fake pool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from .driver import _as_dt
|
|
8
|
+
from .keys import _draft_key, _lock_or_fail
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .driver import _FakeConnection
|
|
12
|
+
from .pool import FakePostgresPool
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _h_insert_assembly_draft(
|
|
16
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
17
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
18
|
+
namespace, draft_id = params[0], params[1]
|
|
19
|
+
key = _draft_key(namespace, draft_id)
|
|
20
|
+
_lock_or_fail(pool, conn, key, timeout)
|
|
21
|
+
if (namespace, draft_id) in pool.assembly_drafts:
|
|
22
|
+
return ([], 0)
|
|
23
|
+
conn._touch(key)
|
|
24
|
+
pool.assembly_drafts[(namespace, draft_id)] = {
|
|
25
|
+
"scope_namespace": namespace,
|
|
26
|
+
"draft_id": draft_id,
|
|
27
|
+
"bundle_id": params[2],
|
|
28
|
+
"source_id": params[3],
|
|
29
|
+
"draft_revision": params[4],
|
|
30
|
+
"state": params[5],
|
|
31
|
+
"schema_version": params[6],
|
|
32
|
+
"envelope": params[7],
|
|
33
|
+
"updated_at": _as_dt(params[8]),
|
|
34
|
+
}
|
|
35
|
+
return ([], 1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _h_read_assembly_draft(
|
|
39
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
40
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
41
|
+
row = pool.assembly_drafts.get((params[0], params[1]))
|
|
42
|
+
if row is None:
|
|
43
|
+
return ([], 0)
|
|
44
|
+
return ([{
|
|
45
|
+
"envelope": row["envelope"],
|
|
46
|
+
"schema_version": row["schema_version"],
|
|
47
|
+
"draft_revision": row["draft_revision"],
|
|
48
|
+
}], 0)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _h_lock_assembly_draft(
|
|
52
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
53
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
54
|
+
row = pool.assembly_drafts.get((params[0], params[1]))
|
|
55
|
+
if row is None:
|
|
56
|
+
return ([], 0)
|
|
57
|
+
_lock_or_fail(pool, conn, _draft_key(params[0], params[1]), timeout)
|
|
58
|
+
return ([{
|
|
59
|
+
"envelope": row["envelope"],
|
|
60
|
+
"schema_version": row["schema_version"],
|
|
61
|
+
"draft_revision": row["draft_revision"],
|
|
62
|
+
}], 0)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _h_replace_assembly_draft(
|
|
66
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
67
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
68
|
+
namespace, draft_id, expected_revision = params[7], params[8], params[9]
|
|
69
|
+
key = _draft_key(namespace, draft_id)
|
|
70
|
+
_lock_or_fail(pool, conn, key, timeout)
|
|
71
|
+
row = pool.assembly_drafts.get((namespace, draft_id))
|
|
72
|
+
if row is None or row["draft_revision"] != expected_revision:
|
|
73
|
+
return ([], 0)
|
|
74
|
+
conn._touch(key)
|
|
75
|
+
row.update(
|
|
76
|
+
bundle_id=params[0],
|
|
77
|
+
source_id=params[1],
|
|
78
|
+
draft_revision=params[2],
|
|
79
|
+
state=params[3],
|
|
80
|
+
schema_version=params[4],
|
|
81
|
+
envelope=params[5],
|
|
82
|
+
updated_at=_as_dt(params[6]),
|
|
83
|
+
)
|
|
84
|
+
return ([], 1)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""Lifecycle-event and retention-cleanup statement handlers.
|
|
2
|
+
|
|
3
|
+
The cleanup handlers mirror the real SQL's protection semantics: active
|
|
4
|
+
pointer targets, history entries, and required dependencies are never
|
|
5
|
+
removed, and removal happens in bounded ordered batches.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from typing import TYPE_CHECKING, Any
|
|
12
|
+
|
|
13
|
+
from .driver import _as_dt
|
|
14
|
+
from .keys import (
|
|
15
|
+
_audit_entry_key,
|
|
16
|
+
_event_key,
|
|
17
|
+
_lock_or_fail,
|
|
18
|
+
_publication_key,
|
|
19
|
+
_snap_key,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from .driver import _FakeConnection
|
|
24
|
+
from .pool import FakePostgresPool
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _h_insert_event(
|
|
28
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
29
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
30
|
+
namespace, event_id = params[0], params[1]
|
|
31
|
+
key = _event_key(namespace, event_id)
|
|
32
|
+
# Lock the key slot before checking existence so concurrent inserts of
|
|
33
|
+
# the same event serialize like the real unique index (second one no-ops).
|
|
34
|
+
_lock_or_fail(pool, conn, key, timeout)
|
|
35
|
+
if (namespace, event_id) in pool.events:
|
|
36
|
+
return ([], 0)
|
|
37
|
+
conn._touch(key)
|
|
38
|
+
pool.events[(namespace, event_id)] = {
|
|
39
|
+
"scope_namespace": namespace,
|
|
40
|
+
"event_id": event_id,
|
|
41
|
+
"kind": params[2],
|
|
42
|
+
"member_id": params[3],
|
|
43
|
+
"schema_version": params[4],
|
|
44
|
+
"payload": params[5],
|
|
45
|
+
"occurred_at": _as_dt(params[6]),
|
|
46
|
+
}
|
|
47
|
+
return ([], 1)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _active_bundle_fingerprints(
|
|
51
|
+
pool: FakePostgresPool, active: set[tuple[str, str, str]]
|
|
52
|
+
) -> set[str]:
|
|
53
|
+
"""Catalog/snapshot fingerprints referenced by active bundle envelopes.
|
|
54
|
+
|
|
55
|
+
Mirrors the SQL ``referenced_catalog_fingerprints`` CTE: descriptor
|
|
56
|
+
catalog fingerprints, source catalog fingerprints, and compatibility
|
|
57
|
+
fingerprints of publications currently selected by a bundle pointer.
|
|
58
|
+
"""
|
|
59
|
+
referenced: set[str] = set()
|
|
60
|
+
for row in pool.publications.values():
|
|
61
|
+
if (
|
|
62
|
+
row["scope_namespace"], row["bundle_id"], row["model_version"]
|
|
63
|
+
) not in active:
|
|
64
|
+
continue
|
|
65
|
+
try:
|
|
66
|
+
envelope = json.loads(row["envelope"])
|
|
67
|
+
except ValueError as error:
|
|
68
|
+
raise ValueError(
|
|
69
|
+
"invalid envelope json in bundle_publications"
|
|
70
|
+
) from error
|
|
71
|
+
if envelope.get("kind") != "bundle":
|
|
72
|
+
continue
|
|
73
|
+
payload = envelope.get("payload") or {}
|
|
74
|
+
descriptor = payload.get("descriptor") or {}
|
|
75
|
+
fingerprint = descriptor.get("catalog_fingerprint")
|
|
76
|
+
if fingerprint:
|
|
77
|
+
referenced.add(fingerprint)
|
|
78
|
+
for source in payload.get("sources") or []:
|
|
79
|
+
fingerprint = source.get("catalog_fingerprint")
|
|
80
|
+
if fingerprint:
|
|
81
|
+
referenced.add(fingerprint)
|
|
82
|
+
compatibility = payload.get("compatibility") or {}
|
|
83
|
+
referenced.update(
|
|
84
|
+
compatibility.get("compatible_catalog_fingerprints") or []
|
|
85
|
+
)
|
|
86
|
+
return referenced
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _snapshot_required_by(row: dict[str, Any], referenced: set[str]) -> bool:
|
|
90
|
+
"""True when an active bundle references this snapshot row.
|
|
91
|
+
|
|
92
|
+
Bundles reference a snapshot either by the snapshot's own fingerprint
|
|
93
|
+
(``descriptor.catalog_fingerprint``) or by the source catalog
|
|
94
|
+
fingerprint the snapshot documents (``payload.source.catalog_fingerprint``).
|
|
95
|
+
A row without a source catalog fingerprint is conservatively retained,
|
|
96
|
+
mirroring the SQL ``NOT IN`` semantics that never match NULL.
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
envelope = json.loads(row["envelope"])
|
|
100
|
+
except ValueError as error:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
"invalid envelope json in metadata_snapshots"
|
|
103
|
+
) from error
|
|
104
|
+
source = (envelope.get("payload") or {}).get("source") or {}
|
|
105
|
+
fingerprint = source.get("catalog_fingerprint")
|
|
106
|
+
return fingerprint is None or fingerprint in referenced
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _h_delete_expired_snapshots(
|
|
110
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
111
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
112
|
+
current = _as_dt(params[0])
|
|
113
|
+
limit = int(params[1])
|
|
114
|
+
pointed = {
|
|
115
|
+
row["snapshot_fingerprint"] for row in pool.snapshot_pointers.values()
|
|
116
|
+
}
|
|
117
|
+
active = {
|
|
118
|
+
(row["scope_namespace"], row["bundle_id"], row["model_version"])
|
|
119
|
+
for row in pool.bundle_pointers.values()
|
|
120
|
+
}
|
|
121
|
+
referenced = _active_bundle_fingerprints(pool, active)
|
|
122
|
+
candidates = [
|
|
123
|
+
((row["retained_until"], fingerprint), (namespace, fingerprint))
|
|
124
|
+
for (namespace, fingerprint), row in pool.snapshots.items()
|
|
125
|
+
if row["retained_until"] is not None
|
|
126
|
+
and row["retained_until"] < current
|
|
127
|
+
and fingerprint not in pointed
|
|
128
|
+
and fingerprint not in referenced
|
|
129
|
+
and not _snapshot_required_by(row, referenced)
|
|
130
|
+
]
|
|
131
|
+
removed = 0
|
|
132
|
+
for _, key in sorted(candidates)[:limit]:
|
|
133
|
+
_lock_or_fail(pool, conn, _snap_key(*key), timeout)
|
|
134
|
+
row = pool.snapshots.get(key)
|
|
135
|
+
if row is None:
|
|
136
|
+
continue
|
|
137
|
+
conn._touch(_snap_key(*key))
|
|
138
|
+
del pool.snapshots[key]
|
|
139
|
+
removed += 1
|
|
140
|
+
return ([], removed)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _h_delete_expired_publications(
|
|
144
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
145
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
146
|
+
current = _as_dt(params[0])
|
|
147
|
+
limit = int(params[1])
|
|
148
|
+
protected = {
|
|
149
|
+
(row["scope_namespace"], row["bundle_id"], row["model_version"])
|
|
150
|
+
for row in pool.bundle_pointers.values()
|
|
151
|
+
} | {
|
|
152
|
+
(row["scope_namespace"], row["bundle_id"], row["model_version"])
|
|
153
|
+
for history in pool.bundle_history.values()
|
|
154
|
+
for row in history.values()
|
|
155
|
+
}
|
|
156
|
+
for pub_row in pool.publications.values():
|
|
157
|
+
try:
|
|
158
|
+
envelope = json.loads(pub_row["envelope"])
|
|
159
|
+
except ValueError as error:
|
|
160
|
+
raise ValueError(
|
|
161
|
+
"invalid envelope json in bundle_publications"
|
|
162
|
+
) from error
|
|
163
|
+
if envelope.get("kind") != "bundle":
|
|
164
|
+
continue
|
|
165
|
+
for dependency in (envelope.get("payload") or {}).get(
|
|
166
|
+
"dependencies"
|
|
167
|
+
) or []:
|
|
168
|
+
protected.add(
|
|
169
|
+
(
|
|
170
|
+
pub_row["scope_namespace"],
|
|
171
|
+
dependency.get("bundle_id"),
|
|
172
|
+
dependency.get("version"),
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
candidates = [
|
|
176
|
+
(
|
|
177
|
+
(row["published_at"], bundle_id, version),
|
|
178
|
+
(namespace, bundle_id, version),
|
|
179
|
+
)
|
|
180
|
+
for (namespace, bundle_id, version), row in pool.publications.items()
|
|
181
|
+
if row["published_at"] < current
|
|
182
|
+
and (namespace, bundle_id, version) not in protected
|
|
183
|
+
]
|
|
184
|
+
removed = 0
|
|
185
|
+
for _, key in sorted(candidates)[:limit]:
|
|
186
|
+
_lock_or_fail(pool, conn, _publication_key(*key), timeout)
|
|
187
|
+
row = pool.publications.get(key)
|
|
188
|
+
if row is None:
|
|
189
|
+
continue
|
|
190
|
+
conn._touch(_publication_key(*key))
|
|
191
|
+
del pool.publications[key]
|
|
192
|
+
removed += 1
|
|
193
|
+
return ([], removed)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _h_delete_expired_events(
|
|
197
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
198
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
199
|
+
cutoff = _as_dt(params[0])
|
|
200
|
+
limit = int(params[1])
|
|
201
|
+
candidates = [
|
|
202
|
+
((row["occurred_at"], event_id), (namespace, event_id))
|
|
203
|
+
for (namespace, event_id), row in pool.events.items()
|
|
204
|
+
if row["occurred_at"] < cutoff
|
|
205
|
+
]
|
|
206
|
+
removed = 0
|
|
207
|
+
for _, key in sorted(candidates)[:limit]:
|
|
208
|
+
_lock_or_fail(pool, conn, _event_key(*key), timeout)
|
|
209
|
+
row = pool.events.get(key)
|
|
210
|
+
if row is None:
|
|
211
|
+
continue
|
|
212
|
+
conn._touch(_event_key(*key))
|
|
213
|
+
del pool.events[key]
|
|
214
|
+
removed += 1
|
|
215
|
+
return ([], removed)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _non_retired_audit_fingerprints(pool: FakePostgresPool) -> set[tuple[str, str]]:
|
|
219
|
+
"""(namespace, fingerprint) pairs of non-retired published versions."""
|
|
220
|
+
return {
|
|
221
|
+
(row["scope_namespace"], row["bundle_fingerprint"])
|
|
222
|
+
for row in pool.published_versions.values()
|
|
223
|
+
if row["lifecycle_state"] != "retired"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _predecessors_of_protected(
|
|
228
|
+
pool: FakePostgresPool, protected: set[tuple[str, str]]
|
|
229
|
+
) -> set[str]:
|
|
230
|
+
"""Event ids referenced as predecessors by protected audit entries."""
|
|
231
|
+
ids: set[str] = set()
|
|
232
|
+
for (namespace, _event_id), row in pool.audit_entries.items():
|
|
233
|
+
if (namespace, row["bundle_fingerprint"]) not in protected:
|
|
234
|
+
continue
|
|
235
|
+
envelope = json.loads(row["envelope"])
|
|
236
|
+
ids.update((envelope.get("payload") or {}).get("predecessor_event_ids") or [])
|
|
237
|
+
return ids
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _h_delete_expired_audit_entries(
|
|
241
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
242
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
243
|
+
cutoff = _as_dt(params[0])
|
|
244
|
+
limit = int(params[1])
|
|
245
|
+
protected = _non_retired_audit_fingerprints(pool)
|
|
246
|
+
protected_ids = _predecessors_of_protected(pool, protected)
|
|
247
|
+
candidates = [
|
|
248
|
+
((row["occurred_at"], event_id), (namespace, event_id))
|
|
249
|
+
for (namespace, event_id), row in pool.audit_entries.items()
|
|
250
|
+
if row["occurred_at"] < cutoff
|
|
251
|
+
and (namespace, row["bundle_fingerprint"]) not in protected
|
|
252
|
+
and event_id not in protected_ids
|
|
253
|
+
]
|
|
254
|
+
removed = 0
|
|
255
|
+
for _, key in sorted(candidates)[:limit]:
|
|
256
|
+
_lock_or_fail(pool, conn, _audit_entry_key(*key), timeout)
|
|
257
|
+
row = pool.audit_entries.get(key)
|
|
258
|
+
if row is None:
|
|
259
|
+
continue
|
|
260
|
+
conn._touch(_audit_entry_key(*key))
|
|
261
|
+
del pool.audit_entries[key]
|
|
262
|
+
removed += 1
|
|
263
|
+
return ([], removed)
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Publication, accepted-manifest, evidence, and audit statement handlers.
|
|
2
|
+
|
|
3
|
+
Writers lock the key slot before checking existence so concurrent writers
|
|
4
|
+
serialize like the real unique indexes (the second one no-ops or raises
|
|
5
|
+
``UniqueViolation`` exactly where the real constraint would).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from .driver import UniqueViolation, _as_dt
|
|
13
|
+
from .keys import (
|
|
14
|
+
_audit_key,
|
|
15
|
+
_lock_or_fail,
|
|
16
|
+
_manifest_key,
|
|
17
|
+
_publication_key,
|
|
18
|
+
_verification_evidence_key,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from .driver import _FakeConnection
|
|
23
|
+
from .pool import FakePostgresPool
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _h_lock_publication_series(
|
|
27
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
28
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
29
|
+
_lock_or_fail(pool, conn, ("publication_series", params[0], params[1]), timeout)
|
|
30
|
+
return ([{"pg_advisory_xact_lock": None}], 0)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _h_insert_publication(
|
|
34
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
35
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
36
|
+
namespace, bundle_id, version = params[0], params[1], params[2]
|
|
37
|
+
key = _publication_key(namespace, bundle_id, version)
|
|
38
|
+
# Lock the key slot before checking existence so concurrent publishes of
|
|
39
|
+
# the same version serialize like the real unique index (second one no-ops).
|
|
40
|
+
_lock_or_fail(pool, conn, key, timeout)
|
|
41
|
+
if (namespace, bundle_id, version) in pool.publications:
|
|
42
|
+
return ([], 0)
|
|
43
|
+
conn._touch(key)
|
|
44
|
+
pool.publications[(namespace, bundle_id, version)] = {
|
|
45
|
+
"scope_namespace": namespace,
|
|
46
|
+
"bundle_id": bundle_id,
|
|
47
|
+
"model_version": version,
|
|
48
|
+
"bundle_fingerprint": params[3],
|
|
49
|
+
"schema_version": params[4],
|
|
50
|
+
"envelope": params[5],
|
|
51
|
+
"published_at": _as_dt(params[6]),
|
|
52
|
+
}
|
|
53
|
+
return ([], 1)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _h_read_publication(
|
|
57
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
58
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
59
|
+
row = pool.publications.get((params[0], params[1], params[2]))
|
|
60
|
+
if row is None:
|
|
61
|
+
return ([], 0)
|
|
62
|
+
return (
|
|
63
|
+
[
|
|
64
|
+
{
|
|
65
|
+
"envelope": row["envelope"],
|
|
66
|
+
"schema_version": row["schema_version"],
|
|
67
|
+
"published_at": row["published_at"],
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
0,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _h_read_publication_fingerprint(
|
|
75
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
76
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
77
|
+
row = pool.publications.get((params[0], params[1], params[2]))
|
|
78
|
+
if row is None:
|
|
79
|
+
return ([], 0)
|
|
80
|
+
return ([{"bundle_fingerprint": row["bundle_fingerprint"]}], 0)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _h_read_publication_by_fingerprint(
|
|
84
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
85
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
86
|
+
namespace, bundle_id, fingerprint = params
|
|
87
|
+
for (scope, candidate_id, _version), row in pool.publications.items():
|
|
88
|
+
if (
|
|
89
|
+
scope == namespace
|
|
90
|
+
and candidate_id == bundle_id
|
|
91
|
+
and row["bundle_fingerprint"] == fingerprint
|
|
92
|
+
):
|
|
93
|
+
return ([{
|
|
94
|
+
"envelope": row["envelope"],
|
|
95
|
+
"schema_version": row["schema_version"],
|
|
96
|
+
"published_at": row["published_at"],
|
|
97
|
+
"model_version": row["model_version"],
|
|
98
|
+
}], 0)
|
|
99
|
+
return ([], 0)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _h_list_publications(
|
|
103
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
104
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
105
|
+
namespace, bundle_id = params[0], params[1]
|
|
106
|
+
items = [
|
|
107
|
+
(
|
|
108
|
+
row["published_at"],
|
|
109
|
+
row["model_version"],
|
|
110
|
+
row["schema_version"],
|
|
111
|
+
row["envelope"],
|
|
112
|
+
)
|
|
113
|
+
for (ns, bid, _version), row in pool.publications.items()
|
|
114
|
+
if ns == namespace and bid == bundle_id
|
|
115
|
+
]
|
|
116
|
+
items.sort() # ORDER BY published_at, model_version
|
|
117
|
+
rows = [
|
|
118
|
+
{
|
|
119
|
+
"envelope": envelope,
|
|
120
|
+
"model_version": model_version,
|
|
121
|
+
"schema_version": schema_version,
|
|
122
|
+
}
|
|
123
|
+
for _published_at, model_version, schema_version, envelope in items
|
|
124
|
+
]
|
|
125
|
+
return (rows, len(rows))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _h_insert_accepted_manifest(
|
|
129
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
130
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
131
|
+
key = _manifest_key(params[0], params[1], params[2])
|
|
132
|
+
_lock_or_fail(pool, conn, key, timeout)
|
|
133
|
+
if key[1:] in pool.accepted_manifests:
|
|
134
|
+
raise UniqueViolation("accepted manifest already exists")
|
|
135
|
+
conn._touch(key)
|
|
136
|
+
pool.accepted_manifests[key[1:]] = {
|
|
137
|
+
"scope_namespace": params[0],
|
|
138
|
+
"bundle_id": params[1],
|
|
139
|
+
"bundle_fingerprint": params[2],
|
|
140
|
+
"schema_version": params[3],
|
|
141
|
+
"envelope": params[4],
|
|
142
|
+
"created_at": _as_dt(params[5]),
|
|
143
|
+
}
|
|
144
|
+
return ([], 1)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _h_read_accepted_manifest(
|
|
148
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
149
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
150
|
+
row = pool.accepted_manifests.get(params)
|
|
151
|
+
if row is None:
|
|
152
|
+
return ([], 0)
|
|
153
|
+
return ([{"envelope": row["envelope"], "schema_version": row["schema_version"]}], 0)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _h_insert_verification_evidence(
|
|
157
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
158
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
159
|
+
key = _verification_evidence_key(params[0], params[1], params[2])
|
|
160
|
+
_lock_or_fail(pool, conn, key, timeout)
|
|
161
|
+
if key[1:] in pool.verification_evidence:
|
|
162
|
+
raise UniqueViolation("verification evidence already exists")
|
|
163
|
+
if any(
|
|
164
|
+
row["scope_namespace"] == params[0]
|
|
165
|
+
and row["evidence_fingerprint"] == params[3]
|
|
166
|
+
for row in pool.verification_evidence.values()
|
|
167
|
+
):
|
|
168
|
+
raise UniqueViolation("verification evidence fingerprint already exists")
|
|
169
|
+
conn._touch(key)
|
|
170
|
+
pool.verification_evidence[key[1:]] = {
|
|
171
|
+
"scope_namespace": params[0],
|
|
172
|
+
"bundle_id": params[1],
|
|
173
|
+
"bundle_fingerprint": params[2],
|
|
174
|
+
"evidence_fingerprint": params[3],
|
|
175
|
+
"schema_version": params[4],
|
|
176
|
+
"envelope": params[5],
|
|
177
|
+
"created_at": _as_dt(params[6]),
|
|
178
|
+
}
|
|
179
|
+
return ([], 1)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _h_read_verification_evidence(
|
|
183
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
184
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
185
|
+
row = pool.verification_evidence.get(params)
|
|
186
|
+
if row is None:
|
|
187
|
+
return ([], 0)
|
|
188
|
+
return (
|
|
189
|
+
[
|
|
190
|
+
{
|
|
191
|
+
"evidence_fingerprint": row["evidence_fingerprint"],
|
|
192
|
+
"envelope": row["envelope"],
|
|
193
|
+
"schema_version": row["schema_version"],
|
|
194
|
+
}
|
|
195
|
+
],
|
|
196
|
+
0,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _h_insert_publish_audit(
|
|
201
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
202
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
203
|
+
key = _audit_key(params[0], params[1], params[2])
|
|
204
|
+
_lock_or_fail(pool, conn, key, timeout)
|
|
205
|
+
if key[1:] in pool.publish_audits:
|
|
206
|
+
raise UniqueViolation("publish audit already exists")
|
|
207
|
+
if params[4] is not None and any(
|
|
208
|
+
row["scope_namespace"] == params[0]
|
|
209
|
+
and row["idempotency_key"] == params[4]
|
|
210
|
+
for row in pool.publish_audits.values()
|
|
211
|
+
):
|
|
212
|
+
raise UniqueViolation("idempotency key already exists")
|
|
213
|
+
conn._touch(key)
|
|
214
|
+
pool.publish_audits[key[1:]] = {
|
|
215
|
+
"scope_namespace": params[0],
|
|
216
|
+
"bundle_id": params[1],
|
|
217
|
+
"bundle_fingerprint": params[2],
|
|
218
|
+
"audit_id": params[3],
|
|
219
|
+
"idempotency_key": params[4],
|
|
220
|
+
"schema_version": params[5],
|
|
221
|
+
"envelope": params[6],
|
|
222
|
+
"created_at": _as_dt(params[7]),
|
|
223
|
+
}
|
|
224
|
+
return ([], 1)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _h_read_publish_audit(
|
|
228
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
229
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
230
|
+
row = pool.publish_audits.get(params)
|
|
231
|
+
if row is None:
|
|
232
|
+
return ([], 0)
|
|
233
|
+
return ([{"envelope": row["envelope"], "schema_version": row["schema_version"]}], 0)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _h_read_publish_by_idempotency_key(
|
|
237
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
238
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
239
|
+
namespace, idempotency_key = params
|
|
240
|
+
for row in pool.publish_audits.values():
|
|
241
|
+
if (
|
|
242
|
+
row["scope_namespace"] == namespace
|
|
243
|
+
and row["idempotency_key"] == idempotency_key
|
|
244
|
+
):
|
|
245
|
+
return ([{
|
|
246
|
+
"bundle_id": row["bundle_id"],
|
|
247
|
+
"bundle_fingerprint": row["bundle_fingerprint"],
|
|
248
|
+
}], 0)
|
|
249
|
+
return ([], 0)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Schema-version statement handlers for the fake pool."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .driver import _FakeConnection
|
|
9
|
+
from .pool import FakePostgresPool
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _h_read_schema_version(
|
|
13
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
14
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
15
|
+
value = pool.schema_metadata.get("schema_version")
|
|
16
|
+
return ([{"value": value}] if value is not None else [], 0)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _h_write_schema_version(
|
|
20
|
+
pool: FakePostgresPool, conn: _FakeConnection, params: tuple[Any, ...], timeout: float
|
|
21
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
22
|
+
pool.schema_metadata["schema_version"] = str(params[0])
|
|
23
|
+
return ([], 1)
|