tigrbl 0.3.2__tar.gz → 0.3.3__tar.gz

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 (270) hide show
  1. {tigrbl-0.3.2 → tigrbl-0.3.3}/PKG-INFO +1 -1
  2. {tigrbl-0.3.2 → tigrbl-0.3.3}/pyproject.toml +1 -1
  3. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/api/_api.py +5 -0
  4. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/api/tigrbl_api.py +6 -0
  5. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/app/_app.py +24 -0
  6. tigrbl-0.3.3/tigrbl/app/mro_collect.py +106 -0
  7. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/app/tigrbl_app.py +93 -2
  8. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/model_helpers.py +10 -10
  9. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/collection.py +77 -3
  10. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/member.py +3 -3
  11. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/field_spec.py +5 -4
  12. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/shortcuts.py +14 -3
  13. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/response/__init__.py +6 -1
  14. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/response/decorators.py +8 -0
  15. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/builder/build_schema.py +6 -0
  16. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/diagnostics/hookz.py +1 -2
  17. tigrbl-0.3.3/tigrbl/system/diagnostics/methodz.py +43 -0
  18. tigrbl-0.3.3/tigrbl/table/_base.py +439 -0
  19. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/table/mro_collect.py +22 -5
  20. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/__init__.py +1 -0
  21. tigrbl-0.3.2/tigrbl/app/mro_collect.py +0 -67
  22. tigrbl-0.3.2/tigrbl/system/diagnostics/methodz.py +0 -43
  23. tigrbl-0.3.2/tigrbl/table/_base.py +0 -271
  24. {tigrbl-0.3.2 → tigrbl-0.3.3}/LICENSE +0 -0
  25. {tigrbl-0.3.2 → tigrbl-0.3.3}/README.md +0 -0
  26. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/README.md +0 -0
  27. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/__init__.py +0 -0
  28. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/api/__init__.py +0 -0
  29. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/api/api_spec.py +0 -0
  30. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/api/mro_collect.py +0 -0
  31. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/api/shortcuts.py +0 -0
  32. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/app/__init__.py +0 -0
  33. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/app/_model_registry.py +0 -0
  34. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/app/app_spec.py +0 -0
  35. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/app/shortcuts.py +0 -0
  36. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/__init__.py +0 -0
  37. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/api/__init__.py +0 -0
  38. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/api/common.py +0 -0
  39. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/api/include.py +0 -0
  40. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/api/resource_proxy.py +0 -0
  41. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/api/rpc.py +0 -0
  42. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/columns.py +0 -0
  43. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/handlers/__init__.py +0 -0
  44. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/handlers/builder.py +0 -0
  45. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/handlers/ctx.py +0 -0
  46. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/handlers/identifiers.py +0 -0
  47. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/handlers/namespaces.py +0 -0
  48. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/handlers/steps.py +0 -0
  49. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/hooks.py +0 -0
  50. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/model.py +0 -0
  51. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/model_registry.py +0 -0
  52. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/__init__.py +0 -0
  53. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/attach.py +0 -0
  54. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/common.py +0 -0
  55. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/fastapi.py +0 -0
  56. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/helpers.py +0 -0
  57. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/io.py +0 -0
  58. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/io_headers.py +0 -0
  59. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/router.py +0 -0
  60. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rest/routing.py +0 -0
  61. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/rpc.py +0 -0
  62. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/schemas/__init__.py +0 -0
  63. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/schemas/builder.py +0 -0
  64. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/schemas/defaults.py +0 -0
  65. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/bindings/schemas/utils.py +0 -0
  66. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/README.md +0 -0
  67. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/__init__.py +0 -0
  68. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/_column.py +0 -0
  69. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/column_spec.py +0 -0
  70. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/infer/__init__.py +0 -0
  71. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/infer/core.py +0 -0
  72. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/infer/jsonhints.py +0 -0
  73. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/infer/planning.py +0 -0
  74. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/infer/types.py +0 -0
  75. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/infer/utils.py +0 -0
  76. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/io_spec.py +0 -0
  77. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/mro_collect.py +0 -0
  78. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/column/storage_spec.py +0 -0
  79. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/config/__init__.py +0 -0
  80. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/config/constants.py +0 -0
  81. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/config/defaults.py +0 -0
  82. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/config/resolver.py +0 -0
  83. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/__init__.py +0 -0
  84. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/__init__.py +0 -0
  85. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/bulk.py +0 -0
  86. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/helpers/__init__.py +0 -0
  87. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/helpers/db.py +0 -0
  88. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/helpers/enum.py +0 -0
  89. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/helpers/filters.py +0 -0
  90. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/helpers/model.py +0 -0
  91. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/helpers/normalize.py +0 -0
  92. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/core/crud/ops.py +0 -0
  93. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/ddl/__init__.py +0 -0
  94. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/decorators.py +0 -0
  95. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/deps/__init__.py +0 -0
  96. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/deps/fastapi.py +0 -0
  97. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/deps/favicon.svg +0 -0
  98. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/deps/jinja.py +0 -0
  99. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/deps/pydantic.py +0 -0
  100. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/deps/sqlalchemy.py +0 -0
  101. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/deps/starlette.py +0 -0
  102. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/docs/verbosity.md +0 -0
  103. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/__init__.py +0 -0
  104. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/_engine.py +0 -0
  105. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/bind.py +0 -0
  106. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/builders.py +0 -0
  107. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/capabilities.py +0 -0
  108. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/collect.py +0 -0
  109. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/decorators.py +0 -0
  110. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/docs/PLUGINS.md +0 -0
  111. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/engine_spec.py +0 -0
  112. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/plugins.py +0 -0
  113. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/registry.py +0 -0
  114. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/resolver.py +0 -0
  115. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/engine/shortcuts.py +0 -0
  116. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/hook/__init__.py +0 -0
  117. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/hook/_hook.py +0 -0
  118. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/hook/decorators.py +0 -0
  119. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/hook/hook_spec.py +0 -0
  120. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/hook/mro_collect.py +0 -0
  121. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/hook/shortcuts.py +0 -0
  122. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/hook/types.py +0 -0
  123. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/__init__.py +0 -0
  124. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/_op.py +0 -0
  125. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/canonical.py +0 -0
  126. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/collect.py +0 -0
  127. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/decorators.py +0 -0
  128. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/model_registry.py +0 -0
  129. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/mro_collect.py +0 -0
  130. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/resolver.py +0 -0
  131. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/op/types.py +0 -0
  132. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/__init__.py +0 -0
  133. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/_RowBound.py +0 -0
  134. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/__init__.py +0 -0
  135. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/bootstrappable.py +0 -0
  136. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/bound.py +0 -0
  137. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/edges.py +0 -0
  138. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/fields.py +0 -0
  139. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/hierarchy.py +0 -0
  140. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/key_digest.py +0 -0
  141. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/lifecycle.py +0 -0
  142. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/locks.py +0 -0
  143. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/markers.py +0 -0
  144. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/operations.py +0 -0
  145. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/ownable.py +0 -0
  146. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/principals.py +0 -0
  147. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/tenant_bound.py +0 -0
  148. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/upsertable.py +0 -0
  149. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/mixins/utils.py +0 -0
  150. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/__init__.py +0 -0
  151. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/_base.py +0 -0
  152. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/audit.py +0 -0
  153. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/client.py +0 -0
  154. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/group.py +0 -0
  155. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/org.py +0 -0
  156. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/rbac.py +0 -0
  157. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/status.py +0 -0
  158. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/tenant.py +0 -0
  159. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/orm/tables/user.py +0 -0
  160. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/response/README.md +0 -0
  161. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/response/bind.py +0 -0
  162. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/response/resolver.py +0 -0
  163. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/response/shortcuts.py +0 -0
  164. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/response/types.py +0 -0
  165. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/rest/__init__.py +0 -0
  166. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/README.md +0 -0
  167. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/__init__.py +0 -0
  168. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/__init__.py +0 -0
  169. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/emit/__init__.py +0 -0
  170. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/emit/paired_post.py +0 -0
  171. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/emit/paired_pre.py +0 -0
  172. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/emit/readtime_alias.py +0 -0
  173. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/out/__init__.py +0 -0
  174. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/out/masking.py +0 -0
  175. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/refresh/__init__.py +0 -0
  176. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/refresh/demand.py +0 -0
  177. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/resolve/__init__.py +0 -0
  178. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/resolve/assemble.py +0 -0
  179. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/resolve/paired_gen.py +0 -0
  180. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/response/__init__.py +0 -0
  181. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/response/headers_from_payload.py +0 -0
  182. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/response/negotiate.py +0 -0
  183. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/response/negotiation.py +0 -0
  184. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/response/render.py +0 -0
  185. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/response/renderer.py +0 -0
  186. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/response/template.py +0 -0
  187. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/response/templates.py +0 -0
  188. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/schema/__init__.py +0 -0
  189. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/schema/collect_in.py +0 -0
  190. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/schema/collect_out.py +0 -0
  191. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/storage/__init__.py +0 -0
  192. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/storage/to_stored.py +0 -0
  193. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/wire/__init__.py +0 -0
  194. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/wire/build_in.py +0 -0
  195. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/wire/build_out.py +0 -0
  196. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/wire/dump.py +0 -0
  197. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/atoms/wire/validate_in.py +0 -0
  198. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/context.py +0 -0
  199. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/errors/__init__.py +0 -0
  200. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/errors/converters.py +0 -0
  201. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/errors/exceptions.py +0 -0
  202. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/errors/mappings.py +0 -0
  203. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/errors/utils.py +0 -0
  204. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/events.py +0 -0
  205. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/executor/__init__.py +0 -0
  206. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/executor/guards.py +0 -0
  207. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/executor/helpers.py +0 -0
  208. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/executor/invoke.py +0 -0
  209. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/executor/types.py +0 -0
  210. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/kernel.py +0 -0
  211. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/labels.py +0 -0
  212. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/opview.py +0 -0
  213. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/ordering.py +0 -0
  214. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/system.py +0 -0
  215. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/runtime/trace.py +0 -0
  216. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/__init__.py +0 -0
  217. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/_schema.py +0 -0
  218. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/builder/__init__.py +0 -0
  219. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/builder/cache.py +0 -0
  220. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/builder/compat.py +0 -0
  221. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/builder/extras.py +0 -0
  222. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/builder/helpers.py +0 -0
  223. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/builder/list_params.py +0 -0
  224. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/builder/strip_parent_fields.py +0 -0
  225. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/collect.py +0 -0
  226. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/decorators.py +0 -0
  227. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/get_schema.py +0 -0
  228. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/schema_spec.py +0 -0
  229. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/shortcuts.py +0 -0
  230. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/types.py +0 -0
  231. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/schema/utils.py +0 -0
  232. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/session/README.md +0 -0
  233. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/session/__init__.py +0 -0
  234. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/session/abc.py +0 -0
  235. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/session/base.py +0 -0
  236. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/session/decorators.py +0 -0
  237. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/session/default.py +0 -0
  238. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/session/shortcuts.py +0 -0
  239. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/session/spec.py +0 -0
  240. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/shortcuts.py +0 -0
  241. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/specs.py +0 -0
  242. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/__init__.py +0 -0
  243. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/diagnostics/__init__.py +0 -0
  244. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/diagnostics/compat.py +0 -0
  245. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/diagnostics/healthz.py +0 -0
  246. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/diagnostics/kernelz.py +0 -0
  247. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/diagnostics/router.py +0 -0
  248. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/diagnostics/utils.py +0 -0
  249. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/system/uvicorn.py +0 -0
  250. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/table/__init__.py +0 -0
  251. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/table/_table.py +0 -0
  252. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/table/shortcuts.py +0 -0
  253. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/table/table_spec.py +0 -0
  254. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/transport/__init__.py +0 -0
  255. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/transport/jsonrpc/__init__.py +0 -0
  256. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/transport/jsonrpc/dispatcher.py +0 -0
  257. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/transport/jsonrpc/helpers.py +0 -0
  258. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/transport/jsonrpc/models.py +0 -0
  259. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/transport/rest/__init__.py +0 -0
  260. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/transport/rest/aggregator.py +0 -0
  261. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/allow_anon_provider.py +0 -0
  262. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/authn_abc.py +0 -0
  263. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/nested_path_provider.py +0 -0
  264. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/op.py +0 -0
  265. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/op_config_provider.py +0 -0
  266. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/op_verb_alias_provider.py +0 -0
  267. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/request_extras_provider.py +0 -0
  268. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/response_extras_provider.py +0 -0
  269. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/table_config_provider.py +0 -0
  270. {tigrbl-0.3.2 → tigrbl-0.3.3}/tigrbl/types/uuid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: Automatic API generation tools by Swarmauri.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tigrbl"
