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,319 @@
1
+ # tigrbl/v3/app/tigrbl_app.py
2
+ from __future__ import annotations
3
+
4
+ import copy
5
+ from types import SimpleNamespace
6
+ from typing import (
7
+ Any,
8
+ Callable,
9
+ Dict,
10
+ Iterable,
11
+ Mapping,
12
+ Optional,
13
+ Sequence,
14
+ Tuple,
15
+ )
16
+
17
+ from ._app import App as _App
18
+ from ..engine.engine_spec import EngineCfg
19
+ from ..engine import resolver as _resolver
20
+ from ..ddl import initialize as _ddl_initialize
21
+ from ..bindings.api import (
22
+ include_model as _include_model,
23
+ include_models as _include_models,
24
+ rpc_call as _rpc_call,
25
+ _seed_security_and_deps,
26
+ _mount_router,
27
+ _default_prefix,
28
+ AttrDict,
29
+ )
30
+ from ..bindings.model import rebind as _rebind, bind as _bind
31
+ from ..bindings.rest import build_router_and_attach as _build_router_and_attach
32
+ from ..transport import mount_jsonrpc as _mount_jsonrpc
33
+ from ..system import mount_diagnostics as _mount_diagnostics
34
+ from ..op import get_registry, OpSpec
35
+ from ._model_registry import initialize_model_registry
36
+
37
+
38
+ # optional compat: legacy transactional decorator
39
+ try:
40
+ from .compat.transactional import transactional as _txn_decorator
41
+ except Exception: # pragma: no cover
42
+ _txn_decorator = None
43
+
44
+
45
+ class TigrblApp(_App):
46
+ """
47
+ Monolithic facade that owns:
48
+ • containers (models, schemas, handlers, hooks, rpc, rest, routers, columns, table_config, core proxies)
49
+ • model inclusion (REST + RPC wiring)
50
+ • JSON-RPC / diagnostics mounting
51
+ • (optional) legacy-friendly helpers (transactional decorator, auth flags)
52
+
53
+ It composes v3 primitives; you can still use the functions directly if you prefer.
54
+ """
55
+
56
+ TITLE = "TigrblApp"
57
+ VERSION = "0.1.0"
58
+ LIFESPAN = None
59
+ MIDDLEWARES: Sequence[Any] = ()
60
+ APIS: Sequence[Any] = ()
61
+ MODELS: Sequence[Any] = ()
62
+
63
+ # --- optional auth knobs recognized by some middlewares/dispatchers (kept for back-compat) ---
64
+ _authn: Any = None
65
+ _allow_anon: bool = True
66
+ _authorize: Any = None
67
+ _optional_authn_dep: Any = None
68
+ _allow_anon_ops: set[str] = set()
69
+
70
+ def __init__(
71
+ self,
72
+ *,
73
+ engine: EngineCfg | None = None,
74
+ jsonrpc_prefix: str = "/rpc",
75
+ system_prefix: str = "/system",
76
+ api_hooks: Mapping[str, Iterable[Callable]]
77
+ | Mapping[str, Mapping[str, Iterable[Callable]]]
78
+ | None = None,
79
+ **fastapi_kwargs: Any,
80
+ ) -> None:
81
+ title = fastapi_kwargs.pop("title", None)
82
+ if title is not None:
83
+ self.TITLE = title
84
+ version = fastapi_kwargs.pop("version", None)
85
+ if version is not None:
86
+ self.VERSION = version
87
+ lifespan = fastapi_kwargs.pop("lifespan", None)
88
+ if lifespan is not None:
89
+ self.LIFESPAN = lifespan
90
+ super().__init__(engine=engine, **fastapi_kwargs)
91
+ # capture initial routes so refreshes retain FastAPI defaults
92
+ self._base_routes = list(self.router.routes)
93
+ self.jsonrpc_prefix = jsonrpc_prefix
94
+ self.system_prefix = system_prefix
95
+
96
+ # public containers (mirrors used by bindings.api)
97
+ self.models = initialize_model_registry(getattr(self, "MODELS", ()))
98
+ self.schemas = SimpleNamespace()
99
+ self.handlers = SimpleNamespace()
100
+ self.hooks = SimpleNamespace()
101
+ self.rpc = SimpleNamespace()
102
+ self.rest = SimpleNamespace()
103
+ self.routers: Dict[str, Any] = {}
104
+ self.tables = AttrDict()
105
+ self.columns: Dict[str, Tuple[str, ...]] = {}
106
+ self.table_config: Dict[str, Dict[str, Any]] = {}
107
+ self.core = SimpleNamespace()
108
+ self.core_raw = SimpleNamespace()
109
+
110
+ # API-level hooks map (merged into each model at include-time; precedence handled in bindings.hooks)
111
+ self._api_hooks_map = copy.deepcopy(api_hooks) if api_hooks else None
112
+
113
+ # ------------------------- internal helpers -------------------------
114
+
115
+ @staticmethod
116
+ def _merge_api_hooks_into_model(model: type, hooks_map: Any) -> None:
117
+ """
118
+ Install API-level hooks on the model so the binder can see them.
119
+ Accepted shapes:
120
+ {phase: [fn, ...]} # global, all aliases
121
+ {alias: {phase: [fn, ...]}, "*": {...}} # per-alias + wildcard
122
+ If the model already has __tigrbl_api_hooks__, we shallow-merge keys.
123
+ """
124
+ if not hooks_map:
125
+ return
126
+ existing = getattr(model, "__tigrbl_api_hooks__", None)
127
+ if existing is None:
128
+ setattr(model, "__tigrbl_api_hooks__", copy.deepcopy(hooks_map))
129
+ return
130
+
131
+ # shallow merge (alias or phase keys); values are lists we extend
132
+ merged = copy.deepcopy(existing)
133
+ for k, v in (hooks_map or {}).items():
134
+ if k not in merged:
135
+ merged[k] = copy.deepcopy(v)
136
+ else:
137
+ # when both are dicts, merge phase lists
138
+ if isinstance(v, Mapping) and isinstance(merged[k], Mapping):
139
+ for ph, fns in v.items():
140
+ merged[k].setdefault(ph, [])
141
+ merged[k][ph] = list(merged[k][ph]) + list(fns or [])
142
+ else:
143
+ # fallback: prefer model-local value, then append api-level
144
+ if isinstance(merged[k], list):
145
+ merged[k] = list(merged[k]) + list(v or [])
146
+ else:
147
+ merged[k] = v
148
+ setattr(model, "__tigrbl_api_hooks__", merged)
149
+
150
+ # ------------------------- primary operations -------------------------
151
+
152
+ def include_model(
153
+ self, model: type, *, prefix: str | None = None, mount_router: bool = True
154
+ ) -> Tuple[type, Any]:
155
+ """
156
+ Bind a model, mount its REST router, and attach all namespaces to this facade.
157
+ """
158
+ # inject API-level hooks so the binder merges them
159
+ self._merge_api_hooks_into_model(model, self._api_hooks_map)
160
+ return _include_model(self, model, prefix=prefix, mount_router=mount_router)
161
+
162
+ def include_models(
163
+ self,
164
+ models: Sequence[type],
165
+ *,
166
+ base_prefix: str | None = None,
167
+ mount_router: bool = True,
168
+ ) -> Dict[str, Any]:
169
+ for m in models:
170
+ self._merge_api_hooks_into_model(m, self._api_hooks_map)
171
+ return _include_models(
172
+ self,
173
+ models,
174
+ base_prefix=base_prefix,
175
+ mount_router=mount_router,
176
+ )
177
+
178
+ async def rpc_call(
179
+ self,
180
+ model_or_name: type | str,
181
+ method: str,
182
+ payload: Any = None,
183
+ *,
184
+ db: Any,
185
+ request: Any = None,
186
+ ctx: Optional[Dict[str, Any]] = None,
187
+ ) -> Any:
188
+ return await _rpc_call(
189
+ self, model_or_name, method, payload, db=db, request=request, ctx=ctx
190
+ )
191
+
192
+ # ------------------------- extras / mounting -------------------------
193
+
194
+ def mount_jsonrpc(self, *, prefix: str | None = None) -> Any:
195
+ """Mount JSON-RPC router onto this app."""
196
+ px = prefix if prefix is not None else self.jsonrpc_prefix
197
+ prov = _resolver.resolve_provider(api=self)
198
+ get_db = prov.get_db if prov else None
199
+ router = _mount_jsonrpc(
200
+ self,
201
+ self,
202
+ prefix=px,
203
+ get_db=get_db,
204
+ )
205
+ self._base_routes = list(self.router.routes)
206
+ return router
207
+
208
+ def attach_diagnostics(
209
+ self, *, prefix: str | None = None, app: Any | None = None
210
+ ) -> Any:
211
+ """Mount diagnostics router onto this app or the provided ``app``."""
212
+ px = prefix if prefix is not None else self.system_prefix
213
+ prov = _resolver.resolve_provider(api=self)
214
+ get_db = prov.get_db if prov else None
215
+ router = _mount_diagnostics(self, get_db=get_db)
216
+ include_self = getattr(self, "include_router", None)
217
+ if callable(include_self):
218
+ include_self(router, prefix=px)
219
+ if app is not None and app is not self:
220
+ include_other = getattr(app, "include_router", None)
221
+ if callable(include_other):
222
+ include_other(router, prefix=px)
223
+ if app is None:
224
+ self._base_routes = list(self.router.routes)
225
+ return router
226
+
227
+ # ------------------------- registry passthroughs -------------------------
228
+
229
+ def registry(self, model: type):
230
+ """Return the per-model OpspecRegistry."""
231
+ return get_registry(model)
232
+
233
+ def bind(self, model: type) -> Tuple[OpSpec, ...]:
234
+ """Bind/rebuild a model in place (without mounting)."""
235
+ self._merge_api_hooks_into_model(model, self._api_hooks_map)
236
+ return _bind(model)
237
+
238
+ def rebind(
239
+ self, model: type, *, changed_keys: Optional[set[tuple[str, str]]] = None
240
+ ) -> Tuple[OpSpec, ...]:
241
+ """Targeted rebuild of a bound model."""
242
+ return _rebind(model, changed_keys=changed_keys)
243
+
244
+ # ------------------------- legacy helpers -------------------------
245
+
246
+ def transactional(self, *dargs, **dkw):
247
+ """
248
+ Legacy-friendly decorator: @api.transactional(...)
249
+ Wraps a function as a v3 custom op with START_TX/END_TX.
250
+ """
251
+ if _txn_decorator is None:
252
+ raise RuntimeError("transactional decorator not available")
253
+ return _txn_decorator(self, *dargs, **dkw)
254
+
255
+ # Optional: let callers set auth knobs used by some middlewares/dispatchers
256
+ def set_auth(
257
+ self,
258
+ *,
259
+ authn: Any = None,
260
+ allow_anon: Optional[bool] = None,
261
+ authorize: Any = None,
262
+ optional_authn_dep: Any = None,
263
+ ) -> None:
264
+ if authn is not None:
265
+ self._authn = authn
266
+ if allow_anon is None:
267
+ allow_anon = False
268
+ if allow_anon is not None:
269
+ self._allow_anon = bool(allow_anon)
270
+ if authorize is not None:
271
+ self._authorize = authorize
272
+ if optional_authn_dep is not None:
273
+ self._optional_authn_dep = optional_authn_dep
274
+
275
+ # Refresh already-included models so routers pick up new auth settings
276
+ if self.models:
277
+ self._refresh_security()
278
+
279
+ def _refresh_security(self) -> None:
280
+ """Re-seed auth deps on models and rebuild routers."""
281
+ # Reset router to baseline and allow_anon ops cache
282
+ self.router.routes = list(self._base_routes)
283
+ self._allow_anon_ops = set()
284
+ for model in self.models.values():
285
+ _seed_security_and_deps(self, model)
286
+ specs = getattr(getattr(model, "opspecs", SimpleNamespace()), "all", ())
287
+ if specs:
288
+ _build_router_and_attach(model, list(specs))
289
+ router = getattr(getattr(model, "rest", SimpleNamespace()), "router", None)
290
+ if router is None:
291
+ continue
292
+ # update api-level references
293
+ mname = model.__name__
294
+ rest_ns = getattr(self.rest, mname, SimpleNamespace())
295
+ rest_ns.router = router
296
+ setattr(self.rest, mname, rest_ns)
297
+ self.routers[mname] = router
298
+ prefix = _default_prefix(model)
299
+ _mount_router(self, router, prefix=prefix)
300
+
301
+ def _collect_tables(self):
302
+ # dedupe; handle multiple DeclarativeBases (multiple metadatas)
303
+ seen = set()
304
+ tables = []
305
+ for m in self.models.values():
306
+ t = getattr(m, "__table__", None)
307
+ if t is not None and not t.columns:
308
+ continue
309
+ if t is not None and t not in seen:
310
+ seen.add(t)
311
+ tables.append(t)
312
+ return tables
313
+
314
+ initialize = _ddl_initialize
315
+
316
+ # ------------------------- repr -------------------------
317
+
318
+ def __repr__(self) -> str: # pragma: no cover
319
+ return f"<TigrblApp models={list(self.models)} rpc={list(getattr(self.rpc, '__dict__', {}).keys())}>"
@@ -0,0 +1,73 @@
1
+ # tigrbl/v3/bindings/__init__.py
2
+ """
3
+ Tigrbl v3 – Bindings package.
4
+
5
+ This package wires OpSpec-derived artifacts onto models and an API facade.
6
+ Concerns are kept strictly separated:
7
+ • Security deps & extra deps → transport/router only (REST/RPC)
8
+ • System steps → injected by runtime lifecycle (Kernel), not by bindings
9
+ • Atoms → discovered/injected by runtime, not by bindings
10
+ • Hooks → bindable at API / model / op levels (no imperative hooks)
11
+
12
+ Public surface (re-exports)
13
+
14
+ Model binding:
15
+ - bind(model, *, only_keys=None) → builds/refreshes model namespaces
16
+ - rebind(model, *, changed_keys=None) → targeted refresh
17
+
18
+ Per-concern builders:
19
+ - build_schemas(model, specs, *, only_keys=None)
20
+ • Seeds schemas declared via @schema_ctx before computing defaults.
21
+ • Supports overrides via SchemaRef("alias","in|out"), "alias.in"/"alias.out", or "raw".
22
+ - build_hooks(model, specs, *, only_keys=None)
23
+ • Merges API → MODEL → OP for pre-like phases; OP → MODEL → API for post/error.
24
+ • No imperative hook source.
25
+ - build_handlers(model, specs, *, only_keys=None)
26
+ • Inserts the core/raw step into the HANDLER phase (other phases handled by runtime).
27
+ - register_rpc(model, specs, *, only_keys=None)
28
+ - build_rest(model, specs, *, only_keys=None)
29
+ • Router-level only; serializes responses iff a response schema exists.
30
+
31
+ API integration:
32
+ - include_model(api, model, *, app=None, prefix=None, mount_router=True)
33
+ - include_models(api, models, *, app=None, base_prefix=None, mount_router=True)
34
+ - rpc_call(api, model_or_name, method, payload=None, *, db, request=None, ctx=None)
35
+ """
36
+
37
+ from __future__ import annotations
38
+ import logging
39
+
40
+ # Core model orchestrator
41
+ from .model import bind, rebind
42
+
43
+ # Per-concern builders (aliased for a clean public API)
44
+ from .schemas import build_and_attach as build_schemas
45
+ from .hooks import normalize_and_attach as build_hooks
46
+ from .handlers import build_and_attach as build_handlers
47
+ from .rpc import register_and_attach as register_rpc
48
+ from .rest import build_router_and_attach as build_rest
49
+ from ..response.bind import bind as bind_response
50
+
51
+ # API facade integration
52
+ from .api import include_model, include_models, rpc_call
53
+
54
+ logger = logging.getLogger("uvicorn")
55
+ logger.debug("Loaded module v3/bindings/__init__")
56
+
57
+
58
+ __all__ = [
59
+ # model orchestrator
60
+ "bind",
61
+ "rebind",
62
+ # per-concern builders
63
+ "build_schemas",
64
+ "build_hooks",
65
+ "build_handlers",
66
+ "register_rpc",
67
+ "build_rest",
68
+ "bind_response",
69
+ # api integration
70
+ "include_model",
71
+ "include_models",
72
+ "rpc_call",
73
+ ]
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+ import logging
3
+
4
+ from .common import AttrDict, _default_prefix, _mount_router # noqa: F401
5
+ from .include import include_model, include_models, _seed_security_and_deps # noqa: F401
6
+ from .rpc import rpc_call
7
+
8
+ logging.getLogger("uvicorn").setLevel(logging.DEBUG)
9
+ logger = logging.getLogger("uvicorn")
10
+ logger.debug("Loaded module v3/bindings/api/__init__")
11
+
12
+ __all__ = ["include_model", "include_models", "rpc_call"]
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from types import SimpleNamespace
5
+ from typing import Any
6
+
7
+ logging.getLogger("uvicorn").setLevel(logging.DEBUG)
8
+ logger = logging.getLogger("uvicorn")
9
+ logger.debug("Loaded module v3/bindings/api/common")
10
+
11
+
12
+ class AttrDict(dict):
13
+ """Dictionary providing attribute-style access."""
14
+
15
+ def __getattr__(self, item: str) -> Any: # pragma: no cover - trivial
16
+ try:
17
+ return self[item]
18
+ except KeyError as e: # pragma: no cover - debug aid
19
+ raise AttributeError(item) from e
20
+
21
+ def __setattr__(self, key: str, value: Any) -> None: # pragma: no cover - trivial
22
+ self[key] = value
23
+
24
+
25
+ # Public type for the API facade object users pass to include_model(...)
26
+ ApiLike = Any
27
+
28
+
29
+ def _resource_name(model: type) -> str:
30
+ """
31
+ Compute the API resource segment.
32
+
33
+ Policy:
34
+ - Prefer explicit `__resource__` when present (caller-controlled).
35
+ - Otherwise, use the model *class name* in lowercase.
36
+ - DO NOT use `__tablename__` here (strictly DB-only per project policy).
37
+ """
38
+ if hasattr(model, "__resource__"):
39
+ resource = model.__resource__
40
+ logger.debug("Using explicit resource '%s' for %s", resource, model.__name__)
41
+ else:
42
+ resource = model.__name__.lower()
43
+ logger.debug("Derived resource '%s' for %s", resource, model.__name__)
44
+ return resource
45
+
46
+
47
+ def _default_prefix(model: type) -> str:
48
+ """Default mount prefix for a model router.
49
+
50
+ Historically routers were mounted under ``/{resource}``, resulting in
51
+ duplicated path segments such as ``/item/item``. To expose REST endpoints
52
+ under ``/item`` we now mount routers at the application root by default.
53
+ """
54
+ logger.debug("Default prefix for %s is root '/'", model.__name__)
55
+ return ""
56
+
57
+
58
+ def _has_include_router(obj: Any) -> bool:
59
+ has_router = hasattr(obj, "include_router") and callable(
60
+ getattr(obj, "include_router")
61
+ )
62
+ logger.debug("Object %s has include_router: %s", obj, has_router)
63
+ return has_router
64
+
65
+
66
+ def _mount_router(app_or_router: Any, router: Any, *, prefix: str) -> None:
67
+ """
68
+ Best-effort mount onto a FastAPI app or Router.
69
+ If not available, we still attach router under api.routers for later use.
70
+ """
71
+ if app_or_router is None:
72
+ logger.debug("No app/router; skipping mount for prefix %s", prefix)
73
+ return
74
+ try:
75
+ if _has_include_router(app_or_router):
76
+ logger.debug("Mounting router %s at prefix %s", router, prefix)
77
+ app_or_router.include_router(router, prefix=prefix) # FastAPI / Router
78
+ else:
79
+ logger.debug(
80
+ "Provided object %s lacks include_router; not mounting router",
81
+ app_or_router,
82
+ )
83
+ except Exception:
84
+ logger.exception("Failed to mount router at %s", prefix)
85
+
86
+
87
+ def _ensure_api_ns(api: ApiLike) -> None:
88
+ """
89
+ Ensure containers exist on the api facade object.
90
+ """
91
+ for attr, default in (
92
+ ("models", {}),
93
+ ("tables", AttrDict()),
94
+ ("schemas", SimpleNamespace()),
95
+ ("handlers", SimpleNamespace()),
96
+ ("hooks", SimpleNamespace()),
97
+ ("rpc", SimpleNamespace()),
98
+ ("rest", SimpleNamespace()),
99
+ ("routers", {}),
100
+ ("columns", {}),
101
+ ("table_config", {}),
102
+ ("core", SimpleNamespace()), # helper method proxies
103
+ ("core_raw", SimpleNamespace()),
104
+ ):
105
+ if not hasattr(api, attr):
106
+ setattr(api, attr, default)
107
+ logger.debug("Initialized api.%s", attr)
108
+ else:
109
+ logger.debug("api already has attribute %s", attr)