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,348 @@
1
+ from __future__ import annotations
2
+
3
+ # tigrbl/v3/bindings/schemas/builder.py
4
+
5
+ import logging
6
+ from types import SimpleNamespace
7
+ from typing import Dict, Optional, Sequence, Type
8
+
9
+ from pydantic import BaseModel
10
+
11
+ from ...op import OpSpec
12
+ from ...schema import collect_decorated_schemas
13
+ from .defaults import _default_schemas_for_spec
14
+ from .utils import _alias_schema, _ensure_alias_namespace, _resolve_schema_arg, _Key
15
+
16
+ logging.getLogger("uvicorn").setLevel(logging.DEBUG)
17
+ logger = logging.getLogger("uvicorn")
18
+ logger.debug("Loaded module v3/bindings/schemas/builder")
19
+
20
+
21
+ def build_and_attach(
22
+ model: type, specs: Sequence[OpSpec], *, only_keys: Optional[Sequence[_Key]] = None
23
+ ) -> None:
24
+ """
25
+ Build request and response schemas per OpSpec and attach them under:
26
+ model.schemas.<alias>.in_ -> request model (or None)
27
+ model.schemas.<alias>.out -> response model (or None)
28
+
29
+ Two-pass strategy:
30
+ 0) Seed namespaces from @schema_ctx declarations (so SchemaRef targets exist)
31
+ 1) Attach DEFAULT schemas for canonical ops (custom stays raw)
32
+ 2) Apply per-spec overrides (SchemaRef / 'alias.in'|'alias.out' / 'raw' / None)
33
+
34
+ If `only_keys` is provided, overrides are limited to those (alias,target) pairs.
35
+ Defaults are still ensured for all specs so cross-op SchemaRefs resolve reliably
36
+ for canonical ops.
37
+ """
38
+ logger.debug(
39
+ "Building schemas for %s with %d specs (only_keys=%s)",
40
+ model.__name__,
41
+ len(specs),
42
+ only_keys,
43
+ )
44
+ if not hasattr(model, "schemas"):
45
+ model.schemas = SimpleNamespace()
46
+ logger.debug("Created new schemas namespace on %s", model.__name__)
47
+ else:
48
+ logger.debug("Using existing schemas namespace on %s", model.__name__)
49
+
50
+ wanted = set(only_keys or ())
51
+ if wanted:
52
+ logger.debug("Filtering overrides to keys: %s", wanted)
53
+
54
+ # Pass 0: attach schemas declared via @schema_ctx
55
+ declared = collect_decorated_schemas(model) # {alias: {"in": cls, "out": cls}}
56
+ for alias, kinds in (declared or {}).items():
57
+ logger.debug("Applying declared schemas for alias '%s'", alias)
58
+ ns = _ensure_alias_namespace(model, alias)
59
+ if "in" in kinds:
60
+ setattr(ns, "in_", kinds["in"])
61
+ logger.debug("Declared request schema for %s.%s", model.__name__, alias)
62
+ if "out" in kinds:
63
+ setattr(ns, "out", kinds["out"])
64
+ logger.debug("Declared response schema for %s.%s", model.__name__, alias)
65
+
66
+ # Ensure a namespace per op alias (even if empty)
67
+ for sp in specs:
68
+ _ = _ensure_alias_namespace(model, sp.alias)
69
+ logger.debug("Ensured namespace for alias '%s'", sp.alias)
70
+
71
+ # Pass 1: attach defaults for all specs and capture them so canonical
72
+ # defaults can be restored later if needed.
73
+ # Existing schemas that lack fields are treated as missing so they are
74
+ # replaced with freshly built defaults. This protects against earlier
75
+ # auto-binding passes that may have produced placeholder models.
76
+ defaults: Dict[_Key, Dict[str, Optional[Type[BaseModel]]]] = {}
77
+ for sp in specs:
78
+ ns = _ensure_alias_namespace(model, sp.alias)
79
+ shapes = _default_schemas_for_spec(model, sp)
80
+ defaults[(sp.alias, sp.target)] = shapes
81
+
82
+ if shapes.get("in_") is not None:
83
+ existing_in = getattr(ns, "in_", None)
84
+ if existing_in is None or not getattr(existing_in, "model_fields", None):
85
+ setattr(ns, "in_", shapes["in_"])
86
+ logger.debug(
87
+ "Attached default request schema for %s.%s",
88
+ model.__name__,
89
+ sp.alias,
90
+ )
91
+ else:
92
+ logger.debug(
93
+ "Keeping existing request schema for %s.%s",
94
+ model.__name__,
95
+ sp.alias,
96
+ )
97
+ else:
98
+ logger.debug(
99
+ "No default request schema for %s.%s", model.__name__, sp.alias
100
+ )
101
+
102
+ if shapes.get("in_item") is not None:
103
+ existing_in_item = getattr(ns, "in_item", None)
104
+ if existing_in_item is None or not getattr(
105
+ existing_in_item, "model_fields", None
106
+ ):
107
+ setattr(ns, "in_item", shapes["in_item"])
108
+ logger.debug(
109
+ "Attached default request item schema for %s.%s",
110
+ model.__name__,
111
+ sp.alias,
112
+ )
113
+ else:
114
+ logger.debug(
115
+ "Keeping existing request item schema for %s.%s",
116
+ model.__name__,
117
+ sp.alias,
118
+ )
119
+ else:
120
+ logger.debug(
121
+ "No default request item schema for %s.%s", model.__name__, sp.alias
122
+ )
123
+
124
+ if shapes.get("out") is not None:
125
+ existing_out = getattr(ns, "out", None)
126
+ if existing_out is None or not getattr(existing_out, "model_fields", None):
127
+ setattr(ns, "out", shapes["out"])
128
+ logger.debug(
129
+ "Attached default response schema for %s.%s",
130
+ model.__name__,
131
+ sp.alias,
132
+ )
133
+ else:
134
+ logger.debug(
135
+ "Keeping existing response schema for %s.%s",
136
+ model.__name__,
137
+ sp.alias,
138
+ )
139
+ else:
140
+ logger.debug(
141
+ "No default response schema for %s.%s", model.__name__, sp.alias
142
+ )
143
+
144
+ if shapes.get("out_item") is not None:
145
+ existing_out_item = getattr(ns, "out_item", None)
146
+ if existing_out_item is None or not getattr(
147
+ existing_out_item, "model_fields", None
148
+ ):
149
+ setattr(ns, "out_item", shapes["out_item"])
150
+ logger.debug(
151
+ "Attached default response item schema for %s.%s",
152
+ model.__name__,
153
+ sp.alias,
154
+ )
155
+ else:
156
+ logger.debug(
157
+ "Keeping existing response item schema for %s.%s",
158
+ model.__name__,
159
+ sp.alias,
160
+ )
161
+ else:
162
+ logger.debug(
163
+ "No default response item schema for %s.%s", model.__name__, sp.alias
164
+ )
165
+
166
+ logger.debug(
167
+ "schemas(default): %s.%s -> in=%s out=%s",
168
+ model.__name__,
169
+ sp.alias,
170
+ getattr(ns, "in_", None).__name__ if getattr(ns, "in_", None) else None,
171
+ getattr(ns, "out", None).__name__ if getattr(ns, "out", None) else None,
172
+ )
173
+
174
+ # Pass 2: apply per-spec overrides (respect only_keys if provided)
175
+ for sp in specs:
176
+ key = (sp.alias, sp.target)
177
+ if wanted and key not in wanted:
178
+ logger.debug(
179
+ "Skipping overrides for %s.%s not in only_keys",
180
+ model.__name__,
181
+ sp.alias,
182
+ )
183
+ continue
184
+
185
+ ns = _ensure_alias_namespace(model, sp.alias)
186
+
187
+ if sp.request_model is not None:
188
+ logger.debug(
189
+ "Resolving request override for %s.%s", model.__name__, sp.alias
190
+ )
191
+ try:
192
+ resolved_in = _resolve_schema_arg(
193
+ model, sp.request_model
194
+ ) # Optional[Type[BaseModel]]
195
+ except Exception as e:
196
+ logger.exception(
197
+ "Failed resolving request schema for %s.%s: %s",
198
+ model.__name__,
199
+ sp.alias,
200
+ e,
201
+ )
202
+ raise
203
+ setattr(ns, "in_", resolved_in)
204
+ logger.debug(
205
+ "Request override for %s.%s resolved to %s",
206
+ model.__name__,
207
+ sp.alias,
208
+ getattr(resolved_in, "__name__", None),
209
+ )
210
+ else:
211
+ logger.debug("No request override for %s.%s", model.__name__, sp.alias)
212
+
213
+ if sp.response_model is not None:
214
+ logger.debug(
215
+ "Resolving response override for %s.%s", model.__name__, sp.alias
216
+ )
217
+ try:
218
+ resolved_out = _resolve_schema_arg(
219
+ model, sp.response_model
220
+ ) # Optional[Type[BaseModel]]
221
+ except Exception as e:
222
+ logger.exception(
223
+ "Failed resolving response schema for %s.%s: %s",
224
+ model.__name__,
225
+ sp.alias,
226
+ e,
227
+ )
228
+ raise
229
+ setattr(ns, "out", resolved_out)
230
+ logger.debug(
231
+ "Response override for %s.%s resolved to %s",
232
+ model.__name__,
233
+ sp.alias,
234
+ getattr(resolved_out, "__name__", None),
235
+ )
236
+ else:
237
+ logger.debug("No response override for %s.%s", model.__name__, sp.alias)
238
+
239
+ logger.debug(
240
+ "schemas(override): %s.%s -> in=%s out=%s",
241
+ model.__name__,
242
+ sp.alias,
243
+ getattr(ns, "in_", None).__name__ if getattr(ns, "in_", None) else None,
244
+ getattr(ns, "out", None).__name__ if getattr(ns, "out", None) else None,
245
+ )
246
+
247
+ # Pass 2b: restore canonical defaults if overrides cleared them
248
+ for sp in specs:
249
+ if sp.target == "custom":
250
+ logger.debug(
251
+ "Skipping default restoration for custom target %s.%s",
252
+ model.__name__,
253
+ sp.alias,
254
+ )
255
+ continue
256
+ ns = _ensure_alias_namespace(model, sp.alias)
257
+ shapes = defaults.get((sp.alias, sp.target)) or {}
258
+ if getattr(ns, "in_", None) is None and shapes.get("in_") is not None:
259
+ setattr(ns, "in_", shapes["in_"])
260
+ logger.debug(
261
+ "Restored default request schema for %s.%s", model.__name__, sp.alias
262
+ )
263
+ else:
264
+ logger.debug(
265
+ "No request schema restoration needed for %s.%s",
266
+ model.__name__,
267
+ sp.alias,
268
+ )
269
+ if getattr(ns, "in_item", None) is None and shapes.get("in_item") is not None:
270
+ setattr(ns, "in_item", shapes["in_item"])
271
+ logger.debug(
272
+ "Restored default request item schema for %s.%s",
273
+ model.__name__,
274
+ sp.alias,
275
+ )
276
+ else:
277
+ logger.debug(
278
+ "No request item schema restoration needed for %s.%s",
279
+ model.__name__,
280
+ sp.alias,
281
+ )
282
+ if getattr(ns, "out", None) is None and shapes.get("out") is not None:
283
+ setattr(ns, "out", shapes["out"])
284
+ logger.debug(
285
+ "Restored default response schema for %s.%s", model.__name__, sp.alias
286
+ )
287
+ else:
288
+ logger.debug(
289
+ "No response schema restoration needed for %s.%s",
290
+ model.__name__,
291
+ sp.alias,
292
+ )
293
+ if getattr(ns, "out_item", None) is None and shapes.get("out_item") is not None:
294
+ setattr(ns, "out_item", shapes["out_item"])
295
+ logger.debug(
296
+ "Restored default response item schema for %s.%s",
297
+ model.__name__,
298
+ sp.alias,
299
+ )
300
+ else:
301
+ logger.debug(
302
+ "No response item schema restoration needed for %s.%s",
303
+ model.__name__,
304
+ sp.alias,
305
+ )
306
+
307
+ # Pass 3: ensure alias-specific request/response schema names
308
+ for sp in specs:
309
+ ns = _ensure_alias_namespace(model, sp.alias)
310
+ in_model = getattr(ns, "in_", None)
311
+ if (
312
+ isinstance(in_model, type)
313
+ and issubclass(in_model, BaseModel)
314
+ and getattr(in_model, "__tigrbl_schema_decl__", None) is None
315
+ ):
316
+ logger.debug("Aliasing request schema for %s.%s", model.__name__, sp.alias)
317
+ setattr(
318
+ ns,
319
+ "in_",
320
+ _alias_schema(in_model, model=model, alias=sp.alias, kind="Request"),
321
+ )
322
+ else:
323
+ logger.debug(
324
+ "Request schema for %s.%s already aliased or missing",
325
+ model.__name__,
326
+ sp.alias,
327
+ )
328
+ out_model = getattr(ns, "out", None)
329
+ if (
330
+ isinstance(out_model, type)
331
+ and issubclass(out_model, BaseModel)
332
+ and getattr(out_model, "__tigrbl_schema_decl__", None) is None
333
+ ):
334
+ logger.debug("Aliasing response schema for %s.%s", model.__name__, sp.alias)
335
+ setattr(
336
+ ns,
337
+ "out",
338
+ _alias_schema(out_model, model=model, alias=sp.alias, kind="Response"),
339
+ )
340
+ else:
341
+ logger.debug(
342
+ "Response schema for %s.%s already aliased or missing",
343
+ model.__name__,
344
+ sp.alias,
345
+ )
346
+
347
+
348
+ __all__ = ["build_and_attach"]
@@ -0,0 +1,260 @@
1
+ from __future__ import annotations
2
+ import logging
3
+
4
+ # tigrbl/v3/bindings/schemas/defaults.py
5
+
6
+ from typing import Dict, Optional, Type
7
+
8
+ from pydantic import BaseModel
9
+
10
+ from ...op import OpSpec
11
+ from ...schema import (
12
+ _build_schema,
13
+ _build_list_params,
14
+ _make_bulk_rows_model,
15
+ _make_bulk_rows_response_model,
16
+ _make_bulk_ids_model,
17
+ _make_deleted_response_model,
18
+ _make_pk_model,
19
+ )
20
+ from .utils import _pk_info
21
+
22
+ logging.getLogger("uvicorn").setLevel(logging.DEBUG)
23
+ logger = logging.getLogger("uvicorn")
24
+ logger.debug("Loaded module v3/bindings/schemas/defaults")
25
+
26
+
27
+ def _default_schemas_for_spec(
28
+ model: type, sp: OpSpec
29
+ ) -> Dict[str, Optional[Type[BaseModel]]]:
30
+ """
31
+ Decide default IN/OUT schemas for a given OpSpec (ignores sp.request_model/response_model).
32
+
33
+ New rules:
34
+ • Canonical targets → provide canonical defaults.
35
+ • Custom target → no defaults (raw) unless explicitly overridden.
36
+ """
37
+ target = sp.target
38
+ result: Dict[str, Optional[Type[BaseModel]]] = {
39
+ "in_": None,
40
+ "out": None,
41
+ "in_item": None,
42
+ "out_item": None,
43
+ }
44
+
45
+ # Element schema for many OUT shapes
46
+ read_schema: Optional[Type[BaseModel]] = _build_schema(model, verb="read")
47
+ logger.debug(
48
+ "Resolved base read schema for %s as %s",
49
+ model.__name__,
50
+ read_schema.__name__ if read_schema else None,
51
+ )
52
+
53
+ # Canonical targets
54
+ logger.debug(
55
+ "Building default schemas for %s.%s (target=%s)",
56
+ model.__name__,
57
+ sp.alias,
58
+ target,
59
+ )
60
+ if target == "create":
61
+ logger.debug("Using create defaults for %s.%s", model.__name__, sp.alias)
62
+ item_in = _build_schema(model, verb="create")
63
+ result["in_"] = item_in
64
+ result["out"] = read_schema
65
+
66
+ elif target == "read":
67
+ logger.debug("Using read defaults for %s.%s", model.__name__, sp.alias)
68
+ pk_name, pk_type = _pk_info(model)
69
+ result["in_"] = _make_pk_model(model, "read", pk_name, pk_type)
70
+ result["out"] = read_schema
71
+
72
+ elif target == "update":
73
+ logger.debug("Using update defaults for %s.%s", model.__name__, sp.alias)
74
+ pk_name, _ = _pk_info(model)
75
+ result["in_"] = _build_schema(model, verb="update", exclude={pk_name})
76
+ result["out"] = read_schema
77
+
78
+ elif target == "replace":
79
+ logger.debug("Using replace defaults for %s.%s", model.__name__, sp.alias)
80
+ pk_name, _ = _pk_info(model)
81
+ result["in_"] = _build_schema(model, verb="replace", exclude={pk_name})
82
+ result["out"] = read_schema
83
+
84
+ elif target == "merge":
85
+ logger.debug("Using merge defaults for %s.%s", model.__name__, sp.alias)
86
+ pk_name, _ = _pk_info(model)
87
+ result["in_"] = _build_schema(model, verb="update", exclude={pk_name})
88
+ result["out"] = read_schema
89
+
90
+ elif target == "delete":
91
+ logger.debug("Using delete defaults for %s.%s", model.__name__, sp.alias)
92
+ # For RPC delete, a body with PK is allowed; REST delete ignores body.
93
+ result["in_"] = _build_schema(model, verb="delete")
94
+ result["out"] = read_schema
95
+
96
+ elif target == "list":
97
+ logger.debug("Using list defaults for %s.%s", model.__name__, sp.alias)
98
+ params = _build_list_params(model)
99
+ result["in_"] = params
100
+ result["out"] = read_schema
101
+
102
+ elif target == "clear":
103
+ logger.debug("Using clear defaults for %s.%s", model.__name__, sp.alias)
104
+ params = _build_list_params(model)
105
+ result["in_"] = params
106
+ result["out"] = _make_deleted_response_model(model, "clear")
107
+
108
+ elif target == "bulk_create":
109
+ logger.debug("Using bulk_create defaults for %s.%s", model.__name__, sp.alias)
110
+ item_in = _build_schema(
111
+ model,
112
+ verb="create",
113
+ name=f"{model.__name__}BulkCreateItem",
114
+ )
115
+ result["in_"] = _make_bulk_rows_model(model, "bulk_create", item_in)
116
+ result["in_item"] = item_in
117
+ if read_schema:
118
+ result["out"] = _make_bulk_rows_response_model(
119
+ model, "bulk_create", read_schema
120
+ )
121
+ result["out_item"] = read_schema
122
+ logger.debug(
123
+ "Built bulk_create response schemas for %s.%s", model.__name__, sp.alias
124
+ )
125
+ else:
126
+ result["out"] = None
127
+ result["out_item"] = None
128
+ logger.debug(
129
+ "No read schema available for bulk_create %s.%s",
130
+ model.__name__,
131
+ sp.alias,
132
+ )
133
+
134
+ elif target == "bulk_update":
135
+ logger.debug("Using bulk_update defaults for %s.%s", model.__name__, sp.alias)
136
+ item_in = _build_schema(
137
+ model,
138
+ verb="update",
139
+ name=f"{model.__name__}BulkUpdateItem",
140
+ )
141
+ result["in_"] = _make_bulk_rows_model(model, "bulk_update", item_in)
142
+ result["in_item"] = item_in
143
+ if read_schema:
144
+ result["out"] = _make_bulk_rows_response_model(
145
+ model, "bulk_update", read_schema
146
+ )
147
+ result["out_item"] = read_schema
148
+ logger.debug(
149
+ "Built bulk_update response schemas for %s.%s", model.__name__, sp.alias
150
+ )
151
+ else:
152
+ result["out"] = None
153
+ result["out_item"] = None
154
+ logger.debug(
155
+ "No read schema available for bulk_update %s.%s",
156
+ model.__name__,
157
+ sp.alias,
158
+ )
159
+
160
+ elif target == "bulk_replace":
161
+ logger.debug("Using bulk_replace defaults for %s.%s", model.__name__, sp.alias)
162
+ item_in = _build_schema(
163
+ model,
164
+ verb="replace",
165
+ name=f"{model.__name__}BulkReplaceItem",
166
+ )
167
+ result["in_"] = _make_bulk_rows_model(model, "bulk_replace", item_in)
168
+ result["in_item"] = item_in
169
+ if read_schema:
170
+ result["out"] = _make_bulk_rows_response_model(
171
+ model, "bulk_replace", read_schema
172
+ )
173
+ result["out_item"] = read_schema
174
+ logger.debug(
175
+ "Built bulk_replace response schemas for %s.%s",
176
+ model.__name__,
177
+ sp.alias,
178
+ )
179
+ else:
180
+ result["out"] = None
181
+ result["out_item"] = None
182
+ logger.debug(
183
+ "No read schema available for bulk_replace %s.%s",
184
+ model.__name__,
185
+ sp.alias,
186
+ )
187
+
188
+ elif target == "bulk_merge":
189
+ logger.debug("Using bulk_merge defaults for %s.%s", model.__name__, sp.alias)
190
+ item_in = _build_schema(
191
+ model,
192
+ verb="update",
193
+ name=f"{model.__name__}BulkMergeItem",
194
+ )
195
+ result["in_"] = _make_bulk_rows_model(model, "bulk_merge", item_in)
196
+ result["in_item"] = item_in
197
+ if read_schema:
198
+ result["out"] = _make_bulk_rows_response_model(
199
+ model, "bulk_merge", read_schema
200
+ )
201
+ result["out_item"] = read_schema
202
+ logger.debug(
203
+ "Built bulk_merge response schemas for %s.%s", model.__name__, sp.alias
204
+ )
205
+ else:
206
+ result["out"] = None
207
+ result["out_item"] = None
208
+ logger.debug(
209
+ "No read schema available for bulk_merge %s.%s",
210
+ model.__name__,
211
+ sp.alias,
212
+ )
213
+
214
+ elif target == "bulk_delete":
215
+ logger.debug("Using bulk_delete defaults for %s.%s", model.__name__, sp.alias)
216
+ pk_name, pk_type = _pk_info(model)
217
+ result["in_"] = _make_bulk_ids_model(model, "bulk_delete", pk_type)
218
+ result["out"] = _make_deleted_response_model(model, "bulk_delete")
219
+
220
+ elif target == "custom":
221
+ logger.debug("Using custom defaults for %s.%s", model.__name__, sp.alias)
222
+ # Build schemas for custom operations based on verb-specific IO specs
223
+ alias = sp.alias
224
+ specs = getattr(model, "__tigrbl_cols__", {})
225
+ in_fields = {
226
+ name
227
+ for name, spec in specs.items()
228
+ if alias in set(getattr(getattr(spec, "io", None), "in_verbs", []) or [])
229
+ }
230
+ out_fields = {
231
+ name
232
+ for name, spec in specs.items()
233
+ if alias in set(getattr(getattr(spec, "io", None), "out_verbs", []) or [])
234
+ }
235
+ result["in_"] = (
236
+ _build_schema(model, verb=alias, include=in_fields) if in_fields else None
237
+ )
238
+ result["out"] = (
239
+ _build_schema(model, verb=alias, include=out_fields) if out_fields else None
240
+ )
241
+
242
+ else:
243
+ logger.debug(
244
+ "Target '%s' unknown for %s.%s, leaving schemas raw",
245
+ target,
246
+ model.__name__,
247
+ sp.alias,
248
+ )
249
+ # Defensive default: treat unknown like custom (raw)
250
+ result["in_"] = None
251
+ result["out"] = None
252
+
253
+ logger.debug(
254
+ "Built default schemas for %s.%s -> in=%s out=%s",
255
+ model.__name__,
256
+ sp.alias,
257
+ result["in_"].__name__ if result["in_"] else None,
258
+ result["out"].__name__ if result["out"] else None,
259
+ )
260
+ return result