3
- version = "0.3.2"
3
+ version = "0.3.3"
4
4
  description = "Automatic API generation tools by Swarmauri."
5
5
  license = "Apache-2.0"
6
6
  readme = "README.md"
@@ -26,6 +26,11 @@ class Api(APISpec, ApiRouter):
26
26
  def __eq__(self, other: object) -> bool: # pragma: no cover - identity compare
27
27
  return self is other
28
28
 
29
+ @property
30
+ def router(self) -> "Api": # pragma: no cover - simple alias
31
+ """Mirror FastAPI-style router access for API instances."""
32
+ return self
33
+
29
34
  def __init__(
30
35
  self, *, engine: EngineCfg | None = None, **router_kwargs: Any
31
36
  ) -> None:
@@ -62,6 +62,8 @@ class TigrblApi(_Api):
62
62
  self,
63
63
  *,
64
64
  engine: EngineCfg | None = None,
65
+ models: Sequence[type] | None = None,
66
+ prefix: str | None = None,
65
67
  jsonrpc_prefix: str = "/rpc",
66
68
  system_prefix: str = "/system",
67
69
  api_hooks: Mapping[str, Iterable[Callable]]
@@ -69,6 +71,8 @@ class TigrblApi(_Api):
69
71
  | None = None,
70
72
  **router_kwargs: Any,
