tigrbl_engine_rom 0.4.5__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.
- tigrbl_engine_rom/__init__.py +18 -0
- tigrbl_engine_rom/engine.py +105 -0
- tigrbl_engine_rom/plugin.py +54 -0
- tigrbl_engine_rom/session.py +227 -0
- tigrbl_engine_rom-0.4.5.dist-info/METADATA +167 -0
- tigrbl_engine_rom-0.4.5.dist-info/RECORD +10 -0
- tigrbl_engine_rom-0.4.5.dist-info/WHEEL +4 -0
- tigrbl_engine_rom-0.4.5.dist-info/entry_points.txt +2 -0
- tigrbl_engine_rom-0.4.5.dist-info/licenses/LICENSE +17 -0
- tigrbl_engine_rom-0.4.5.dist-info/licenses/NOTICE +7 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Read-only memory engine for Tigrbl."""
|
|
2
|
+
|
|
3
|
+
from .engine import ROMEngine
|
|
4
|
+
from .plugin import build_rom, capabilities, register
|
|
5
|
+
from .session import READ_ONLY_ERROR, ROMResult, ROMSession
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"READ_ONLY_ERROR",
|
|
9
|
+
"ROMEngine",
|
|
10
|
+
"ROMResult",
|
|
11
|
+
"ROMSession",
|
|
12
|
+
"build_rom",
|
|
13
|
+
"capabilities",
|
|
14
|
+
"register",
|
|
15
|
+
"__version__",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
__version__ = "0.4.5.dev4"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from types import MappingProxyType
|
|
5
|
+
from typing import Any, Iterable, Mapping
|
|
6
|
+
|
|
7
|
+
from tigrbl_base._base import EngineBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _freeze(value: Any) -> Any:
|
|
11
|
+
"""Return an immutable, detached representation of configuration data."""
|
|
12
|
+
if isinstance(value, Mapping):
|
|
13
|
+
return MappingProxyType({key: _freeze(item) for key, item in value.items()})
|
|
14
|
+
if isinstance(value, (list, tuple)):
|
|
15
|
+
return tuple(_freeze(item) for item in value)
|
|
16
|
+
if isinstance(value, (set, frozenset)):
|
|
17
|
+
return frozenset(_freeze(item) for item in value)
|
|
18
|
+
return deepcopy(value)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _thaw(value: Any) -> Any:
|
|
22
|
+
"""Return a caller-owned copy of a frozen value."""
|
|
23
|
+
if isinstance(value, Mapping):
|
|
24
|
+
return {key: _thaw(item) for key, item in value.items()}
|
|
25
|
+
if isinstance(value, tuple):
|
|
26
|
+
return [_thaw(item) for item in value]
|
|
27
|
+
if isinstance(value, frozenset):
|
|
28
|
+
return {_thaw(item) for item in value}
|
|
29
|
+
return deepcopy(value)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ROMEngine(EngineBase):
|
|
33
|
+
"""Process-local immutable row image bound as an input to one table."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
rows: Iterable[Mapping[str, Any]] | None = None,
|
|
38
|
+
*,
|
|
39
|
+
primary_key: str = "id",
|
|
40
|
+
namespace: str = "default",
|
|
41
|
+
spec: Any = None,
|
|
42
|
+
) -> None:
|
|
43
|
+
if not isinstance(namespace, str) or not namespace:
|
|
44
|
+
raise ValueError("namespace must be a non-empty string")
|
|
45
|
+
if not isinstance(primary_key, str) or not primary_key:
|
|
46
|
+
raise ValueError("primary_key must be a non-empty string")
|
|
47
|
+
self.namespace = namespace
|
|
48
|
+
self.spec = spec
|
|
49
|
+
self.primary_key = primary_key
|
|
50
|
+
frozen_rows: list[Mapping[str, Any]] = []
|
|
51
|
+
index: dict[Any, Mapping[str, Any]] = {}
|
|
52
|
+
for position, source_row in enumerate(rows or ()):
|
|
53
|
+
if not isinstance(source_row, Mapping):
|
|
54
|
+
raise TypeError(f"row {position} must be a mapping")
|
|
55
|
+
if primary_key not in source_row:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"row {position} is missing primary key {primary_key!r}"
|
|
58
|
+
)
|
|
59
|
+
key = deepcopy(source_row[primary_key])
|
|
60
|
+
try:
|
|
61
|
+
duplicate = key in index
|
|
62
|
+
except TypeError as exc:
|
|
63
|
+
raise TypeError(
|
|
64
|
+
f"primary key {primary_key!r} must be hashable"
|
|
65
|
+
) from exc
|
|
66
|
+
if duplicate:
|
|
67
|
+
raise ValueError(f"duplicate primary key {key!r}")
|
|
68
|
+
frozen_row = _freeze(source_row)
|
|
69
|
+
frozen_rows.append(frozen_row)
|
|
70
|
+
index[key] = frozen_row
|
|
71
|
+
|
|
72
|
+
self._rows = tuple(frozen_rows)
|
|
73
|
+
self._index = MappingProxyType(index)
|
|
74
|
+
|
|
75
|
+
def to_provider(self) -> "ROMEngine":
|
|
76
|
+
"""Expose this immutable engine as its own provider-like object."""
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
async def _executeloop_impl(self, statements: Any) -> Any:
|
|
80
|
+
from .session import ROMSession
|
|
81
|
+
|
|
82
|
+
session = ROMSession(self)
|
|
83
|
+
try:
|
|
84
|
+
return await session.executeloop(statements)
|
|
85
|
+
finally:
|
|
86
|
+
await session.close()
|
|
87
|
+
|
|
88
|
+
async def _executemany_impl(self, stmt: Any, parameter_sets: Any) -> Any:
|
|
89
|
+
from .session import ROMSession
|
|
90
|
+
|
|
91
|
+
session = ROMSession(self)
|
|
92
|
+
try:
|
|
93
|
+
return await session.executemany(stmt, parameter_sets)
|
|
94
|
+
finally:
|
|
95
|
+
await session.close()
|
|
96
|
+
|
|
97
|
+
def get(self, ident: Any) -> dict[str, Any] | None:
|
|
98
|
+
row = self._index.get(ident)
|
|
99
|
+
return None if row is None else _thaw(row)
|
|
100
|
+
|
|
101
|
+
def scan(self) -> list[dict[str, Any]]:
|
|
102
|
+
return [_thaw(row) for row in self._rows]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
__all__ = ["ROMEngine"]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from tigrbl.engine.registry import register_engine
|
|
6
|
+
|
|
7
|
+
from .engine import ROMEngine
|
|
8
|
+
from .session import ROMSession
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def capabilities(*, spec: Any = None, mapping: Any = None) -> dict[str, Any]:
|
|
12
|
+
return {
|
|
13
|
+
"engine": "rom",
|
|
14
|
+
"transactional": False,
|
|
15
|
+
"async_native": True,
|
|
16
|
+
"isolation_levels": set(),
|
|
17
|
+
"read_only_enforced": True,
|
|
18
|
+
"persistence": "process",
|
|
19
|
+
"storage": "immutable-memory",
|
|
20
|
+
"read_consistency": "immutable-snapshot",
|
|
21
|
+
"binding_scopes": {"table"},
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_rom(*, mapping=None, spec=None, dsn=None, **_: Any) -> tuple[object, object]:
|
|
26
|
+
if dsn:
|
|
27
|
+
raise ValueError("ROM engine accepts embedded mapping data, not a DSN")
|
|
28
|
+
config = dict(mapping or {})
|
|
29
|
+
engine = ROMEngine(
|
|
30
|
+
rows=config.get("rows"),
|
|
31
|
+
primary_key=str(config.get("primary_key", "id")),
|
|
32
|
+
namespace=str(config.get("namespace", "default")),
|
|
33
|
+
spec=spec,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def sessionmaker(session_spec=None) -> ROMSession:
|
|
37
|
+
return ROMSession(engine, spec=session_spec)
|
|
38
|
+
|
|
39
|
+
return engine, sessionmaker
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class _Registration:
|
|
43
|
+
def build(self, *, mapping, spec, dsn):
|
|
44
|
+
return build_rom(mapping=mapping, spec=spec, dsn=dsn)
|
|
45
|
+
|
|
46
|
+
def capabilities(self, *, spec, mapping=None):
|
|
47
|
+
return capabilities(spec=spec, mapping=mapping)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def register() -> None:
|
|
51
|
+
register_engine("rom", _Registration())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = ["build_rom", "capabilities", "register"]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
from dataclasses import replace
|
|
5
|
+
from typing import Any, Callable, Iterable, Mapping, Sequence
|
|
6
|
+
|
|
7
|
+
from tigrbl_base._base import EngineSessionBase
|
|
8
|
+
from tigrbl_core._spec.engine_session_spec import EngineSessionSpec
|
|
9
|
+
|
|
10
|
+
from .engine import ROMEngine
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
READ_ONLY_ERROR = "write attempted against read-only memory engine"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ROMResult:
|
|
17
|
+
"""Small result facade compatible with Tigrbl's scalar query helpers."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, items: Sequence[Any]) -> None:
|
|
20
|
+
self._items = tuple(items)
|
|
21
|
+
self.rowcount = len(self._items)
|
|
22
|
+
|
|
23
|
+
def scalars(self) -> "ROMResult":
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def all(self) -> list[Any]:
|
|
27
|
+
return list(self._items)
|
|
28
|
+
|
|
29
|
+
def first(self) -> Any | None:
|
|
30
|
+
return self._items[0] if self._items else None
|
|
31
|
+
|
|
32
|
+
def scalar_one(self) -> Any:
|
|
33
|
+
if len(self._items) != 1:
|
|
34
|
+
raise RuntimeError(f"expected exactly one row, found {len(self._items)}")
|
|
35
|
+
return self._items[0]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ROMSession(EngineSessionBase):
|
|
39
|
+
"""Async Tigrbl session over an immutable :class:`ROMEngine` snapshot."""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self, engine: ROMEngine, spec: EngineSessionSpec | None = None
|
|
43
|
+
) -> None:
|
|
44
|
+
enforced = replace(spec or EngineSessionSpec(), read_only=True)
|
|
45
|
+
super().__init__(enforced)
|
|
46
|
+
self._engine = engine
|
|
47
|
+
self._closed = False
|
|
48
|
+
self._tracked: dict[int, tuple[Any, dict[str, Any]]] = {}
|
|
49
|
+
|
|
50
|
+
def apply_spec(self, spec: EngineSessionSpec | None) -> None:
|
|
51
|
+
enforced = replace(spec or EngineSessionSpec(), read_only=True)
|
|
52
|
+
super().apply_spec(enforced)
|
|
53
|
+
|
|
54
|
+
def get_row(self, ident: Any) -> dict[str, Any] | None:
|
|
55
|
+
self._require_open_session()
|
|
56
|
+
return self._engine.get(ident)
|
|
57
|
+
|
|
58
|
+
def query_rows(
|
|
59
|
+
self,
|
|
60
|
+
predicate: Callable[[Mapping[str, Any]], bool] | None = None,
|
|
61
|
+
) -> list[dict[str, Any]]:
|
|
62
|
+
self._require_open_session()
|
|
63
|
+
rows = self._engine.scan()
|
|
64
|
+
if predicate is not None:
|
|
65
|
+
rows = [row for row in rows if predicate(row)]
|
|
66
|
+
return rows
|
|
67
|
+
|
|
68
|
+
async def _tx_begin_impl(self) -> None:
|
|
69
|
+
self._require_open_session()
|
|
70
|
+
|
|
71
|
+
async def _tx_commit_impl(self) -> None:
|
|
72
|
+
self._require_open_session()
|
|
73
|
+
|
|
74
|
+
async def _tx_rollback_impl(self) -> None:
|
|
75
|
+
self._require_open_session()
|
|
76
|
+
for obj, snapshot in self._tracked.values():
|
|
77
|
+
for name, value in snapshot.items():
|
|
78
|
+
setattr(obj, name, deepcopy(value))
|
|
79
|
+
|
|
80
|
+
def _add_impl(self, obj: Any) -> None:
|
|
81
|
+
raise RuntimeError(READ_ONLY_ERROR)
|
|
82
|
+
|
|
83
|
+
async def _delete_impl(self, obj: Any) -> None:
|
|
84
|
+
raise RuntimeError(READ_ONLY_ERROR)
|
|
85
|
+
|
|
86
|
+
async def _flush_impl(self) -> None:
|
|
87
|
+
self._require_open_session()
|
|
88
|
+
for obj, snapshot in self._tracked.values():
|
|
89
|
+
if self._snapshot(obj) != snapshot:
|
|
90
|
+
raise RuntimeError(READ_ONLY_ERROR)
|
|
91
|
+
|
|
92
|
+
async def _refresh_impl(self, obj: Any) -> None:
|
|
93
|
+
self._require_open_session()
|
|
94
|
+
row = self._engine.get(getattr(obj, self._engine.primary_key))
|
|
95
|
+
if row is None:
|
|
96
|
+
return
|
|
97
|
+
for name, value in row.items():
|
|
98
|
+
setattr(obj, name, value)
|
|
99
|
+
self._track(obj)
|
|
100
|
+
|
|
101
|
+
async def _get_impl(self, model: type, ident: Any) -> Any | None:
|
|
102
|
+
self._require_open_session()
|
|
103
|
+
row = self._engine.get(ident)
|
|
104
|
+
return None if row is None else self._inflate(model, row)
|
|
105
|
+
|
|
106
|
+
async def _execute_impl(self, stmt: Any) -> ROMResult:
|
|
107
|
+
self._require_open_session()
|
|
108
|
+
if type(stmt).__name__.lower() != "select":
|
|
109
|
+
raise RuntimeError(READ_ONLY_ERROR)
|
|
110
|
+
model = self._extract_model(stmt)
|
|
111
|
+
items = [self._inflate(model, row) for row in self._engine.scan()]
|
|
112
|
+
for name, operator, value in self._extract_predicates(stmt):
|
|
113
|
+
if operator == "eq":
|
|
114
|
+
items = [item for item in items if getattr(item, name, None) == value]
|
|
115
|
+
elif operator == "in":
|
|
116
|
+
accepted = set(value)
|
|
117
|
+
items = [
|
|
118
|
+
item for item in items if getattr(item, name, None) in accepted
|
|
119
|
+
]
|
|
120
|
+
else: # pragma: no cover - protected by extraction
|
|
121
|
+
raise NotImplementedError(f"unsupported ROM predicate: {operator}")
|
|
122
|
+
for name, descending in reversed(self._extract_ordering(stmt)):
|
|
123
|
+
items.sort(key=lambda item: getattr(item, name, None), reverse=descending)
|
|
124
|
+
offset = self._clause_int(getattr(stmt, "_offset_clause", None)) or 0
|
|
125
|
+
limit = self._clause_int(getattr(stmt, "_limit_clause", None))
|
|
126
|
+
items = items[max(0, offset) :]
|
|
127
|
+
if limit is not None:
|
|
128
|
+
items = items[: max(0, limit)]
|
|
129
|
+
return ROMResult(items)
|
|
130
|
+
|
|
131
|
+
async def _executeloop_impl(self, statements: Iterable[Any]) -> list[ROMResult]:
|
|
132
|
+
return [await self._execute_impl(statement) for statement in statements]
|
|
133
|
+
|
|
134
|
+
async def _executemany_impl(self, stmt: Any, parameter_sets: Any) -> Any:
|
|
135
|
+
raise RuntimeError(
|
|
136
|
+
"parameterized batch execution is unavailable on read-only memory engine"
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
async def _close_impl(self) -> None:
|
|
140
|
+
self._closed = True
|
|
141
|
+
self._open = False
|
|
142
|
+
self._dirty = False
|
|
143
|
+
self._tracked.clear()
|
|
144
|
+
|
|
145
|
+
def _require_open_session(self) -> None:
|
|
146
|
+
if self._closed:
|
|
147
|
+
raise RuntimeError("ROM session is closed")
|
|
148
|
+
|
|
149
|
+
def _inflate(self, model: type, row: Mapping[str, Any]) -> Any:
|
|
150
|
+
try:
|
|
151
|
+
obj = model(**row)
|
|
152
|
+
except TypeError:
|
|
153
|
+
obj = model()
|
|
154
|
+
for name, value in row.items():
|
|
155
|
+
setattr(obj, name, value)
|
|
156
|
+
self._track(obj)
|
|
157
|
+
return obj
|
|
158
|
+
|
|
159
|
+
def _track(self, obj: Any) -> None:
|
|
160
|
+
self._tracked[id(obj)] = (obj, self._snapshot(obj))
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _snapshot(obj: Any) -> dict[str, Any]:
|
|
164
|
+
return {
|
|
165
|
+
name: deepcopy(value)
|
|
166
|
+
for name, value in vars(obj).items()
|
|
167
|
+
if not name.startswith("_sa_")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _extract_model(stmt: Any) -> type:
|
|
172
|
+
for description in getattr(stmt, "column_descriptions", ()):
|
|
173
|
+
entity = description.get("entity")
|
|
174
|
+
if isinstance(entity, type):
|
|
175
|
+
return entity
|
|
176
|
+
raise NotImplementedError("ROM selects must target one mapped model")
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def _extract_predicates(cls, stmt: Any) -> list[tuple[str, str, Any]]:
|
|
180
|
+
predicates: list[tuple[str, str, Any]] = []
|
|
181
|
+
for criterion in getattr(stmt, "_where_criteria", ()):
|
|
182
|
+
clauses = getattr(criterion, "clauses", None)
|
|
183
|
+
if clauses is not None:
|
|
184
|
+
boolean_operator = getattr(
|
|
185
|
+
getattr(criterion, "operator", None), "__name__", ""
|
|
186
|
+
)
|
|
187
|
+
if boolean_operator != "and_":
|
|
188
|
+
raise NotImplementedError(
|
|
189
|
+
"ROM supports conjunctions of equality and IN predicates only"
|
|
190
|
+
)
|
|
191
|
+
for clause in clauses:
|
|
192
|
+
predicates.extend(cls._extract_binary_predicate(clause))
|
|
193
|
+
else:
|
|
194
|
+
predicates.extend(cls._extract_binary_predicate(criterion))
|
|
195
|
+
return predicates
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def _extract_binary_predicate(expr: Any) -> list[tuple[str, str, Any]]:
|
|
199
|
+
name = getattr(getattr(expr, "left", None), "key", None)
|
|
200
|
+
operator_name = getattr(getattr(expr, "operator", None), "__name__", "")
|
|
201
|
+
right = getattr(expr, "right", None)
|
|
202
|
+
value = getattr(right, "effective_value", getattr(right, "value", None))
|
|
203
|
+
if not name or operator_name not in {"eq", "in_op"}:
|
|
204
|
+
raise NotImplementedError("ROM supports equality and IN predicates only")
|
|
205
|
+
return [(name, "in" if operator_name == "in_op" else "eq", value)]
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def _extract_ordering(stmt: Any) -> list[tuple[str, bool]]:
|
|
209
|
+
ordering: list[tuple[str, bool]] = []
|
|
210
|
+
for clause in getattr(stmt, "_order_by_clauses", ()):
|
|
211
|
+
element = getattr(clause, "element", clause)
|
|
212
|
+
name = getattr(element, "key", None)
|
|
213
|
+
if not name:
|
|
214
|
+
raise NotImplementedError("ROM ordering must target mapped columns")
|
|
215
|
+
modifier = getattr(getattr(clause, "modifier", None), "__name__", "")
|
|
216
|
+
ordering.append((name, modifier == "desc_op"))
|
|
217
|
+
return ordering
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
def _clause_int(clause: Any) -> int | None:
|
|
221
|
+
if clause is None:
|
|
222
|
+
return None
|
|
223
|
+
value = getattr(clause, "effective_value", getattr(clause, "value", clause))
|
|
224
|
+
return int(value)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
__all__ = ["READ_ONLY_ERROR", "ROMResult", "ROMSession"]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tigrbl_engine_rom
|
|
3
|
+
Version: 0.4.5
|
|
4
|
+
Summary: Immutable read-only memory engine plugin for Tigrbl.
|
|
5
|
+
Project-URL: Homepage, https://github.com/tigrbl/tigrbl
|
|
6
|
+
Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/90_engines/tigrbl_engine_rom
|
|
7
|
+
Project-URL: Issues, https://github.com/tigrbl/tigrbl/issues
|
|
8
|
+
Author-email: Jacob Stewart <jacob@swarmauri.com>
|
|
9
|
+
License: Apache License
|
|
10
|
+
Version 2.0, January 2004
|
|
11
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Copyright 2026 Swarmauri
|
|
14
|
+
|
|
15
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
16
|
+
you may not use this file except in compliance with the License.
|
|
17
|
+
You may obtain a copy of the License at
|
|
18
|
+
|
|
19
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
20
|
+
|
|
21
|
+
Unless required by applicable law or agreed to in writing, software
|
|
22
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
23
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
24
|
+
See the License for the specific language governing permissions and
|
|
25
|
+
limitations under the License.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
License-File: NOTICE
|
|
28
|
+
Keywords: engine,immutable,memory,plugin,readonly,tigrbl
|
|
29
|
+
Classifier: Development Status :: 3 - Alpha
|
|
30
|
+
Classifier: Framework :: AsyncIO
|
|
31
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
32
|
+
Classifier: Operating System :: OS Independent
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
40
|
+
Classifier: Topic :: Database
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Requires-Python: <3.15,>=3.10
|
|
43
|
+
Requires-Dist: tigrbl>=0.3.0.dev4
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
<div align="center">
|
|
47
|
+
<h1>tigrbl_engine_rom</h1>
|
|
48
|
+
<p><strong>Immutable read-only memory engine plugin for Tigrbl.</strong></p>
|
|
49
|
+
<a href="https://pypi.org/project/tigrbl_engine_rom/"><img src="https://img.shields.io/pypi/v/tigrbl_engine_rom?label=PyPI" alt="PyPI version"/></a>
|
|
50
|
+
<a href="https://discord.gg/K4YTAPapjR"><img src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord"/></a>
|
|
51
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
|
|
52
|
+
<a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab" alt="Python 3.10 | 3.11 | 3.12 | 3.13 | 3.14"/></a>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
## What is tigrbl_engine_rom?
|
|
56
|
+
|
|
57
|
+
`tigrbl_engine_rom` loads one mapping-backed row image and serves it through Tigrbl's engine-session contract. The engine configuration is bound as an input to a table.
|
|
58
|
+
|
|
59
|
+
## Why use tigrbl_engine_rom?
|
|
60
|
+
|
|
61
|
+
Use it for embedded catalogs, fixtures, policy data, reference tables, and other datasets that must never change at runtime.
|
|
62
|
+
|
|
63
|
+
## When should I install tigrbl_engine_rom?
|
|
64
|
+
|
|
65
|
+
Install it when your application needs fast reads from a fixed memory image and should fail closed on every write.
|
|
66
|
+
|
|
67
|
+
## Who is tigrbl_engine_rom for?
|
|
68
|
+
|
|
69
|
+
It is for application developers shipping trusted reference data or deterministic read-only test images.
|
|
70
|
+
|
|
71
|
+
## Where does tigrbl_engine_rom fit?
|
|
72
|
+
|
|
73
|
+
It is a standalone package under `pkgs/90_engines` and is discovered through the `tigrbl.engine` entry-point group as kind `rom`.
|
|
74
|
+
|
|
75
|
+
## How does tigrbl_engine_rom work?
|
|
76
|
+
|
|
77
|
+
`ROMEngine` subclasses Tigrbl's `EngineBase`. Construction validates the bound table's rows and primary key, deeply detaches the source data, and freezes it. Reads always return caller-owned copies. Sessions force `read_only=True`, even if a caller requests otherwise.
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uv add tigrbl_engine_rom
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Surface Coverage
|
|
86
|
+
|
|
87
|
+
| Surface | Value |
|
|
88
|
+
|---|---|
|
|
89
|
+
| Engine kind | `rom` |
|
|
90
|
+
| Python import | `tigrbl_engine_rom` |
|
|
91
|
+
| Entry point | `tigrbl.engine` |
|
|
92
|
+
| Persistence | process-local immutable memory |
|
|
93
|
+
| Supported Python | `3.10 | 3.11 | 3.12 | 3.13 | 3.14` |
|
|
94
|
+
|
|
95
|
+
## What It Owns
|
|
96
|
+
|
|
97
|
+
The package owns the ROM image, read-only session, plugin registration, and scalar result facade.
|
|
98
|
+
|
|
99
|
+
## Public API and Import Surface
|
|
100
|
+
|
|
101
|
+
Public symbols are `ROMEngine`, `ROMSession`, `ROMResult`, `build_rom`, `capabilities`, and `register`.
|
|
102
|
+
|
|
103
|
+
## Usage Examples
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from tigrbl_engine_rom import build_rom
|
|
107
|
+
|
|
108
|
+
engine, sessionmaker = build_rom(mapping={
|
|
109
|
+
"rows": [
|
|
110
|
+
{"code": "US", "name": "United States"},
|
|
111
|
+
{"code": "CA", "name": "Canada"},
|
|
112
|
+
],
|
|
113
|
+
"primary_key": "code",
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
session = sessionmaker()
|
|
117
|
+
assert session.get_row("CA")["name"] == "Canada"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Bind the engine configuration into a table:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from tigrbl.factories.table import defineTableSpec
|
|
124
|
+
|
|
125
|
+
class CountrySpec(defineTableSpec(engine={
|
|
126
|
+
"kind": "rom",
|
|
127
|
+
"rows": [
|
|
128
|
+
{"code": "US", "name": "United States"},
|
|
129
|
+
{"code": "CA", "name": "Canada"},
|
|
130
|
+
],
|
|
131
|
+
"primary_key": "code",
|
|
132
|
+
})):
|
|
133
|
+
pass
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## How To Choose This Package
|
|
137
|
+
|
|
138
|
+
Choose ROM for immutable embedded data. Choose `tigrbl_engine_inmemory` when transactions and runtime writes are required.
|
|
139
|
+
|
|
140
|
+
## Related Packages
|
|
141
|
+
|
|
142
|
+
- `tigrbl`
|
|
143
|
+
- `tigrbl_engine_inmemory`
|
|
144
|
+
- `tigrbl_engine_memkv`
|
|
145
|
+
|
|
146
|
+
## Documentation Links
|
|
147
|
+
|
|
148
|
+
- [Workspace docs](../../../docs/README.md)
|
|
149
|
+
- [Package catalog](../../../docs/developer/PACKAGE_CATALOG.md)
|
|
150
|
+
- [SSOT registry](../../../.ssot/registry.json)
|
|
151
|
+
|
|
152
|
+
## Support
|
|
153
|
+
|
|
154
|
+
- [Discord](https://discord.gg/K4YTAPapjR)
|
|
155
|
+
- [GitHub issues](https://github.com/tigrbl/tigrbl/issues)
|
|
156
|
+
|
|
157
|
+
## Certification Status
|
|
158
|
+
|
|
159
|
+
The package is implemented with package-local behavioral tests. Certification remains governed by the repository SSOT registry and release workflows.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
Licensed under the Apache License, Version 2.0. See `LICENSE` and `NOTICE`.
|
|
164
|
+
|
|
165
|
+
## Workspace documentation
|
|
166
|
+
|
|
167
|
+
This README is the package-local distribution entry point. See [workspace documentation](../../../docs/README.md), [current conformance target](../../../docs/conformance/CURRENT_TARGET.md), [package catalog](../../../docs/developer/PACKAGE_CATALOG.md), [package layout](../../../docs/developer/PACKAGE_LAYOUT.md), and [SSOT registry](../../../.ssot/registry.json).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
tigrbl_engine_rom/__init__.py,sha256=-Vo3g8X_WlICo9l0bSAhqSeBzRUDcxUqvJsO3tjjdpg,377
|
|
2
|
+
tigrbl_engine_rom/engine.py,sha256=TIThltMj8idUPMrpl68iAkZBqZKvRnKvF83eMZ6HLT8,3682
|
|
3
|
+
tigrbl_engine_rom/plugin.py,sha256=E9oDh0_msL4F3xNqpa5wZIs2WGTPkMO4omXT8VYmfoM,1508
|
|
4
|
+
tigrbl_engine_rom/session.py,sha256=k342vQTAq0-TNpnisyTt0lW_vghQYbULKxrV2P5ZdqA,8654
|
|
5
|
+
tigrbl_engine_rom-0.4.5.dist-info/METADATA,sha256=RIrazWk42f5ohr2DD0m_TfS2ReolmtWLAjyYAE-medo,6317
|
|
6
|
+
tigrbl_engine_rom-0.4.5.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
7
|
+
tigrbl_engine_rom-0.4.5.dist-info/entry_points.txt,sha256=KHczrTslM6l70hEoKETsz4bdq4bqsMMIX5Sg3L5xJTE,64
|
|
8
|
+
tigrbl_engine_rom-0.4.5.dist-info/licenses/LICENSE,sha256=Z59l_PeraPrA4KbufxEZ0ft3xquZACI-vxI-4RYLp54,637
|
|
9
|
+
tigrbl_engine_rom-0.4.5.dist-info/licenses/NOTICE,sha256=_I9mpUItIZUweT3-Ypbwn3Cdjdqfpm7v_P6b9CwYX4c,222
|
|
10
|
+
tigrbl_engine_rom-0.4.5.dist-info/RECORD,,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Swarmauri
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|