tigrbl 0.0.1.dev1__py3-none-any.whl → 0.3.0__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 (269) 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 +97 -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 +291 -0
  9. tigrbl/app/__init__.py +0 -0
  10. tigrbl/app/_app.py +86 -0
  11. tigrbl/app/_model_registry.py +41 -0
  12. tigrbl/app/app_spec.py +42 -0
  13. tigrbl/app/mro_collect.py +67 -0
  14. tigrbl/app/shortcuts.py +65 -0
  15. tigrbl/app/tigrbl_app.py +319 -0
  16. tigrbl/bindings/__init__.py +73 -0
  17. tigrbl/bindings/api/__init__.py +12 -0
  18. tigrbl/bindings/api/common.py +109 -0
  19. tigrbl/bindings/api/include.py +256 -0
  20. tigrbl/bindings/api/resource_proxy.py +149 -0
  21. tigrbl/bindings/api/rpc.py +111 -0
  22. tigrbl/bindings/columns.py +49 -0
  23. tigrbl/bindings/handlers/__init__.py +11 -0
  24. tigrbl/bindings/handlers/builder.py +119 -0
  25. tigrbl/bindings/handlers/ctx.py +74 -0
  26. tigrbl/bindings/handlers/identifiers.py +228 -0
  27. tigrbl/bindings/handlers/namespaces.py +51 -0
  28. tigrbl/bindings/handlers/steps.py +276 -0
  29. tigrbl/bindings/hooks.py +311 -0
  30. tigrbl/bindings/model.py +194 -0
  31. tigrbl/bindings/model_helpers.py +139 -0
  32. tigrbl/bindings/model_registry.py +77 -0
  33. tigrbl/bindings/rest/__init__.py +7 -0
  34. tigrbl/bindings/rest/attach.py +34 -0
  35. tigrbl/bindings/rest/collection.py +286 -0
  36. tigrbl/bindings/rest/common.py +120 -0
  37. tigrbl/bindings/rest/fastapi.py +76 -0
  38. tigrbl/bindings/rest/helpers.py +119 -0
  39. tigrbl/bindings/rest/io.py +317 -0
  40. tigrbl/bindings/rest/io_headers.py +49 -0
  41. tigrbl/bindings/rest/member.py +386 -0
  42. tigrbl/bindings/rest/router.py +296 -0
  43. tigrbl/bindings/rest/routing.py +153 -0
  44. tigrbl/bindings/rpc.py +364 -0
  45. tigrbl/bindings/schemas/__init__.py +11 -0
  46. tigrbl/bindings/schemas/builder.py +348 -0
  47. tigrbl/bindings/schemas/defaults.py +260 -0
  48. tigrbl/bindings/schemas/utils.py +193 -0
  49. tigrbl/column/README.md +62 -0
  50. tigrbl/column/__init__.py +72 -0
  51. tigrbl/column/_column.py +96 -0
  52. tigrbl/column/column_spec.py +40 -0
  53. tigrbl/column/field_spec.py +31 -0
  54. tigrbl/column/infer/__init__.py +25 -0
  55. tigrbl/column/infer/core.py +92 -0
  56. tigrbl/column/infer/jsonhints.py +44 -0
  57. tigrbl/column/infer/planning.py +133 -0
  58. tigrbl/column/infer/types.py +102 -0
  59. tigrbl/column/infer/utils.py +59 -0
  60. tigrbl/column/io_spec.py +136 -0
  61. tigrbl/column/mro_collect.py +59 -0
  62. tigrbl/column/shortcuts.py +89 -0
  63. tigrbl/column/storage_spec.py +65 -0
  64. tigrbl/config/__init__.py +19 -0
  65. tigrbl/config/constants.py +224 -0
  66. tigrbl/config/defaults.py +29 -0
  67. tigrbl/config/resolver.py +295 -0
  68. tigrbl/core/__init__.py +47 -0
  69. tigrbl/core/crud/__init__.py +36 -0
  70. tigrbl/core/crud/bulk.py +168 -0
  71. tigrbl/core/crud/helpers/__init__.py +76 -0
  72. tigrbl/core/crud/helpers/db.py +92 -0
  73. tigrbl/core/crud/helpers/enum.py +86 -0
  74. tigrbl/core/crud/helpers/filters.py +162 -0
  75. tigrbl/core/crud/helpers/model.py +123 -0
  76. tigrbl/core/crud/helpers/normalize.py +99 -0
  77. tigrbl/core/crud/ops.py +235 -0
  78. tigrbl/ddl/__init__.py +344 -0
  79. tigrbl/decorators.py +17 -0
  80. tigrbl/deps/__init__.py +20 -0
  81. tigrbl/deps/fastapi.py +45 -0
  82. tigrbl/deps/favicon.svg +4 -0
  83. tigrbl/deps/jinja.py +27 -0
  84. tigrbl/deps/pydantic.py +10 -0
  85. tigrbl/deps/sqlalchemy.py +94 -0
  86. tigrbl/deps/starlette.py +36 -0
  87. tigrbl/engine/__init__.py +45 -0
  88. tigrbl/engine/_engine.py +144 -0
  89. tigrbl/engine/bind.py +33 -0
  90. tigrbl/engine/builders.py +236 -0
  91. tigrbl/engine/capabilities.py +29 -0
  92. tigrbl/engine/collect.py +111 -0
  93. tigrbl/engine/decorators.py +110 -0
  94. tigrbl/engine/docs/PLUGINS.md +49 -0
  95. tigrbl/engine/engine_spec.py +355 -0
  96. tigrbl/engine/plugins.py +52 -0
  97. tigrbl/engine/registry.py +36 -0
  98. tigrbl/engine/resolver.py +224 -0
  99. tigrbl/engine/shortcuts.py +216 -0
  100. tigrbl/hook/__init__.py +21 -0
  101. tigrbl/hook/_hook.py +22 -0
  102. tigrbl/hook/decorators.py +28 -0
  103. tigrbl/hook/hook_spec.py +24 -0
  104. tigrbl/hook/mro_collect.py +98 -0
  105. tigrbl/hook/shortcuts.py +44 -0
  106. tigrbl/hook/types.py +76 -0
  107. tigrbl/op/__init__.py +50 -0
  108. tigrbl/op/_op.py +31 -0
  109. tigrbl/op/canonical.py +31 -0
  110. tigrbl/op/collect.py +11 -0
  111. tigrbl/op/decorators.py +238 -0
  112. tigrbl/op/model_registry.py +301 -0
  113. tigrbl/op/mro_collect.py +99 -0
  114. tigrbl/op/resolver.py +216 -0
  115. tigrbl/op/types.py +136 -0
  116. tigrbl/orm/__init__.py +1 -0
  117. tigrbl/orm/mixins/_RowBound.py +83 -0
  118. tigrbl/orm/mixins/__init__.py +95 -0
  119. tigrbl/orm/mixins/bootstrappable.py +113 -0
  120. tigrbl/orm/mixins/bound.py +47 -0
  121. tigrbl/orm/mixins/edges.py +40 -0
  122. tigrbl/orm/mixins/fields.py +165 -0
  123. tigrbl/orm/mixins/hierarchy.py +54 -0
  124. tigrbl/orm/mixins/key_digest.py +44 -0
  125. tigrbl/orm/mixins/lifecycle.py +115 -0
  126. tigrbl/orm/mixins/locks.py +51 -0
  127. tigrbl/orm/mixins/markers.py +16 -0
  128. tigrbl/orm/mixins/operations.py +57 -0
  129. tigrbl/orm/mixins/ownable.py +337 -0
  130. tigrbl/orm/mixins/principals.py +98 -0
  131. tigrbl/orm/mixins/tenant_bound.py +301 -0
  132. tigrbl/orm/mixins/upsertable.py +118 -0
  133. tigrbl/orm/mixins/utils.py +49 -0
  134. tigrbl/orm/tables/__init__.py +72 -0
  135. tigrbl/orm/tables/_base.py +8 -0
  136. tigrbl/orm/tables/audit.py +56 -0
  137. tigrbl/orm/tables/client.py +25 -0
  138. tigrbl/orm/tables/group.py +29 -0
  139. tigrbl/orm/tables/org.py +30 -0
  140. tigrbl/orm/tables/rbac.py +76 -0
  141. tigrbl/orm/tables/status.py +106 -0
  142. tigrbl/orm/tables/tenant.py +22 -0
  143. tigrbl/orm/tables/user.py +39 -0
  144. tigrbl/response/README.md +34 -0
  145. tigrbl/response/__init__.py +33 -0
  146. tigrbl/response/bind.py +12 -0
  147. tigrbl/response/decorators.py +37 -0
  148. tigrbl/response/resolver.py +83 -0
  149. tigrbl/response/shortcuts.py +171 -0
  150. tigrbl/response/types.py +49 -0
  151. tigrbl/rest/__init__.py +27 -0
  152. tigrbl/runtime/README.md +129 -0
  153. tigrbl/runtime/__init__.py +20 -0
  154. tigrbl/runtime/atoms/__init__.py +102 -0
  155. tigrbl/runtime/atoms/emit/__init__.py +42 -0
  156. tigrbl/runtime/atoms/emit/paired_post.py +158 -0
  157. tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
  158. tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
  159. tigrbl/runtime/atoms/out/__init__.py +38 -0
  160. tigrbl/runtime/atoms/out/masking.py +135 -0
  161. tigrbl/runtime/atoms/refresh/__init__.py +38 -0
  162. tigrbl/runtime/atoms/refresh/demand.py +130 -0
  163. tigrbl/runtime/atoms/resolve/__init__.py +40 -0
  164. tigrbl/runtime/atoms/resolve/assemble.py +167 -0
  165. tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
  166. tigrbl/runtime/atoms/response/__init__.py +19 -0
  167. tigrbl/runtime/atoms/response/headers_from_payload.py +57 -0
  168. tigrbl/runtime/atoms/response/negotiate.py +30 -0
  169. tigrbl/runtime/atoms/response/negotiation.py +43 -0
  170. tigrbl/runtime/atoms/response/render.py +36 -0
  171. tigrbl/runtime/atoms/response/renderer.py +116 -0
  172. tigrbl/runtime/atoms/response/template.py +44 -0
  173. tigrbl/runtime/atoms/response/templates.py +88 -0
  174. tigrbl/runtime/atoms/schema/__init__.py +40 -0
  175. tigrbl/runtime/atoms/schema/collect_in.py +21 -0
  176. tigrbl/runtime/atoms/schema/collect_out.py +21 -0
  177. tigrbl/runtime/atoms/storage/__init__.py +38 -0
  178. tigrbl/runtime/atoms/storage/to_stored.py +167 -0
  179. tigrbl/runtime/atoms/wire/__init__.py +45 -0
  180. tigrbl/runtime/atoms/wire/build_in.py +166 -0
  181. tigrbl/runtime/atoms/wire/build_out.py +87 -0
  182. tigrbl/runtime/atoms/wire/dump.py +206 -0
  183. tigrbl/runtime/atoms/wire/validate_in.py +227 -0
  184. tigrbl/runtime/context.py +206 -0
  185. tigrbl/runtime/errors/__init__.py +61 -0
  186. tigrbl/runtime/errors/converters.py +214 -0
  187. tigrbl/runtime/errors/exceptions.py +124 -0
  188. tigrbl/runtime/errors/mappings.py +71 -0
  189. tigrbl/runtime/errors/utils.py +150 -0
  190. tigrbl/runtime/events.py +209 -0
  191. tigrbl/runtime/executor/__init__.py +6 -0
  192. tigrbl/runtime/executor/guards.py +132 -0
  193. tigrbl/runtime/executor/helpers.py +88 -0
  194. tigrbl/runtime/executor/invoke.py +150 -0
  195. tigrbl/runtime/executor/types.py +84 -0
  196. tigrbl/runtime/kernel.py +644 -0
  197. tigrbl/runtime/labels.py +353 -0
  198. tigrbl/runtime/opview.py +89 -0
  199. tigrbl/runtime/ordering.py +256 -0
  200. tigrbl/runtime/system.py +279 -0
  201. tigrbl/runtime/trace.py +330 -0
  202. tigrbl/schema/__init__.py +38 -0
  203. tigrbl/schema/_schema.py +27 -0
  204. tigrbl/schema/builder/__init__.py +17 -0
  205. tigrbl/schema/builder/build_schema.py +209 -0
  206. tigrbl/schema/builder/cache.py +24 -0
  207. tigrbl/schema/builder/compat.py +16 -0
  208. tigrbl/schema/builder/extras.py +85 -0
  209. tigrbl/schema/builder/helpers.py +51 -0
  210. tigrbl/schema/builder/list_params.py +117 -0
  211. tigrbl/schema/builder/strip_parent_fields.py +70 -0
  212. tigrbl/schema/collect.py +79 -0
  213. tigrbl/schema/decorators.py +68 -0
  214. tigrbl/schema/get_schema.py +86 -0
  215. tigrbl/schema/schema_spec.py +20 -0
  216. tigrbl/schema/shortcuts.py +42 -0
  217. tigrbl/schema/types.py +34 -0
  218. tigrbl/schema/utils.py +143 -0
  219. tigrbl/session/README.md +14 -0
  220. tigrbl/session/__init__.py +28 -0
  221. tigrbl/session/abc.py +76 -0
  222. tigrbl/session/base.py +151 -0
  223. tigrbl/session/decorators.py +43 -0
  224. tigrbl/session/default.py +118 -0
  225. tigrbl/session/shortcuts.py +50 -0
  226. tigrbl/session/spec.py +112 -0
  227. tigrbl/shortcuts.py +22 -0
  228. tigrbl/specs.py +44 -0
  229. tigrbl/system/__init__.py +13 -0
  230. tigrbl/system/diagnostics/__init__.py +24 -0
  231. tigrbl/system/diagnostics/compat.py +31 -0
  232. tigrbl/system/diagnostics/healthz.py +41 -0
  233. tigrbl/system/diagnostics/hookz.py +51 -0
  234. tigrbl/system/diagnostics/kernelz.py +20 -0
  235. tigrbl/system/diagnostics/methodz.py +43 -0
  236. tigrbl/system/diagnostics/router.py +73 -0
  237. tigrbl/system/diagnostics/utils.py +43 -0
  238. tigrbl/system/uvicorn.py +60 -0
  239. tigrbl/table/__init__.py +9 -0
  240. tigrbl/table/_base.py +260 -0
  241. tigrbl/table/_table.py +54 -0
  242. tigrbl/table/mro_collect.py +69 -0
  243. tigrbl/table/shortcuts.py +57 -0
  244. tigrbl/table/table_spec.py +28 -0
  245. tigrbl/transport/__init__.py +74 -0
  246. tigrbl/transport/jsonrpc/__init__.py +19 -0
  247. tigrbl/transport/jsonrpc/dispatcher.py +352 -0
  248. tigrbl/transport/jsonrpc/helpers.py +115 -0
  249. tigrbl/transport/jsonrpc/models.py +41 -0
  250. tigrbl/transport/rest/__init__.py +25 -0
  251. tigrbl/transport/rest/aggregator.py +132 -0
  252. tigrbl/types/__init__.py +170 -0
  253. tigrbl/types/allow_anon_provider.py +19 -0
  254. tigrbl/types/authn_abc.py +30 -0
  255. tigrbl/types/nested_path_provider.py +22 -0
  256. tigrbl/types/op.py +35 -0
  257. tigrbl/types/op_config_provider.py +17 -0
  258. tigrbl/types/op_verb_alias_provider.py +33 -0
  259. tigrbl/types/request_extras_provider.py +22 -0
  260. tigrbl/types/response_extras_provider.py +22 -0
  261. tigrbl/types/table_config_provider.py +13 -0
  262. tigrbl/types/uuid.py +55 -0
  263. tigrbl-0.3.0.dist-info/METADATA +516 -0
  264. tigrbl-0.3.0.dist-info/RECORD +266 -0
  265. {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dist-info}/WHEEL +1 -1
  266. tigrbl-0.3.0.dist-info/licenses/LICENSE +201 -0
  267. tigrbl/ExampleAgent.py +0 -1
  268. tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
  269. tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
