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,194 @@
1
+ # tigrbl/v3/bindings/model.py
2
+ from __future__ import annotations
3
+
4
+ import logging
5
+ from dataclasses import replace
6
+ from types import SimpleNamespace
7
+ from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple
8
+
9
+ from ..op import OpSpec
10
+ from ..op.mro_collect import mro_alias_map_for, mro_collect_decorated_ops
11
+ from ..op import resolve as resolve_ops
12
+ from ..op.types import PHASES # phase allowlist for hook merges
13
+
14
+ # Ctx-only decorators integration
15
+ from ..hook.mro_collect import mro_collect_decorated_hooks
16
+
17
+ # Sub-binders (implemented elsewhere)
18
+ from . import (
19
+ schemas as _schemas_binding,
20
+ ) # build_and_attach(model, specs, only_keys=None) -> None
21
+ from . import (
22
+ hooks as _hooks_binding,
23
+ ) # normalize_and_attach(model, specs, only_keys=None) -> None
24
+ from . import (
25
+ handlers as _handlers_binding,
26
+ ) # build_and_attach(model, specs, only_keys=None) -> None
27
+ from . import (
28
+ rpc as _rpc_binding,
29
+ ) # register_and_attach(model, specs, only_keys=None) -> None
30
+ from . import (
31
+ rest as _rest_binding,
32
+ ) # build_router_and_attach(model, specs, api=None, only_keys=None) -> None
33
+ from . import columns as _columns_binding
34
+ from .model_helpers import (
35
+ _Key,
36
+ _drop_old_entries,
37
+ _ensure_model_namespaces,
38
+ _filter_specs,
39
+ _index_specs,
40
+ _key,
41
+ )
42
+ from .model_registry import _ensure_op_ctx_attach_hook, _ensure_registry_listener
43
+
44
+ logger = logging.getLogger("uvicorn")
45
+ logger.debug("Loaded module v3/bindings/model")
46
+
47
+
48
+ def _dedupe_by_name(funcs: Iterable[Callable[..., Any]]) -> List[Callable[..., Any]]:
49
+ """Return callables deduplicated by qualified name preserving last occurrence."""
50
+ return list({getattr(fn, "__qualname__", str(fn)): fn for fn in funcs}.values())
51
+
52
+
53
+ # ───────────────────────────────────────────────────────────────────────────────
54
+ # Public API
55
+ # ───────────────────────────────────────────────────────────────────────────────
56
+
57
+
58
+ def bind(
59
+ model: type, *, api: Any | None = None, only_keys: Optional[Set[_Key]] = None
60
+ ) -> Tuple[OpSpec, ...]:
61
+ """
62
+ Build (or refresh) all Tigrbl namespaces on the model class.
63
+
64
+ Steps:
65
+ 1) Ensure model namespaces exist.
66
+ 2) Resolve canonical OpSpecs and merge ctx-only ops (@op_ctx). If both define
67
+ the same (alias,target), the ctx-only op overrides.
68
+ 3) Optionally drop old entries for targeted (alias,target) keys.
69
+ 4) Merge ctx-only hooks (@hook_ctx) into model.__tigrbl_hooks__ (alias-aware),
70
+ filtering to known PHASES.
71
+ 5) Rebuild & attach (scoped by only_keys when provided):
72
+ • schemas (in_/out)
73
+ • hooks (phase chains, with START_TX/END_TX or mark_skip_persist defaults)
74
+ • handlers (raw & handler entrypoint for HANDLER)
75
+ • rpc (model.rpc.<alias>)
76
+ • rest (model.rest.router)
77
+ 6) Index ops under model.ops.{all, by_key, by_alias}
78
+ 7) Install a registry listener (once) so imperative updates rebind automatically.
79
+
80
+ Returns:
81
+ tuple of OpSpec (the effective set).
82
+ """
83
+ _ensure_model_namespaces(model)
84
+
85
+ # 0) Columns first
86
+ _columns_binding.build_and_attach(model)
87
+
88
+ # 1) Resolve canonical specs (source of truth)
89
+ base_specs: List[OpSpec] = list(resolve_ops(model))
90
+
91
+ # 2) Add ctx-only ops discovered via decorators (tables + mixins)
92
+ ctx_specs: List[OpSpec] = list(mro_collect_decorated_ops(model))
93
+
94
+ # 2a) Inherit canonical schemas for aliased ops lacking explicit schemas
95
+ base_by_target: Dict[str, OpSpec] = {sp.target: sp for sp in base_specs}
96
+ fixed_ctx_specs: List[OpSpec] = []
97
+ for sp in ctx_specs:
98
+ if (
99
+ sp.alias != sp.target
100
+ and sp.target != "custom"
101
+ and sp.request_model is None
102
+ and sp.response_model is None
103
+ ):
104
+ base = base_by_target.get(sp.target)
105
+ if base:
106
+ sp = replace(
107
+ sp,
108
+ request_model=base.request_model,
109
+ response_model=base.response_model,
110
+ )
111
+ fixed_ctx_specs.append(sp)
112
+ ctx_specs = fixed_ctx_specs
113
+
114
+ # 2b) De-dupe by (alias,target) with ctx-only overriding canonical/defaults
115
+ merged_by_key: Dict[_Key, OpSpec] = {}
116
+ for sp in base_specs:
117
+ merged_by_key[_key(sp)] = sp
118
+ for sp in ctx_specs:
119
+ merged_by_key[_key(sp)] = sp
120
+
121
+ all_merged_specs: List[OpSpec] = list(merged_by_key.values())
122
+
123
+ # 3) Targeted rebuild support: drop old entries and restrict working set if requested
124
+ _drop_old_entries(model, keys=only_keys)
125
+ specs: List[OpSpec] = _filter_specs(all_merged_specs, only_keys)
126
+
127
+ # 4) Merge ctx-only hooks (alias-aware) BEFORE normalization/attachment
128
+ visible_aliases = (
129
+ {sp.alias for sp in specs} if specs else {sp.alias for sp in all_merged_specs}
130
+ )
131
+ ctx_hooks = mro_collect_decorated_hooks(model, visible_aliases=visible_aliases)
132
+ base_hooks = getattr(model, "__tigrbl_hooks__", {}) or {}
133
+
134
+ # Coerce any pre-existing phase sequences to mutable lists and deduplicate
135
+ for phases in base_hooks.values():
136
+ for phase, fns in list(phases.items()):
137
+ phases[phase] = _dedupe_by_name(fns if isinstance(fns, list) else list(fns))
138
+
139
+ for alias, phases in ctx_hooks.items():
140
+ per = base_hooks.setdefault(alias, {})
141
+ for phase, fns in phases.items():
142
+ if phase in PHASES:
143
+ existing = per.setdefault(phase, [])
144
+ per[phase] = _dedupe_by_name([*existing, *fns])
145
+
146
+ setattr(model, "__tigrbl_hooks__", base_hooks)
147
+
148
+ # 5) Attach schemas, hooks, handlers, rpc, router (sub-binders honor only_keys)
149
+ _schemas_binding.build_and_attach(model, specs, only_keys=only_keys)
150
+ _hooks_binding.normalize_and_attach(model, specs, only_keys=only_keys)
151
+ _handlers_binding.build_and_attach(model, specs, only_keys=only_keys)
152
+ _rpc_binding.register_and_attach(model, specs, only_keys=only_keys)
153
+ _rest_binding.build_router_and_attach(model, specs, api=api, only_keys=only_keys)
154
+
155
+ # 6) Index on the model (always overwrite with fresh views)
156
+ all_specs, by_key, by_alias = _index_specs(all_merged_specs)
157
+ model.ops = SimpleNamespace(all=all_specs, by_key=by_key, by_alias=by_alias)
158
+ # Maintain `.opspecs` alias for backward compatibility
159
+ model.opspecs = model.ops
160
+
161
+ # (Optional) expose resolved alias map for diagnostics
162
+ try:
163
+ model.alias_map = mro_alias_map_for(model)
164
+ except Exception: # defensive
165
+ pass
166
+
167
+ # 7) Ensure we have a registry listener to refresh on changes
168
+ _ensure_registry_listener(model)
169
+ _ensure_op_ctx_attach_hook(model)
170
+ setattr(model, "__tigrbl_op_ctx_watch__", True)
171
+
172
+ logger.debug(
173
+ "tigrbl.bindings.model.bind(%s): %d ops bound (visible=%d)",
174
+ model.__name__,
175
+ len(all_specs),
176
+ len(specs),
177
+ )
178
+ return all_specs
179
+
180
+
181
+ def rebind(
182
+ model: type,
183
+ *,
184
+ api: Any | None = None,
185
+ changed_keys: Optional[Set[_Key]] = None,
186
+ ) -> Tuple[OpSpec, ...]:
187
+ """
188
+ Public helper to trigger a rebind for the model. If `changed_keys` is provided,
189
+ we attempt a targeted refresh; otherwise we rebuild everything.
190
+ """
191
+ return bind(model, api=api, only_keys=changed_keys)
192
+
193
+
194
+ __all__ = ["bind", "rebind"]
@@ -0,0 +1,139 @@
1
+ # tigrbl/v3/bindings/model_helpers.py
2
+ """Internal helpers for the model bindings."""
3
+
4
+ from __future__ import annotations
5
+ import logging
6
+
7
+ from types import SimpleNamespace
8
+ from typing import Dict, List, Optional, Sequence, Set, Tuple
9
+
10
+ from ..op import OpSpec
11
+
12
+ logger = logging.getLogger("uvicorn")
13
+ logger.debug("Loaded module v3/bindings/model_helpers")
14
+
15
+
16
+ _Key = Tuple[str, str] # (alias, target)
17
+
18
+
19
+ def _key(sp: OpSpec) -> _Key:
20
+ return (sp.alias, sp.target)
21
+
22
+
23
+ def _ensure_model_namespaces(model: type) -> None:
24
+ """Create top-level namespaces on the model class if missing."""
25
+
26
+ # op indexes & metadata
27
+ if not hasattr(model, "ops"):
28
+ if hasattr(model, "opspecs"):
29
+ model.ops = model.opspecs
30
+ else:
31
+ model.ops = SimpleNamespace(all=(), by_key={}, by_alias={})
32
+ # Backwards compatibility: older code may still expect `model.opspecs`
33
+ model.opspecs = model.ops
34
+ # pydantic schemas: .<alias>.in_ / .<alias>.out
35
+ if not hasattr(model, "schemas"):
36
+ model.schemas = SimpleNamespace()
37
+ # hooks: phase chains & raw hook descriptors if you want to expose them
38
+ if not hasattr(model, "hooks"):
39
+ model.hooks = SimpleNamespace()
40
+ # handlers: .<alias>.raw (core/custom), .<alias>.handler (HANDLER chain entry point)
41
+ if not hasattr(model, "handlers"):
42
+ model.handlers = SimpleNamespace()
43
+ # rpc: callables to be registered/mounted elsewhere as JSON-RPC methods
44
+ if not hasattr(model, "rpc"):
45
+ model.rpc = SimpleNamespace()
46
+ # rest: .router (FastAPI Router or compatible) – built in rest binding
47
+ if not hasattr(model, "rest"):
48
+ model.rest = SimpleNamespace(router=None)
49
+ # basic table metadata for convenience (introspective only; NEVER used for HTTP paths)
50
+ if not hasattr(model, "columns"):
51
+ table = getattr(model, "__table__", None)
52
+ cols = tuple(getattr(table, "columns", ()) or ())
53
+ model.columns = tuple(
54
+ getattr(c, "name", None) for c in cols if getattr(c, "name", None)
55
+ )
56
+ if not hasattr(model, "table_config"):
57
+ table = getattr(model, "__table__", None)
58
+ model.table_config = dict(getattr(table, "kwargs", {}) or {})
59
+ # ensure raw hook store exists for decorator merges
60
+ if not hasattr(model, "__tigrbl_hooks__"):
61
+ setattr(model, "__tigrbl_hooks__", {})
62
+
63
+
64
+ def _index_specs(
65
+ specs: Sequence[OpSpec],
66
+ ) -> Tuple[Tuple[OpSpec, ...], Dict[_Key, OpSpec], Dict[str, List[OpSpec]]]:
67
+ all_specs: Tuple[OpSpec, ...] = tuple(specs)
68
+ by_key: Dict[_Key, OpSpec] = {}
69
+ by_alias: Dict[str, List[OpSpec]] = {}
70
+ for sp in specs:
71
+ k = _key(sp)
72
+ by_key[k] = sp
73
+ by_alias.setdefault(sp.alias, []).append(sp)
74
+ return all_specs, by_key, by_alias
75
+
76
+
77
+ def _drop_old_entries(model: type, *, keys: Set[_Key] | None) -> None:
78
+ """
79
+ Remove per-op artifacts for the provided keys before a targeted rebuild.
80
+ Safe no-ops if keys are None (full rebuild happens cleanly by overwrite).
81
+ """
82
+
83
+ if not keys:
84
+ return
85
+ # schemas
86
+ for alias, _target in keys:
87
+ ns = getattr(model.schemas, alias, None)
88
+ if ns:
89
+ for attr in ("in_", "out", "list"):
90
+ try:
91
+ delattr(ns, attr)
92
+ except Exception:
93
+ pass
94
+ if not ns.__dict__:
95
+ try:
96
+ delattr(model.schemas, alias)
97
+ except Exception:
98
+ pass
99
+ # handlers
100
+ for alias, _target in keys:
101
+ if hasattr(model.handlers, alias):
102
+ try:
103
+ delattr(model.handlers, alias)
104
+ except Exception:
105
+ pass
106
+ # hooks
107
+ for alias, _target in keys:
108
+ if hasattr(model.hooks, alias):
109
+ try:
110
+ delattr(model.hooks, alias)
111
+ except Exception:
112
+ pass
113
+ # rpc
114
+ for alias, _target in keys:
115
+ if hasattr(model.rpc, alias):
116
+ try:
117
+ delattr(model.rpc, alias)
118
+ except Exception:
119
+ pass
120
+ # REST endpoints are refreshed wholesale by rest binding as needed
121
+
122
+
123
+ def _filter_specs(
124
+ specs: Sequence[OpSpec], only_keys: Optional[Set[_Key]]
125
+ ) -> List[OpSpec]:
126
+ if not only_keys:
127
+ return list(specs)
128
+ ok = only_keys
129
+ return [sp for sp in specs if _key(sp) in ok]
130
+
131
+
132
+ __all__ = [
133
+ "_Key",
134
+ "_key",
135
+ "_ensure_model_namespaces",
136
+ "_index_specs",
137
+ "_drop_old_entries",
138
+ "_filter_specs",
139
+ ]
@@ -0,0 +1,77 @@
1
+ # tigrbl/v3/bindings/model_registry.py
2
+ """Registry helpers for model bindings."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import logging
7
+ from typing import Set
8
+
9
+ from ..config.constants import TIGRBL_REGISTRY_LISTENER_ATTR
10
+ from ..op import OpspecRegistry, get_registry
11
+
12
+ from .model_helpers import _Key
13
+
14
+ logger = logging.getLogger("uvicorn")
15
+ logger.debug("Loaded module v3/bindings/model_registry")
16
+
17
+
18
+ def _ensure_registry_listener(model: type) -> None:
19
+ """
20
+ Subscribe (once) to the per-model OpspecRegistry so future register_ops/add/remove/set
21
+ calls automatically refresh the model namespaces.
22
+ """
23
+
24
+ reg: OpspecRegistry = get_registry(model)
25
+
26
+ # If we already subscribed, skip
27
+ if getattr(model, TIGRBL_REGISTRY_LISTENER_ATTR, None):
28
+ return
29
+
30
+ def _on_registry_change(registry: OpspecRegistry, changed: Set[_Key]) -> None:
31
+ from .model import rebind
32
+
33
+ try:
34
+ rebind(model, changed_keys=changed) # targeted rebind
35
+ except Exception as e: # pragma: no cover
36
+ logger.exception(
37
+ "tigrbl: rebind failed for %s on ops %s: %s",
38
+ model.__name__,
39
+ changed,
40
+ e,
41
+ )
42
+
43
+ reg.subscribe(_on_registry_change)
44
+ # Keep a reference to avoid GC of the closure and to prevent double-subscribe
45
+ setattr(model, TIGRBL_REGISTRY_LISTENER_ATTR, _on_registry_change)
46
+
47
+
48
+ def _ensure_op_ctx_attach_hook(model: type) -> None:
49
+ """Patch the model's metaclass to auto-rebind on ctx-only op attachment."""
50
+
51
+ meta = type(model)
52
+ if getattr(meta, "__tigrbl_op_ctx_meta_patch__", False):
53
+ return
54
+
55
+ orig_meta_setattr = meta.__setattr__
56
+
57
+ def _meta_setattr(cls, name, value):
58
+ from .model import rebind
59
+ from ..op.mro_collect import mro_collect_decorated_ops
60
+
61
+ orig_meta_setattr(cls, name, value)
62
+ fn = getattr(value, "__func__", value)
63
+ decl = getattr(fn, "__tigrbl_op_decl__", None)
64
+ if decl and getattr(cls, "__tigrbl_op_ctx_watch__", False):
65
+ alias = decl.alias or name
66
+ target = decl.target or "custom"
67
+ mro_collect_decorated_ops.cache_clear()
68
+ rebind(cls, changed_keys={(alias, target)})
69
+
70
+ meta.__setattr__ = _meta_setattr # type: ignore[attr-defined]
71
+ setattr(meta, "__tigrbl_op_ctx_meta_patch__", True)
72
+
73
+
74
+ __all__ = [
75
+ "_ensure_registry_listener",
76
+ "_ensure_op_ctx_attach_hook",
77
+ ]
@@ -0,0 +1,7 @@
1
+ import logging
2
+ from .attach import build_router_and_attach
3
+
4
+ logger = logging.getLogger("uvicorn")
5
+ logger.debug("Loaded module v3/bindings/rest/__init__")
6
+
7
+ __all__ = ["build_router_and_attach"]
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+ import logging
3
+
4
+ from types import SimpleNamespace
5
+ from typing import Any, Optional, Sequence
6
+
7
+ from .common import OpSpec, _Key
8
+ from .router import _build_router
9
+
10
+ logger = logging.getLogger("uvicorn")
11
+ logger.debug("Loaded module v3/bindings/rest/attach")
12
+
13
+
14
+ def build_router_and_attach(
15
+ model: type,
16
+ specs: Sequence[OpSpec],
17
+ *,
18
+ api: Any | None = None,
19
+ only_keys: Optional[Sequence[_Key]] = None,
20
+ ) -> None:
21
+ """
22
+ Build a Router for the model and attach it to `model.rest.router`.
23
+ For simplicity and correctness with FastAPI, we **rebuild the entire router**
24
+ on each call (FastAPI does not support removing individual routes cleanly).
25
+ """
26
+ router = _build_router(model, specs, api=api)
27
+ rest_ns = getattr(model, "rest", None) or SimpleNamespace()
28
+ rest_ns.router = router
29
+ setattr(model, "rest", rest_ns)
30
+ logger.debug(
31
+ "rest: %s router attached with %d routes",
32
+ model.__name__,
33
+ len(getattr(router, "routes", []) or []),
34
+ )