lorebase-core 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. lorebase_core/__init__.py +1 -0
  2. lorebase_core/canonical/__init__.py +35 -0
  3. lorebase_core/canonical/hashing.py +128 -0
  4. lorebase_core/canonical/jcs.py +162 -0
  5. lorebase_core/expr/__init__.py +118 -0
  6. lorebase_core/expr/ast.py +443 -0
  7. lorebase_core/expr/checker.py +428 -0
  8. lorebase_core/expr/compiler.py +87 -0
  9. lorebase_core/expr/context.py +82 -0
  10. lorebase_core/expr/derivations.py +306 -0
  11. lorebase_core/expr/environment.py +176 -0
  12. lorebase_core/expr/errors.py +98 -0
  13. lorebase_core/expr/evaluator.py +414 -0
  14. lorebase_core/expr/functions.py +898 -0
  15. lorebase_core/expr/limits.py +52 -0
  16. lorebase_core/expr/operations.py +479 -0
  17. lorebase_core/expr/ops.py +243 -0
  18. lorebase_core/expr/resolver.py +96 -0
  19. lorebase_core/expr/rows.py +148 -0
  20. lorebase_core/expr/sensitivity.py +337 -0
  21. lorebase_core/expr/types.py +227 -0
  22. lorebase_core/expr/validation.py +51 -0
  23. lorebase_core/expr/values.py +169 -0
  24. lorebase_core/expr/visibility.py +363 -0
  25. lorebase_core/infer/__init__.py +36 -0
  26. lorebase_core/infer/columns.py +621 -0
  27. lorebase_core/infer/engine.py +546 -0
  28. lorebase_core/infer/keys.py +164 -0
  29. lorebase_core/infer/names.py +145 -0
  30. lorebase_core/infer/options.py +290 -0
  31. lorebase_core/infer/profile.py +54 -0
  32. lorebase_core/infer/proposal.py +122 -0
  33. lorebase_core/infer/references.py +314 -0
  34. lorebase_core/infer/suggestions.py +731 -0
  35. lorebase_core/infer/tables.py +81 -0
  36. lorebase_core/merge/__init__.py +89 -0
  37. lorebase_core/merge/conflicts.py +262 -0
  38. lorebase_core/merge/consistency.py +440 -0
  39. lorebase_core/merge/data_merge.py +522 -0
  40. lorebase_core/merge/engine.py +476 -0
  41. lorebase_core/merge/index.py +100 -0
  42. lorebase_core/merge/objects.py +204 -0
  43. lorebase_core/merge/policies.py +322 -0
  44. lorebase_core/merge/resolve.py +321 -0
  45. lorebase_core/merge/schema_merge.py +429 -0
  46. lorebase_core/merge/state.py +443 -0
  47. lorebase_core/model/__init__.py +245 -0
  48. lorebase_core/model/attribute.py +72 -0
  49. lorebase_core/model/base.py +61 -0
  50. lorebase_core/model/derivation.py +95 -0
  51. lorebase_core/model/domain.py +71 -0
  52. lorebase_core/model/embedding.py +193 -0
  53. lorebase_core/model/ids.py +78 -0
  54. lorebase_core/model/kind.py +85 -0
  55. lorebase_core/model/lineage.py +114 -0
  56. lorebase_core/model/operation.py +161 -0
  57. lorebase_core/model/paths.py +141 -0
  58. lorebase_core/model/record.py +127 -0
  59. lorebase_core/model/relation.py +83 -0
  60. lorebase_core/model/schema.py +219 -0
  61. lorebase_core/model/source.py +204 -0
  62. lorebase_core/model/trait.py +37 -0
  63. lorebase_core/model/vault.py +43 -0
  64. lorebase_core/model/yaml_schema.py +1058 -0
  65. lorebase_core/query/__init__.py +97 -0
  66. lorebase_core/query/dsl.py +227 -0
  67. lorebase_core/query/resolve.py +794 -0
  68. lorebase_core/revision/__init__.py +59 -0
  69. lorebase_core/revision/ancestry.py +272 -0
  70. lorebase_core/revision/changeset.py +62 -0
  71. lorebase_core/revision/revision.py +121 -0
  72. lorebase_core/tree/__init__.py +76 -0
  73. lorebase_core/tree/diff.py +119 -0
  74. lorebase_core/tree/hamt.py +333 -0
  75. lorebase_core/tree/node.py +280 -0
  76. lorebase_core/tree/params.py +33 -0
  77. lorebase_core/tree/root.py +70 -0
  78. lorebase_core/tree/store.py +98 -0
  79. lorebase_core/types/__init__.py +71 -0
  80. lorebase_core/types/casts.py +271 -0
  81. lorebase_core/types/constraints.py +299 -0
  82. lorebase_core/types/encoding.py +538 -0
  83. lorebase_core/types/errors.py +41 -0
  84. lorebase_core/types/matching.py +40 -0
  85. lorebase_core/types/types.py +187 -0
  86. lorebase_core/validate/__init__.py +49 -0
  87. lorebase_core/validate/changes.py +70 -0
  88. lorebase_core/validate/changeset.py +551 -0
  89. lorebase_core/validate/data_rules.py +291 -0
  90. lorebase_core/validate/issues.py +123 -0
  91. lorebase_core/validate/schema_rules.py +1304 -0
  92. lorebase_core-0.2.3.dist-info/METADATA +9 -0
  93. lorebase_core-0.2.3.dist-info/RECORD +94 -0
  94. lorebase_core-0.2.3.dist-info/WHEEL +4 -0
