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