tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.0.dev3__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 (252) hide show
  1. tigrbl/README.md +94 -0
  2. tigrbl/__init__.py +139 -14
  3. tigrbl/api/__init__.py +6 -0
  4. tigrbl/api/_api.py +72 -0
  5. tigrbl/api/api_spec.py +30 -0
  6. tigrbl/api/mro_collect.py +43 -0
  7. tigrbl/api/shortcuts.py +56 -0
  8. tigrbl/api/tigrbl_api.py +286 -0
  9. tigrbl/app/__init__.py +0 -0
  10. tigrbl/app/_app.py +61 -0
  11. tigrbl/app/app_spec.py +42 -0
  12. tigrbl/app/mro_collect.py +67 -0
  13. tigrbl/app/shortcuts.py +65 -0
  14. tigrbl/app/tigrbl_app.py +314 -0
  15. tigrbl/bindings/__init__.py +73 -0
  16. tigrbl/bindings/api/__init__.py +12 -0
  17. tigrbl/bindings/api/common.py +109 -0
  18. tigrbl/bindings/api/include.py +256 -0
  19. tigrbl/bindings/api/resource_proxy.py +149 -0
  20. tigrbl/bindings/api/rpc.py +111 -0
  21. tigrbl/bindings/columns.py +49 -0
  22. tigrbl/bindings/handlers/__init__.py +11 -0
  23. tigrbl/bindings/handlers/builder.py +119 -0
  24. tigrbl/bindings/handlers/ctx.py +74 -0
  25. tigrbl/bindings/handlers/identifiers.py +228 -0
  26. tigrbl/bindings/handlers/namespaces.py +51 -0
  27. tigrbl/bindings/handlers/steps.py +276 -0
  28. tigrbl/bindings/hooks.py +311 -0
  29. tigrbl/bindings/model.py +194 -0
  30. tigrbl/bindings/model_helpers.py +139 -0
  31. tigrbl/bindings/model_registry.py +77 -0
  32. tigrbl/bindings/rest/__init__.py +7 -0
  33. tigrbl/bindings/rest/attach.py +34 -0
  34. tigrbl/bindings/rest/collection.py +265 -0
  35. tigrbl/bindings/rest/common.py +116 -0
  36. tigrbl/bindings/rest/fastapi.py +76 -0
  37. tigrbl/bindings/rest/helpers.py +119 -0
  38. tigrbl/bindings/rest/io.py +317 -0
  39. tigrbl/bindings/rest/member.py +367 -0
  40. tigrbl/bindings/rest/router.py +292 -0
  41. tigrbl/bindings/rest/routing.py +133 -0
  42. tigrbl/bindings/rpc.py +364 -0
  43. tigrbl/bindings/schemas/__init__.py +11 -0
  44. tigrbl/bindings/schemas/builder.py +348 -0
  45. tigrbl/bindings/schemas/defaults.py +260 -0
  46. tigrbl/bindings/schemas/utils.py +193 -0
  47. tigrbl/column/README.md +62 -0
  48. tigrbl/column/__init__.py +72 -0
  49. tigrbl/column/_column.py +96 -0
  50. tigrbl/column/column_spec.py +40 -0
  51. tigrbl/column/field_spec.py +31 -0
  52. tigrbl/column/infer/__init__.py +25 -0
  53. tigrbl/column/infer/core.py +92 -0
  54. tigrbl/column/infer/jsonhints.py +44 -0
  55. tigrbl/column/infer/planning.py +133 -0
  56. tigrbl/column/infer/types.py +102 -0
  57. tigrbl/column/infer/utils.py +59 -0
  58. tigrbl/column/io_spec.py +133 -0
  59. tigrbl/column/mro_collect.py +59 -0
  60. tigrbl/column/shortcuts.py +89 -0
  61. tigrbl/column/storage_spec.py +65 -0
  62. tigrbl/config/__init__.py +19 -0
  63. tigrbl/config/constants.py +224 -0
  64. tigrbl/config/defaults.py +29 -0
  65. tigrbl/config/resolver.py +295 -0
  66. tigrbl/core/__init__.py +47 -0
  67. tigrbl/core/crud/__init__.py +36 -0
  68. tigrbl/core/crud/bulk.py +168 -0
  69. tigrbl/core/crud/helpers/__init__.py +76 -0
  70. tigrbl/core/crud/helpers/db.py +92 -0
  71. tigrbl/core/crud/helpers/enum.py +86 -0
  72. tigrbl/core/crud/helpers/filters.py +162 -0
  73. tigrbl/core/crud/helpers/model.py +123 -0
  74. tigrbl/core/crud/helpers/normalize.py +99 -0
  75. tigrbl/core/crud/ops.py +235 -0
  76. tigrbl/ddl/__init__.py +344 -0
  77. tigrbl/decorators.py +17 -0
  78. tigrbl/deps/__init__.py +20 -0
  79. tigrbl/deps/fastapi.py +45 -0
  80. tigrbl/deps/favicon.svg +4 -0
  81. tigrbl/deps/jinja.py +27 -0
  82. tigrbl/deps/pydantic.py +10 -0
  83. tigrbl/deps/sqlalchemy.py +94 -0
  84. tigrbl/deps/starlette.py +36 -0
  85. tigrbl/engine/__init__.py +26 -0
  86. tigrbl/engine/_engine.py +130 -0
  87. tigrbl/engine/bind.py +33 -0
  88. tigrbl/engine/builders.py +236 -0
  89. tigrbl/engine/collect.py +111 -0
  90. tigrbl/engine/decorators.py +108 -0
  91. tigrbl/engine/engine_spec.py +261 -0
  92. tigrbl/engine/resolver.py +224 -0
  93. tigrbl/engine/shortcuts.py +216 -0
  94. tigrbl/hook/__init__.py +21 -0
  95. tigrbl/hook/_hook.py +22 -0
  96. tigrbl/hook/decorators.py +28 -0
  97. tigrbl/hook/hook_spec.py +24 -0
  98. tigrbl/hook/mro_collect.py +98 -0
  99. tigrbl/hook/shortcuts.py +44 -0
  100. tigrbl/hook/types.py +76 -0
  101. tigrbl/op/__init__.py +50 -0
  102. tigrbl/op/_op.py +31 -0
  103. tigrbl/op/canonical.py +31 -0
  104. tigrbl/op/collect.py +11 -0
  105. tigrbl/op/decorators.py +238 -0
  106. tigrbl/op/model_registry.py +301 -0
  107. tigrbl/op/mro_collect.py +99 -0
  108. tigrbl/op/resolver.py +216 -0
  109. tigrbl/op/types.py +136 -0
  110. tigrbl/orm/__init__.py +1 -0
  111. tigrbl/orm/mixins/_RowBound.py +83 -0
  112. tigrbl/orm/mixins/__init__.py +95 -0
  113. tigrbl/orm/mixins/bootstrappable.py +113 -0
  114. tigrbl/orm/mixins/bound.py +47 -0
  115. tigrbl/orm/mixins/edges.py +40 -0
  116. tigrbl/orm/mixins/fields.py +165 -0
  117. tigrbl/orm/mixins/hierarchy.py +54 -0
  118. tigrbl/orm/mixins/key_digest.py +44 -0
  119. tigrbl/orm/mixins/lifecycle.py +115 -0
  120. tigrbl/orm/mixins/locks.py +51 -0
  121. tigrbl/orm/mixins/markers.py +16 -0
  122. tigrbl/orm/mixins/operations.py +57 -0
  123. tigrbl/orm/mixins/ownable.py +337 -0
  124. tigrbl/orm/mixins/principals.py +98 -0
  125. tigrbl/orm/mixins/tenant_bound.py +301 -0
  126. tigrbl/orm/mixins/upsertable.py +111 -0
  127. tigrbl/orm/mixins/utils.py +49 -0
  128. tigrbl/orm/tables/__init__.py +72 -0
  129. tigrbl/orm/tables/_base.py +8 -0
  130. tigrbl/orm/tables/audit.py +56 -0
  131. tigrbl/orm/tables/client.py +25 -0
  132. tigrbl/orm/tables/group.py +29 -0
  133. tigrbl/orm/tables/org.py +30 -0
  134. tigrbl/orm/tables/rbac.py +76 -0
  135. tigrbl/orm/tables/status.py +106 -0
  136. tigrbl/orm/tables/tenant.py +22 -0
  137. tigrbl/orm/tables/user.py +39 -0
  138. tigrbl/response/README.md +34 -0
  139. tigrbl/response/__init__.py +33 -0
  140. tigrbl/response/bind.py +12 -0
  141. tigrbl/response/decorators.py +37 -0
  142. tigrbl/response/resolver.py +83 -0
  143. tigrbl/response/shortcuts.py +144 -0
  144. tigrbl/response/types.py +49 -0
  145. tigrbl/rest/__init__.py +27 -0
  146. tigrbl/runtime/README.md +129 -0
  147. tigrbl/runtime/__init__.py +20 -0
  148. tigrbl/runtime/atoms/__init__.py +102 -0
  149. tigrbl/runtime/atoms/emit/__init__.py +42 -0
  150. tigrbl/runtime/atoms/emit/paired_post.py +158 -0
  151. tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
  152. tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
  153. tigrbl/runtime/atoms/out/__init__.py +38 -0
  154. tigrbl/runtime/atoms/out/masking.py +135 -0
  155. tigrbl/runtime/atoms/refresh/__init__.py +38 -0
  156. tigrbl/runtime/atoms/refresh/demand.py +130 -0
  157. tigrbl/runtime/atoms/resolve/__init__.py +40 -0
  158. tigrbl/runtime/atoms/resolve/assemble.py +167 -0
  159. tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
  160. tigrbl/runtime/atoms/response/__init__.py +17 -0
  161. tigrbl/runtime/atoms/response/negotiate.py +30 -0
  162. tigrbl/runtime/atoms/response/negotiation.py +43 -0
  163. tigrbl/runtime/atoms/response/render.py +36 -0
  164. tigrbl/runtime/atoms/response/renderer.py +116 -0
  165. tigrbl/runtime/atoms/response/template.py +44 -0
  166. tigrbl/runtime/atoms/response/templates.py +88 -0
  167. tigrbl/runtime/atoms/schema/__init__.py +40 -0
  168. tigrbl/runtime/atoms/schema/collect_in.py +21 -0
  169. tigrbl/runtime/atoms/schema/collect_out.py +21 -0
  170. tigrbl/runtime/atoms/storage/__init__.py +38 -0
  171. tigrbl/runtime/atoms/storage/to_stored.py +167 -0
  172. tigrbl/runtime/atoms/wire/__init__.py +45 -0
  173. tigrbl/runtime/atoms/wire/build_in.py +166 -0
  174. tigrbl/runtime/atoms/wire/build_out.py +87 -0
  175. tigrbl/runtime/atoms/wire/dump.py +206 -0
  176. tigrbl/runtime/atoms/wire/validate_in.py +227 -0
  177. tigrbl/runtime/context.py +206 -0
  178. tigrbl/runtime/errors/__init__.py +61 -0
  179. tigrbl/runtime/errors/converters.py +214 -0
  180. tigrbl/runtime/errors/exceptions.py +124 -0
  181. tigrbl/runtime/errors/mappings.py +71 -0
  182. tigrbl/runtime/errors/utils.py +150 -0
  183. tigrbl/runtime/events.py +209 -0
  184. tigrbl/runtime/executor/__init__.py +6 -0
  185. tigrbl/runtime/executor/guards.py +132 -0
  186. tigrbl/runtime/executor/helpers.py +88 -0
  187. tigrbl/runtime/executor/invoke.py +150 -0
  188. tigrbl/runtime/executor/types.py +84 -0
  189. tigrbl/runtime/kernel.py +628 -0
  190. tigrbl/runtime/labels.py +353 -0
  191. tigrbl/runtime/opview.py +87 -0
  192. tigrbl/runtime/ordering.py +256 -0
  193. tigrbl/runtime/system.py +279 -0
  194. tigrbl/runtime/trace.py +330 -0
  195. tigrbl/schema/__init__.py +38 -0
  196. tigrbl/schema/_schema.py +27 -0
  197. tigrbl/schema/builder/__init__.py +17 -0
  198. tigrbl/schema/builder/build_schema.py +209 -0
  199. tigrbl/schema/builder/cache.py +24 -0
  200. tigrbl/schema/builder/compat.py +16 -0
  201. tigrbl/schema/builder/extras.py +85 -0
  202. tigrbl/schema/builder/helpers.py +51 -0
  203. tigrbl/schema/builder/list_params.py +117 -0
  204. tigrbl/schema/builder/strip_parent_fields.py +70 -0
  205. tigrbl/schema/collect.py +55 -0
  206. tigrbl/schema/decorators.py +68 -0
  207. tigrbl/schema/get_schema.py +86 -0
  208. tigrbl/schema/schema_spec.py +20 -0
  209. tigrbl/schema/shortcuts.py +42 -0
  210. tigrbl/schema/types.py +34 -0
  211. tigrbl/schema/utils.py +143 -0
  212. tigrbl/shortcuts.py +22 -0
  213. tigrbl/specs.py +44 -0
  214. tigrbl/system/__init__.py +12 -0
  215. tigrbl/system/diagnostics/__init__.py +24 -0
  216. tigrbl/system/diagnostics/compat.py +31 -0
  217. tigrbl/system/diagnostics/healthz.py +41 -0
  218. tigrbl/system/diagnostics/hookz.py +51 -0
  219. tigrbl/system/diagnostics/kernelz.py +20 -0
  220. tigrbl/system/diagnostics/methodz.py +43 -0
  221. tigrbl/system/diagnostics/router.py +73 -0
  222. tigrbl/system/diagnostics/utils.py +43 -0
  223. tigrbl/table/__init__.py +9 -0
  224. tigrbl/table/_base.py +237 -0
  225. tigrbl/table/_table.py +54 -0
  226. tigrbl/table/mro_collect.py +69 -0
  227. tigrbl/table/shortcuts.py +57 -0
  228. tigrbl/table/table_spec.py +28 -0
  229. tigrbl/transport/__init__.py +74 -0
  230. tigrbl/transport/jsonrpc/__init__.py +19 -0
  231. tigrbl/transport/jsonrpc/dispatcher.py +352 -0
  232. tigrbl/transport/jsonrpc/helpers.py +115 -0
  233. tigrbl/transport/jsonrpc/models.py +41 -0
  234. tigrbl/transport/rest/__init__.py +25 -0
  235. tigrbl/transport/rest/aggregator.py +132 -0
  236. tigrbl/types/__init__.py +174 -0
  237. tigrbl/types/allow_anon_provider.py +19 -0
  238. tigrbl/types/authn_abc.py +30 -0
  239. tigrbl/types/nested_path_provider.py +22 -0
  240. tigrbl/types/op.py +35 -0
  241. tigrbl/types/op_config_provider.py +17 -0
  242. tigrbl/types/op_verb_alias_provider.py +33 -0
  243. tigrbl/types/request_extras_provider.py +22 -0
  244. tigrbl/types/response_extras_provider.py +22 -0
  245. tigrbl/types/table_config_provider.py +13 -0
  246. tigrbl-0.3.0.dev3.dist-info/LICENSE +201 -0
  247. tigrbl-0.3.0.dev3.dist-info/METADATA +501 -0
  248. tigrbl-0.3.0.dev3.dist-info/RECORD +249 -0
  249. tigrbl/ExampleAgent.py +0 -1
  250. tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
  251. tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
  252. {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dev3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,628 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import logging
5
+ import pkgutil
6
+ import threading
7
+ import weakref
8
+ import time
9
+ from dataclasses import dataclass
10
+ from types import SimpleNamespace
11
+ from typing import (
12
+ Any,
13
+ Callable,
14
+ Dict,
15
+ Iterable,
16
+ List,
17
+ Mapping,
18
+ Optional,
19
+ Sequence,
20
+ Tuple,
21
+ cast,
22
+ Generic,
23
+ TypeVar,
24
+ )
25
+
26
+ from .executor import _invoke, _Ctx
27
+ from . import events as _ev, ordering as _ordering, system as _sys
28
+ from ..op.types import PHASES, StepFn
29
+ from ..column.mro_collect import mro_collect_columns
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ K = TypeVar("K")
35
+ V = TypeVar("V")
36
+
37
+
38
+ class _WeakMaybeDict(Generic[K, V]):
39
+ """Dictionary that uses weak references when possible.
40
+
41
+ Falls back to strong references when ``key`` cannot be weakly referenced.
42
+ """
43
+
44
+ def __init__(self) -> None:
45
+ self._weak: "weakref.WeakKeyDictionary[Any, V]" = weakref.WeakKeyDictionary()
46
+ self._strong: Dict[int, tuple[Any, V]] = {}
47
+
48
+ def _use_weak(self, key: Any) -> bool:
49
+ try:
50
+ weakref.ref(key)
51
+ return True
52
+ except TypeError:
53
+ return False
54
+
55
+ def __setitem__(self, key: K, value: V) -> None:
56
+ if self._use_weak(key):
57
+ self._weak[key] = value
58
+ else:
59
+ self._strong[id(key)] = (key, value)
60
+
61
+ def __getitem__(self, key: K) -> V:
62
+ if self._use_weak(key):
63
+ return self._weak[key]
64
+ return self._strong[id(key)][1]
65
+
66
+ def get(self, key: K, default: Optional[V] = None) -> Optional[V]:
67
+ if self._use_weak(key):
68
+ return self._weak.get(key, default)
69
+ return self._strong.get(id(key), (None, default))[1]
70
+
71
+ def setdefault(self, key: K, default: V) -> V:
72
+ if self._use_weak(key):
73
+ return self._weak.setdefault(key, default)
74
+ return self._strong.setdefault(id(key), (key, default))[1]
75
+
76
+ def pop(self, key: K, default: Optional[V] = None) -> Optional[V]:
77
+ if self._use_weak(key):
78
+ return self._weak.pop(key, default)
79
+ return self._strong.pop(id(key), (None, default))[1]
80
+
81
+
82
+ # ---- OpView shapes -------------------------------------------------
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class SchemaIn:
87
+ fields: Tuple[str, ...]
88
+ by_field: Dict[str, Dict[str, object]]
89
+
90
+
91
+ @dataclass(frozen=True)
92
+ class SchemaOut:
93
+ fields: Tuple[str, ...]
94
+ by_field: Dict[str, Dict[str, object]]
95
+ expose: Tuple[str, ...]
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class OpView:
100
+ schema_in: SchemaIn
101
+ schema_out: SchemaOut
102
+ paired_index: Dict[str, Dict[str, object]]
103
+ virtual_producers: Dict[str, Callable[[object, dict], object]]
104
+ to_stored_transforms: Dict[str, Callable[[object, dict], object]]
105
+ refresh_hints: Tuple[str, ...]
106
+
107
+
108
+ # ──────────────────────────── Discovery ────────────────────────────
109
+
110
+ _AtomRun = Callable[[Optional[object], Any], Any]
111
+ _DiscoveredAtom = tuple[str, _AtomRun]
112
+
113
+
114
+ def _discover_atoms() -> list[_DiscoveredAtom]:
115
+ out: list[_DiscoveredAtom] = []
116
+ try:
117
+ import tigrbl.runtime.atoms as atoms_pkg # type: ignore
118
+ except Exception:
119
+ return out
120
+ for info in pkgutil.walk_packages(atoms_pkg.__path__, atoms_pkg.__name__ + "."): # type: ignore[attr-defined]
121
+ if info.ispkg:
122
+ continue
123
+ try:
124
+ mod = importlib.import_module(info.name)
125
+ anchor = getattr(mod, "ANCHOR", None)
126
+ run = getattr(mod, "run", None)
127
+ if isinstance(anchor, str) and callable(run):
128
+ out.append((anchor, run))
129
+ except Exception:
130
+ continue
131
+ logger.debug("kernel: discovered %d atoms", len(out))
132
+ return out
133
+
134
+
135
+ # ──────────────────────────── Labeling ─────────────────────────────
136
+
137
+
138
+ def _infer_domain_subject(run: _AtomRun) -> tuple[Optional[str], Optional[str]]:
139
+ mod = getattr(run, "__module__", "") or ""
140
+ parts = mod.split(".")
141
+ try:
142
+ i = parts.index("atoms")
143
+ return (
144
+ parts[i + 1] if i + 1 < len(parts) else None,
145
+ parts[i + 2] if i + 2 < len(parts) else None,
146
+ )
147
+ except ValueError:
148
+ return None, None
149
+
150
+
151
+ def _make_label(anchor: str, run: _AtomRun) -> Optional[str]:
152
+ d, s = _infer_domain_subject(run)
153
+ return f"atom:{d}:{s}@{anchor}" if (d and s) else None
154
+
155
+
156
+ def _wrap_atom(run: _AtomRun, *, anchor: str) -> StepFn:
157
+ async def _step(ctx: Any) -> Any:
158
+ rv = run(None, ctx)
159
+ if hasattr(rv, "__await__"):
160
+ return await cast(Any, rv)
161
+ return rv
162
+
163
+ label = _make_label(anchor, run)
164
+ if label:
165
+ setattr(_step, "__tigrbl_label", label)
166
+ return _step
167
+
168
+
169
+ # ──────────────────────────── Specs cache (once) ────────────────────
170
+
171
+
172
+ class _SpecsOnceCache:
173
+ """Thread-safe, compute-once cache of per-model column specs."""
174
+
175
+ def __init__(self) -> None:
176
+ self._d: Dict[type, Mapping[str, Any]] = {}
177
+ self._lock = threading.Lock()
178
+
179
+ def get(self, model: type) -> Mapping[str, Any]:
180
+ try:
181
+ return self._d[model]
182
+ except KeyError:
183
+ pass
184
+ with self._lock:
185
+ rv = self._d.get(model)
186
+ if rv is None:
187
+ rv = mro_collect_columns(model)
188
+ self._d[model] = rv
189
+ return rv
190
+
191
+ def prime(self, models: Sequence[type]) -> None:
192
+ for m in models:
193
+ self.get(m)
194
+
195
+ def invalidate(self, model: Optional[type] = None) -> None:
196
+ with self._lock:
197
+ if model is None:
198
+ self._d.clear()
199
+ else:
200
+ self._d.pop(model, None)
201
+
202
+
203
+ # ──────────────────────────── Hooks & Phases ────────────────────────
204
+
205
+
206
+ def _hook_phase_chains(model: type, alias: str) -> Dict[str, List[StepFn]]:
207
+ hooks_root = getattr(model, "hooks", None) or SimpleNamespace()
208
+ alias_ns = getattr(hooks_root, alias, None)
209
+ out: Dict[str, List[StepFn]] = {ph: [] for ph in PHASES}
210
+ if alias_ns is None:
211
+ return out
212
+ for ph in PHASES:
213
+ out[ph] = list(getattr(alias_ns, ph, []) or [])
214
+ return out
215
+
216
+
217
+ def _is_persistent(chains: Mapping[str, Sequence[StepFn]]) -> bool:
218
+ for fn in chains.get("START_TX", ()) or ():
219
+ if getattr(fn, "__name__", "") == "start_tx":
220
+ return True
221
+ for fn in chains.get("PRE_TX_BEGIN", ()) or ():
222
+ if getattr(fn, "__name__", "") == "mark_skip_persist":
223
+ return False
224
+ return False
225
+
226
+
227
+ def _inject_atoms(
228
+ chains: Dict[str, List[StepFn]],
229
+ atoms: Iterable[_DiscoveredAtom],
230
+ *,
231
+ persistent: bool,
232
+ ) -> None:
233
+ order = {name: i for i, name in enumerate(_ev.all_events_ordered())}
234
+
235
+ def _sort_key(item: _DiscoveredAtom) -> tuple[int, int]:
236
+ anchor, run = item
237
+ anchor_idx = order.get(anchor, 10_000)
238
+ d, s = _infer_domain_subject(run)
239
+ token = f"{d}:{s}" if d and s else ""
240
+ pref = _ordering._PREF.get(anchor, ())
241
+ token_idx = pref.index(token) if token in pref else 10_000
242
+ return anchor_idx, token_idx
243
+
244
+ for anchor, run in sorted(atoms, key=_sort_key):
245
+ try:
246
+ info = _ev.get_anchor_info(anchor)
247
+ except Exception:
248
+ continue
249
+ if info.phase in ("START_TX", "END_TX"):
250
+ continue
251
+ if not persistent and info.persist_tied:
252
+ continue
253
+ chains.setdefault(info.phase, []).append(_wrap_atom(run, anchor=anchor))
254
+
255
+
256
+ # ───────────────────────────── Kernel ──────────────────────────────
257
+
258
+
259
+ class Kernel:
260
+ """
261
+ SSoT for runtime scheduling. One Kernel per App (not per API).
262
+ Auto-primed under the hood. Downstream users never touch this.
263
+ """
264
+
265
+ def __init__(self, atoms: Optional[Sequence[_DiscoveredAtom]] = None):
266
+ self._atoms_cache: Optional[list[_DiscoveredAtom]] = (
267
+ list(atoms) if atoms else None
268
+ )
269
+ self._specs_cache = _SpecsOnceCache()
270
+ self._opviews: _WeakMaybeDict[Any, Dict[Tuple[type, str], OpView]] = (
271
+ _WeakMaybeDict()
272
+ )
273
+ self._kernelz_payload: _WeakMaybeDict[Any, Dict[str, Dict[str, List[str]]]] = (
274
+ _WeakMaybeDict()
275
+ )
276
+ self._primed: _WeakMaybeDict[Any, bool] = _WeakMaybeDict()
277
+ self._lock = threading.Lock()
278
+
279
+ # ——— atoms ———
280
+ def _atoms(self) -> list[_DiscoveredAtom]:
281
+ if self._atoms_cache is None:
282
+ self._atoms_cache = _discover_atoms()
283
+ return self._atoms_cache
284
+
285
+ # ——— specs cache (maintainers only; used internally) ———
286
+ def get_specs(self, model: type) -> Mapping[str, Any]:
287
+ return self._specs_cache.get(model)
288
+
289
+ def prime_specs(self, models: Sequence[type]) -> None:
290
+ self._specs_cache.prime(models)
291
+
292
+ def invalidate_specs(self, model: Optional[type] = None) -> None:
293
+ self._specs_cache.invalidate(model)
294
+
295
+ # ——— build / plan ———
296
+ def build(self, model: type, alias: str) -> Dict[str, List[StepFn]]:
297
+ chains = _hook_phase_chains(model, alias)
298
+ specs = getattr(getattr(model, "ops", SimpleNamespace()), "by_alias", {})
299
+ sp_list = specs.get(alias) or ()
300
+ sp = sp_list[0] if sp_list else None
301
+ target = (getattr(sp, "target", alias) or "").lower()
302
+ persist_policy = getattr(sp, "persist", "default")
303
+ persistent = (
304
+ persist_policy != "skip" and target not in {"read", "list"}
305
+ ) or _is_persistent(chains)
306
+ try:
307
+ _inject_atoms(chains, self._atoms() or (), persistent=persistent)
308
+ except Exception:
309
+ logger.exception(
310
+ "kernel: atom injection failed for %s.%s",
311
+ getattr(model, "__name__", model),
312
+ alias,
313
+ )
314
+ if persistent:
315
+ try:
316
+ start_anchor, start_run = _sys.get("txn", "begin")
317
+ end_anchor, end_run = _sys.get("txn", "commit")
318
+ chains.setdefault(start_anchor, []).append(
319
+ _wrap_atom(start_run, anchor=start_anchor)
320
+ )
321
+ chains.setdefault(end_anchor, []).append(
322
+ _wrap_atom(end_run, anchor=end_anchor)
323
+ )
324
+ except Exception:
325
+ logger.exception(
326
+ "kernel: failed to inject txn system steps for %s.%s",
327
+ getattr(model, "__name__", model),
328
+ alias,
329
+ )
330
+ for ph in PHASES:
331
+ chains.setdefault(ph, [])
332
+ return chains
333
+
334
+ def plan_labels(self, model: type, alias: str) -> list[str]:
335
+ labels: list[str] = []
336
+ chains = self.build(model, alias)
337
+ ordered_anchors = _ev.all_events_ordered()
338
+ phase_for = {a: _ev.get_anchor_info(a).phase for a in ordered_anchors}
339
+ for anchor in ordered_anchors:
340
+ phase = phase_for[anchor]
341
+ for step in chains.get(phase, []) or []:
342
+ lbl = getattr(step, "__tigrbl_label", None)
343
+ if isinstance(lbl, str) and lbl.endswith(f"@{anchor}"):
344
+ labels.append(lbl)
345
+ return labels
346
+
347
+ async def invoke(
348
+ self,
349
+ *,
350
+ model: type,
351
+ alias: str,
352
+ db: Any,
353
+ request: Any | None = None,
354
+ ctx: Optional[Mapping[str, Any]] = None,
355
+ ) -> Any:
356
+ """Execute an operation for ``model.alias`` using the executor."""
357
+ phases = self.build(model, alias)
358
+ base_ctx = _Ctx.ensure(request=request, db=db, seed=ctx)
359
+ base_ctx.model = model
360
+ base_ctx.op = alias
361
+ specs = self.get_specs(model)
362
+ base_ctx.opview = self._compile_opview_from_specs(
363
+ specs, SimpleNamespace(alias=alias)
364
+ )
365
+ return await _invoke(request=request, db=db, phases=phases, ctx=base_ctx)
366
+
367
+ # ——— per-App autoprime (hidden) ———
368
+ def ensure_primed(self, app: Any) -> None:
369
+ """Autoprime once per App: specs → OpViews → /kernelz payload."""
370
+ with self._lock:
371
+ if self._primed.get(app):
372
+ return
373
+ from ..system.diagnostics.utils import (
374
+ model_iter as _model_iter,
375
+ opspecs as _opspecs,
376
+ )
377
+
378
+ models = list(_model_iter(app))
379
+
380
+ # 1) per-model specs once
381
+ for m in models:
382
+ self._specs_cache.get(m)
383
+
384
+ # 2) compile OpViews per (model, alias)
385
+ ov_map: Dict[Tuple[type, str], OpView] = {}
386
+ for m in models:
387
+ specs = self._specs_cache.get(m)
388
+ for sp in _opspecs(m):
389
+ ov_map[(m, sp.alias)] = self._compile_opview_from_specs(specs, sp)
390
+ self._opviews[app] = ov_map
391
+
392
+ # 3) build /kernelz payload once (dedup wire hooks)
393
+ payload = self._build_kernelz_payload_internal(app)
394
+ self._kernelz_payload[app] = payload
395
+ self._primed[app] = True
396
+
397
+ def get_opview(self, app: Any, model: type, alias: str) -> OpView:
398
+ """Return OpView for (model, alias); compile on-demand if missing."""
399
+ self.ensure_primed(app)
400
+
401
+ ov_map: Dict[Tuple[type, str], OpView] = self._opviews.setdefault(app, {})
402
+ ov = ov_map.get((model, alias))
403
+ if ov is not None:
404
+ return ov
405
+
406
+ try:
407
+ specs = self._specs_cache.get(model)
408
+ from types import SimpleNamespace
409
+ from ..system.diagnostics.utils import opspecs as _opspecs
410
+
411
+ found = False
412
+ for sp in _opspecs(model):
413
+ ov_map.setdefault(
414
+ (model, sp.alias), self._compile_opview_from_specs(specs, sp)
415
+ )
416
+ if sp.alias == alias:
417
+ found = True
418
+
419
+ if not found:
420
+ temp_sp = SimpleNamespace(alias=alias)
421
+ ov_map[(model, alias)] = self._compile_opview_from_specs(specs, temp_sp)
422
+
423
+ return ov_map[(model, alias)]
424
+ except Exception:
425
+ raise RuntimeError(
426
+ f"opview_missing: app={app!r} model={getattr(model, '__name__', model)!r} alias={alias!r}"
427
+ )
428
+
429
+ def kernelz_payload(self, app: Any) -> Dict[str, Dict[str, List[str]]]:
430
+ """Thin accessor for endpoint: guarantees primed, returns cached payload."""
431
+ self.ensure_primed(app)
432
+ return self._kernelz_payload[app]
433
+
434
+ def invalidate_kernelz_payload(self, app: Optional[Any] = None) -> None:
435
+ with self._lock:
436
+ if app is None:
437
+ self._kernelz_payload = _WeakMaybeDict()
438
+ self._opviews = _WeakMaybeDict()
439
+ self._primed = _WeakMaybeDict()
440
+ else:
441
+ self._kernelz_payload.pop(app, None)
442
+ self._opviews.pop(app, None)
443
+ self._primed.pop(app, None)
444
+
445
+ def _compile_opview_from_specs(self, specs: Mapping[str, Any], sp: Any) -> OpView:
446
+ """Build a basic OpView from collected specs when no app/model is present."""
447
+ alias = getattr(sp, "alias", "")
448
+
449
+ in_fields: list[str] = []
450
+ out_fields: list[str] = []
451
+ by_field_in: Dict[str, Dict[str, object]] = {}
452
+ by_field_out: Dict[str, Dict[str, object]] = {}
453
+
454
+ for name, spec in specs.items():
455
+ io = getattr(spec, "io", None)
456
+ fs = getattr(spec, "field", None)
457
+ storage = getattr(spec, "storage", None)
458
+ in_verbs = set(getattr(io, "in_verbs", ()) or ())
459
+ out_verbs = set(getattr(io, "out_verbs", ()) or ())
460
+
461
+ if alias in in_verbs:
462
+ in_fields.append(name)
463
+ meta: Dict[str, object] = {"in_enabled": True}
464
+ if storage is None:
465
+ meta["virtual"] = True
466
+ df = getattr(spec, "default_factory", None)
467
+ if callable(df):
468
+ meta["default_factory"] = df
469
+ alias_in = getattr(io, "alias_in", None)
470
+ if alias_in:
471
+ meta["alias_in"] = alias_in
472
+ required = bool(fs and alias in getattr(fs, "required_in", ()))
473
+ meta["required"] = required
474
+ base_nullable = (
475
+ True if storage is None else getattr(storage, "nullable", True)
476
+ )
477
+ meta["nullable"] = base_nullable
478
+ by_field_in[name] = meta
479
+
480
+ if alias in out_verbs:
481
+ out_fields.append(name)
482
+ meta_out: Dict[str, object] = {}
483
+ alias_out = getattr(io, "alias_out", None)
484
+ if alias_out:
485
+ meta_out["alias_out"] = alias_out
486
+ if storage is None:
487
+ meta_out["virtual"] = True
488
+ py_t = getattr(getattr(fs, "py_type", None), "__name__", None)
489
+ if py_t:
490
+ meta_out["py_type"] = py_t
491
+ by_field_out[name] = meta_out
492
+
493
+ schema_in = SchemaIn(
494
+ fields=tuple(sorted(in_fields)),
495
+ by_field={f: by_field_in.get(f, {}) for f in sorted(in_fields)},
496
+ )
497
+ schema_out = SchemaOut(
498
+ fields=tuple(sorted(out_fields)),
499
+ by_field={f: by_field_out.get(f, {}) for f in sorted(out_fields)},
500
+ expose=tuple(sorted(out_fields)),
501
+ )
502
+ paired_index: Dict[str, Dict[str, object]] = {}
503
+ for field, col in specs.items():
504
+ io = getattr(col, "io", None)
505
+ cfg = getattr(io, "_paired", None)
506
+ if cfg and sp.alias in getattr(cfg, "verbs", ()): # type: ignore[attr-defined]
507
+ field_spec = getattr(col, "field", None)
508
+ max_len = None
509
+ if field_spec is not None:
510
+ max_len = getattr(
511
+ getattr(field_spec, "constraints", {}),
512
+ "get",
513
+ lambda k, d=None: None,
514
+ )("max_length")
515
+ paired_index[field] = {
516
+ "alias": cfg.alias,
517
+ "gen": cfg.gen,
518
+ "store": cfg.store,
519
+ "mask_last": cfg.mask_last,
520
+ "max_length": max_len,
521
+ }
522
+
523
+ return OpView(
524
+ schema_in=schema_in,
525
+ schema_out=schema_out,
526
+ paired_index=paired_index,
527
+ virtual_producers={},
528
+ to_stored_transforms={},
529
+ refresh_hints=(),
530
+ )
531
+
532
+ # ——— internal: endpoint-ready payload (once per App) ———
533
+ def _build_kernelz_payload_internal(
534
+ self, app: Any
535
+ ) -> Dict[str, Dict[str, List[str]]]:
536
+ from ..system.diagnostics.utils import (
537
+ model_iter as _model_iter,
538
+ opspecs as _opspecs,
539
+ label_callable as _label_callable,
540
+ label_hook as _label_hook,
541
+ )
542
+
543
+ start = time.monotonic()
544
+ out: Dict[str, Dict[str, List[str]]] = {}
545
+ for model in _model_iter(app):
546
+ self.get_specs(model) # ensure cached
547
+ mname = getattr(model, "__name__", "Model")
548
+ model_map: Dict[str, List[str]] = {}
549
+ for sp in _opspecs(model):
550
+ seq: List[str] = []
551
+
552
+ # PRE_TX: secdeps / deps
553
+ secdeps = [
554
+ _label_callable(d) if callable(d) else str(d)
555
+ for d in (getattr(sp, "secdeps", []) or [])
556
+ ]
557
+ deps = [
558
+ _label_callable(d) if callable(d) else str(d)
559
+ for d in (getattr(sp, "deps", []) or [])
560
+ ]
561
+ seq.extend(f"PRE_TX:secdep:{s}" for s in secdeps)
562
+ seq.extend(f"PRE_TX:dep:{d}" for d in deps)
563
+
564
+ # Chains and system hooks in canonical phase order
565
+ chains = self.build(model, sp.alias)
566
+ persist = getattr(sp, "persist", "default") != "skip"
567
+
568
+ for ph in PHASES:
569
+ if ph == "START_TX" and persist:
570
+ seq.append("START_TX:hook:sys:txn:begin@START_TX")
571
+
572
+ for step in chains.get(ph, []) or []:
573
+ lbl = getattr(step, "__tigrbl_label", None)
574
+ seq.append(
575
+ f"{ph}:{lbl}"
576
+ if isinstance(lbl, str)
577
+ else f"{ph}:{_label_hook(step, ph)}"
578
+ )
579
+
580
+ if ph == "END_TX" and persist:
581
+ seq.append("END_TX:hook:sys:txn:commit@END_TX")
582
+
583
+ # De-dup wire hooks (memory/perf friendly)
584
+ seen, dedup = set(), []
585
+ for lbl in seq:
586
+ if ":hook:wire:" in lbl:
587
+ if lbl in seen:
588
+ continue
589
+ seen.add(lbl)
590
+ dedup.append(lbl)
591
+
592
+ model_map[sp.alias] = dedup
593
+
594
+ if model_map:
595
+ out[mname] = model_map
596
+ duration = time.monotonic() - start
597
+ logger.debug("kernel: built kernelz payload for app %s in %.3fs", app, duration)
598
+ return out
599
+
600
+
601
+ # ───────────────────────── Module-level exports ────────────────────
602
+
603
+ _default_kernel = Kernel()
604
+
605
+
606
+ def get_cached_specs(model: type) -> Mapping[str, Any]:
607
+ """Atoms can call this; zero per-request collection."""
608
+ return _default_kernel.get_specs(model)
609
+
610
+
611
+ def build_phase_chains(model: type, alias: str) -> Dict[str, List[StepFn]]:
612
+ return _default_kernel.build(model, alias)
613
+
614
+
615
+ async def run(
616
+ model: type,
617
+ alias: str,
618
+ *,
619
+ db: Any,
620
+ request: Any | None = None,
621
+ ctx: Optional[Mapping[str, Any]] = None,
622
+ ) -> Any:
623
+ phases = _default_kernel.build(model, alias)
624
+ base_ctx = _Ctx.ensure(request=request, db=db, seed=ctx)
625
+ return await _invoke(request=request, db=db, phases=phases, ctx=base_ctx)
626
+
627
+
628
+ __all__ = ["Kernel", "get_cached_specs", "_default_kernel", "build_phase_chains", "run"]