71
73
  ) -> None:
74
+ if prefix is not None:
75
+ self.PREFIX = prefix
72
76
  _Api.__init__(self, engine=engine, **router_kwargs)
73
77
  self.jsonrpc_prefix = jsonrpc_prefix
74
78
  self.system_prefix = system_prefix
@@ -89,6 +93,8 @@ class TigrblApi(_Api):
89
93
 
90
94
  # API-level hooks map (merged into each model at include-time; precedence handled in bindings.hooks)
91
95
  self._api_hooks_map = copy.deepcopy(api_hooks) if api_hooks else None
96
+ if models:
97
+ self.include_models(list(models))
92
98
 
93
99
  # ------------------------- internal helpers -------------------------
94
100
 
@@ -12,12 +12,36 @@ from .app_spec import AppSpec
12
12
 
13
13
 
14
14
  class App(AppSpec, FastAPI):
15
+ TITLE = "Tigrbl"
16
+ VERSION = "0.1.0"
17
+ LIFESPAN = None
18
+ MIDDLEWARES = ()
19
+ APIS = ()
20
+ OPS = ()
21
+ MODELS = ()
22
+ SCHEMAS = ()
23
+ HOOKS = ()
24
+ SECURITY_DEPS = ()
25
+ DEPS = ()
26
+ RESPONSE = None
27
+ JSONRPC_PREFIX = "/rpc"
28
+ SYSTEM_PREFIX = "/system"
29
+
15
30
  def __init__(
16
31
  self, *, engine: EngineCfg | None = None, **fastapi_kwargs: Any
17
32
  ) -> None:
