tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.0.dev3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. tigrbl/README.md +94 -0
  2. tigrbl/__init__.py +139 -14
  3. tigrbl/api/__init__.py +6 -0
  4. tigrbl/api/_api.py +72 -0
  5. tigrbl/api/api_spec.py +30 -0
  6. tigrbl/api/mro_collect.py +43 -0
  7. tigrbl/api/shortcuts.py +56 -0
  8. tigrbl/api/tigrbl_api.py +286 -0
  9. tigrbl/app/__init__.py +0 -0
  10. tigrbl/app/_app.py +61 -0
  11. tigrbl/app/app_spec.py +42 -0
  12. tigrbl/app/mro_collect.py +67 -0
  13. tigrbl/app/shortcuts.py +65 -0
  14. tigrbl/app/tigrbl_app.py +314 -0
  15. tigrbl/bindings/__init__.py +73 -0
  16. tigrbl/bindings/api/__init__.py +12 -0
  17. tigrbl/bindings/api/common.py +109 -0
  18. tigrbl/bindings/api/include.py +256 -0
  19. tigrbl/bindings/api/resource_proxy.py +149 -0
  20. tigrbl/bindings/api/rpc.py +111 -0
  21. tigrbl/bindings/columns.py +49 -0
  22. tigrbl/bindings/handlers/__init__.py +11 -0
  23. tigrbl/bindings/handlers/builder.py +119 -0
  24. tigrbl/bindings/handlers/ctx.py +74 -0
  25. tigrbl/bindings/handlers/identifiers.py +228 -0
  26. tigrbl/bindings/handlers/namespaces.py +51 -0
  27. tigrbl/bindings/handlers/steps.py +276 -0
  28. tigrbl/bindings/hooks.py +311 -0
  29. tigrbl/bindings/model.py +194 -0
  30. tigrbl/bindings/model_helpers.py +139 -0
  31. tigrbl/bindings/model_registry.py +77 -0
  32. tigrbl/bindings/rest/__init__.py +7 -0
  33. tigrbl/bindings/rest/attach.py +34 -0
  34. tigrbl/bindings/rest/collection.py +265 -0
  35. tigrbl/bindings/rest/common.py +116 -0
  36. tigrbl/bindings/rest/fastapi.py +76 -0
  37. tigrbl/bindings/rest/helpers.py +119 -0
  38. tigrbl/bindings/rest/io.py +317 -0
  39. tigrbl/bindings/rest/member.py +367 -0
  40. tigrbl/bindings/rest/router.py +292 -0
  41. tigrbl/bindings/rest/routing.py +133 -0
  42. tigrbl/bindings/rpc.py +364 -0
  43. tigrbl/bindings/schemas/__init__.py +11 -0
  44. tigrbl/bindings/schemas/builder.py +348 -0
  45. tigrbl/bindings/schemas/defaults.py +260 -0
  46. tigrbl/bindings/schemas/utils.py +193 -0
  47. tigrbl/column/README.md +62 -0
  48. tigrbl/column/__init__.py +72 -0
  49. tigrbl/column/_column.py +96 -0
  50. tigrbl/column/column_spec.py +40 -0
  51. tigrbl/column/field_spec.py +31 -0
  52. tigrbl/column/infer/__init__.py +25 -0
  53. tigrbl/column/infer/core.py +92 -0
  54. tigrbl/column/infer/jsonhints.py +44 -0
  55. tigrbl/column/infer/planning.py +133 -0
  56. tigrbl/column/infer/types.py +102 -0
  57. tigrbl/column/infer/utils.py +59 -0
  58. tigrbl/column/io_spec.py +133 -0
  59. tigrbl/column/mro_collect.py +59 -0
  60. tigrbl/column/shortcuts.py +89 -0
  61. tigrbl/column/storage_spec.py +65 -0
  62. tigrbl/config/__init__.py +19 -0
  63. tigrbl/config/constants.py +224 -0
  64. tigrbl/config/defaults.py +29 -0
  65. tigrbl/config/resolver.py +295 -0
  66. tigrbl/core/__init__.py +47 -0
  67. tigrbl/core/crud/__init__.py +36 -0
  68. tigrbl/core/crud/bulk.py +168 -0
  69. tigrbl/core/crud/helpers/__init__.py +76 -0
  70. tigrbl/core/crud/helpers/db.py +92 -0
  71. tigrbl/core/crud/helpers/enum.py +86 -0
  72. tigrbl/core/crud/helpers/filters.py +162 -0
  73. tigrbl/core/crud/helpers/model.py +123 -0
  74. tigrbl/core/crud/helpers/normalize.py +99 -0
  75. tigrbl/core/crud/ops.py +235 -0
  76. tigrbl/ddl/__init__.py +344 -0
  77. tigrbl/decorators.py +17 -0
  78. tigrbl/deps/__init__.py +20 -0
  79. tigrbl/deps/fastapi.py +45 -0
  80. tigrbl/deps/favicon.svg +4 -0
  81. tigrbl/deps/jinja.py +27 -0
  82. tigrbl/deps/pydantic.py +10 -0
  83. tigrbl/deps/sqlalchemy.py +94 -0
  84. tigrbl/deps/starlette.py +36 -0
  85. tigrbl/engine/__init__.py +26 -0
  86. tigrbl/engine/_engine.py +130 -0
  87. tigrbl/engine/bind.py +33 -0
  88. tigrbl/engine/builders.py +236 -0
  89. tigrbl/engine/collect.py +111 -0
  90. tigrbl/engine/decorators.py +108 -0
  91. tigrbl/engine/engine_spec.py +261 -0
  92. tigrbl/engine/resolver.py +224 -0
  93. tigrbl/engine/shortcuts.py +216 -0
  94. tigrbl/hook/__init__.py +21 -0
  95. tigrbl/hook/_hook.py +22 -0
  96. tigrbl/hook/decorators.py +28 -0
  97. tigrbl/hook/hook_spec.py +24 -0
  98. tigrbl/hook/mro_collect.py +98 -0
  99. tigrbl/hook/shortcuts.py +44 -0
  100. tigrbl/hook/types.py +76 -0
  101. tigrbl/op/__init__.py +50 -0
  102. tigrbl/op/_op.py +31 -0
  103. tigrbl/op/canonical.py +31 -0
  104. tigrbl/op/collect.py +11 -0
  105. tigrbl/op/decorators.py +238 -0
  106. tigrbl/op/model_registry.py +301 -0
  107. tigrbl/op/mro_collect.py +99 -0
  108. tigrbl/op/resolver.py +216 -0
  109. tigrbl/op/types.py +136 -0
  110. tigrbl/orm/__init__.py +1 -0
  111. tigrbl/orm/mixins/_RowBound.py +83 -0
  112. tigrbl/orm/mixins/__init__.py +95 -0
  113. tigrbl/orm/mixins/bootstrappable.py +113 -0
  114. tigrbl/orm/mixins/bound.py +47 -0
  115. tigrbl/orm/mixins/edges.py +40 -0
  116. tigrbl/orm/mixins/fields.py +165 -0
  117. tigrbl/orm/mixins/hierarchy.py +54 -0
  118. tigrbl/orm/mixins/key_digest.py +44 -0
  119. tigrbl/orm/mixins/lifecycle.py +115 -0
  120. tigrbl/orm/mixins/locks.py +51 -0
  121. tigrbl/orm/mixins/markers.py +16 -0
  122. tigrbl/orm/mixins/operations.py +57 -0
  123. tigrbl/orm/mixins/ownable.py +337 -0
  124. tigrbl/orm/mixins/principals.py +98 -0
  125. tigrbl/orm/mixins/tenant_bound.py +301 -0
  126. tigrbl/orm/mixins/upsertable.py +111 -0
  127. tigrbl/orm/mixins/utils.py +49 -0
  128. tigrbl/orm/tables/__init__.py +72 -0
  129. tigrbl/orm/tables/_base.py +8 -0
  130. tigrbl/orm/tables/audit.py +56 -0
  131. tigrbl/orm/tables/client.py +25 -0
  132. tigrbl/orm/tables/group.py +29 -0
  133. tigrbl/orm/tables/org.py +30 -0
  134. tigrbl/orm/tables/rbac.py +76 -0
  135. tigrbl/orm/tables/status.py +106 -0
  136. tigrbl/orm/tables/tenant.py +22 -0
  137. tigrbl/orm/tables/user.py +39 -0
  138. tigrbl/response/README.md +34 -0
  139. tigrbl/response/__init__.py +33 -0
  140. tigrbl/response/bind.py +12 -0
  141. tigrbl/response/decorators.py +37 -0
  142. tigrbl/response/resolver.py +83 -0
  143. tigrbl/response/shortcuts.py +144 -0
  144. tigrbl/response/types.py +49 -0
  145. tigrbl/rest/__init__.py +27 -0
  146. tigrbl/runtime/README.md +129 -0
  147. tigrbl/runtime/__init__.py +20 -0
  148. tigrbl/runtime/atoms/__init__.py +102 -0
  149. tigrbl/runtime/atoms/emit/__init__.py +42 -0
  150. tigrbl/runtime/atoms/emit/paired_post.py +158 -0
  151. tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
  152. tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
  153. tigrbl/runtime/atoms/out/__init__.py +38 -0
  154. tigrbl/runtime/atoms/out/masking.py +135 -0
  155. tigrbl/runtime/atoms/refresh/__init__.py +38 -0
  156. tigrbl/runtime/atoms/refresh/demand.py +130 -0
  157. tigrbl/runtime/atoms/resolve/__init__.py +40 -0
  158. tigrbl/runtime/atoms/resolve/assemble.py +167 -0
  159. tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
  160. tigrbl/runtime/atoms/response/__init__.py +17 -0
  161. tigrbl/runtime/atoms/response/negotiate.py +30 -0
  162. tigrbl/runtime/atoms/response/negotiation.py +43 -0
  163. tigrbl/runtime/atoms/response/render.py +36 -0
  164. tigrbl/runtime/atoms/response/renderer.py +116 -0
  165. tigrbl/runtime/atoms/response/template.py +44 -0
  166. tigrbl/runtime/atoms/response/templates.py +88 -0
  167. tigrbl/runtime/atoms/schema/__init__.py +40 -0
  168. tigrbl/runtime/atoms/schema/collect_in.py +21 -0
  169. tigrbl/runtime/atoms/schema/collect_out.py +21 -0
  170. tigrbl/runtime/atoms/storage/__init__.py +38 -0
  171. tigrbl/runtime/atoms/storage/to_stored.py +167 -0
  172. tigrbl/runtime/atoms/wire/__init__.py +45 -0
  173. tigrbl/runtime/atoms/wire/build_in.py +166 -0
  174. tigrbl/runtime/atoms/wire/build_out.py +87 -0
  175. tigrbl/runtime/atoms/wire/dump.py +206 -0
  176. tigrbl/runtime/atoms/wire/validate_in.py +227 -0
  177. tigrbl/runtime/context.py +206 -0
  178. tigrbl/runtime/errors/__init__.py +61 -0
  179. tigrbl/runtime/errors/converters.py +214 -0
  180. tigrbl/runtime/errors/exceptions.py +124 -0
  181. tigrbl/runtime/errors/mappings.py +71 -0
  182. tigrbl/runtime/errors/utils.py +150 -0
  183. tigrbl/runtime/events.py +209 -0
  184. tigrbl/runtime/executor/__init__.py +6 -0
  185. tigrbl/runtime/executor/guards.py +132 -0
  186. tigrbl/runtime/executor/helpers.py +88 -0
  187. tigrbl/runtime/executor/invoke.py +150 -0
  188. tigrbl/runtime/executor/types.py +84 -0
  189. tigrbl/runtime/kernel.py +628 -0
  190. tigrbl/runtime/labels.py +353 -0
  191. tigrbl/runtime/opview.py +87 -0
  192. tigrbl/runtime/ordering.py +256 -0
  193. tigrbl/runtime/system.py +279 -0
  194. tigrbl/runtime/trace.py +330 -0
  195. tigrbl/schema/__init__.py +38 -0
  196. tigrbl/schema/_schema.py +27 -0
  197. tigrbl/schema/builder/__init__.py +17 -0
  198. tigrbl/schema/builder/build_schema.py +209 -0
  199. tigrbl/schema/builder/cache.py +24 -0
  200. tigrbl/schema/builder/compat.py +16 -0
  201. tigrbl/schema/builder/extras.py +85 -0
  202. tigrbl/schema/builder/helpers.py +51 -0
  203. tigrbl/schema/builder/list_params.py +117 -0
  204. tigrbl/schema/builder/strip_parent_fields.py +70 -0
  205. tigrbl/schema/collect.py +55 -0
  206. tigrbl/schema/decorators.py +68 -0
  207. tigrbl/schema/get_schema.py +86 -0
  208. tigrbl/schema/schema_spec.py +20 -0
  209. tigrbl/schema/shortcuts.py +42 -0
  210. tigrbl/schema/types.py +34 -0
  211. tigrbl/schema/utils.py +143 -0
  212. tigrbl/shortcuts.py +22 -0
  213. tigrbl/specs.py +44 -0
  214. tigrbl/system/__init__.py +12 -0
  215. tigrbl/system/diagnostics/__init__.py +24 -0
  216. tigrbl/system/diagnostics/compat.py +31 -0
  217. tigrbl/system/diagnostics/healthz.py +41 -0
  218. tigrbl/system/diagnostics/hookz.py +51 -0
  219. tigrbl/system/diagnostics/kernelz.py +20 -0
  220. tigrbl/system/diagnostics/methodz.py +43 -0
  221. tigrbl/system/diagnostics/router.py +73 -0
  222. tigrbl/system/diagnostics/utils.py +43 -0
  223. tigrbl/table/__init__.py +9 -0
  224. tigrbl/table/_base.py +237 -0
  225. tigrbl/table/_table.py +54 -0
  226. tigrbl/table/mro_collect.py +69 -0
  227. tigrbl/table/shortcuts.py +57 -0
  228. tigrbl/table/table_spec.py +28 -0
  229. tigrbl/transport/__init__.py +74 -0
  230. tigrbl/transport/jsonrpc/__init__.py +19 -0
  231. tigrbl/transport/jsonrpc/dispatcher.py +352 -0
  232. tigrbl/transport/jsonrpc/helpers.py +115 -0
  233. tigrbl/transport/jsonrpc/models.py +41 -0
  234. tigrbl/transport/rest/__init__.py +25 -0
  235. tigrbl/transport/rest/aggregator.py +132 -0
  236. tigrbl/types/__init__.py +174 -0
  237. tigrbl/types/allow_anon_provider.py +19 -0
  238. tigrbl/types/authn_abc.py +30 -0
  239. tigrbl/types/nested_path_provider.py +22 -0
  240. tigrbl/types/op.py +35 -0
  241. tigrbl/types/op_config_provider.py +17 -0
  242. tigrbl/types/op_verb_alias_provider.py +33 -0
  243. tigrbl/types/request_extras_provider.py +22 -0
  244. tigrbl/types/response_extras_provider.py +22 -0
  245. tigrbl/types/table_config_provider.py +13 -0
  246. tigrbl-0.3.0.dev3.dist-info/LICENSE +201 -0
  247. tigrbl-0.3.0.dev3.dist-info/METADATA +501 -0
  248. tigrbl-0.3.0.dev3.dist-info/RECORD +249 -0
  249. tigrbl/ExampleAgent.py +0 -1
  250. tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
  251. tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
  252. {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dev3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,286 @@
1
+ # tigrbl/v3/api/tigrbl_api.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 ._api import Api as _Api
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
+
36
+
37
+ class TigrblApi(_Api):
38
+ """
39
+ Canonical router-focused facade that owns:
40
+ • containers (models, schemas, handlers, hooks, rpc, rest, routers, columns, table_config, core proxies)
41
+ • model inclusion (REST + RPC wiring)
42
+ • JSON-RPC / diagnostics mounting
43
+ • (optional) auth knobs recognized by some middlewares/dispatchers
44
+
45
+ It composes v3 primitives; you can still use the functions directly if you prefer.
46
+ """
47
+
48
+ PREFIX = ""
49
+ TAGS: Sequence[Any] = ()
50
+ APIS: Sequence[Any] = ()
51
+ MODELS: Sequence[Any] = ()
52
+
53
+ # --- optional auth knobs recognized by some middlewares/dispatchers (kept for back-compat) ---
54
+ _authn: Any = None
55
+ _allow_anon: bool = True
56
+ _authorize: Any = None
57
+ _optional_authn_dep: Any = None
58
+ _allow_anon_ops: set[str] = set()
59
+
60
+ def __init__(
61
+ self,
62
+ *,
63
+ engine: EngineCfg | None = None,
64
+ jsonrpc_prefix: str = "/rpc",
65
+ system_prefix: str = "/system",
66
+ api_hooks: Mapping[str, Iterable[Callable]]
67
+ | Mapping[str, Mapping[str, Iterable[Callable]]]
68
+ | None = None,
69
+ **router_kwargs: Any,
70
+ ) -> None:
71
+ _Api.__init__(self, engine=engine, **router_kwargs)
72
+ self.jsonrpc_prefix = jsonrpc_prefix
73
+ self.system_prefix = system_prefix
74
+
75
+ # public containers (mirrors used by bindings.api)
76
+ self.models: Dict[str, type] = {}
77
+ self.schemas = SimpleNamespace()
78
+ self.handlers = SimpleNamespace()
79
+ self.hooks = SimpleNamespace()
80
+ self.rpc = SimpleNamespace()
81
+ self.rest = SimpleNamespace()
82
+ self.routers: Dict[str, Any] = {}
83
+ self.tables = AttrDict()
84
+ self.columns: Dict[str, Tuple[str, ...]] = {}
85
+ self.table_config: Dict[str, Dict[str, Any]] = {}
86
+ self.core = SimpleNamespace()
87
+ self.core_raw = SimpleNamespace()
88
+
89
+ # API-level hooks map (merged into each model at include-time; precedence handled in bindings.hooks)
90
+ self._api_hooks_map = copy.deepcopy(api_hooks) if api_hooks else None
91
+
92
+ # ------------------------- internal helpers -------------------------
93
+
94
+ @staticmethod
95
+ def _merge_api_hooks_into_model(model: type, hooks_map: Any) -> None:
96
+ """
97
+ Install API-level hooks on the model so the binder can see them.
98
+ Accepted shapes:
99
+ {phase: [fn, ...]} # global, all aliases
100
+ {alias: {phase: [fn, ...]}, "*": {...}} # per-alias + wildcard
101
+ If the model already has __tigrbl_api_hooks__, we shallow-merge keys.
102
+ """
103
+ if not hooks_map:
104
+ return
105
+ existing = getattr(model, "__tigrbl_api_hooks__", None)
106
+ if existing is None:
107
+ setattr(model, "__tigrbl_api_hooks__", copy.deepcopy(hooks_map))
108
+ return
109
+
110
+ # shallow merge (alias or phase keys); values are lists we extend
111
+ merged = copy.deepcopy(existing)
112
+ for k, v in (hooks_map or {}).items():
113
+ if k not in merged:
114
+ merged[k] = copy.deepcopy(v)
115
+ else:
116
+ # when both are dicts, merge phase lists
117
+ if isinstance(v, Mapping) and isinstance(merged[k], Mapping):
118
+ for ph, fns in v.items():
119
+ merged[k].setdefault(ph, [])
120
+ merged[k][ph] = list(merged[k][ph]) + list(fns or [])
121
+ else:
122
+ # fallback: prefer model-local value, then append api-level
123
+ if isinstance(merged[k], list):
124
+ merged[k] = list(merged[k]) + list(v or [])
125
+ else:
126
+ merged[k] = v
127
+ setattr(model, "__tigrbl_api_hooks__", merged)
128
+
129
+ # ------------------------- primary operations -------------------------
130
+
131
+ def include_model(
132
+ self, model: type, *, prefix: str | None = None, mount_router: bool = True
133
+ ) -> Tuple[type, Any]:
134
+ """
135
+ Bind a model, mount its REST router, and attach all namespaces to this facade.
136
+ """
137
+ # inject API-level hooks so the binder merges them
138
+ self._merge_api_hooks_into_model(model, self._api_hooks_map)
139
+ return _include_model(
140
+ self, model, app=None, prefix=prefix, mount_router=mount_router
141
+ )
142
+
143
+ def include_models(
144
+ self,
145
+ models: Sequence[type],
146
+ *,
147
+ base_prefix: str | None = None,
148
+ mount_router: bool = True,
149
+ ) -> Dict[str, Any]:
150
+ for m in models:
151
+ self._merge_api_hooks_into_model(m, self._api_hooks_map)
152
+ return _include_models(
153
+ self,
154
+ models,
155
+ app=None,
156
+ base_prefix=base_prefix,
157
+ mount_router=mount_router,
158
+ )
159
+
160
+ async def rpc_call(
161
+ self,
162
+ model_or_name: type | str,
163
+ method: str,
164
+ payload: Any = None,
165
+ *,
166
+ db: Any,
167
+ request: Any = None,
168
+ ctx: Optional[Dict[str, Any]] = None,
169
+ ) -> Any:
170
+ return await _rpc_call(
171
+ self, model_or_name, method, payload, db=db, request=request, ctx=ctx
172
+ )
173
+
174
+ # ------------------------- extras / mounting -------------------------
175
+
176
+ def mount_jsonrpc(self, *, prefix: str | None = None) -> Any:
177
+ """Mount a JSON-RPC router onto this TigrblApi instance."""
178
+ px = prefix if prefix is not None else self.jsonrpc_prefix
179
+ prov = _resolver.resolve_provider(api=self)
180
+ get_db = prov.get_db if prov else None
181
+ router = _mount_jsonrpc(
182
+ self,
183
+ self,
184
+ prefix=px,
185
+ get_db=get_db,
186
+ )
187
+ return router
188
+
189
+ def attach_diagnostics(
190
+ self, *, prefix: str | None = None, app: Any | None = None
191
+ ) -> Any:
192
+ """Mount a diagnostics router onto this TigrblApi instance or ``app``."""
193
+ px = prefix if prefix is not None else self.system_prefix
194
+ prov = _resolver.resolve_provider(api=self)
195
+ get_db = prov.get_db if prov else None
196
+ router = _mount_diagnostics(self, get_db=get_db)
197
+ include_self = getattr(self, "include_router", None)
198
+ if callable(include_self):
199
+ include_self(router, prefix=px)
200
+ if app is not None and app is not self:
201
+ include_other = getattr(app, "include_router", None)
202
+ if callable(include_other):
203
+ include_other(router, prefix=px)
204
+ return router
205
+
206
+ # ------------------------- registry passthroughs -------------------------
207
+
208
+ def registry(self, model: type):
209
+ """Return the per-model OpspecRegistry."""
210
+ return get_registry(model)
211
+
212
+ def bind(self, model: type) -> Tuple[OpSpec, ...]:
213
+ """Bind/rebuild a model in place (without mounting)."""
214
+ self._merge_api_hooks_into_model(model, self._api_hooks_map)
215
+ return _bind(model)
216
+
217
+ def rebind(
218
+ self, model: type, *, changed_keys: Optional[set[tuple[str, str]]] = None
219
+ ) -> Tuple[OpSpec, ...]:
220
+ """Targeted rebuild of a bound model."""
221
+ return _rebind(model, changed_keys=changed_keys)
222
+
223
+ # Optional: let callers set auth knobs used by some middlewares/dispatchers
224
+ def set_auth(
225
+ self,
226
+ *,
227
+ authn: Any = None,
228
+ allow_anon: Optional[bool] = None,
229
+ authorize: Any = None,
230
+ optional_authn_dep: Any = None,
231
+ ) -> None:
232
+ if authn is not None:
233
+ self._authn = authn
234
+ if allow_anon is not None:
235
+ self._allow_anon = bool(allow_anon)
236
+ if authorize is not None:
237
+ self._authorize = authorize
238
+ if optional_authn_dep is not None:
239
+ self._optional_authn_dep = optional_authn_dep
240
+
241
+ # Refresh already-included models so routers pick up new auth settings
242
+ if self.models:
243
+ self._refresh_security()
244
+
245
+ def _refresh_security(self) -> None:
246
+ """Re-seed auth deps on models and rebuild routers."""
247
+ # Reset routes and allow_anon ops cache
248
+ self.routes = []
249
+ self._allow_anon_ops = set()
250
+ for model in self.models.values():
251
+ _seed_security_and_deps(self, model)
252
+ specs = getattr(getattr(model, "opspecs", SimpleNamespace()), "all", ())
253
+ if specs:
254
+ _build_router_and_attach(model, list(specs))
255
+ router = getattr(getattr(model, "rest", SimpleNamespace()), "router", None)
256
+ if router is None:
257
+ continue
258
+ # update api-level references
259
+ mname = model.__name__
260
+ rest_ns = getattr(self.rest, mname, SimpleNamespace())
261
+ rest_ns.router = router
262
+ setattr(self.rest, mname, rest_ns)
263
+ self.routers[mname] = router
264
+ prefix = _default_prefix(model)
265
+ _mount_router(self, router, prefix=prefix)
266
+
267
+ def _collect_tables(self):
268
+ # dedupe; handle multiple DeclarativeBases (multiple metadatas)
269
+ seen = set()
270
+ tables = []
271
+ for m in self.models.values():
272
+ t = getattr(m, "__table__", None)
273
+ if t is not None and t not in seen:
274
+ seen.add(t)
275
+ tables.append(t)
276
+ return tables
277
+
278
+ initialize = _ddl_initialize
279
+
280
+ # ------------------------- repr -------------------------
281
+
282
+ def __repr__(self) -> str: # pragma: no cover
283
+ models = list(getattr(self, "models", {}))
284
+ rpc_ns = getattr(self, "rpc", None)
285
+ rpc_keys = list(getattr(rpc_ns, "__dict__", {}).keys()) if rpc_ns else []
286
+ return f"<TigrblApi models={models} rpc={rpc_keys}>"
tigrbl/app/__init__.py ADDED
File without changes
tigrbl/app/_app.py ADDED
@@ -0,0 +1,61 @@
1
+ # tigrbl/tigrbl/v3/app/_app.py
2
+ from __future__ import annotations
3
+ from typing import Any
4
+
5
+ from ..deps.fastapi import FastAPI
6
+ from ..engine.engine_spec import EngineCfg
7
+ from ..engine import resolver as _resolver
8
+ from ..engine import install_from_objects
9
+ from .app_spec import AppSpec
10
+
11
+
12
+ class App(AppSpec, FastAPI):
13
+ def __init__(
14
+ self, *, engine: EngineCfg | None = None, **fastapi_kwargs: Any
15
+ ) -> None:
16
+ # Manually mirror ``AppSpec`` fields so the dataclass-generated ``repr``
17
+ # and friends have expected attributes while runtime structures remain
18
+ # mutable dictionaries or lists as needed.
19
+ self.title = self.TITLE
20
+ self.version = self.VERSION
21
+ self.engine = engine if engine is not None else getattr(self, "ENGINE", None)
22
+ self.apis = tuple(getattr(self, "APIS", ()))
23
+ self.ops = tuple(getattr(self, "OPS", ()))
24
+ # Runtime registries use mutable containers (dict/namespace), but the
25
+ # dataclass fields expect sequences. Storing a dict here satisfies both.
26
+ self.models = {}
27
+ self.schemas = tuple(getattr(self, "SCHEMAS", ()))
28
+ self.hooks = tuple(getattr(self, "HOOKS", ()))
29
+ self.security_deps = tuple(getattr(self, "SECURITY_DEPS", ()))
30
+ self.deps = tuple(getattr(self, "DEPS", ()))
31
+ self.response = getattr(self, "RESPONSE", None)
32
+ self.jsonrpc_prefix = getattr(self, "JSONRPC_PREFIX", "/rpc")
33
+ self.system_prefix = getattr(self, "SYSTEM_PREFIX", "/system")
34
+ self.middlewares = tuple(getattr(self, "MIDDLEWARES", ()))
35
+ self.lifespan = self.LIFESPAN
36
+
37
+ FastAPI.__init__(
38
+ self,
39
+ title=self.title,
40
+ version=self.version,
41
+ lifespan=self.lifespan,
42
+ **fastapi_kwargs,
43
+ )
44
+ _engine_ctx = self.engine
45
+ if _engine_ctx is not None:
46
+ _resolver.set_default(_engine_ctx)
47
+ _resolver.resolve_provider()
48
+ for mw in getattr(self, "MIDDLEWARES", []):
49
+ self.add_middleware(mw.__class__, **getattr(mw, "kwargs", {}))
50
+
51
+ def install_engines(
52
+ self, *, api: Any = None, models: tuple[Any, ...] | None = None
53
+ ) -> None:
54
+ # If class declared APIS/MODELS, use them unless explicit args are passed.
55
+ apis = (api,) if api is not None else self.APIS
56
+ models = models if models is not None else self.MODELS
57
+ if apis:
58
+ for a in apis:
59
+ install_from_objects(app=self, api=a, models=models)
60
+ else:
61
+ install_from_objects(app=self, api=None, models=models)
tigrbl/app/app_spec.py ADDED
@@ -0,0 +1,42 @@
1
+ # tigrbl/tigrbl/v3/app/app_spec.py
2
+ from __future__ import annotations
3
+ from dataclasses import dataclass, field
4
+ from typing import Any, Callable, Optional, Sequence
5
+
6
+ from ..engine.engine_spec import EngineCfg
7
+ from ..response.types import ResponseSpec
8
+
9
+
10
+ @dataclass(eq=False)
11
+ class AppSpec:
12
+ """
13
+ Used to *produce an App subclass* via App.from_spec().
14
+ """
15
+
16
+ title: str = "Tigrbl"
17
+ version: str = "0.1.0"
18
+ engine: Optional[EngineCfg] = None
19
+
20
+ # NEW: multi-API composition (store API classes or instances)
21
+ apis: Sequence[Any] = field(default_factory=tuple)
22
+
23
+ # NEW: orchestration/topology knobs
24
+ ops: Sequence[Any] = field(default_factory=tuple) # op descriptors or specs
25
+ models: Sequence[Any] = field(default_factory=tuple) # ORM classes
26
+ schemas: Sequence[Any] = field(default_factory=tuple) # schema classes/defs
27
+ hooks: Sequence[Callable[..., Any]] = field(default_factory=tuple)
28
+
29
+ # security/dep stacks (FastAPI dependencies or callables)
30
+ security_deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
31
+ deps: Sequence[Callable[..., Any]] = field(default_factory=tuple)
32
+
33
+ # response defaults
34
+ response: Optional[ResponseSpec] = None
35
+
36
+ # system routing prefixes (REST/JSON-RPC namespaces)
37
+ jsonrpc_prefix: str = "/rpc"
38
+ system_prefix: str = "/system"
39
+
40
+ # optional framework bits
41
+ middlewares: Sequence[Any] = field(default_factory=tuple)
42
+ lifespan: Optional[Callable[..., Any]] = None
@@ -0,0 +1,67 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from functools import lru_cache
5
+ from typing import Any, Tuple
6
+
7
+ from .app_spec import AppSpec
8
+
9
+ logger = logging.getLogger("uvicorn")
10
+
11
+
12
+ def _merge_seq_attr(app: type, attr: str) -> Tuple[Any, ...]:
13
+ values: list[Any] = []
14
+ for base in reversed(app.__mro__):
15
+ seq = getattr(base, attr, ()) or ()
16
+ try:
17
+ values.extend(seq)
18
+ except TypeError: # non-iterable
19
+ values.append(seq)
20
+ return tuple(values)
21
+
22
+
23
+ @lru_cache(maxsize=None)
24
+ def mro_collect_app_spec(app: type) -> AppSpec:
25
+ """Collect AppSpec-like declarations across the app's MRO."""
26
+ logger.info("Collecting app spec for %s", app.__name__)
27
+
28
+ title = "Tigrbl"
29
+ version = "0.1.0"
30
+ engine: Any | None = None
31
+ response = None
32
+ jsonrpc_prefix = "/rpc"
33
+ system_prefix = "/system"
34
+ lifespan = None
35
+
36
+ for base in reversed(app.__mro__):
37
+ title = getattr(base, "TITLE", title)
38
+ version = getattr(base, "VERSION", version)
39
+ eng = getattr(base, "ENGINE", None)
40
+ if eng is not None:
41
+ engine = eng
42
+ response = getattr(base, "RESPONSE", response)
43
+ jsonrpc_prefix = getattr(base, "JSONRPC_PREFIX", jsonrpc_prefix)
44
+ system_prefix = getattr(base, "SYSTEM_PREFIX", system_prefix)
45
+ lifespan = getattr(base, "LIFESPAN", lifespan)
46
+
47
+ spec = AppSpec(
48
+ title=title,
49
+ version=version,
50
+ engine=engine,
51
+ apis=_merge_seq_attr(app, "APIS"),
52
+ ops=_merge_seq_attr(app, "OPS"),
53
+ models=_merge_seq_attr(app, "MODELS"),
54
+ schemas=_merge_seq_attr(app, "SCHEMAS"),
55
+ hooks=_merge_seq_attr(app, "HOOKS"),
56
+ security_deps=_merge_seq_attr(app, "SECURITY_DEPS"),
57
+ deps=_merge_seq_attr(app, "DEPS"),
58
+ response=response,
59
+ jsonrpc_prefix=jsonrpc_prefix,
60
+ system_prefix=system_prefix,
61
+ middlewares=_merge_seq_attr(app, "MIDDLEWARES"),
62
+ lifespan=lifespan,
63
+ )
64
+ return spec
65
+
66
+
67
+ __all__ = ["mro_collect_app_spec"]
@@ -0,0 +1,65 @@
1
+ # tigrbl/tigrbl/v3/app/shortcuts.py
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Sequence, Type
5
+
6
+ from .app_spec import AppSpec
7
+ from ._app import App
8
+
9
+
10
+ def defineAppSpec(
11
+ *,
12
+ title: str = "Tigrbl",
13
+ version: str = "0.1.0",
14
+ engine: Any = None,
15
+ # composition
16
+ apis: Sequence[Any] = (),
17
+ ops: Sequence[Any] = (),
18
+ models: Sequence[Any] = (),
19
+ schemas: Sequence[Any] = (),
20
+ hooks: Sequence[Any] = (),
21
+ # deps & security
22
+ security_deps: Sequence[Any] = (),
23
+ deps: Sequence[Any] = (),
24
+ # prefixes
25
+ jsonrpc_prefix: str = "/rpc",
26
+ system_prefix: str = "/system",
27
+ # framework bits
28
+ middlewares: Sequence[Any] = (),
29
+ lifespan: Any = None,
30
+ ) -> Type[AppSpec]:
31
+ """
32
+ Build an App-spec class with class attributes only (no instances).
33
+ Use it directly in your class MRO:
34
+
35
+ class MyApp(defineAppSpec(title="Svc", engine=...)):
36
+ pass
37
+
38
+ or pass it to `deriveApp(...)` to get a concrete App subclass.
39
+ """
40
+ attrs = dict(
41
+ TITLE=title,
42
+ VERSION=version,
43
+ ENGINE=engine,
44
+ APIS=tuple(apis or ()),
45
+ OPS=tuple(ops or ()),
46
+ MODELS=tuple(models or ()),
47
+ SCHEMAS=tuple(schemas or ()),
48
+ HOOKS=tuple(hooks or ()),
49
+ SECURITY_DEPS=tuple(security_deps or ()),
50
+ DEPS=tuple(deps or ()),
51
+ JSONRPC_PREFIX=jsonrpc_prefix,
52
+ SYSTEM_PREFIX=system_prefix,
53
+ MIDDLEWARES=tuple(middlewares or ()),
54
+ LIFESPAN=lifespan,
55
+ )
56
+ return type("AppSpec", (AppSpec,), attrs)
57
+
58
+
59
+ def deriveApp(**kw: Any) -> Type[App]:
60
+ """Produce a concrete :class:`App` subclass that inherits the spec."""
61
+ Spec = defineAppSpec(**kw)
62
+ return type("AppWithSpec", (Spec, App), {})
63
+
64
+
65
+ __all__ = ["defineAppSpec", "deriveApp"]