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,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional
4
+ from .spec import SessionSpec, SessionCfg
5
+
6
+
7
+ def _normalize(cfg: Optional[SessionCfg] = None, **kw: Any) -> SessionSpec:
8
+ if cfg is not None and kw:
9
+ raise ValueError("Pass either a mapping/spec or keyword args, not both")
10
+ return SessionSpec.from_any(cfg or kw) or SessionSpec()
11
+
12
+
13
+ def session_ctx(cfg: Optional[SessionCfg] = None, /, **kw: Any):
14
+ """
15
+ Attach a SessionSpec to an App, API, Model/Table, or op handler.
16
+
17
+ Precedence is evaluated by the resolver using:
18
+ op > model > api > app
19
+ (Resolver is part of the runtime/engine layer and is independent of this decorator.)
20
+ """
21
+ spec = _normalize(cfg, **kw)
22
+
23
+ def _apply(obj: Any) -> Any:
24
+ setattr(obj, "__tigrbl_session_ctx__", spec)
25
+ return obj
26
+
27
+ return _apply
28
+
29
+
30
+ def read_only_session(obj: Any = None, /, *, isolation: Optional[str] = None):
31
+ """
32
+ Convenience decorator for read-only sessions.
33
+ """
34
+
35
+ def _wrap(o: Any) -> Any:
36
+ setattr(
37
+ o,
38
+ "__tigrbl_session_ctx__",
39
+ SessionSpec(read_only=True, isolation=isolation),
40
+ )
41
+ return o
42
+
43
+ return _wrap(obj) if obj is not None else _wrap
@@ -0,0 +1,118 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from typing import Any, Callable, Optional
5
+
6
+ from .base import TigrblSessionBase
7
+ from .spec import SessionSpec
8
+
9
+
10
+ class DefaultSession(TigrblSessionBase):
11
+ """
12
+ Delegating session that wraps an underlying native session object
13
+ (sync or async) and exposes the Tigrbl Session ABC.
14
+
15
+ No third-party imports: we rely on duck-typed methods on the underlying
16
+ object (begin/commit/rollback, add/delete/flush/refresh/get/execute/close).
17
+ """
18
+
19
+ def __init__(self, underlying: Any, spec: Optional[SessionSpec] = None) -> None:
20
+ super().__init__(spec)
21
+ self._u = underlying
22
+
23
+ # ---- async marker ----
24
+ async def run_sync(self, fn: Callable[[Any], Any]) -> Any:
25
+ rv = fn(self._u)
26
+ if inspect.isawaitable(rv):
27
+ return await rv
28
+ return rv
29
+
30
+ # ---- TX primitives ----
31
+ async def _tx_begin_impl(self) -> None:
32
+ fn = getattr(self._u, "begin", None)
33
+ if not callable(fn):
34
+ raise RuntimeError("underlying session does not support begin()")
35
+ rv = fn()
36
+ if inspect.isawaitable(rv):
37
+ await rv
38
+
39
+ async def _tx_commit_impl(self) -> None:
40
+ fn = getattr(self._u, "commit", None)
41
+ if not callable(fn):
42
+ raise RuntimeError("underlying session does not support commit()")
43
+ rv = fn()
44
+ if inspect.isawaitable(rv):
45
+ await rv
46
+
47
+ async def _tx_rollback_impl(self) -> None:
48
+ fn = getattr(self._u, "rollback", None)
49
+ if not callable(fn):
50
+ raise RuntimeError("underlying session does not support rollback()")
51
+ rv = fn()
52
+ if inspect.isawaitable(rv):
53
+ await rv
54
+
55
+ def in_transaction(self) -> bool:
56
+ it = getattr(self._u, "in_transaction", None)
57
+ if callable(it):
58
+ try:
59
+ return bool(it())
60
+ except Exception:
61
+ pass
62
+ return super().in_transaction()
63
+
64
+ # ---- CRUD primitives ----
65
+ def _add_impl(self, obj: Any) -> Any:
66
+ fn = getattr(self._u, "add", None)
67
+ if not callable(fn):
68
+ raise NotImplementedError("underlying session does not implement add(obj)")
69
+ return fn(obj)
70
+
71
+ async def _delete_impl(self, obj: Any) -> None:
72
+ fn = getattr(self._u, "delete", None)
73
+ if not callable(fn):
74
+ raise NotImplementedError(
75
+ "underlying session does not implement delete(obj)"
76
+ )
77
+ rv = fn(obj)
78
+ if inspect.isawaitable(rv):
79
+ await rv
80
+
81
+ async def _flush_impl(self) -> None:
82
+ fn = getattr(self._u, "flush", None)
83
+ if callable(fn):
84
+ rv = fn()
85
+ if inspect.isawaitable(rv):
86
+ await rv
87
+
88
+ async def _refresh_impl(self, obj: Any) -> None:
89
+ fn = getattr(self._u, "refresh", None)
90
+ if callable(fn):
91
+ rv = fn(obj)
92
+ if inspect.isawaitable(rv):
93
+ await rv
94
+
95
+ async def _get_impl(self, model: type, ident: Any) -> Any | None:
96
+ fn = getattr(self._u, "get", None)
97
+ if not callable(fn):
98
+ raise NotImplementedError(
99
+ "underlying session does not implement get(model, ident)"
100
+ )
101
+ rv = fn(model, ident)
102
+ return await rv if inspect.isawaitable(rv) else rv
103
+
104
+ async def _execute_impl(self, stmt: Any) -> Any:
105
+ fn = getattr(self._u, "execute", None)
106
+ if not callable(fn):
107
+ raise NotImplementedError(
108
+ "underlying session does not implement execute(stmt)"
109
+ )
110
+ rv = fn(stmt)
111
+ return await rv if inspect.isawaitable(rv) else rv
112
+
113
+ async def _close_impl(self) -> None:
114
+ fn = getattr(self._u, "close", None)
115
+ if callable(fn):
116
+ rv = fn()
117
+ if inspect.isawaitable(rv):
118
+ await rv
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Optional
4
+
5
+ from .default import DefaultSession
6
+ from .spec import SessionSpec, SessionCfg
7
+
8
+
9
+ def session_spec(cfg: SessionCfg = None, /, **kw: Any) -> SessionSpec:
10
+ """
11
+ Build a SessionSpec from either a mapping/spec or kwargs.
12
+ """
13
+ if cfg is not None and kw:
14
+ raise ValueError("Provide either a mapping/spec or kwargs, not both")
15
+ return SessionSpec.from_any(cfg or kw) or SessionSpec()
16
+
17
+
18
+ # Isolation presets
19
+ def tx_read_committed(*, read_only: Optional[bool] = None) -> SessionSpec:
20
+ return SessionSpec(isolation="read_committed", read_only=read_only)
21
+
22
+
23
+ def tx_repeatable_read(*, read_only: Optional[bool] = None) -> SessionSpec:
24
+ return SessionSpec(isolation="repeatable_read", read_only=read_only)
25
+
26
+
27
+ def tx_serializable(*, read_only: Optional[bool] = None) -> SessionSpec:
28
+ return SessionSpec(isolation="serializable", read_only=read_only)
29
+
30
+
31
+ def readonly() -> SessionSpec:
32
+ return SessionSpec(read_only=True)
33
+
34
+
35
+ # Provider/sessionmaker wrapper
36
+ def wrap_sessionmaker(
37
+ maker: Callable[[], Any], spec: SessionSpec
38
+ ) -> Callable[[], DefaultSession]:
39
+ """
40
+ Wrap any provider's session factory to yield DefaultSession instances that
41
+ enforce the Tigrbl Session ABC and policy.
42
+ """
43
+
44
+ def _mk() -> DefaultSession:
45
+ underlying = maker()
46
+ s = DefaultSession(underlying, spec)
47
+ s.apply_spec(spec)
48
+ return s
49
+
50
+ return _mk
tigrbl/session/spec.py ADDED
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, fields
4
+ from typing import Any, Mapping, MutableMapping, Optional, Union
5
+
6
+ SessionCfg = Union["SessionSpec", Mapping[str, Any], None]
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class SessionSpec:
11
+ """
12
+ Per-session policy for Tigrbl sessions.
13
+
14
+ These fields are backend-agnostic hints and constraints. Adapters should
15
+ validate and apply them where supported; critical ones (like isolation and
16
+ read_only) SHOULD be validated and enforced.
17
+ """
18
+
19
+ # Transaction policy
20
+ isolation: Optional[str] = (
21
+ None # "read_committed" | "repeatable_read" | "snapshot" | "serializable"
22
+ )
23
+ read_only: Optional[bool] = None
24
+ autobegin: Optional[bool] = True
25
+ expire_on_commit: Optional[bool] = None
26
+
27
+ # Retries & backoff
28
+ retry_on_conflict: Optional[bool] = None
29
+ max_retries: int = 0
30
+ backoff_ms: int = 0
31
+ backoff_jitter: bool = True
32
+
33
+ # Timeouts / resources
34
+ statement_timeout_ms: Optional[int] = None
35
+ lock_timeout_ms: Optional[int] = None
36
+ fetch_rows: Optional[int] = None
37
+ stream_chunk_rows: Optional[int] = None
38
+
39
+ # Consistency coordinates
40
+ min_lsn: Optional[str] = None
41
+ as_of_ts: Optional[str] = None
42
+ consistency: Optional[str] = None # "strong" | "bounded_staleness" | "eventual"
43
+ staleness_ms: Optional[int] = None
44
+
45
+ # Tenancy & security
46
+ tenant_id: Optional[str] = None
47
+ role: Optional[str] = None
48
+ rls_context: Mapping[str, str] = None
49
+
50
+ # Observability
51
+ trace_id: Optional[str] = None
52
+ query_tag: Optional[str] = None
53
+ tag: Optional[str] = None
54
+ tracing_sample: Optional[float] = None
55
+
56
+ # Cache / index hints
57
+ cache_read: Optional[bool] = None
58
+ cache_write: Optional[bool] = None
59
+ namespace: Optional[str] = None
60
+
61
+ # Data protection / compliance
62
+ kms_key_alias: Optional[str] = None
63
+ classification: Optional[str] = None
64
+ audit: Optional[bool] = None
65
+
66
+ # Idempotency & pagination
67
+ idempotency_key: Optional[str] = None
68
+ page_snapshot: Optional[str] = None
69
+
70
+ def merge(self, higher: "SessionSpec | Mapping[str, Any] | None") -> "SessionSpec":
71
+ """
72
+ Overlay another spec on top of this one (non-None fields take precedence).
73
+ Use to implement op > model > api > app precedence.
74
+ """
75
+ if higher is None:
76
+ return self
77
+ h = higher if isinstance(higher, SessionSpec) else SessionSpec.from_any(higher)
78
+ if h is None:
79
+ return self
80
+ vals: MutableMapping[str, Any] = {
81
+ f.name: getattr(self, f.name) for f in fields(SessionSpec)
82
+ }
83
+ for f in fields(SessionSpec):
84
+ hv = getattr(h, f.name)
85
+ if hv is not None:
86
+ vals[f.name] = hv
87
+ return SessionSpec(**vals) # type: ignore[arg-type]
88
+
89
+ def to_kwargs(self) -> dict[str, Any]:
90
+ """Return only non-None items as a plain dict (adapters can **kwargs this)."""
91
+ return {
92
+ f.name: getattr(self, f.name)
93
+ for f in fields(SessionSpec)
94
+ if getattr(self, f.name) is not None
95
+ }
96
+
97
+ @staticmethod
98
+ def from_any(x: SessionCfg) -> Optional["SessionSpec"]:
99
+ if x is None:
100
+ return None
101
+ if isinstance(x, SessionSpec):
102
+ return x
103
+ if isinstance(x, Mapping):
104
+ m = dict(x)
105
+ # aliases
106
+ if "readonly" in m and "read_only" not in m:
107
+ m["read_only"] = bool(m.pop("readonly"))
108
+ if "iso" in m and "isolation" not in m:
109
+ m["isolation"] = str(m.pop("iso"))
110
+ allowed = {f.name for f in fields(SessionSpec)}
111
+ return SessionSpec(**{k: v for k, v in m.items() if k in allowed})
112
+ raise TypeError(f"Unsupported SessionSpec input: {type(x)}")
tigrbl/shortcuts.py ADDED
@@ -0,0 +1,22 @@
1
+ # tigrbl/tigrbl/v3/shortcuts.py
2
+ from __future__ import annotations
3
+
4
+ from .app import shortcuts as app_shortcuts
5
+ from .api import shortcuts as api_shortcuts
6
+ from .engine import shortcuts as engine_shortcuts
7
+ from .specs import shortcuts as specs_shortcuts
8
+ from .table import shortcuts as table_shortcuts
9
+
10
+ from .app.shortcuts import * # noqa: F401,F403
11
+ from .api.shortcuts import * # noqa: F401,F403
12
+ from .engine.shortcuts import * # noqa: F401,F403
13
+ from .specs.shortcuts import * # noqa: F401,F403
14
+ from .table.shortcuts import * # noqa: F401,F403
15
+
16
+ __all__ = [
17
+ *app_shortcuts.__all__,
18
+ *api_shortcuts.__all__,
19
+ *engine_shortcuts.__all__,
20
+ *specs_shortcuts.__all__,
21
+ *table_shortcuts.__all__,
22
+ ]
tigrbl/specs.py ADDED
@@ -0,0 +1,44 @@
1
+ # tigrbl/v3/specs.py
2
+ """Compatibility layer that re-exports column specs.
3
+
4
+ Importing from ``tigrbl.specs`` remains supported but the
5
+ implementation now lives under :mod:`tigrbl.column`.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import sys
11
+
12
+ from .column import * # noqa: F401,F403
13
+ from .column import (
14
+ __all__ as _all,
15
+ column_spec as _column_spec,
16
+ field_spec as _field_spec,
17
+ infer as _infer,
18
+ io_spec as _io_spec,
19
+ shortcuts as _shortcuts,
20
+ storage_spec as _storage_spec,
21
+ )
22
+
23
+ __all__ = list(_all)
24
+
25
+ # Allow submodule imports like ``tigrbl.specs.storage_spec``
26
+ __path__: list[str] = []
27
+ _mod = sys.modules[__name__]
28
+ _mod.column_spec = _column_spec
29
+ _mod.field_spec = _field_spec
30
+ _mod.infer = _infer
31
+ _mod.io_spec = _io_spec
32
+ _mod.shortcuts = _shortcuts
33
+ _mod.storage_spec = _storage_spec
34
+
35
+ sys.modules[f"{__name__}.column_spec"] = _column_spec
36
+ sys.modules[f"{__name__}.field_spec"] = _field_spec
37
+ sys.modules[f"{__name__}.infer"] = _infer
38
+ sys.modules[f"{__name__}.io_spec"] = _io_spec
39
+ sys.modules[f"{__name__}.shortcuts"] = _shortcuts
40
+ sys.modules[f"{__name__}.storage_spec"] = _storage_spec
41
+
42
+
43
+ def __dir__() -> list[str]:
44
+ return sorted(__all__)
@@ -0,0 +1,13 @@
1
+ # tigrbl/v3/system/__init__.py
2
+ """
3
+ Tigrbl v3 – System/Diagnostics helpers.
4
+
5
+ - mount_diagnostics(api, *, get_db=None) -> Router
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .diagnostics import mount_diagnostics
11
+ from .uvicorn import stop_uvicorn_server
12
+
13
+ __all__ = ["mount_diagnostics", "stop_uvicorn_server"]
@@ -0,0 +1,24 @@
1
+ from ...runtime.kernel import _default_kernel, build_phase_chains
2
+ from .router import mount_diagnostics
3
+ from .methodz import build_methodz_endpoint as _build_methodz_endpoint
4
+ from .hookz import build_hookz_endpoint as _build_hookz_endpoint
5
+ from .kernelz import build_kernelz_endpoint as _build_kernelz_endpoint
6
+ from .utils import (
7
+ model_iter as _model_iter,
8
+ opspecs as _opspecs,
9
+ label_callable as _label_callable,
10
+ label_hook as _label_hook,
11
+ )
12
+
13
+ __all__ = [
14
+ "mount_diagnostics",
15
+ "_build_methodz_endpoint",
16
+ "_build_hookz_endpoint",
17
+ "_build_kernelz_endpoint",
18
+ "_model_iter",
19
+ "_opspecs",
20
+ "_label_callable",
21
+ "_label_hook",
22
+ "build_phase_chains",
23
+ "_default_kernel",
24
+ ]
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Callable, Sequence
4
+ from types import SimpleNamespace
5
+
6
+ try:
7
+ from ...types import Router, Request, Depends
8
+ from fastapi.responses import JSONResponse
9
+ except Exception: # pragma: no cover
10
+
11
+ class Router: # type: ignore
12
+ def __init__(self, *a, **kw):
13
+ self.routes = []
14
+
15
+ def add_api_route(
16
+ self, path: str, endpoint: Callable, methods: Sequence[str], **opts
17
+ ):
18
+ self.routes.append((path, methods, endpoint, opts))
19
+
20
+ class Request: # type: ignore
21
+ def __init__(self, scope=None):
22
+ self.scope = scope or {}
23
+ self.state = SimpleNamespace()
24
+ self.query_params = {}
25
+
26
+ class JSONResponse(dict): # type: ignore
27
+ def __init__(self, content: Any, status_code: int = 200):
28
+ super().__init__(content=content, status_code=status_code)
29
+
30
+ def Depends(fn: Callable[..., Any]): # type: ignore
31
+ return fn
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import Any, Callable, Optional
5
+
6
+ from .compat import Depends, JSONResponse, Request
7
+ from .utils import maybe_execute
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ def build_healthz_endpoint(dep: Optional[Callable[..., Any]]):
13
+ """
14
+ Returns a FastAPI endpoint function for /healthz.
15
+ If `dep` is provided, it's used as a dependency to supply `db`.
16
+ Otherwise, we try request.state.db.
17
+ """
18
+ if dep is not None:
19
+
20
+ async def _healthz(db: Any = Depends(dep)):
21
+ try:
22
+ await maybe_execute(db, "SELECT 1")
23
+ return {"ok": True}
24
+ except Exception as e: # pragma: no cover
25
+ logger.exception("/healthz failed")
26
+ return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
27
+
28
+ return _healthz
29
+
30
+ async def _healthz(request: Request):
31
+ db = getattr(request.state, "db", None)
32
+ if db is None:
33
+ return {"ok": True, "warning": "no-db"}
34
+ try:
35
+ await maybe_execute(db, "SELECT 1")
36
+ return {"ok": True}
37
+ except Exception as e: # pragma: no cover
38
+ logger.exception("/healthz failed")
39
+ return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
40
+
41
+ return _healthz
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Optional
4
+ from types import SimpleNamespace
5
+
6
+ from ...op.types import PHASES
7
+
8
+
9
+ def build_hookz_endpoint(api: Any):
10
+ cache: Optional[Dict[str, Dict[str, Dict[str, List[str]]]]] = None
11
+
12
+ async def _hookz():
13
+ nonlocal cache
14
+ """
15
+ Expose hook execution order for each method.
16
+
17
+ Phases appear in runner order; error phases trail.
18
+ Within each phase, hooks are listed in execution order: global (None) hooks,
19
+ then method-specific hooks.
20
+ """
21
+ if cache is not None:
22
+ return cache
23
+
24
+ from . import _model_iter, _opspecs, _label_callable
25
+
26
+ out: Dict[str, Dict[str, Dict[str, List[str]]]] = {}
27
+ for model in _model_iter(api):
28
+ mname = getattr(model, "__name__", "Model")
29
+ hooks_root = getattr(model, "hooks", SimpleNamespace())
30
+ alias_sources = set()
31
+ rpc_ns = getattr(model, "rpc", SimpleNamespace())
32
+ alias_sources.update(getattr(rpc_ns, "__dict__", {}).keys())
33
+ for sp in _opspecs(model):
34
+ alias_sources.add(sp.alias)
35
+
36
+ model_map: Dict[str, Dict[str, List[str]]] = {}
37
+ for alias in sorted(alias_sources):
38
+ alias_ns = getattr(hooks_root, alias, None) or SimpleNamespace()
39
+ phase_map: Dict[str, List[str]] = {}
40
+ for ph in PHASES:
41
+ steps = list(getattr(alias_ns, ph, []) or [])
42
+ if steps:
43
+ phase_map[ph] = [_label_callable(fn) for fn in steps]
44
+ if phase_map:
45
+ model_map[alias] = phase_map
46
+ if model_map:
47
+ out[mname] = model_map
48
+ cache = out
49
+ return cache
50
+
51
+ return _hookz
@@ -0,0 +1,20 @@
1
+ """Diagnostic endpoint exposing kernel phase plans."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ...runtime.kernel import _default_kernel as K
8
+
9
+
10
+ def build_kernelz_endpoint(api: Any):
11
+ """Return an async handler that serves the Kernel's cached plan."""
12
+
13
+ async def _kernelz():
14
+ K.ensure_primed(api)
15
+ return K.kernelz_payload(api)
16
+
17
+ return _kernelz
18
+
19
+
20
+ __all__ = ["build_kernelz_endpoint"]
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, List, Optional
4
+
5
+
6
+ def build_methodz_endpoint(api: Any):
7
+ cache: Optional[Dict[str, List[Dict[str, Any]]]] = None
8
+
9
+ async def _methodz():
10
+ nonlocal cache
11
+ """Ordered, canonical operation list."""
12
+ if cache is not None:
13
+ return cache
14
+
15
+ from . import _model_iter, _opspecs
16
+
17
+ methods: List[Dict[str, Any]] = []
18
+ for model in _model_iter(api):
19
+ mname = getattr(model, "__name__", "Model")
20
+ for sp in _opspecs(model):
21
+ if not getattr(sp, "expose_rpc", True):
22
+ continue
23
+ methods.append(
24
+ {
25
+ "method": f"{mname}.{sp.alias}",
26
+ "model": mname,
27
+ "alias": sp.alias,
28
+ "target": sp.target,
29
+ "arity": sp.arity,
30
+ "persist": sp.persist,
31
+ "request_model": getattr(sp, "request_model", None) is not None,
32
+ "response_model": getattr(sp, "response_model", None)
33
+ is not None,
34
+ "routes": bool(getattr(sp, "expose_routes", True)),
35
+ "rpc": bool(getattr(sp, "expose_rpc", True)),
36
+ "tags": list(getattr(sp, "tags", ()) or (mname,)),
37
+ }
38
+ )
39
+ methods.sort(key=lambda x: (x["model"], x["alias"]))
40
+ cache = {"methods": methods}
41
+ return cache
42
+
43
+ return _methodz