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,224 @@
1
+ # tigrbl/v3/config/constants.py
2
+ """
3
+ Tigrbl v3 – Configuration constants
4
+
5
+ Centralizes “well-known” names and defaults used across v3. These are
6
+ pure-Python constants; there is no runtime coupling to any web framework.
7
+
8
+ Highlights
9
+ ----------
10
+ • Verbs/targets are *derived* from the v3 OpSpec canon so they always stay in sync.
11
+ • Default HTTP method mapping for REST bindings lives here (used by bindings.rest).
12
+ • Model config keys document the names we look for on SQLAlchemy classes.
13
+
14
+ Nothing in this module performs I/O.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Mapping, Tuple
20
+ import re
21
+
22
+ # NOTE: importing CANON from ``ops.types`` introduces a circular dependency
23
+ # because that module transitively imports this one via ``hook``. To keep the
24
+ # constant values in sync without triggering the circular import at import time,
25
+ # we inline the canonical verb tuple here. This tuple **must** match
26
+ # ``tigrbl.op.types.CANON``.
27
+ CANON: Tuple[str, ...] = (
28
+ "create",
29
+ "read",
30
+ "update",
31
+ "replace",
32
+ "merge",
33
+ "delete",
34
+ "list",
35
+ "clear",
36
+ "bulk_create",
37
+ "bulk_update",
38
+ "bulk_replace",
39
+ "bulk_merge",
40
+ "bulk_delete",
41
+ "custom",
42
+ )
43
+
44
+
45
+ # ───────────────────────────────────────────────────────────────────────────────
46
+ # Verbs / targets
47
+ # ───────────────────────────────────────────────────────────────────────────────
48
+
49
+ # Source of truth: `CANON` keys (strings like "create", "read", "bulk_update", ...)
50
+ ALL_VERBS: frozenset[str] = frozenset(
51
+ str(k) for k in (CANON.keys() if hasattr(CANON, "keys") else CANON)
52
+ ) # type: ignore[arg-type]
53
+
54
+ # Bulk targets are those prefixed with "bulk_"
55
+ BULK_VERBS: frozenset[str] = frozenset(v for v in ALL_VERBS if v.startswith("bulk_"))
56
+
57
+ # Routable canonical (non-bulk) verbs; excludes the synthetic "custom"
58
+ ROUTING_VERBS: frozenset[str] = frozenset(
59
+ v for v in ALL_VERBS if not v.startswith("bulk_") and v != "custom"
60
+ )
61
+
62
+
63
+ # ───────────────────────────────────────────────────────────────────────────────
64
+ # Default HTTP methods per target (bindings.rest uses this unless overridden)
65
+ # ───────────────────────────────────────────────────────────────────────────────
66
+
67
+ DEFAULT_HTTP_METHODS: Mapping[str, Tuple[str, ...]] = {
68
+ "create": ("POST",),
69
+ "read": ("GET",),
70
+ "update": ("PATCH",),
71
+ "replace": ("PUT",),
72
+ "merge": ("PATCH",),
73
+ "delete": ("DELETE",),
74
+ "list": ("GET",),
75
+ "clear": ("DELETE",),
76
+ "bulk_create": ("POST",),
77
+ "bulk_update": ("PATCH",),
78
+ "bulk_replace": ("PUT",),
79
+ "bulk_merge": ("PATCH",),
80
+ "bulk_delete": ("DELETE",),
81
+ "custom": ("POST",),
82
+ }
83
+
84
+
85
+ # ───────────────────────────────────────────────────────────────────────────────
86
+ # Model-level configuration attributes (looked up on the SQLAlchemy class)
87
+ # ───────────────────────────────────────────────────────────────────────────────
88
+
89
+ # Well-known attribute names injected or inspected on ORM models
90
+ TIGRBL_REQUEST_EXTRAS_ATTR = (
91
+ "__tigrbl_request_extras__" # request-only virtual fields per verb
92
+ )
93
+ TIGRBL_RESPONSE_EXTRAS_ATTR = (
94
+ "__tigrbl_response_extras__" # response-only virtual fields per verb
95
+ )
96
+ TIGRBL_OPS_ATTR = "__tigrbl_ops__" # declarative OpSpec list from decorators
97
+ TIGRBL_VERB_ALIASES_ATTR = "__tigrbl_verb_aliases__" # optional verb alias map
98
+ TIGRBL_VERB_ALIAS_POLICY_ATTR = "__tigrbl_verb_alias_policy__" # alias policy override
99
+ TIGRBL_NESTED_PATHS_ATTR = "__tigrbl_nested_paths__" # nested path callback (optional)
100
+ TIGRBL_API_HOOKS_ATTR = "__tigrbl_api_hooks__" # API-level hooks map
101
+ TIGRBL_HOOKS_ATTR = "__tigrbl_hooks__" # model-level hooks map
102
+ TIGRBL_REGISTRY_LISTENER_ATTR = "__tigrbl_registry_listener__" # ops registry listener
103
+ TIGRBL_GET_DB_ATTR = "__tigrbl_get_db__" # DB dependency
104
+ TIGRBL_AUTH_DEP_ATTR = "__tigrbl_auth_dep__" # auth dependency
105
+ TIGRBL_AUTHORIZE_ATTR = "__tigrbl_authorize__" # authorization callable
106
+ TIGRBL_REST_DEPENDENCIES_ATTR = "__tigrbl_rest_dependencies__" # extra REST deps
107
+ TIGRBL_RPC_DEPENDENCIES_ATTR = "__tigrbl_rpc_dependencies__" # extra RPC deps
108
+ TIGRBL_OWNER_POLICY_ATTR = "__tigrbl_owner_policy__" # ownership policy override
109
+ TIGRBL_TENANT_POLICY_ATTR = "__tigrbl_tenant_policy__" # tenancy policy override
110
+ TIGRBL_ALLOW_ANON_ATTR = "__tigrbl_allow_anon__" # verbs callable without auth
111
+ TIGRBL_DEFAULTS_MODE_ATTR = "__tigrbl_defaults_mode__" # canonical verb wiring policy
112
+ TIGRBL_DEFAULTS_INCLUDE_ATTR = "__tigrbl_defaults_include__" # verbs to force include
113
+ TIGRBL_DEFAULTS_EXCLUDE_ATTR = "__tigrbl_defaults_exclude__" # verbs to force exclude
114
+ TIGRBL_SCHEMA_DECLS_ATTR = "__tigrbl_schema_decls__" # declared schemas map
115
+
116
+ # Aggregate of recognized model-level config attributes
117
+ MODEL_LEVEL_CFGS: frozenset[str] = frozenset(
118
+ {
119
+ TIGRBL_REQUEST_EXTRAS_ATTR,
120
+ TIGRBL_RESPONSE_EXTRAS_ATTR,
121
+ TIGRBL_OPS_ATTR,
122
+ TIGRBL_VERB_ALIASES_ATTR,
123
+ TIGRBL_VERB_ALIAS_POLICY_ATTR,
124
+ TIGRBL_NESTED_PATHS_ATTR,
125
+ TIGRBL_API_HOOKS_ATTR,
126
+ TIGRBL_HOOKS_ATTR,
127
+ TIGRBL_REGISTRY_LISTENER_ATTR,
128
+ TIGRBL_GET_DB_ATTR,
129
+ TIGRBL_AUTH_DEP_ATTR,
130
+ TIGRBL_AUTHORIZE_ATTR,
131
+ TIGRBL_REST_DEPENDENCIES_ATTR,
132
+ TIGRBL_RPC_DEPENDENCIES_ATTR,
133
+ TIGRBL_OWNER_POLICY_ATTR,
134
+ TIGRBL_TENANT_POLICY_ATTR,
135
+ TIGRBL_ALLOW_ANON_ATTR,
136
+ TIGRBL_DEFAULTS_MODE_ATTR,
137
+ TIGRBL_DEFAULTS_INCLUDE_ATTR,
138
+ TIGRBL_DEFAULTS_EXCLUDE_ATTR,
139
+ TIGRBL_SCHEMA_DECLS_ATTR,
140
+ "__resource__", # resource name override for REST
141
+ }
142
+ )
143
+
144
+ # Other internal attribute names
145
+ TIGRBL_CUSTOM_OP_ATTR = "__tigrbl_custom_op__" # marker for custom op
146
+ HOOK_DECLS_ATTR = "__tigrbl_hook_decls__" # per-function hook declarations
147
+
148
+ # ───────────────────────────────────────────────────────────────────────────────
149
+ # ‼ Everything is natively transactional now
150
+ # ‼ We will not support transactionals as a custom type or obj moving forward.
151
+ # ‼ Support is not guaranteed.
152
+ # ───────────────────────────────────────────────────────────────────────────────
153
+ TIGRBL_TX_MODELS_ATTR = "__tigrbl_tx_models__" # transactional model cache
154
+
155
+
156
+ # ───────────────────────────────────────────────────────────────────────────────
157
+ # Common context keys used throughout runtime/bindings (non-normative)
158
+ # ───────────────────────────────────────────────────────────────────────────────
159
+
160
+ # These string constants are *conventional* keys you may put into ctx, not
161
+ # enforced by Tigrbl. They’re listed here for discoverability.
162
+ CTX_REQUEST_KEY = "request"
163
+ CTX_DB_KEY = "db"
164
+ CTX_PAYLOAD_KEY = "payload"
165
+ CTX_PATH_PARAMS_KEY = "path_params"
166
+ CTX_ENV_KEY = "env"
167
+ CTX_RPC_ID_KEY = "rpc_id" # used by the JSON-RPC dispatcher
168
+ CTX_SKIP_PERSIST_FLAG = "__tigrbl_skip_persist__" # set by ephemeral ops
169
+
170
+ # Optional auth/multitenancy keys that middlewares may populate for hooks to read
171
+ CTX_USER_ID_KEY = "user_id"
172
+ CTX_TENANT_ID_KEY = "tenant_id"
173
+ CTX_AUTH_KEY = "auth"
174
+
175
+ # Request.state attribute that carries auth context (tenant/user ids)
176
+ TIGRBL_AUTH_CONTEXT_ATTR = "__tigrbl_auth_context__"
177
+
178
+
179
+ # Regex for safe SQL identifiers
180
+ __SAFE_IDENT__ = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$")
181
+
182
+ __all__ = [
183
+ "ALL_VERBS",
184
+ "BULK_VERBS",
185
+ "ROUTING_VERBS",
186
+ "DEFAULT_HTTP_METHODS",
187
+ "MODEL_LEVEL_CFGS",
188
+ "TIGRBL_REQUEST_EXTRAS_ATTR",
189
+ "TIGRBL_RESPONSE_EXTRAS_ATTR",
190
+ "TIGRBL_OPS_ATTR",
191
+ "TIGRBL_VERB_ALIASES_ATTR",
192
+ "TIGRBL_VERB_ALIAS_POLICY_ATTR",
193
+ "TIGRBL_NESTED_PATHS_ATTR",
194
+ "TIGRBL_API_HOOKS_ATTR",
195
+ "TIGRBL_HOOKS_ATTR",
196
+ "TIGRBL_REGISTRY_LISTENER_ATTR",
197
+ "TIGRBL_GET_DB_ATTR",
198
+ "TIGRBL_AUTH_DEP_ATTR",
199
+ "TIGRBL_AUTHORIZE_ATTR",
200
+ "TIGRBL_REST_DEPENDENCIES_ATTR",
201
+ "TIGRBL_RPC_DEPENDENCIES_ATTR",
202
+ "TIGRBL_OWNER_POLICY_ATTR",
203
+ "TIGRBL_TENANT_POLICY_ATTR",
204
+ "TIGRBL_ALLOW_ANON_ATTR",
205
+ "TIGRBL_DEFAULTS_MODE_ATTR",
206
+ "TIGRBL_DEFAULTS_INCLUDE_ATTR",
207
+ "TIGRBL_DEFAULTS_EXCLUDE_ATTR",
208
+ "TIGRBL_SCHEMA_DECLS_ATTR",
209
+ "TIGRBL_CUSTOM_OP_ATTR",
210
+ "HOOK_DECLS_ATTR",
211
+ "TIGRBL_TX_MODELS_ATTR",
212
+ "CTX_REQUEST_KEY",
213
+ "CTX_DB_KEY",
214
+ "CTX_PAYLOAD_KEY",
215
+ "CTX_PATH_PARAMS_KEY",
216
+ "CTX_ENV_KEY",
217
+ "CTX_RPC_ID_KEY",
218
+ "CTX_SKIP_PERSIST_FLAG",
219
+ "CTX_USER_ID_KEY",
220
+ "CTX_TENANT_ID_KEY",
221
+ "CTX_AUTH_KEY",
222
+ "TIGRBL_AUTH_CONTEXT_ATTR",
223
+ "__SAFE_IDENT__",
224
+ ]
@@ -0,0 +1,29 @@
1
+ # tigrbl/v3/config/defaults.py
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, Mapping
5
+
6
+ # Canonical defaults used by config.resolver and runtime atoms.
7
+ # Keep these conservative; adapters/apps can override at any scope.
8
+ DEFAULTS: Mapping[str, Any] = {
9
+ # ── wire/out (response shaping) ────────────────────────────────────────────
10
+ "exclude_none": False, # drop null-valued keys in wire:dump
11
+ "omit_nulls": False, # alias; resolver normalizes both ways
12
+ "response_extras_overwrite": False, # allow extras to replace existing keys
13
+ "extras_overwrite": False, # alias; resolver normalizes both ways
14
+ # ── wire/in (request validation) ───────────────────────────────────────────
15
+ "reject_unknown_fields": False, # if True, unknown input keys cause 422
16
+ # ── refresh / hydration policy ────────────────────────────────────────────
17
+ "refresh_policy": "auto", # 'auto' | 'always' | 'never'
18
+ "refresh_after_write": None, # Optional[bool]; resolver maps → policy
19
+ # ── validation/docs policy buckets (deep-merged) ──────────────────────────
20
+ # Shape: dict[op][field] = bool (true means "required for inbound")
21
+ "required_policy": {},
22
+ # ── docs/openapi knobs (deep-merged) ──────────────────────────────────────
23
+ "openapi": {},
24
+ "docs": {},
25
+ # ── tracing (deep-merged) ─────────────────────────────────────────────────
26
+ "trace": {
27
+ "enabled": True,
28
+ },
29
+ }
@@ -0,0 +1,295 @@
1
+ # tigrbl/v3/config/resolver.py
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import asdict, is_dataclass
5
+ from types import MappingProxyType
6
+ from typing import Any, Dict, Iterable, Mapping, Optional
7
+
8
+ # Optional defaults module (kept tolerant if not present yet)
9
+ try:
10
+ from .defaults import DEFAULTS # expected: Mapping[str, Any]
11
+ except Exception: # pragma: no cover
12
+ DEFAULTS = {
13
+ # wire/out
14
+ "exclude_none": False,
15
+ "omit_nulls": False, # alias; normalized below
16
+ "response_extras_overwrite": False,
17
+ "extras_overwrite": False, # alias
18
+ # wire/in
19
+ "reject_unknown_fields": False,
20
+ # refresh
21
+ "refresh_policy": "auto", # 'auto' | 'always' | 'never'
22
+ "refresh_after_write": None, # Optional[bool] → normalized into refresh_policy
23
+ # validation/docs
24
+ "required_policy": {}, # dict[op][field] = bool
25
+ # misc buckets developers may use
26
+ "openapi": {},
27
+ "docs": {},
28
+ "trace": {"enabled": True},
29
+ }
30
+
31
+
32
+ # Keys that should be deep-merged (dict ← dict) instead of overridden.
33
+ _DEEP_KEYS = {
34
+ "required_policy",
35
+ "openapi",
36
+ "docs",
37
+ "trace",
38
+ "policies",
39
+ }
40
+
41
+
42
+ class CfgView:
43
+ """
44
+ Read-only attribute/dict view over a plain dict.
45
+ Unknown attributes return None (to play nicely with getattr(cfg, 'x', None)).
46
+ """
47
+
48
+ __slots__ = ("_data",)
49
+
50
+ def __init__(self, data: Mapping[str, Any]):
51
+ # freeze top-level mapping
52
+ self._data = MappingProxyType(dict(data))
53
+
54
+ def __getattr__(self, name: str) -> Any:
55
+ return self._data.get(name, None)
56
+
57
+ def __getitem__(self, key: str) -> Any:
58
+ return self._data[key]
59
+
60
+ def get(self, key: str, default: Any = None) -> Any:
61
+ return self._data.get(key, default)
62
+
63
+ def as_dict(self) -> Dict[str, Any]:
64
+ """Copy out a mutable dict (for diagnostics/serialization)."""
65
+ return dict(self._data)
66
+
67
+ def __repr__(self) -> str: # pragma: no cover
68
+ return f"CfgView({dict(self._data)!r})"
69
+
70
+
71
+ # ──────────────────────────────────────────────────────────────────────────────
72
+ # Public API
73
+ # ──────────────────────────────────────────────────────────────────────────────
74
+
75
+
76
+ def resolve_cfg(
77
+ *,
78
+ model: Any = None,
79
+ specs: Optional[Mapping[str, Any]] = None,
80
+ op: Optional[str] = None,
81
+ opspec: Any = None,
82
+ tabspec: Any = None,
83
+ apispec: Any = None,
84
+ appspec: Any = None,
85
+ overrides: Optional[Mapping[str, Any]] = None,
86
+ ) -> CfgView:
87
+ """
88
+ Merge configuration from multiple scopes with precedence:
89
+
90
+ opspec > colspecs > tabspec > apispec > appspec > defaults
91
+
92
+ The result is normalized and returned as a read-only CfgView suitable for ctx.cfg.
93
+
94
+ Notes:
95
+ - 'specs' is the {field -> ColumnSpec} map; any col-level '.cfg' dicts are merged.
96
+ - Non-dict or None layers are ignored.
97
+ - For a few known keys we support aliases and light normalization (see _normalize()).
98
+ """
99
+ layers: list[Mapping[str, Any]] = []
100
+
101
+ # 1) Base defaults (lowest precedence)
102
+ layers.append(_coerce_map(DEFAULTS))
103
+
104
+ # 2) App / API / Tab scopes
105
+ if appspec is not None:
106
+ layers.append(_extract_cfg(appspec))
107
+ if apispec is not None:
108
+ layers.append(_extract_cfg(apispec))
109
+ if tabspec is not None:
110
+ layers.append(_extract_cfg(tabspec))
111
+
112
+ # 3) Column-level aggregation (merged across all columns in stable order)
113
+ if specs:
114
+ col_cfg = _collect_col_cfg(specs)
115
+ if col_cfg:
116
+ layers.append(col_cfg)
117
+
118
+ # 4) Op-spec (highest declared spec layer)
119
+ if opspec is not None:
120
+ layers.append(_extract_cfg(opspec))
121
+
122
+ # 5) Per-request overrides (absolute highest precedence)
123
+ if overrides:
124
+ layers.append(_coerce_map(overrides))
125
+
126
+ # Merge with precedence (later wins), then normalize and freeze
127
+ merged = _merge_layers(layers)
128
+ merged = _normalize(merged, op=op)
129
+
130
+ return CfgView(merged)
131
+
132
+
133
+ # ──────────────────────────────────────────────────────────────────────────────
134
+ # Internals
135
+ # ──────────────────────────────────────────────────────────────────────────────
136
+
137
+
138
+ def _coerce_map(obj: Any) -> Mapping[str, Any]:
139
+ """Best-effort conversion of common config carriers to a plain mapping."""
140
+ if obj is None:
141
+ return {}
142
+ if isinstance(obj, Mapping):
143
+ return obj
144
+ # dataclass?
145
+ if is_dataclass(obj):
146
+ try:
147
+ # Drop keys with ``None`` values so they don't override defaults.
148
+ return {k: v for k, v in asdict(obj).items() if v is not None}
149
+ except Exception:
150
+ pass
151
+ # namespace-like with __dict__
152
+ d = getattr(obj, "__dict__", None)
153
+ if isinstance(d, dict):
154
+ return d
155
+ # pydantic v2 config-like
156
+ if hasattr(obj, "model_dump") and callable(getattr(obj, "model_dump")):
157
+ try:
158
+ return dict(obj.model_dump())
159
+ except Exception:
160
+ pass
161
+ # last resort: single 'cfg' attr if it's a mapping
162
+ cfg = getattr(obj, "cfg", None)
163
+ if isinstance(cfg, Mapping):
164
+ return cfg
165
+ return {}
166
+
167
+
168
+ def _extract_cfg(spec: Any) -> Mapping[str, Any]:
169
+ """
170
+ Pull a config mapping from a spec-like object.
171
+ Accepts: spec.cfg, spec.config, or the object itself if it's a mapping.
172
+ """
173
+ if isinstance(spec, Mapping):
174
+ return spec
175
+ for name in ("cfg", "config"):
176
+ val = getattr(spec, name, None)
177
+ if isinstance(val, Mapping):
178
+ return val
179
+ # dataclass or namespace
180
+ return _coerce_map(spec)
181
+
182
+
183
+ def _collect_col_cfg(specs: Mapping[str, Any]) -> Mapping[str, Any]:
184
+ """
185
+ Merge .cfg from ColumnSpec and its sub-specs (io/field/storage) across all columns.
186
+ Deep-merge dict values for keys in _DEEP_KEYS; override for scalars/others.
187
+ Later fields (lexicographic) win on conflicts to keep determinism.
188
+ """
189
+ acc: Dict[str, Any] = {}
190
+ for field in sorted(specs.keys()):
191
+ col = specs[field]
192
+ # Collect potential cfg dictionaries from multiple places on ColumnSpec
193
+ for obj in (
194
+ col,
195
+ getattr(col, "io", None),
196
+ getattr(col, "field", None),
197
+ getattr(col, "storage", None),
198
+ ):
199
+ mapping = _extract_cfg(obj)
200
+ if mapping:
201
+ acc = _merge_layers([acc, mapping])
202
+ return acc
203
+
204
+
205
+ def _merge_layers(layers: Iterable[Mapping[str, Any]]) -> Dict[str, Any]:
206
+ """
207
+ Precedence-aware merge: later layers override earlier ones.
208
+ Dict values are shallow-merged except for keys in _DEEP_KEYS (deep merge).
209
+ """
210
+ result: Dict[str, Any] = {}
211
+ for layer in layers:
212
+ if not isinstance(layer, Mapping):
213
+ continue
214
+ for k, v in layer.items():
215
+ if (
216
+ k in _DEEP_KEYS
217
+ and isinstance(result.get(k), Mapping)
218
+ and isinstance(v, Mapping)
219
+ ):
220
+ result[k] = _deep_merge_dicts(result[k], v)
221
+ else:
222
+ result[k] = v
223
+ return result
224
+
225
+
226
+ def _deep_merge_dicts(a: Mapping[str, Any], b: Mapping[str, Any]) -> Dict[str, Any]:
227
+ """
228
+ Deep merge two dicts; values in 'b' override 'a'. Only recurses on dicts.
229
+ Lists/sets/tuples are overridden (not concatenated) to remain predictable.
230
+ """
231
+ out: Dict[str, Any] = dict(a)
232
+ for k, v in b.items():
233
+ av = out.get(k)
234
+ if isinstance(av, Mapping) and isinstance(v, Mapping):
235
+ out[k] = _deep_merge_dicts(av, v)
236
+ else:
237
+ out[k] = v
238
+ return out
239
+
240
+
241
+ def _normalize(cfg: Mapping[str, Any], *, op: Optional[str]) -> Dict[str, Any]:
242
+ """
243
+ Produce a normalized config dict with aliases resolved and policy rules applied.
244
+ """
245
+ d = dict(cfg) # copy
246
+
247
+ # 1) Refresh policy normalization
248
+ # - If refresh_after_write is explicitly True/False, that wins.
249
+ # - Otherwise ensure refresh_policy has a sane default.
250
+ raw_after = d.get("refresh_after_write", None)
251
+ if isinstance(raw_after, bool):
252
+ d["refresh_policy"] = "always" if raw_after else "never"
253
+ else:
254
+ pol = d.get("refresh_policy", None)
255
+ if pol not in {"auto", "always", "never"}:
256
+ d["refresh_policy"] = "auto"
257
+
258
+ # 2) Alias handling for omit nulls and extras overwrite
259
+ if "exclude_none" not in d and isinstance(d.get("omit_nulls"), bool):
260
+ d["exclude_none"] = bool(d["omit_nulls"])
261
+ if "omit_nulls" not in d and isinstance(d.get("exclude_none"), bool):
262
+ d["omit_nulls"] = bool(d["exclude_none"])
263
+
264
+ if "response_extras_overwrite" not in d and isinstance(
265
+ d.get("extras_overwrite"), bool
266
+ ):
267
+ d["response_extras_overwrite"] = bool(d["extras_overwrite"])
268
+ if "extras_overwrite" not in d and isinstance(
269
+ d.get("response_extras_overwrite"), bool
270
+ ):
271
+ d["extras_overwrite"] = bool(d["response_extras_overwrite"])
272
+
273
+ # 3) required_policy structure sanity: dict[op][field] = bool
274
+ rp = d.get("required_policy")
275
+ if not isinstance(rp, Mapping):
276
+ d["required_policy"] = {}
277
+ else:
278
+ # ensure nested dicts & booleans; ignore malformed entries
279
+ fixed: Dict[str, Dict[str, bool]] = {}
280
+ for op_name, per_field in rp.items():
281
+ if not isinstance(per_field, Mapping):
282
+ continue
283
+ fixed[str(op_name)] = {
284
+ str(f): bool(v)
285
+ for f, v in per_field.items()
286
+ if isinstance(v, (bool, int))
287
+ }
288
+ d["required_policy"] = fixed
289
+
290
+ # 4) Optional op-specific view (pre-resolved convenience)
291
+ if op:
292
+ per_op = d["required_policy"].get(op, {})
293
+ d.setdefault("_required_for_op", per_op)
294
+
295
+ return d
@@ -0,0 +1,47 @@
1
+ # tigrbl/v3/core/__init__.py
2
+ """
3
+ Tigrbl v3 – Core operations.
4
+
5
+ Re-exports the canonical CRUD bodies implemented in `.crud`.
6
+
7
+ Notes:
8
+ - These functions are **flush-only**. They never call `db.commit()`.
9
+ - Final commits are driven by the runtime executor's `END_TX` phase.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from .crud import (
15
+ create,
16
+ read,
17
+ update,
18
+ replace,
19
+ merge,
20
+ delete,
21
+ list as _list, # avoid shadowing built-in, then re-export as `list`
22
+ clear,
23
+ bulk_create,
24
+ bulk_update,
25
+ bulk_replace,
26
+ bulk_merge,
27
+ bulk_delete,
28
+ )
29
+
30
+ # Public alias named exactly `list` to preserve API surface
31
+ list = _list # noqa: A001 - intentional shadow of built-in for public API
32
+
33
+ __all__ = [
34
+ "create",
35
+ "read",
36
+ "update",
37
+ "replace",
38
+ "merge",
39
+ "delete",
40
+ "list",
41
+ "clear",
42
+ "bulk_create",
43
+ "bulk_update",
44
+ "bulk_replace",
45
+ "bulk_merge",
46
+ "bulk_delete",
47
+ ]
@@ -0,0 +1,36 @@
1
+ from .ops import (
2
+ create,
3
+ read,
4
+ update,
5
+ replace,
6
+ merge,
7
+ delete,
8
+ list as _list,
9
+ clear,
10
+ )
11
+ from .bulk import (
12
+ bulk_create,
13
+ bulk_update,
14
+ bulk_replace,
15
+ bulk_merge,
16
+ bulk_delete,
17
+ )
18
+
19
+ # Public alias named exactly `list` to preserve API surface
20
+ list = _list # noqa: A001 - intentional shadow of built-in for public API
21
+
22
+ __all__ = [
23
+ "create",
24
+ "read",
25
+ "update",
26
+ "replace",
27
+ "merge",
28
+ "delete",
29
+ "list",
30
+ "clear",
31
+ "bulk_create",
32
+ "bulk_update",
33
+ "bulk_replace",
34
+ "bulk_merge",
35
+ "bulk_delete",
36
+ ]