18
33
  # Manually mirror ``AppSpec`` fields so the dataclass-generated ``repr``
19
34
  # and friends have expected attributes while runtime structures remain
20
35
  # mutable dictionaries or lists as needed.
36
+ title = fastapi_kwargs.pop("title", None)
37
+ if title is not None:
38
+ self.TITLE = title
39
+ version = fastapi_kwargs.pop("version", None)
40
+ if version is not None:
41
+ self.VERSION = version
42
+ lifespan = fastapi_kwargs.pop("lifespan", None)
43
+ if lifespan is not None:
44
+ self.LIFESPAN = lifespan
21
45
  self.title = self.TITLE
22
46
  self.version = self.VERSION
23
47
  self.engine = engine if engine is not None else getattr(self, "ENGINE", None)
@@ -0,0 +1,106 @@
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(
13
+ app: type,
14
+ attr: str,
15
+ *,
16
+ include_inherited: bool = False,
17
+ reverse: bool = False,
18
+ ) -> Tuple[Any, ...]:
19
+ values: list[Any] = []
20
+ mro = reversed(app.__mro__) if reverse else app.__mro__
21
+ for base in mro:
22
+ if include_inherited:
23
+ if not hasattr(base, attr):
24
+ continue
25
+ seq = getattr(base, attr) or ()
26
+ else:
27
+ seq = base.__dict__.get(attr, ()) or ()
28
+ try:
29
+ values.extend(seq)
30
+ except TypeError: # non-iterable
31
+ values.append(seq)
32
+ return tuple(values)
33
+
34
+
35
+ @lru_cache(maxsize=None)
36
+ def mro_collect_app_spec(app: type) -> AppSpec:
37
+ """Collect AppSpec-like declarations across the app's MRO."""
38
+ logger.info("Collecting app spec for %s", app.__name__)
39
+
40
+ sentinel = object()
41
+ title: Any = sentinel
42
+ version: Any = sentinel
43
+ engine: Any | None = sentinel # type: ignore[assignment]
44
+ response: Any = sentinel
45
+ jsonrpc_prefix: Any = sentinel
46
+ system_prefix: Any = sentinel
47
+ lifespan: Any = sentinel
48
+
49
+ for base in app.__mro__:
50
+ if "TITLE" in base.__dict__ and title is sentinel:
51
+ title = base.__dict__["TITLE"]
52
+ if "VERSION" in base.__dict__ and version is sentinel:
53
+ version = base.__dict__["VERSION"]
54
+ if "ENGINE" in base.__dict__ and engine is sentinel:
55
+ engine = base.__dict__["ENGINE"]
56
+ if "RESPONSE" in base.__dict__ and response is sentinel:
57
+ response = base.__dict__["RESPONSE"]
58
+ if "JSONRPC_PREFIX" in base.__dict__ and jsonrpc_prefix is sentinel:
59
+ jsonrpc_prefix = base.__dict__["JSONRPC_PREFIX"]
60
+ if "SYSTEM_PREFIX" in base.__dict__ and system_prefix is sentinel:
61
+ system_prefix = base.__dict__["SYSTEM_PREFIX"]
62
+ if "LIFESPAN" in base.__dict__ and lifespan is sentinel:
63
+ lifespan = base.__dict__["LIFESPAN"]
64
+
65
+ if title is sentinel:
66
+ title = "Tigrbl"
67
+ if version is sentinel:
68
+ version = "0.1.0"
69
+ if engine is sentinel:
70
+ engine = None
71
+ if response is sentinel:
72
+ response = None
73
+ if jsonrpc_prefix is sentinel:
74
+ jsonrpc_prefix = "/rpc"
75
+ if system_prefix is sentinel:
76
+ system_prefix = "/system"
77
+ if lifespan is sentinel:
78
+ lifespan = None
79
+
80
+ include_inherited_apis = "APIS" not in app.__dict__
81
+ spec = AppSpec(
82
+ title=title,
83
+ version=version,
84
+ engine=engine,
85
+ apis=_merge_seq_attr(
86
+ app,
87
+ "APIS",
88
+ include_inherited=include_inherited_apis,
89
+ reverse=include_inherited_apis,
90
+ ),
91
+ ops=_merge_seq_attr(app, "OPS"),
92
+ models=_merge_seq_attr(app, "MODELS"),
93
+ schemas=_merge_seq_attr(app, "SCHEMAS"),
94
+ hooks=_merge_seq_attr(app, "HOOKS"),
95
+ security_deps=_merge_seq_attr(app, "SECURITY_DEPS"),
96
+ deps=_merge_seq_attr(app, "DEPS"),
97
+ response=response,
98
+ jsonrpc_prefix=jsonrpc_prefix,
99
+ system_prefix=system_prefix,
100
+ middlewares=_merge_seq_attr(app, "MIDDLEWARES"),
101
+ lifespan=lifespan,
102
+ )
103
+ return spec
104
+
105
+
106
+ __all__ = ["mro_collect_app_spec"]
@@ -1,7 +1,9 @@
1
1
  # tigrbl/v3/app/tigrbl_app.py