tigrbl/hook/types.py ADDED
@@ -0,0 +1,76 @@
1
+ """Hook type definitions for Tigrbl v3."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import Enum
6
+ from typing import Any, Awaitable, Callable, Literal, Tuple
7
+
8
+ # ---------------------------------------------------------------------------
9
+ # Runtime phases (align with runtime/executor.py)
10
+ # ---------------------------------------------------------------------------
11
+
12
+
13
+ class PHASE(str, Enum):
14
+ PRE_TX_BEGIN = "PRE_TX_BEGIN"
15
+ START_TX = "START_TX"
16
+ PRE_HANDLER = "PRE_HANDLER"
17
+ HANDLER = "HANDLER"
18
+ POST_HANDLER = "POST_HANDLER"
19
+ PRE_COMMIT = "PRE_COMMIT"
20
+ END_TX = "END_TX"
21
+ POST_COMMIT = "POST_COMMIT"
22
+ POST_RESPONSE = "POST_RESPONSE"
23
+ ON_ERROR = "ON_ERROR"
24
+ ON_PRE_TX_BEGIN_ERROR = "ON_PRE_TX_BEGIN_ERROR"
25
+ ON_START_TX_ERROR = "ON_START_TX_ERROR"
26
+ ON_PRE_HANDLER_ERROR = "ON_PRE_HANDLER_ERROR"
27
+ ON_HANDLER_ERROR = "ON_HANDLER_ERROR"
28
+ ON_POST_HANDLER_ERROR = "ON_POST_HANDLER_ERROR"
29
+ ON_PRE_COMMIT_ERROR = "ON_PRE_COMMIT_ERROR"
30
+ ON_END_TX_ERROR = "ON_END_TX_ERROR"
31
+ ON_POST_COMMIT_ERROR = "ON_POST_COMMIT_ERROR"
32
+ ON_POST_RESPONSE_ERROR = "ON_POST_RESPONSE_ERROR"
33
+ ON_ROLLBACK = "ON_ROLLBACK"
34
+
35
+
36
+ HookPhase = Literal[
37
+ "PRE_TX_BEGIN",
38
+ "START_TX",
39
+ "PRE_HANDLER",
40
+ "HANDLER",
41
+ "POST_HANDLER",
42
+ "PRE_COMMIT",
43
+ "END_TX",
44
+ "POST_COMMIT",
45
+ "POST_RESPONSE",
46
+ "ON_ERROR",
47
+ "ON_PRE_TX_BEGIN_ERROR",
48
+ "ON_START_TX_ERROR",
49
+ "ON_PRE_HANDLER_ERROR",
50
+ "ON_HANDLER_ERROR",
51
+ "ON_POST_HANDLER_ERROR",
52
+ "ON_PRE_COMMIT_ERROR",
53
+ "ON_END_TX_ERROR",
54
+ "ON_POST_COMMIT_ERROR",
55
+ "ON_POST_RESPONSE_ERROR",
56
+ "ON_ROLLBACK",
57
+ ]
58
+
59
+ PHASES: Tuple[HookPhase, ...] = tuple(p.value for p in PHASE)
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Hook function types
63
+ # ---------------------------------------------------------------------------
64
+
65
+ Ctx = Any
66
+ StepFn = Callable[[Ctx], Awaitable[Any] | Any]
67
+ HookPredicate = Callable[[Any], bool]
68
+
69
+ __all__ = [
70
+ "PHASE",
71
+ "HookPhase",
72
+ "PHASES",
73
+ "Ctx",
74
+ "StepFn",
75
+ "HookPredicate",
76
+ ]
tigrbl/op/__init__.py ADDED
@@ -0,0 +1,50 @@
1
+ """Tigrbl v3 op package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ._op import Op
6
+ from .types import (
7
+ OpSpec,
8
+ OpHook,
9
+ TargetOp,
10
+ Arity,
11
+ PersistPolicy,
12
+ PHASE,
13
+ HookPhase,
14
+ PHASES,
15
+ VerbAliasPolicy,
16
+ )
17
+ from .resolver import resolve
18
+ from .collect import apply_alias
19
+ from .model_registry import (
20
+ OpspecRegistry,
21
+ get_registry,
22
+ register_ops,
23
+ get_registered_ops,
24
+ clear_registry,
25
+ )
26
+ from .decorators import alias, alias_ctx, op_alias, op_ctx
27
+
28
+ __all__ = [
29
+ "Op",
30
+ "OpSpec",
31
+ "OpHook",
32
+ "TargetOp",
33
+ "Arity",
34
+ "PersistPolicy",
35
+ "PHASE",
36
+ "HookPhase",
37
+ "PHASES",
38
+ "VerbAliasPolicy",
39
+ "resolve",
40
+ "apply_alias",
41
+ "OpspecRegistry",
42
+ "get_registry",
43
+ "register_ops",
44
+ "get_registered_ops",
45
+ "clear_registry",
46
+ "alias",
47
+ "alias_ctx",
48
+ "op_alias",
49
+ "op_ctx",
50
+ ]
tigrbl/op/_op.py ADDED
@@ -0,0 +1,31 @@
1
+ # tigrbl/tigrbl/v3/op/_op.py
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import replace
5
+ from typing import Any
6
+
7
+ from .types import OpSpec
8
+
9
+
10
+ class Op(OpSpec):
11
+ """Declarative operation descriptor with optional engine binding."""
12
+
13
+ __slots__ = ()
14
+
15
+ def __set_name__(self, owner: type, name: str) -> None: # noqa: D401
16
+ spec = self
17
+ alias = self.alias or name
18
+ if self.table is not owner or self.alias != alias:
19
+ spec = replace(self, table=owner, alias=alias)
20
+ ops = list(getattr(owner, "__tigrbl_ops__", ()) or [])
21
+ ops.append(spec)
22
+ owner.__tigrbl_ops__ = tuple(ops)
23
+
24
+ def install_engines(
25
+ self, *, api: Any | None = None, model: type | None = None
26
+ ) -> None:
27
+ from ..engine import install_from_objects
28
+
29
+ m = model if model is not None else self.table
30
+ if m is not None:
31
+ install_from_objects(api=api, models=[m])
tigrbl/op/canonical.py ADDED
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Type
4
+
5
+ from ..config.constants import (
6
+ TIGRBL_DEFAULTS_EXCLUDE_ATTR,
7
+ TIGRBL_DEFAULTS_INCLUDE_ATTR,
8
+ TIGRBL_DEFAULTS_MODE_ATTR,
9
+ )
10
+
11
+ DEFAULT_CANON_VERBS = {
12
+ "create",
13
+ "read",
14
+ "update",
15
+ "replace",
16
+ "delete",
17
+ "list",
18
+ "clear",
19
+ }
20
+
21
+
22
+ def should_wire_canonical(table: Type, op: str) -> bool:
23
+ mode = getattr(table, TIGRBL_DEFAULTS_MODE_ATTR, "all")
24
+ inc = set(getattr(table, TIGRBL_DEFAULTS_INCLUDE_ATTR, set()))
25
+ exc = set(getattr(table, TIGRBL_DEFAULTS_EXCLUDE_ATTR, set()))
26
+ if mode == "none":
27
+ return False
28
+ if mode == "some":
29
+ return op in inc
30
+ allowed = (DEFAULT_CANON_VERBS | inc) - exc
31
+ return op in allowed # mode == "all"
tigrbl/op/collect.py ADDED
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict
4
+
5
+
6
+ def apply_alias(verb: str, alias_map: Dict[str, str]) -> str:
7
+ """Resolve canonical verb → alias (falls back to verb)."""
8
+ return alias_map.get(verb, verb)
9
+
10
+
11
+ __all__ = ["apply_alias"]
@@ -0,0 +1,238 @@
1
+ """Operation-related decorators for Tigrbl v3."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from dataclasses import dataclass
7
+ from typing import Any, Callable, Iterable, Optional, Sequence, Union
8
+
9
+ from .types import OpSpec, Arity, TargetOp, PersistPolicy
10
+ from ..schema.types import SchemaArg
11
+
12
+
13
+ # ---------------------------------------------------------------------------
14
+ # Utilities
15
+ # ---------------------------------------------------------------------------
16
+
17
+
18
+ def _unwrap(obj: Any) -> Callable[..., Any]:
19
+ """Get underlying function for (class|static)method; else return obj."""
20
+ if isinstance(obj, (classmethod, staticmethod)):
21
+ return obj.__func__ # type: ignore[attr-defined]
22
+ return obj
23
+
24
+
25
+ def _ensure_cm(func: Any) -> Any:
26
+ """Ensure method is a classmethod so it receives (cls, ctx)."""
27
+ if isinstance(func, (classmethod, staticmethod)):
28
+ return func
29
+ return classmethod(func)
30
+
31
+
32
+ def _maybe_await(v):
33
+ if inspect.isawaitable(v):
34
+ return v
35
+
36
+ async def _done():
37
+ return v
38
+
39
+ return _done()
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # alias_ctx with optional rich overrides
44
+ # ---------------------------------------------------------------------------
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class AliasDecl:
49
+ alias: str
50
+ # Optional overrides (lazy-capable schema args are fine; resolved later)
51
+ request_schema: Optional[SchemaArg] = None
52
+ response_schema: Optional[SchemaArg] = None
53
+ persist: Optional[PersistPolicy] = None
54
+ arity: Optional[Arity] = None
55
+ rest: Optional[bool] = None
56
+
57
+
58
+ def alias(name: str, **kw) -> AliasDecl:
59
+ """Convenience helper: alias('get', response_schema=..., rest=False)."""
60
+ return AliasDecl(alias=name, **kw)
61
+
62
+
63
+ def alias_ctx(**verb_to_alias_or_decl: Union[str, AliasDecl]):
64
+ """Class decorator mapping canonical verbs → aliases with optional overrides."""
65
+
66
+ def deco(cls: type):
67
+ amap = dict(getattr(cls, "__tigrbl_aliases__", {}) or {})
68
+ overrides = dict(getattr(cls, "__tigrbl_alias_overrides__", {}) or {})
69
+
70
+ for canon, value in verb_to_alias_or_decl.items():
71
+ if isinstance(value, AliasDecl):
72
+ amap[canon] = value.alias
73
+ overrides[canon] = {
74
+ "request_schema": value.request_schema,
75
+ "response_schema": value.response_schema,
76
+ "persist": value.persist,
77
+ "arity": value.arity,
78
+ "rest": value.rest,
79
+ }
80
+ elif isinstance(value, str):
81
+ amap[canon] = value
82
+ else:
83
+ raise TypeError(
84
+ f"alias_ctx[{canon}] must be str or AliasDecl, got {type(value)}"
85
+ )
86
+
87
+ setattr(cls, "__tigrbl_aliases__", amap)
88
+ setattr(cls, "__tigrbl_alias_overrides__", overrides)
89
+ try: # clear cached alias maps so late-applied decorators take effect
90
+ from .mro_collect import mro_alias_map_for
91
+
92
+ mro_alias_map_for.cache_clear()
93
+ except Exception: # pragma: no cover - best effort
94
+ pass
95
+ return cls
96
+
97
+ return deco
98
+
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # op_alias (class decorator): attach an OpSpec alias to the model
102
+ # ---------------------------------------------------------------------------
103
+
104
+
105
+ def op_alias(
106
+ *,
107
+ alias: str,
108
+ target: TargetOp,
109
+ arity: Arity | None = None,
110
+ persist: PersistPolicy = "default",
111
+ request_model: SchemaArg | None = None,
112
+ response_model: SchemaArg | None = None,
113
+ http_methods: Sequence[str] | None = None,
114
+ tags: Sequence[str] | None = None,
115
+ rbac_guard_op: TargetOp | None = None,
116
+ ):
117
+ """Class decorator to declare an alias for an operation."""
118
+
119
+ def deco(table_cls: type):
120
+ ops = list(getattr(table_cls, "__tigrbl_ops__", ()))
121
+ spec = OpSpec(
122
+ alias=alias,
123
+ target=target,
124
+ table=table_cls,
125
+ arity=arity or _infer_arity(target),
126
+ persist=_normalize_persist(persist),
127
+ request_model=request_model,
128
+ response_model=response_model,
129
+ http_methods=tuple(http_methods) if http_methods else None,
130
+ tags=tuple(tags or ()),
131
+ rbac_guard_op=rbac_guard_op,
132
+ )
133
+ ops.append(spec)
134
+ table_cls.__tigrbl_ops__ = tuple(ops)
135
+ return table_cls
136
+
137
+ return deco
138
+
139
+
140
+ # ---------------------------------------------------------------------------
141
+ # op_ctx (single path: target + arity) with schema overrides
142
+ # ---------------------------------------------------------------------------
143
+
144
+
145
+ @dataclass
146
+ class _OpDecl:
147
+ alias: Optional[str]
148
+ target: Optional[TargetOp]
149
+ arity: Optional[Arity]
150
+ rest: Optional[bool]
151
+ request_schema: Optional[SchemaArg]
152
+ response_schema: Optional[SchemaArg]
153
+ persist: Optional[PersistPolicy]
154
+ status_code: Optional[int]
155
+
156
+
157
+ def op_ctx(
158
+ *,
159
+ bind: Any | Iterable[Any] | None = None,
160
+ alias: Optional[str] = None,
161
+ target: Optional[TargetOp] = None,
162
+ arity: Optional[Arity] = None,
163
+ rest: Optional[bool] = None,
164
+ request_schema: Optional[SchemaArg] = None,
165
+ response_schema: Optional[SchemaArg] = None,
166
+ persist: Optional[PersistPolicy] = None,
167
+ status_code: Optional[int] = None,
168
+ ):
169
+ """Declare a ctx-only operation whose body is `(cls, ctx)`."""
170
+
171
+ def deco(fn):
172
+ cm = _ensure_cm(fn)
173
+ f = _unwrap(cm)
174
+ f.__tigrbl_ctx_only__ = True
175
+ f.__tigrbl_op_decl__ = _OpDecl(
176
+ alias=alias,
177
+ target=target,
178
+ arity=arity,
179
+ rest=rest,
180
+ request_schema=request_schema,
181
+ response_schema=response_schema,
182
+ persist=persist,
183
+ status_code=status_code,
184
+ )
185
+
186
+ if bind is not None:
187
+ targets = (
188
+ bind
189
+ if isinstance(bind, Iterable) and not isinstance(bind, (str, bytes))
190
+ else [bind]
191
+ )
192
+ for obj in targets:
193
+ setattr(obj, f.__name__, cm)
194
+
195
+ return cm
196
+
197
+ return deco
198
+
199
+
200
+ # ---------------------------------------------------------------------------
201
+ # Collection helpers
202
+ # ---------------------------------------------------------------------------
203
+
204
+ _COLLECTION_VERBS = {
205
+ "create",
206
+ "list",
207
+ "bulk_create",
208
+ "bulk_update",
209
+ "bulk_replace",
210
+ "bulk_merge",
211
+ "bulk_delete",
212
+ "clear",
213
+ "merge",
214
+ }
215
+
216
+
217
+ def _infer_arity(target: str) -> str:
218
+ return "collection" if target in _COLLECTION_VERBS else "member"
219
+
220
+
221
+ def _normalize_persist(p) -> str:
222
+ if p is None:
223
+ return "default"
224
+ p = str(p).lower()
225
+ if p in {"none", "skip", "read"}:
226
+ return "skip"
227
+ if p in {"append"}:
228
+ return "append"
229
+ if p in {"override"}:
230
+ return "override"
231
+ if p in {"prepend"}:
232
+ return "prepend"
233
+ if p in {"write", "default", "persist"}:
234
+ return "default"
235
+ return "default"
236
+
237
+
238
+ __all__ = ["alias", "alias_ctx", "op_alias", "op_ctx"]