backpack-store 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.
@@ -0,0 +1,49 @@
1
+ """Experimental typed local records with explicitly registered codecs."""
2
+
3
+ from .codecs import Codec, CodecRegistry, DataclassCodec, EncodedValue
4
+ from .errors import (
5
+ BackpackError,
6
+ CodecError,
7
+ DatabaseVersionError,
8
+ InvalidCursorError,
9
+ MigrationError,
10
+ NotFoundError,
11
+ RegistrationError,
12
+ RevisionConflictError,
13
+ StorageError,
14
+ StoreClosedError,
15
+ UnknownTypeError,
16
+ UnknownVersionError,
17
+ ValidationError,
18
+ )
19
+ from .records import JSONObject, JSONValue, Provenance, RawRecord, Record
20
+ from .store import Backpack, ExportResult, FindPage, WriteResult
21
+
22
+ __all__ = [
23
+ "Backpack",
24
+ "ExportResult",
25
+ "MigrationError",
26
+ "FindPage",
27
+ "WriteResult",
28
+ "BackpackError",
29
+ "Codec",
30
+ "CodecError",
31
+ "CodecRegistry",
32
+ "DataclassCodec",
33
+ "DatabaseVersionError",
34
+ "EncodedValue",
35
+ "InvalidCursorError",
36
+ "JSONObject",
37
+ "JSONValue",
38
+ "NotFoundError",
39
+ "Provenance",
40
+ "RawRecord",
41
+ "Record",
42
+ "RegistrationError",
43
+ "RevisionConflictError",
44
+ "StorageError",
45
+ "StoreClosedError",
46
+ "UnknownTypeError",
47
+ "UnknownVersionError",
48
+ "ValidationError",
49
+ ]
@@ -0,0 +1,318 @@
1
+ """Trusted, explicit codecs; no stored name is ever treated as an import path."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import types
7
+ from collections.abc import Callable
8
+ from dataclasses import dataclass, fields, is_dataclass
9
+ from typing import (
10
+ Generic,
11
+ Literal,
12
+ Protocol,
13
+ TypeVar,
14
+ Union,
15
+ get_args,
16
+ get_origin,
17
+ get_type_hints,
18
+ )
19
+
20
+ from .errors import (
21
+ CodecError,
22
+ MigrationError,
23
+ RegistrationError,
24
+ UnknownTypeError,
25
+ UnknownVersionError,
26
+ ValidationError,
27
+ )
28
+ from .records import JSONObject, json_object, positive_integer, validate_type_key
29
+
30
+ T = TypeVar("T")
31
+
32
+
33
+ class Codec(Protocol[T]):
34
+ type_key: str
35
+ schema_version: int
36
+ model_type: type[T]
37
+
38
+ def encode(self, value: T) -> JSONObject: ...
39
+
40
+ def decode(self, payload: JSONObject) -> T: ...
41
+
42
+
43
+ def _model_fields(model_type: type) -> dict[str, object]:
44
+ hints = get_type_hints(model_type)
45
+ declared = fields(model_type)
46
+ if any(not field.init or field.name not in hints for field in declared):
47
+ raise RegistrationError("dataclass codecs require annotated constructor fields")
48
+ return {field.name: hints[field.name] for field in declared}
49
+
50
+
51
+ def _check_type(annotation: object, stack: tuple[type, ...] = ()) -> None:
52
+ if annotation in (str, bool, int, float, type(None)):
53
+ return
54
+ origin, args = get_origin(annotation), get_args(annotation)
55
+ if origin is Literal:
56
+ if not args or any(type(v) not in (str, bool, int, type(None)) for v in args):
57
+ raise RegistrationError("Literal fields must contain JSON scalar choices")
58
+ return
59
+ if origin in (Union, types.UnionType):
60
+ for arg in args:
61
+ _check_type(arg, stack)
62
+ return
63
+ if origin in (list, tuple) and args:
64
+ for arg in args:
65
+ if arg is not Ellipsis:
66
+ _check_type(arg, stack)
67
+ return
68
+ if origin is dict and len(args) == 2 and args[0] is str:
69
+ _check_type(args[1], stack)
70
+ return
71
+ if isinstance(annotation, type) and is_dataclass(annotation):
72
+ if annotation in stack:
73
+ raise RegistrationError("recursive dataclasses require a custom codec")
74
+ for field_type in _model_fields(annotation).values():
75
+ _check_type(field_type, (*stack, annotation))
76
+ return
77
+ raise RegistrationError("dataclass fields must use concrete supported JSON types")
78
+
79
+
80
+ def _convert(annotation: object, value: object, *, decode: bool, path: str) -> object:
81
+ def invalid() -> ValidationError:
82
+ return ValidationError(f"{path} does not match its declared type")
83
+
84
+ if annotation in (str, bool, int, type(None)):
85
+ if type(value) is not annotation:
86
+ raise invalid()
87
+ return value
88
+ if annotation is float:
89
+ if type(value) not in (int, float):
90
+ raise invalid()
91
+ try:
92
+ number = float(value)
93
+ except OverflowError as exc:
94
+ raise invalid() from exc
95
+ if not math.isfinite(number):
96
+ raise invalid()
97
+ return number
98
+ origin, args = get_origin(annotation), get_args(annotation)
99
+ if origin is Literal:
100
+ if not any(type(value) is type(option) and value == option for option in args):
101
+ raise invalid()
102
+ return value
103
+ if origin in (Union, types.UnionType):
104
+ for arg in args:
105
+ try:
106
+ return _convert(arg, value, decode=decode, path=path)
107
+ except ValidationError:
108
+ continue
109
+ raise invalid()
110
+ if origin in (list, tuple):
111
+ expected = list if decode or origin is list else tuple
112
+ if type(value) is not expected:
113
+ raise invalid()
114
+ if origin is tuple and not (len(args) == 2 and args[1] is Ellipsis):
115
+ if len(value) != len(args):
116
+ raise invalid()
117
+ element_types = args
118
+ else:
119
+ element_types = (args[0],) * len(value)
120
+ result = [
121
+ _convert(t, v, decode=decode, path=f"{path}[{i}]")
122
+ for i, (t, v) in enumerate(zip(element_types, value, strict=True))
123
+ ]
124
+ return tuple(result) if decode and origin is tuple else result
125
+ if origin is dict:
126
+ if type(value) is not dict or any(type(k) is not str for k in value):
127
+ raise invalid()
128
+ return {
129
+ key: _convert(args[1], child, decode=decode, path=f"{path}.*")
130
+ for key, child in value.items()
131
+ }
132
+ if isinstance(annotation, type) and is_dataclass(annotation):
133
+ model_fields = _model_fields(annotation)
134
+ if decode:
135
+ if type(value) is not dict or set(value) != set(model_fields):
136
+ raise invalid()
137
+ kwargs = {
138
+ name: _convert(t, value[name], decode=True, path=f"{path}.{name}")
139
+ for name, t in model_fields.items()
140
+ }
141
+ try:
142
+ return annotation(**kwargs)
143
+ except Exception as exc: # noqa: BLE001 - trusted constructors are an explicit codec boundary
144
+ raise CodecError("dataclass construction failed") from exc
145
+ if type(value) is not annotation:
146
+ raise invalid()
147
+ return {
148
+ name: _convert(t, getattr(value, name), decode=False, path=f"{path}.{name}")
149
+ for name, t in model_fields.items()
150
+ }
151
+ raise invalid()
152
+
153
+
154
+ @dataclass(frozen=True, init=False)
155
+ class DataclassCodec(Generic[T]):
156
+ """Finite JSON for concrete dataclasses, nested dataclasses and typed containers."""
157
+
158
+ model_type: type[T]
159
+ type_key: str
160
+ schema_version: int
161
+
162
+ def __init__(self, model_type: type[T], *, type_key: str, schema_version: int = 1):
163
+ if not isinstance(model_type, type) or not is_dataclass(model_type):
164
+ raise RegistrationError("model_type must be a dataclass class")
165
+ validate_type_key(type_key)
166
+ positive_integer(schema_version, "schema_version")
167
+ try:
168
+ _check_type(model_type)
169
+ except (NameError, TypeError) as exc:
170
+ raise RegistrationError(
171
+ "dataclass annotations could not be resolved"
172
+ ) from exc
173
+ object.__setattr__(self, "model_type", model_type)
174
+ object.__setattr__(self, "type_key", type_key)
175
+ object.__setattr__(self, "schema_version", schema_version)
176
+
177
+ def encode(self, value: T) -> JSONObject:
178
+ return json_object(
179
+ _convert(self.model_type, value, decode=False, path="payload")
180
+ )
181
+
182
+ def decode(self, payload: JSONObject) -> T:
183
+ return _convert(
184
+ self.model_type, json_object(payload), decode=True, path="payload"
185
+ )
186
+
187
+
188
+ @dataclass(frozen=True)
189
+ class EncodedValue:
190
+ type_key: str
191
+ schema_version: int
192
+ payload: JSONObject
193
+
194
+
195
+ class CodecRegistry:
196
+ def __init__(self) -> None:
197
+ self._by_key: dict[str, Codec] = {}
198
+ self._by_type: dict[type, Codec] = {}
199
+ self._migrations: dict[tuple[str, int], Callable[[JSONObject], JSONObject]] = {}
200
+
201
+ def register(self, codec: Codec[T]) -> None:
202
+ try:
203
+ validate_type_key(codec.type_key)
204
+ positive_integer(codec.schema_version, "schema_version")
205
+ if (
206
+ not isinstance(codec.model_type, type)
207
+ or not callable(codec.encode)
208
+ or not callable(codec.decode)
209
+ ):
210
+ raise RegistrationError(
211
+ "codec requires a model class and encode/decode methods"
212
+ )
213
+ except (AttributeError, ValidationError) as exc:
214
+ raise RegistrationError("codec identity is invalid") from exc
215
+ if codec.type_key in self._by_key or codec.model_type in self._by_type:
216
+ raise RegistrationError(
217
+ "codec type key or model type is already registered"
218
+ )
219
+ self._by_key[codec.type_key] = codec
220
+ self._by_type[codec.model_type] = codec
221
+
222
+ @property
223
+ def registered_keys(self) -> tuple[str, ...]:
224
+ return tuple(sorted(self._by_key))
225
+
226
+ def for_key(self, type_key: str) -> Codec:
227
+ validate_type_key(type_key)
228
+ try:
229
+ return self._by_key[type_key]
230
+ except KeyError as exc:
231
+ raise UnknownTypeError(
232
+ "register a trusted codec for the stored type_key"
233
+ ) from exc
234
+
235
+ def for_type(self, model_type: type[T]) -> Codec[T]:
236
+ try:
237
+ return self._by_type[model_type]
238
+ except (KeyError, TypeError) as exc:
239
+ raise UnknownTypeError(
240
+ "register a trusted codec for this exact Python type"
241
+ ) from exc
242
+
243
+ def encode(self, value: T) -> EncodedValue:
244
+ codec = self.for_type(type(value))
245
+ try:
246
+ payload = json_object(codec.encode(value))
247
+ except ValidationError:
248
+ raise
249
+ except Exception as exc: # noqa: BLE001 - custom codec messages may contain record data
250
+ raise CodecError("registered codec failed to encode the value") from exc
251
+ return EncodedValue(codec.type_key, codec.schema_version, payload)
252
+
253
+ def register_migration(
254
+ self,
255
+ type_key: str,
256
+ *,
257
+ from_version: int,
258
+ to_version: int,
259
+ migrate: Callable[[JSONObject], JSONObject],
260
+ ) -> None:
261
+ codec = self.for_key(type_key)
262
+ try:
263
+ positive_integer(from_version, "from_version")
264
+ positive_integer(to_version, "to_version")
265
+ except ValidationError as exc:
266
+ raise RegistrationError(
267
+ "migration versions must be positive integers"
268
+ ) from exc
269
+ if (
270
+ to_version != from_version + 1
271
+ or to_version > codec.schema_version
272
+ or not callable(migrate)
273
+ ):
274
+ raise RegistrationError(
275
+ "migration must advance one version toward the registered codec"
276
+ )
277
+ key = (type_key, from_version)
278
+ if key in self._migrations:
279
+ raise RegistrationError(
280
+ "migration from this payload version is already registered"
281
+ )
282
+ self._migrations[key] = migrate
283
+
284
+ def _upgrade(self, type_key: str, version: int, payload: JSONObject) -> JSONObject:
285
+ codec = self.for_key(type_key)
286
+ positive_integer(version, "schema_version")
287
+ if version > codec.schema_version:
288
+ raise UnknownVersionError(
289
+ "stored payload is newer than the registered codec"
290
+ )
291
+ current = json_object(payload)
292
+ while version < codec.schema_version:
293
+ migrate = self._migrations.get((type_key, version))
294
+ if migrate is None:
295
+ raise UnknownVersionError(
296
+ "register every explicit payload migration to the current version"
297
+ )
298
+ try:
299
+ current = json_object(migrate(current))
300
+ except Exception: # noqa: BLE001 - sanitize trusted migration failures and reject invalid JSON
301
+ raise MigrationError(
302
+ "registered payload migration failed validation or execution"
303
+ ) from None
304
+ version += 1
305
+ return current
306
+
307
+ def decode(self, type_key: str, schema_version: int, payload: JSONObject) -> object:
308
+ codec = self.for_key(type_key)
309
+ payload = self._upgrade(type_key, schema_version, payload)
310
+ try:
311
+ value = codec.decode(payload)
312
+ except (ValidationError, CodecError):
313
+ raise
314
+ except Exception as exc: # noqa: BLE001 - custom codec messages may contain record data
315
+ raise CodecError("registered codec failed to decode the payload") from exc
316
+ if type(value) is not codec.model_type:
317
+ raise CodecError("registered codec returned a different Python type")
318
+ return value
@@ -0,0 +1,53 @@
1
+ """Actionable errors that do not include record payloads or configured secrets."""
2
+
3
+
4
+ class BackpackError(Exception):
5
+ """Base error for storage, registration and record validation."""
6
+
7
+
8
+ class ValidationError(BackpackError, ValueError):
9
+ """A value does not satisfy the explicit record or codec contract."""
10
+
11
+
12
+ class RegistrationError(BackpackError):
13
+ """A codec conflicts with an existing registration or is invalid."""
14
+
15
+
16
+ class UnknownTypeError(BackpackError):
17
+ """No trusted codec is registered for a Python type or stable key."""
18
+
19
+
20
+ class UnknownVersionError(BackpackError):
21
+ """The registered codec cannot decode this payload schema version."""
22
+
23
+
24
+ class CodecError(BackpackError):
25
+ """A trusted custom codec failed to encode or decode a value."""
26
+
27
+
28
+ class NotFoundError(BackpackError):
29
+ """The requested local record does not exist."""
30
+
31
+
32
+ class RevisionConflictError(BackpackError):
33
+ """The record changed after the caller's expected revision."""
34
+
35
+
36
+ class StorageError(BackpackError):
37
+ """A database operation failed or stored data is malformed."""
38
+
39
+
40
+ class StoreClosedError(StorageError):
41
+ """The owned database connection has already been closed."""
42
+
43
+
44
+ class DatabaseVersionError(StorageError):
45
+ """This database schema version is not supported."""
46
+
47
+
48
+ class InvalidCursorError(ValidationError):
49
+ """A find cursor is malformed or belongs to a different query."""
50
+
51
+
52
+ class MigrationError(CodecError):
53
+ """An explicitly registered payload migration failed."""
backpack_store/host.py ADDED
@@ -0,0 +1,45 @@
1
+ """Local application binding; no default path, database discovery or model imports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from typing import TYPE_CHECKING
7
+
8
+ from .store import Backpack
9
+
10
+ if TYPE_CHECKING:
11
+ from hidden_moves.adapters import CapabilityCatalog
12
+
13
+
14
+ def build_catalog(
15
+ store: Backpack, names: Iterable[str], *, allow_writes: bool = False
16
+ ) -> CapabilityCatalog:
17
+ """Select operations on a configured store; writes require separate host intent."""
18
+ from hidden_moves import Moves, discover_providers, load_provider
19
+ from hidden_moves.adapters import CapabilityCatalog
20
+
21
+ from .integration import WRITE_CAPABILITIES
22
+
23
+ names = tuple(names)
24
+ if not names:
25
+ raise ValueError("Select at least one Backpack capability.")
26
+ if not allow_writes and any(name in WRITE_CAPABILITIES for name in names):
27
+ raise ValueError(
28
+ "Select the local write profile explicitly with allow_writes=True."
29
+ )
30
+ entries = [entry for entry in discover_providers() if entry.name == "backpack"]
31
+ if len(entries) != 1:
32
+ raise ValueError("Install exactly one Backpack provider.")
33
+ moves = Moves(target=store)
34
+ load_provider(entries[0], moves.registry)
35
+ return CapabilityCatalog(moves, names)
36
+
37
+
38
+ async def serve_store(
39
+ store: Backpack, names: Iterable[str], *, allow_writes: bool = False
40
+ ) -> None:
41
+ """Serve selected tools over stdio; the calling application owns cleanup."""
42
+ from hidden_moves_mcp import MCPAdapter, serve_stdio
43
+
44
+ catalog = build_catalog(store, names, allow_writes=allow_writes)
45
+ await serve_stdio(MCPAdapter(catalog).server("backpack"))
@@ -0,0 +1,122 @@
1
+ """Optional selected tools around an explicitly configured local store."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from hidden_moves import MoveAnnotations, MoveSpec
7
+
8
+ from .errors import BackpackError, CodecError
9
+ from .records import Provenance, Record, json_object
10
+ from .store import Backpack, WriteResult
11
+
12
+ READ_CAPABILITIES = ("backpack.records.get", "backpack.records.find")
13
+ WRITE_CAPABILITIES = ("backpack.records.put", "backpack.records.delete")
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class RecordView:
18
+ id: str
19
+ type_key: str
20
+ schema_version: int
21
+ revision: int
22
+ payload: dict[str, Any]
23
+ created_at: str
24
+ updated_at: str
25
+ tags: list[str]
26
+ provenance: Provenance | None
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class RecordPage:
31
+ records: list[RecordView]
32
+ next_cursor: str | None
33
+
34
+
35
+ def _view(record: Record) -> RecordView:
36
+ return RecordView(
37
+ record.id,
38
+ record.type_key,
39
+ record.schema_version,
40
+ record.revision,
41
+ record.payload,
42
+ record.created_at,
43
+ record.updated_at,
44
+ list(record.tags),
45
+ record.provenance,
46
+ )
47
+
48
+
49
+ def get_record(store: Backpack, record_id: str) -> RecordView:
50
+ """Read a local record through its explicitly registered codec."""
51
+ return _view(store.get_record(record_id))
52
+
53
+
54
+ def find_records(
55
+ store: Backpack,
56
+ type_key: str,
57
+ tags: list[str] | None = None,
58
+ limit: int = 50,
59
+ cursor: str | None = None,
60
+ ) -> RecordPage:
61
+ """Read a bounded page for one registered type and all requested exact tags."""
62
+ codec = store.codec_for_key(type_key)
63
+ page = store.find(
64
+ codec.model_type, tags=() if tags is None else tags, limit=limit, cursor=cursor
65
+ )
66
+ return RecordPage([_view(record) for record in page.records], page.next_cursor)
67
+
68
+
69
+ def put_record(
70
+ store: Backpack,
71
+ type_key: str,
72
+ payload: dict[str, Any],
73
+ tags: list[str] | None = None,
74
+ provenance: Provenance | None = None,
75
+ ) -> WriteResult:
76
+ """Insert a new local UUID after validating the current registered payload type."""
77
+ codec = store.codec_for_key(type_key)
78
+ try:
79
+ value = codec.decode(json_object(payload))
80
+ except BackpackError:
81
+ raise
82
+ except Exception: # noqa: BLE001 - trusted custom codec failures must not expose payloads
83
+ raise CodecError(
84
+ "registered codec failed to decode the supplied payload"
85
+ ) from None
86
+ if type(value) is not codec.model_type:
87
+ raise CodecError("registered codec returned a different Python type")
88
+ return store.put(value, tags=() if tags is None else tags, provenance=provenance)
89
+
90
+
91
+ def delete_record(store: Backpack, record_id: str) -> bool:
92
+ """Delete one explicit local UUID, reporting whether it existed."""
93
+ return store.delete(record_id)
94
+
95
+
96
+ def provide_moves() -> tuple[MoveSpec, ...]:
97
+ """Advertise definitions without opening a database or choosing codecs."""
98
+ read = MoveAnnotations(
99
+ read_only=True, destructive=False, idempotent=True, external=False
100
+ )
101
+ insert = MoveAnnotations(
102
+ read_only=False, destructive=False, idempotent=False, external=False
103
+ )
104
+ delete = MoveAnnotations(
105
+ read_only=False, destructive=True, idempotent=True, external=False
106
+ )
107
+ return tuple(
108
+ MoveSpec(
109
+ name=name,
110
+ namespace="backpack.records",
111
+ func=operation,
112
+ bind_target=True,
113
+ target_types=(Backpack,),
114
+ annotations=annotations,
115
+ )
116
+ for name, operation, annotations in (
117
+ ("get", get_record, read),
118
+ ("find", find_records, read),
119
+ ("put", put_record, insert),
120
+ ("delete", delete_record, delete),
121
+ )
122
+ )
@@ -0,0 +1,87 @@
1
+ """Optional Pydantic 2 codec; the core package never imports this extension."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from dataclasses import dataclass
7
+ from typing import Generic, TypeVar
8
+
9
+ try:
10
+ from pydantic import BaseModel
11
+ from pydantic import ValidationError as PydanticValidationError
12
+ from pydantic import __version__ as pydantic_version
13
+ except ImportError:
14
+ raise ImportError(
15
+ "Install backpack-store with its [pydantic] extra to use this codec."
16
+ ) from None
17
+
18
+ from .errors import CodecError, RegistrationError, ValidationError
19
+ from .records import JSONObject, json_object, positive_integer, validate_type_key
20
+
21
+ if not pydantic_version.startswith("2."):
22
+ raise ImportError("The Backpack Pydantic codec supports Pydantic 2 only.")
23
+
24
+ T = TypeVar("T", bound=BaseModel)
25
+
26
+
27
+ @dataclass(frozen=True, init=False)
28
+ class PydanticCodec(Generic[T]):
29
+ """Strict JSON validation for one explicitly registered object-shaped model."""
30
+
31
+ model_type: type[T]
32
+ type_key: str
33
+ schema_version: int
34
+
35
+ def __init__(self, model_type: type[T], *, type_key: str, schema_version: int = 1):
36
+ if (
37
+ not isinstance(model_type, type)
38
+ or not issubclass(model_type, BaseModel)
39
+ or model_type is BaseModel
40
+ or model_type.__pydantic_root_model__
41
+ ):
42
+ raise RegistrationError(
43
+ "model_type must be a concrete object-shaped Pydantic 2 model"
44
+ )
45
+ validate_type_key(type_key)
46
+ positive_integer(schema_version, "schema_version")
47
+ object.__setattr__(self, "model_type", model_type)
48
+ object.__setattr__(self, "type_key", type_key)
49
+ object.__setattr__(self, "schema_version", schema_version)
50
+
51
+ def encode(self, value: T) -> JSONObject:
52
+ if type(value) is not self.model_type:
53
+ raise ValidationError(
54
+ "value must have the exact registered Pydantic model type"
55
+ )
56
+ try:
57
+ payload = json_object(
58
+ value.model_dump(
59
+ mode="json", round_trip=True, by_alias=True, warnings="error"
60
+ )
61
+ )
62
+ except ValidationError:
63
+ raise
64
+ except Exception: # noqa: BLE001 - serializers may include private model values in errors
65
+ raise CodecError(
66
+ "registered Pydantic model could not produce a JSON object"
67
+ ) from None
68
+ # Revalidate serialized data even when model_construct/mutation bypassed model validation.
69
+ self.decode(payload)
70
+ return payload
71
+
72
+ def decode(self, payload: JSONObject) -> T:
73
+ payload = json_object(payload)
74
+ try:
75
+ return self.model_type.model_validate_json(
76
+ json.dumps(payload, ensure_ascii=False, allow_nan=False),
77
+ strict=True,
78
+ extra="forbid",
79
+ )
80
+ except PydanticValidationError:
81
+ raise ValidationError(
82
+ "payload does not satisfy the registered Pydantic model"
83
+ ) from None
84
+ except Exception: # noqa: BLE001 - trusted validators must not expose payloads in errors
85
+ raise CodecError(
86
+ "registered Pydantic model failed to validate the JSON payload"
87
+ ) from None