2
2
  from __future__ import annotations
3
3
 
4
+ import asyncio
4
5
  import copy
6
+ import inspect
5
7
  from types import SimpleNamespace
6
8
  from typing import (
7
9
  Any,
@@ -71,6 +73,7 @@ class TigrblApp(_App):
71
73
  self,
72
74
  *,
73
75
  engine: EngineCfg | None = None,
76
+ apis: Sequence[Any] | None = None,
74
77
  jsonrpc_prefix: str = "/rpc",
75
78
  system_prefix: str = "/system",
76
79
  api_hooks: Mapping[str, Iterable[Callable]]
@@ -106,9 +109,13 @@ class TigrblApp(_App):
106
109
  self.table_config: Dict[str, Dict[str, Any]] = {}
107
110
  self.core = SimpleNamespace()
108
111
  self.core_raw = SimpleNamespace()
112
+ self.apis = list(getattr(self, "APIS", ()))
109
113
 
110
114
  # API-level hooks map (merged into each model at include-time; precedence handled in bindings.hooks)
111
115
  self._api_hooks_map = copy.deepcopy(api_hooks) if api_hooks else None
116
+ if apis:
117
+ self.apis.extend(list(apis))
118
+ self.include_apis(self.apis)
112
119
 
113
120
  # ------------------------- internal helpers -------------------------
114
121
 
@@ -175,6 +182,92 @@ class TigrblApp(_App):
175
182
  mount_router=mount_router,
176
183
  )
177
184
 