@@ -0,0 +1,280 @@
1
+ """Compact binary encoding of tree nodes (design 6.2).
2
+
3
+ Every node starts with a header::
4
+
5
+ version (u8) | node type (u8) | depth (u8) | entry count (varint) | subtree count (varint)
6
+
7
+ followed by a type-specific body:
8
+
9
+ - leaf: per entry ``varint(len(key)) | key utf-8 | object hash``, sorted by ``sha256(key)``;
10
+ - branch: a 256-bit occupancy bitmap (bit ``i`` set when the child for key-hash
11
+ byte ``i`` exists, most significant bit first) and the child hashes in byte order;
12
+ - root: per partition ``varint(len(id)) | id utf-8 | partition root hash | varint(count)``,
13
+ sorted by the UTF-8 bytes of the partition id.
14
+
15
+ Varints are unsigned LEB128. Decoding accepts only the canonical encoding, so a
16
+ node has exactly one byte representation and therefore exactly one hash.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import hashlib
22
+ from dataclasses import dataclass
23
+ from enum import IntEnum
24
+
25
+ from lorebase_core.canonical import HASH_SIZE, node_hash
26
+ from lorebase_core.tree.params import BITS_PER_BYTE, FANOUT, NODE_FORMAT_VERSION
27
+
28
+ __all__ = [
29
+ "BranchNode",
30
+ "LeafNode",
31
+ "Node",
32
+ "NodeFormatError",
33
+ "NodeType",
34
+ "PartitionRef",
35
+ "RootNode",
36
+ "decode_node",
37
+ "encode_node",
38
+ "key_hash",
39
+ "node_hash",
40
+ ]
41
+
42
+ _BITMAP_SIZE = FANOUT // BITS_PER_BYTE
43
+ _VARINT_PAYLOAD_BITS = 7
44
+ _VARINT_PAYLOAD_MASK = (1 << _VARINT_PAYLOAD_BITS) - 1
45
+ _VARINT_CONTINUATION = 1 << _VARINT_PAYLOAD_BITS
46
+ _HIGH_BIT = 1 << (BITS_PER_BYTE - 1)
47
+
48
+
49
+ class NodeType(IntEnum):
50
+ LEAF = 1
51
+ BRANCH = 2
52
+ ROOT = 3
53
+
54
+
55
+ class NodeFormatError(ValueError):
56
+ """Raised when node bytes are malformed, non-canonical or inconsistent."""
57
+
58
+
59
+ def key_hash(key: str) -> bytes:
60
+ """Placement hash of an element key inside a partition: ``sha256(key)``."""
61
+ return hashlib.sha256(key.encode("utf-8")).digest()
62
+
63
+
64
+ @dataclass(frozen=True, slots=True)
65
+ class LeafNode:
66
+ """Leaf at ``depth`` holding ``(element_key, object_hash)`` entries sorted by key hash."""
67
+
68
+ depth: int
69
+ entries: tuple[tuple[str, bytes], ...]
70
+
71
+ @property
72
+ def count(self) -> int:
73
+ return len(self.entries)
74
+
75
+
76
+ @dataclass(frozen=True, slots=True)
77
+ class BranchNode:
78
+ """Branch at ``depth``: ``(byte, child_hash)`` pairs in byte order and the subtree count."""
79
+
80
+ depth: int
81
+ children: tuple[tuple[int, bytes], ...]
82
+ count: int
83
+
84
+
85
+ @dataclass(frozen=True, slots=True)
86
+ class PartitionRef:
87
+ """Root hash and entry count of one non-empty partition."""
88
+
89
+ root: bytes
90
+ count: int
91
+
92
+
93
+ @dataclass(frozen=True, slots=True)
94
+ class RootNode:
95
+ """Revision tree root: non-empty partitions sorted by the UTF-8 bytes of their id."""
96
+
97
+ partitions: tuple[tuple[str, PartitionRef], ...]
98
+
99
+ @property
100
+ def count(self) -> int:
101
+ return sum(ref.count for _, ref in self.partitions)
102
+
103
+
104
+ Node = LeafNode | BranchNode | RootNode
105
+
106
+
107
+ def encode_node(node: Node) -> bytes:
108
+ """Encode a node to its canonical bytes, validating its invariants."""
109
+ out = bytearray()
110
+ if isinstance(node, LeafNode):
111
+ _check_depth(node.depth)
112
+ if not node.entries:
113
+ raise NodeFormatError("a leaf holds at least one entry")
114
+ previous = b""
115
+ for key, value in node.entries:
116
+ current = key_hash(key)
117
+ if current <= previous:
118
+ raise NodeFormatError("leaf entries must be strictly sorted by key hash")
119
+ previous = current
120
+ _write_bytes(out, _utf8(key))
121
+ _write_hash(out, value)
122
+ return bytes(_header(NodeType.LEAF, node.depth, len(node.entries), node.count) + out)
123
+ if isinstance(node, BranchNode):
124
+ _check_depth(node.depth)
125
+ if not node.children:
126
+ raise NodeFormatError("a branch holds at least one child")
127
+ bitmap = bytearray(_BITMAP_SIZE)
128
+ last = -1
129
+ for index, child in node.children:
130
+ if not last < index < FANOUT:
131
+ raise NodeFormatError("branch children must be strictly sorted bytes")
132
+ last = index
133
+ bitmap[index // BITS_PER_BYTE] |= _HIGH_BIT >> (index % BITS_PER_BYTE)
134
+ _write_hash(out, child)
135
+ if node.count < len(node.children):
136
+ raise NodeFormatError("branch subtree count is smaller than its child count")
137
+ header = _header(NodeType.BRANCH, node.depth, len(node.children), node.count)
138
+ return bytes(header + bitmap + out)
139
+ previous_id = b""
140
+ for index, (partition, ref) in enumerate(node.partitions):
141
+ encoded = _utf8(partition)
142
+ if not encoded or (index and encoded <= previous_id):
143
+ raise NodeFormatError("partition ids must be non-empty and strictly sorted")
144
+ if ref.count < 1:
145
+ raise NodeFormatError("empty partitions are omitted from the root")
146
+ previous_id = encoded
147
+ _write_bytes(out, encoded)
148
+ _write_hash(out, ref.root)
149
+ _write_varint(out, ref.count)
150
+ return bytes(_header(NodeType.ROOT, 0, len(node.partitions), node.count) + out)
151
+
152
+
153
+ def decode_node(data: bytes) -> Node:
154
+ """Decode canonical node bytes; raise :class:`NodeFormatError` on any deviation."""
155
+ reader = _Reader(data)
156
+ version = reader.byte()
157
+ if version != NODE_FORMAT_VERSION:
158
+ raise NodeFormatError(f"unsupported node format version {version}")
159
+ try:
160
+ node_type = NodeType(reader.byte())
161
+ except ValueError as exc:
162
+ raise NodeFormatError("unknown node type") from exc
163
+ depth = reader.byte()
164
+ entry_count = reader.varint()
165
+ subtree_count = reader.varint()
166
+ node: Node
167
+ if node_type is NodeType.LEAF:
168
+ entries = []
169
+ for _ in range(entry_count):
170
+ key = _from_utf8(reader.take(reader.varint()))
171
+ entries.append((key, reader.take(HASH_SIZE)))
172
+ node = LeafNode(depth, tuple(entries))
173
+ if subtree_count != entry_count:
174
+ raise NodeFormatError("leaf subtree count must equal its entry count")
175
+ elif node_type is NodeType.BRANCH:
176
+ bitmap = reader.take(_BITMAP_SIZE)
177
+ indexes = [
178
+ i
179
+ for i in range(FANOUT)
180
+ if bitmap[i // BITS_PER_BYTE] & (_HIGH_BIT >> (i % BITS_PER_BYTE))
181
+ ]
182
+ if len(indexes) != entry_count:
183
+ raise NodeFormatError("branch bitmap does not match its entry count")
184
+ node = BranchNode(depth, tuple((i, reader.take(HASH_SIZE)) for i in indexes), subtree_count)
185
+ else:
186
+ partitions = []
187
+ for _ in range(entry_count):
188
+ partition = _from_utf8(reader.take(reader.varint()))
189
+ root = reader.take(HASH_SIZE)
190
+ partitions.append((partition, PartitionRef(root, reader.varint())))
191
+ node = RootNode(tuple(partitions))
192
+ if depth != 0 or node.count != subtree_count:
193
+ raise NodeFormatError("root header is inconsistent with its partitions")
194
+ reader.finish()
195
+ if encode_node(node) != data:
196
+ raise NodeFormatError("node bytes are not in canonical form")
197
+ return node
198
+
199
+
200
+ def _header(node_type: NodeType, depth: int, entries: int, subtree: int) -> bytearray:
201
+ out = bytearray((NODE_FORMAT_VERSION, node_type, depth))
202
+ _write_varint(out, entries)
203
+ _write_varint(out, subtree)
204
+ return out
205
+
206
+
207
+ def _check_depth(depth: int) -> None:
208
+ if not 0 <= depth < HASH_SIZE:
209
+ raise NodeFormatError(f"depth {depth} out of range")
210
+
211
+
212
+ def _utf8(text: str) -> bytes:
213
+ try:
214
+ return text.encode("utf-8")
215
+ except UnicodeEncodeError as exc:
216
+ raise NodeFormatError("keys must be valid Unicode text") from exc
217
+
218
+
219
+ def _from_utf8(data: bytes) -> str:
220
+ try:
221
+ return data.decode("utf-8")
222
+ except UnicodeDecodeError as exc:
223
+ raise NodeFormatError("keys must be valid UTF-8") from exc
224
+
225
+
226
+ def _write_varint(out: bytearray, value: int) -> None:
227
+ if value < 0:
228
+ raise NodeFormatError("varints are unsigned")
229
+ while True:
230
+ low = value & _VARINT_PAYLOAD_MASK
231
+ value >>= _VARINT_PAYLOAD_BITS
232
+ if value:
233
+ out.append(low | _VARINT_CONTINUATION)
234
+ else:
235
+ out.append(low)
236
+ return
237
+
238
+
239
+ def _write_bytes(out: bytearray, data: bytes) -> None:
240
+ _write_varint(out, len(data))
241
+ out += data
242
+
243
+
244
+ def _write_hash(out: bytearray, value: bytes) -> None:
245
+ if len(value) != HASH_SIZE:
246
+ raise NodeFormatError(f"hashes are {HASH_SIZE} bytes")
247
+ out += value
248
+
249
+
250
+ class _Reader:
251
+ __slots__ = ("_data", "_pos")
252
+
253
+ def __init__(self, data: bytes) -> None:
254
+ self._data = data
255
+ self._pos = 0
256
+
257
+ def byte(self) -> int:
258
+ return self.take(1)[0]
259
+
260
+ def take(self, size: int) -> bytes:
261
+ end = self._pos + size
262
+ if end > len(self._data):
263
+ raise NodeFormatError("truncated node")
264
+ chunk = self._data[self._pos : end]
265
+ self._pos = end
266
+ return bytes(chunk)
267
+
268
+ def varint(self) -> int:
269
+ value = 0
270
+ shift = 0
271
+ while True:
272
+ byte = self.byte()
273
+ value |= (byte & _VARINT_PAYLOAD_MASK) << shift
274
+ if not byte & _VARINT_CONTINUATION:
275
+ return value
276
+ shift += _VARINT_PAYLOAD_BITS
277
+
278
+ def finish(self) -> None:
279
+ if self._pos != len(self._data):
280
+ raise NodeFormatError("trailing bytes after node")
@@ -0,0 +1,33 @@
1
+ """Tree parameters fixed per Domain (design 6.2, AC-11)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ __all__ = ["BITS_PER_BYTE", "DEFAULT_LEAF_SIZE", "FANOUT", "NODE_FORMAT_VERSION", "TreeParams"]
8
+
9
+ BITS_PER_BYTE = 8
10
+
11
+ FANOUT = 1 << BITS_PER_BYTE
12
+ """Children per branch node: one per value of the key-hash byte at the node depth."""
13
+
14
+ DEFAULT_LEAF_SIZE = 512
15
+ """Default leaf threshold ``L``: a node is a leaf iff its subtree holds at most ``L`` entries."""
16
+
17
+ NODE_FORMAT_VERSION = 1
18
+ """Version byte written in every node header."""
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class TreeParams:
23
+ """Parameters that are part of the hashing contract of a Domain's trees.
24
+
25
+ They are fixed when the Domain is created: changing them changes every
26
+ root hash of the same content.
27
+ """
28
+
29
+ leaf_size: int = DEFAULT_LEAF_SIZE
30
+
31
+ def __post_init__(self) -> None:
32
+ if self.leaf_size < 1:
33
+ raise ValueError("leaf_size must be at least 1")
@@ -0,0 +1,70 @@
1
+ """Revision tree root: the partition map (design 6.2)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from dataclasses import dataclass
7
+ from types import MappingProxyType
8
+
9
+ from lorebase_core.canonical import node_hash
10
+ from lorebase_core.tree.node import NodeFormatError, PartitionRef, RootNode, encode_node
11
+ from lorebase_core.tree.store import NodeStore, read_node
12
+
13
+ __all__ = [
14
+ "LINK_KEY_SEPARATOR",
15
+ "PARTITION_SCHEMA",
16
+ "Tree",
17
+ "kind_partition",
18
+ "link_key",
19
+ "relation_partition",
20
+ "write_root",
21
+ ]
22
+
23
+ PARTITION_SCHEMA = "schema"
24
+ LINK_KEY_SEPARATOR = "\x1f"
25
+ _KIND_PREFIX = "k:"
26
+ _RELATION_PREFIX = "r:"
27
+
28
+
29
+ def kind_partition(kind_id: str) -> str:
30
+ """Partition id holding the Records of a Kind, keyed by Record key."""
31
+ return _KIND_PREFIX + kind_id
32
+
33
+
34
+ def relation_partition(relation_id: str) -> str:
35
+ """Partition id holding the Links of a Relation, keyed by :func:`link_key`."""
36
+ return _RELATION_PREFIX + relation_id
37
+
38
+
39
+ def link_key(from_key: str, to_key: str) -> str:
40
+ """Element key of a Link inside its Relation partition."""
41
+ return from_key + LINK_KEY_SEPARATOR + to_key
42
+
43
+
44
+ @dataclass(frozen=True, eq=False)
45
+ class Tree:
46
+ """A Revision tree: its root hash and the partition map it points to."""
47
+
48
+ root: bytes
49
+ partitions: Mapping[str, PartitionRef]
50
+
51
+ @classmethod
52
+ def load(cls, store: NodeStore, root: bytes) -> Tree:
53
+ node = read_node(store, root)
54
+ if not isinstance(node, RootNode):
55
+ raise NodeFormatError("hash does not reference a tree root")
56
+ return cls(root, MappingProxyType(dict(node.partitions)))
57
+
58
+ @property
59
+ def count(self) -> int:
60
+ """Total number of entries across partitions."""
61
+ return sum(ref.count for ref in self.partitions.values())
62
+
63
+
64
+ def write_root(store: NodeStore, partitions: Mapping[str, PartitionRef]) -> bytes:
65
+ """Encode and store a root node for ``partitions``; return its hash."""
66
+ ordered = sorted(partitions.items(), key=lambda item: item[0].encode("utf-8"))
67
+ body = encode_node(RootNode(tuple(ordered)))
68
+ root = node_hash(body)
69
+ store.put(root, body)
70
+ return root
@@ -0,0 +1,98 @@
1
+ """Node storage protocol and in-memory implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from typing import Protocol, runtime_checkable
7
+
8
+ from lorebase_core.canonical import node_hash
9
+ from lorebase_core.tree.node import Node, decode_node
10
+
11
+ __all__ = [
12
+ "InMemoryNodeStore",
13
+ "MemoryNodeStore",
14
+ "MissingNodeError",
15
+ "NodeStore",
16
+ "StagingNodeStore",
17
+ "read_node",
18
+ ]
19
+
20
+
21
+ class MissingNodeError(KeyError):
22
+ """Raised when a node referenced by a tree is not in the store."""
23
+
24
+
25
+ @runtime_checkable
26
+ class NodeStore(Protocol):
27
+ """Content-addressed storage of encoded tree nodes, keyed by node hash."""
28
+
29
+ def get(self, node_hash: bytes) -> bytes | None: ...
30
+
31
+ def put(self, node_hash: bytes, body: bytes) -> None: ...
32
+
33
+ def has(self, node_hash: bytes) -> bool: ...
34
+
35
+
36
+ class InMemoryNodeStore:
37
+ """Dictionary-backed :class:`NodeStore` that verifies hashes on write."""
38
+
39
+ def __init__(self) -> None:
40
+ self._nodes: dict[bytes, bytes] = {}
41
+
42
+ def get(self, node_hash: bytes) -> bytes | None:
43
+ return self._nodes.get(node_hash)
44
+
45
+ def put(self, node_hash: bytes, body: bytes) -> None:
46
+ _verify(node_hash, body)
47
+ self._nodes[node_hash] = body
48
+
49
+ def has(self, node_hash: bytes) -> bool:
50
+ return node_hash in self._nodes
51
+
52
+ def __len__(self) -> int:
53
+ return len(self._nodes)
54
+
55
+
56
+ MemoryNodeStore = InMemoryNodeStore
57
+
58
+
59
+ class StagingNodeStore:
60
+ """Reads through to ``base`` and keeps new writes aside in :attr:`written`.
61
+
62
+ Lets a caller collect the nodes produced by a batch update and persist them
63
+ in bulk, or drop them.
64
+ """
65
+
66
+ def __init__(self, base: NodeStore) -> None:
67
+ self._base = base
68
+ self._written: dict[bytes, bytes] = {}
69
+
70
+ @property
71
+ def written(self) -> Mapping[bytes, bytes]:
72
+ """Nodes written through this store that the base store does not hold."""
73
+ return self._written
74
+
75
+ def get(self, node_hash: bytes) -> bytes | None:
76
+ body = self._written.get(node_hash)
77
+ return body if body is not None else self._base.get(node_hash)
78
+
79
+ def put(self, node_hash: bytes, body: bytes) -> None:
80
+ _verify(node_hash, body)
81
+ if node_hash not in self._written and not self._base.has(node_hash):
82
+ self._written[node_hash] = body
83
+
84
+ def has(self, node_hash: bytes) -> bool:
85
+ return node_hash in self._written or self._base.has(node_hash)
86
+
87
+
88
+ def read_node(store: NodeStore, node_hash: bytes) -> Node:
89
+ """Fetch and decode a node, raising :class:`MissingNodeError` when absent."""
90
+ body = store.get(node_hash)
91
+ if body is None:
92
+ raise MissingNodeError(node_hash.hex())
93
+ return decode_node(body)
94
+
95
+
96
+ def _verify(expected: bytes, body: bytes) -> None:
97
+ if node_hash(body) != expected:
98
+ raise ValueError("node hash does not match node body")
@@ -0,0 +1,71 @@
1
+ """Type system, cast rules and canonical value encoding (design 5.4 and 6.1)."""
2
+
3
+ from lorebase_core.types.casts import CAST_MATRIX, ELEMENT_RULE, CastRule, cast, cast_rule
4
+ from lorebase_core.types.constraints import (
5
+ ConstraintName,
6
+ Constraints,
7
+ ConstraintViolation,
8
+ check_constraints,
9
+ compile_pattern,
10
+ constraint_problems,
11
+ )
12
+ from lorebase_core.types.encoding import (
13
+ decode_value,
14
+ encode_value,
15
+ format_date,
16
+ format_duration,
17
+ format_timestamp,
18
+ is_canonical,
19
+ normalize_key,
20
+ normalize_text,
21
+ to_text,
22
+ )
23
+ from lorebase_core.types.errors import CastError, EncodingError, EncodingReason, TypeSystemError
24
+ from lorebase_core.types.matching import fold_text, matching_form
25
+ from lorebase_core.types.types import (
26
+ INT64_MAX,
27
+ INT64_MIN,
28
+ ORDERED_TYPES,
29
+ TEXTUAL_TYPES,
30
+ AttributeType,
31
+ CanonicalValue,
32
+ GeoPoint,
33
+ TypeSpec,
34
+ )
35
+
36
+ __all__ = [
37
+ "CAST_MATRIX",
38
+ "ELEMENT_RULE",
39
+ "INT64_MAX",
40
+ "INT64_MIN",
41
+ "ORDERED_TYPES",
42
+ "TEXTUAL_TYPES",
43
+ "AttributeType",
44
+ "CanonicalValue",
45
+ "CastError",
46
+ "CastRule",
47
+ "ConstraintName",
48
+ "ConstraintViolation",
49
+ "Constraints",
50
+ "EncodingError",
51
+ "EncodingReason",
52
+ "GeoPoint",
53
+ "TypeSpec",
54
+ "TypeSystemError",
55
+ "cast",
56
+ "cast_rule",
57
+ "check_constraints",
58
+ "compile_pattern",
59
+ "constraint_problems",
60
+ "decode_value",
61
+ "encode_value",
62
+ "fold_text",
63
+ "format_date",
64
+ "format_duration",
65
+ "format_timestamp",
66
+ "is_canonical",
67
+ "matching_form",
68
+ "normalize_key",
69
+ "normalize_text",
70
+ "to_text",
71
+ ]