185
+ def include_api(
186
+ self,
187
+ api: Any,
188
+ *,
189
+ prefix: str | None = None,
190
+ mount_router: bool = True,
191
+ ) -> Any:
192
+ """Mount a Tigrbl API router onto this app and track it."""
193
+ if api not in self.apis:
194
+ self.apis.append(api)
195
+ if not mount_router:
196
+ return api
197
+ router = getattr(api, "router", api)
198
+ if hasattr(self, "include_router"):
199
+ self.include_router(router, prefix=prefix or "")
200
+ return api
201
+
202
+ def include_router(self, router: Any, *args: Any, **kwargs: Any) -> None:
203
+ """Extend FastAPI include_router to track Tigrbl APIs."""
204
+ if hasattr(router, "models") and hasattr(router, "initialize"):
205
+ self.include_api(
206
+ router,
207
+ prefix=kwargs.get("prefix"),
208
+ mount_router=False,
209
+ )
210
+ super().include_router(router, *args, **kwargs)
211
+
212
+ def include_apis(self, apis: Sequence[Any]) -> None:
213
+ """Mount multiple APIs, supporting optional per-item prefixes."""
214
+ for entry in apis:
215
+ prefix = None
216
+ api = entry
217
+ if isinstance(entry, tuple) and entry:
218
+ api = entry[0]
219
+ if len(entry) > 1:
220
+ value = entry[1]
221
+ if isinstance(value, dict):
222
+ prefix = value.get("prefix")
223
+ elif isinstance(value, str):
224
+ prefix = value
225
+ self.include_api(api, prefix=prefix)
226
+
227
+ def initialize(
228
+ self,
229
+ *,
230
+ schemas: Iterable[str] | None = None,
231
+ sqlite_attachments: Mapping[str, str] | None = None,
232
+ tables: Iterable[Any] | None = None,
233
+ ):
234
+ """Initialize DDL for the app and any attached APIs."""
235
+ result = _ddl_initialize(
236
+ self,
237
+ schemas=schemas,
238
+ sqlite_attachments=sqlite_attachments,
239
+ tables=tables,
240
+ )
241
+
242
+ api_results = []
243
+ for api in self.apis:
244
+ init = getattr(api, "initialize", None)
245
+ if callable(init):
246
+ api_results.append(
247
+ init(
248
+ schemas=schemas,
249
+ sqlite_attachments=sqlite_attachments,
250
+ tables=tables,
251
+ )
252
+ )
253
+
254
+ awaitables = [r for r in [result, *api_results] if inspect.isawaitable(r)]
255
+ if not awaitables:
256
+ return None
257
+
258
+ async def _inner():
259
+ for item in [result, *api_results]:
260
+ if inspect.isawaitable(item):
261
+ await item
262
+
263
+ try:
264
+ loop = asyncio.get_running_loop()
265
+ except RuntimeError:
266
+ asyncio.run(_inner())
267
+ return None
268
+
269
+ return loop.create_task(_inner())
270
+
178
271
  async def rpc_call(
179
272
  self,
180
273
  model_or_name: type | str,
@@ -311,8 +404,6 @@ class TigrblApp(_App):
311
404
  tables.append(t)
312
405
  return tables
313
406
 
314
- initialize = _ddl_initialize
315
-
316
407
  # ------------------------- repr -------------------------
317
408
 
318
409
  def __repr__(self) -> str: # pragma: no cover
@@ -24,40 +24,40 @@ def _ensure_model_namespaces(model: type) -> None:
24
24
  """Create top-level namespaces on the model class if missing."""
25
25
 
26
26
  # op indexes & metadata
27
- if not hasattr(model, "ops"):
28
- if hasattr(model, "opspecs"):
27
+ if "ops" not in model.__dict__:
28
+ if "opspecs" in model.__dict__:
29
29
  model.ops = model.opspecs
30
30
  else:
31
31
  model.ops = SimpleNamespace(all=(), by_key={}, by_alias={})
32
32
  # Backwards compatibility: older code may still expect `model.opspecs`
33
33
  model.opspecs = model.ops
34
34
  # pydantic schemas: .<alias>.in_ / .<alias>.out
35
- if not hasattr(model, "schemas"):
35
+ if "schemas" not in model.__dict__:
36
36
  model.schemas = SimpleNamespace()
37
37
  # hooks: phase chains & raw hook descriptors if you want to expose them
38
- if not hasattr(model, "hooks"):
38
+ if "hooks" not in model.__dict__:
39
39
  model.hooks = SimpleNamespace()
40
40
  # handlers: .<alias>.raw (core/custom), .<alias>.handler (HANDLER chain entry point)
41
- if not hasattr(model, "handlers"):
41
+ if "handlers" not in model.__dict__:
42
42
  model.handlers = SimpleNamespace()
43
43
  # rpc: callables to be registered/mounted elsewhere as JSON-RPC methods
44
- if not hasattr(model, "rpc"):
44
+ if "rpc" not in model.__dict__:
45
45
  model.rpc = SimpleNamespace()
46
46
  # rest: .router (FastAPI Router or compatible) – built in rest binding
47
- if not hasattr(model, "rest"):
47
+ if "rest" not in model.__dict__:
48
48
  model.rest = SimpleNamespace(router=None)
49
49
  # basic table metadata for convenience (introspective only; NEVER used for HTTP paths)
50
- if not hasattr(model, "columns"):
50
+ if "columns" not in model.__dict__:
51
51
  table = getattr(model, "__table__", None)
52
52
  cols = tuple(getattr(table, "columns", ()) or ())
53
53
  model.columns = tuple(
54
54
  getattr(c, "name", None) for c in cols if getattr(c, "name", None)
55
55
  )
56
- if not hasattr(model, "table_config"):
56
+ if "table_config" not in model.__dict__:
57
57
  table = getattr(model, "__table__", None)
58
58
  model.table_config = dict(getattr(table, "kwargs", {}) or {})
59
59
  # ensure raw hook store exists for decorator merges
60
- if not hasattr(model, "__tigrbl_hooks__"):
60
+ if "__tigrbl_hooks__" not in model.__dict__:
61
61
  setattr(model, "__tigrbl_hooks__", {})
62
62
 
63
63
 
@@ -178,7 +178,79 @@ def _make_collection_endpoint(
178
178
  _endpoint.__signature__ = inspect.Signature(params)
179
179
  else:
180
180
  body_model = _request_model_for(sp, model)
181
+ if body_model is None and sp.request_model is None and target == "custom":
182
+
183
+ async def _endpoint(
184
+ request: Request,
185
+ db: Any = Depends(db_dep),
186
+ h: Mapping[str, Any] = Depends(hdr_dep),
187
+ **kw: Any,
188
+ ):
189
+ parent_kw = {k: kw[k] for k in nested_vars if k in kw}
190
+ _coerce_parent_kw(model, parent_kw)
191
+ payload: Mapping[str, Any] = dict(parent_kw)
192
+ if isinstance(h, Mapping):
193
+ payload = {**payload, **dict(h)}
194
+ ctx = _ctx(model, alias, target, request, db, payload, parent_kw, api)
195
+
196
+ def _serializer(r, _ctx=ctx):
197
+ out = _serialize_output(model, alias, target, sp, r)
198
+ temp = (
199
+ getattr(_ctx, "temp", {}) if isinstance(_ctx, Mapping) else {}
200
+ )
201
+ extras = (
202
+ temp.get("response_extras", {})
203
+ if isinstance(temp, Mapping)
204
+ else {}
205
+ )
206
+ if isinstance(out, dict) and isinstance(extras, dict):
207
+ out.update(extras)
208
+ return out
209
+
210
+ ctx["response_serializer"] = _serializer
211
+ phases = _get_phase_chains(model, alias)
212
+ result = await _executor._invoke(
213
+ request=request,
214
+ db=db,
215
+ phases=phases,
216
+ ctx=ctx,
217
+ )
218
+ return result
219
+
220
+ _endpoint.__signature__ = _sig(
221
+ nested_vars,
222
+ [
223
+ inspect.Parameter(
224
+ "request",
225
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
226
+ annotation=Request,
227
+ ),
228
+ inspect.Parameter(
229
+ "db",
230
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
231
+ annotation=Annotated[Any, Depends(db_dep)],
232
+ ),
233
+ inspect.Parameter(
234
+ "h",
235
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
236
+ annotation=Annotated[Mapping[str, Any], Depends(hdr_dep)],
237
+ ),
238
+ ],
239
+ )
240
+ _endpoint.__name__ = f"rest_{model.__name__}_{alias}_collection"
241
+ _endpoint.__qualname__ = _endpoint.__name__
242
+ _endpoint.__doc__ = (
243
+ f"REST collection endpoint for {model.__name__}.{alias} ({target})"
244
+ )
245
+ return _endpoint
246
+
181
247
  base = body_model or Mapping[str, Any]
248
+ body_required = target in {
249
+ "create",
250
+ "update",
251
+ "replace",
252
+ "merge",
253
+ } or target.startswith("bulk_")
182
254
  if target.startswith("bulk_"):
183
255
  alias_ns = getattr(
184
256
  getattr(model, "schemas", None) or SimpleNamespace(), alias, None
@@ -197,13 +269,13 @@ def _make_collection_endpoint(
197
269
  _list_ann(Mapping[str, Any]),
198
270
  )
199
271
  else:
200
- body_annotation = base
272
+ body_annotation = _union(base, type(None)) if not body_required else base
201
273
 
202
274
  async def _endpoint(
203
275
  request: Request,
204
276
  db: Any = Depends(db_dep),
205
277
  h: Mapping[str, Any] = Depends(hdr_dep),
206
- body=Body(...),
278
+ body=None,
207
279
  **kw: Any,
208
280
  ):
209
281
  parent_kw = {k: kw[k] for k in nested_vars if k in kw}
@@ -251,6 +323,7 @@ def _make_collection_endpoint(
251
323
  return result
252
324
  return result
253
325
 
326
+ body_default = ... if body_required else None
254
327
  _endpoint.__signature__ = _sig(
255
328
  nested_vars,
256
329
  [
@@ -272,7 +345,8 @@ def _make_collection_endpoint(
272
345
  inspect.Parameter(
273
346
  "body",
274
347
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
275
- annotation=Annotated[body_annotation, Body(...)],
348
+ annotation=Annotated[body_annotation, Body()],
349
+ default=body_default,
276
350
  ),
277
351
  ],
278
352
  )
@@ -134,7 +134,7 @@ def _make_member_endpoint(
134
134
  params.extend(
135
135
  [
136
136
  inspect.Parameter(
137
- "item_id",
137
+ pk_param,
138
138
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
139
139
  annotation=Annotated[Any, Path(...)],
140
140
  ),
@@ -230,7 +230,7 @@ def _make_member_endpoint(
230
230
  params.extend(
231
231
  [
232
232
  inspect.Parameter(
233
- "item_id",
233
+ pk_param,
234
234
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
235
235
  annotation=Annotated[Any, Path(...)],
236
236
  ),
@@ -350,7 +350,7 @@ def _make_member_endpoint(
350
350
  params.extend(
351
351
  [
352
352
  inspect.Parameter(
353
- "item_id",
353
+ pk_param,
354
354
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
355
355
  annotation=Annotated[Any, Path(...)],
356
356
  ),
@@ -15,16 +15,17 @@ class FieldSpec:
15
15
  ``py_type`` denotes the expected Python type and may be omitted when the
16
16
  model attribute is annotated; the type will then be inferred. ``constraints``
17
17
  mirrors arguments accepted by :func:`pydantic.Field` and participates in
18
- schema generation. ``required_in`` and ``allow_null_in`` govern which API
19
- verbs must supply the value or may explicitly send ``null`` in requests.
20
- Responses rely on Pydantic's built-in encoders based solely on the
21
- declared type.
18
+ schema generation. ``description`` provides a convenience field for schema
19
+ metadata. ``required_in`` and ``allow_null_in`` govern which API verbs must
20
+ supply the value or may explicitly send ``null`` in requests. Responses rely
21
+ on Pydantic's built-in encoders based solely on the declared type.
22
22
  """
23
23
 
24
24
  py_type: Any = Any
25
25
 
26
26
  # For request/response schema generation (+ pydantic.Field)
27
27
  constraints: Dict[str, Any] = dc_field(default_factory=dict)
28
+ description: str | None = None
28
29
 
29
30
  # Request policy (DB nullability lives in StorageSpec.nullable)
30
31
  required_in: Tuple[str, ...] = ()
@@ -64,10 +64,21 @@ def makeVirtualColumn(
64
64
  ) -> Column:
65
65
  """Convenience for wire-only virtual columns."""
66
66
  if spec is not None:
67
- if any(
68
- x is not None for x in (field, io, default_factory, producer, read_producer)
69
- ):
67
+ if any(x is not None for x in (field, io, default_factory)):
70
68
  raise ValueError("Provide either spec or individual components, not both.")
69
+ if producer is not None and read_producer is not None:
70
+ raise ValueError(
71
+ "Provide only one of producer= or read_producer=, not both."
72
+ )
73
+ rp = read_producer or producer
74
+ if rp is not None:
75
+ spec = ColumnSpec(
76
+ storage=spec.storage,
77
+ field=spec.field,
78
+ io=spec.io,
79
+ default_factory=spec.default_factory,
80
+ read_producer=rp,
81
+ )
71
82
  return Column(spec=spec, **kw)
72
83
  if producer is not None and read_producer is not None:
73
84
  raise ValueError("Provide only one of producer= or read_producer=, not both.")
@@ -1,4 +1,8 @@
1
- from .decorators import response_ctx, get_attached_response_spec
1
+ from .decorators import (
2
+ response_ctx,
3
+ get_attached_response_spec,
4
+ get_attached_response_alias,
5
+ )
2
6
  from .types import (
3
7
  Response,
4
8
  ResponseKind,
@@ -14,6 +18,7 @@ from ..runtime.atoms.response.templates import render_template
14
18
  __all__ = [
15
19
  "response_ctx",
16
20
  "get_attached_response_spec",
21
+ "get_attached_response_alias",
17
22
  "ResponseSpec",
18
23
  "ResponseKind",
19
24
  "TemplateSpec",
@@ -5,6 +5,7 @@ from .types import ResponseSpec
5
5
 
6
6
  T = TypeVar("T")
7
7
  _ATTR = "__tigrbl_response_spec__"
8
+ _ALIAS_ATTR = "__tigrbl_response_alias__"
8
9
 
9
10
 
10
11
  def _to_spec(spec: Optional[ResponseSpec] = None, **kwargs: Any) -> ResponseSpec:
@@ -24,10 +25,13 @@ def response_ctx(**kwargs: Any) -> Callable[[T], T]: ...
24
25
 
25
26
 
26
27
  def response_ctx(*args: Any, **kwargs: Any) -> Callable[[T], T]:
28
+ alias = kwargs.pop("alias", None)
27
29
  spec = _to_spec(*args, **kwargs)
28
30
 
29
31
  def decorator(target: T) -> T:
30
32
  setattr(target, _ATTR, spec)
33
+ if alias is not None:
34
+ setattr(target, _ALIAS_ATTR, alias)
31
35
  return target
32
36
 
33
37
  return decorator
@@ -35,3 +39,7 @@ def response_ctx(*args: Any, **kwargs: Any) -> Callable[[T], T]:
35
39
 
36
40
  def get_attached_response_spec(obj: Any) -> Optional[ResponseSpec]:
37
41
  return getattr(obj, _ATTR, None)
42
+
43
+
44
+ def get_attached_response_alias(obj: Any) -> Optional[str]:
45
+ return getattr(obj, _ALIAS_ATTR, None)
@@ -98,6 +98,9 @@ def _build_schema(
98
98
  # Field construction (collect kwargs then create Field once)
99
99
  fs = getattr(spec, "field", None)
100
100
  field_kwargs: Dict[str, Any] = dict(getattr(fs, "constraints", {}) or {})
101
+ description = getattr(fs, "description", None)
102
+ if description and "description" not in field_kwargs:
103
+ field_kwargs["description"] = description
101
104
 
102
105
  default_factory = getattr(spec, "default_factory", None)
103
106
  if default_factory and verb in set(getattr(io, "in_verbs", []) or []):
@@ -163,6 +166,9 @@ def _build_schema(
163
166
  allow_null = bool(fs and verb in getattr(fs, "allow_null_in", ()))
164
167
  nullable = bool(getattr(spec, "nullable", True))
165
168
  field_kwargs: Dict[str, Any] = dict(getattr(fs, "constraints", {}) or {})
169
+ description = getattr(fs, "description", None)
170
+ if description and "description" not in field_kwargs:
171
+ field_kwargs["description"] = description
166
172
 
167
173
  default_factory = getattr(spec, "default_factory", None)
168
174
  if default_factory and verb in set(getattr(spec.io, "in_verbs", []) or []):
@@ -43,8 +43,7 @@ def build_hookz_endpoint(api: Any):
43
43
  phase_map[ph] = [_label_callable(fn) for fn in steps]
44
44
  if phase_map:
45
45
  model_map[alias] = phase_map
46
- if model_map:
47
- out[mname] = model_map
46
+ out[mname] = model_map
48
47
  cache = out
49
48
  return cache
50
49