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,21 @@
1
+ from ..config.constants import HOOK_DECLS_ATTR
2
+ from .decorators import hook_ctx
3
+ from .types import PHASE, HookPhase, PHASES, Ctx, StepFn, HookPredicate
4
+ from .shortcuts import hook, hook_spec
5
+ from ._hook import Hook
6
+ from .hook_spec import HookSpec
7
+
8
+ __all__ = [
9
+ "hook_ctx",
10
+ "HOOK_DECLS_ATTR",
11
+ "Hook",
12
+ "PHASE",
13
+ "HookPhase",
14
+ "PHASES",
15
+ "Ctx",
16
+ "StepFn",
17
+ "HookPredicate",
18
+ "hook",
19
+ "hook_spec",
20
+ "HookSpec",
21
+ ]
tigrbl/hook/_hook.py ADDED
@@ -0,0 +1,22 @@
1
+ """Runtime hook wrapper for Tigrbl v3."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Iterable, Optional, Union
7
+
8
+ from .types import HookPhase, StepFn
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class Hook:
13
+ """Concrete hook bound to a phase and one or more ops."""
14
+
15
+ phase: HookPhase
16
+ fn: StepFn
17
+ ops: Union[str, Iterable[str]]
18
+ name: Optional[str] = None
19
+ description: Optional[str] = None
20
+
21
+
22
+ __all__ = ["Hook"]
@@ -0,0 +1,28 @@
1
+ """Hook-related decorators for Tigrbl v3."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterable, Union
6
+
7
+ from ..config.constants import HOOK_DECLS_ATTR
8
+ from ._hook import Hook
9
+
10
+
11
+ def hook_ctx(ops: Union[str, Iterable[str]], *, phase: str):
12
+ """Declare a ctx-only hook for one/many ops at a given phase."""
13
+
14
+ def deco(fn):
15
+ from ..op.decorators import _ensure_cm, _unwrap
16
+
17
+ cm = _ensure_cm(fn)
18
+ f = _unwrap(cm)
19
+ f.__tigrbl_ctx_only__ = True
20
+ lst = getattr(f, HOOK_DECLS_ATTR, [])
21
+ lst.append(Hook(phase=phase, fn=f, ops=ops))
22
+ setattr(f, HOOK_DECLS_ATTR, lst)
23
+ return cm
24
+
25
+ return deco
26
+
27
+
28
+ __all__ = ["hook_ctx", "HOOK_DECLS_ATTR"]
@@ -0,0 +1,24 @@
1
+ """Hook specification for Tigrbl v3."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+
8
+ from .types import HookPhase, StepFn, HookPredicate
9
+
10
+
11
+ @dataclass(frozen=True, slots=True)
12
+ class HookSpec:
13
+ phase: HookPhase
14
+ fn: StepFn
15
+ order: int = 0
16
+ when: Optional[HookPredicate] = None
17
+ name: Optional[str] = None
18
+ description: Optional[str] = None
19
+
20
+
21
+ # Backwards compatibility alias
22
+ OpHook = HookSpec
23
+
24
+ __all__ = ["HookSpec", "OpHook"]
@@ -0,0 +1,98 @@
1
+ """Helpers for collecting ctx-only hooks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from functools import lru_cache
7
+ from typing import Any, Callable, Dict, Iterable, Union
8
+
9
+ from ..runtime.executor import _Ctx
10
+ from ..op.collect import apply_alias
11
+ from ..op.mro_collect import mro_alias_map_for
12
+ from ..op.decorators import _maybe_await, _unwrap
13
+ from .decorators import HOOK_DECLS_ATTR, Hook
14
+
15
+ logger = logging.getLogger("uvicorn")
16
+
17
+
18
+ def _phase_io_key(phase: str) -> str | None:
19
+ p = str(phase)
20
+ if p.startswith("PRE_"):
21
+ return "payload"
22
+ if p.startswith("POST_"):
23
+ return "result"
24
+ if p.startswith("ON_"):
25
+ return "error"
26
+ return None
27
+
28
+
29
+ def _wrap_ctx_hook(
30
+ table: type, func: Callable[..., Any], phase: str
31
+ ) -> Callable[..., Any]:
32
+ io_key = _phase_io_key(phase)
33
+
34
+ async def hook(
35
+ value=None, *, db=None, request=None, ctx: Dict[str, Any] | None = None
36
+ ):
37
+ ctx = _Ctx.ensure(request=request, db=db, seed=ctx)
38
+ if io_key is not None and value is not None:
39
+ ctx[io_key] = value
40
+ bound = func.__get__(table, table)
41
+ _ = await _maybe_await(bound(ctx))
42
+ if io_key is None:
43
+ return None
44
+ return ctx.get(io_key, value)
45
+
46
+ hook.__name__ = getattr(func, "__name__", "hook")
47
+ hook.__qualname__ = getattr(func, "__qualname__", hook.__name__)
48
+ return hook
49
+
50
+
51
+ @lru_cache(maxsize=None)
52
+ def _mro_collect_decorated_hooks_cached(
53
+ table: type, visible_aliases_fs: frozenset[str]
54
+ ) -> Dict[str, Dict[str, list[Callable[..., Any]]]]:
55
+ """Cached helper for :func:`mro_collect_decorated_hooks`."""
56
+
57
+ visible_aliases = set(visible_aliases_fs)
58
+ logger.info("Collecting hooks for %s", table.__name__)
59
+ mapping: Dict[str, Dict[str, list[Callable[..., Any]]]] = {}
60
+ aliases = mro_alias_map_for(table)
61
+
62
+ def _resolve_ops(spec: Union[str, Iterable[str]]) -> Iterable[str]:
63
+ if spec == "*":
64
+ return visible_aliases
65
+ if isinstance(spec, str):
66
+ return [spec if spec in visible_aliases else apply_alias(spec, aliases)]
67
+ out: list[str] = []
68
+ for x in spec:
69
+ out.append(x if x in visible_aliases else apply_alias(x, aliases))
70
+ return out
71
+
72
+ for base in reversed(table.__mro__):
73
+ for name, attr in base.__dict__.items():
74
+ func = _unwrap(attr)
75
+ decls: list[Hook] | None = getattr(func, HOOK_DECLS_ATTR, None)
76
+ if not decls:
77
+ continue
78
+ for d in decls:
79
+ for op in _resolve_ops(d.ops):
80
+ if op not in visible_aliases:
81
+ continue
82
+ ph = d.phase
83
+ mapping.setdefault(op, {}).setdefault(ph, []).append(
84
+ _wrap_ctx_hook(table, d.fn, ph)
85
+ )
86
+ logger.debug("Collected hooks for aliases: %s", list(mapping.keys()))
87
+ return mapping
88
+
89
+
90
+ def mro_collect_decorated_hooks(
91
+ table: type, *, visible_aliases: set[str]
92
+ ) -> Dict[str, Dict[str, list[Callable[..., Any]]]]:
93
+ """Collect alias→phase→[hook] declarations across a table's MRO."""
94
+
95
+ return _mro_collect_decorated_hooks_cached(table, frozenset(visible_aliases))
96
+
97
+
98
+ __all__ = ["mro_collect_decorated_hooks"]
@@ -0,0 +1,44 @@
1
+ """Shortcut helpers for building Hook specs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Iterable, Union
6
+
7
+ from .types import HookPhase, HookPredicate, StepFn
8
+ from ._hook import Hook
9
+ from .hook_spec import HookSpec
10
+
11
+
12
+ def hook(
13
+ phase: HookPhase,
14
+ ops: Union[str, Iterable[str]],
15
+ fn: StepFn,
16
+ *,
17
+ name: str | None = None,
18
+ description: str | None = None,
19
+ ) -> Hook:
20
+ """Build a :class:`Hook` instance."""
21
+ return Hook(phase=phase, fn=fn, ops=ops, name=name, description=description)
22
+
23
+
24
+ def hook_spec(
25
+ phase: HookPhase,
26
+ fn: StepFn,
27
+ *,
28
+ order: int = 0,
29
+ when: HookPredicate | None = None,
30
+ name: str | None = None,
31
+ description: str | None = None,
32
+ ) -> HookSpec:
33
+ """Build a :class:`HookSpec` instance."""
34
+ return HookSpec(
35
+ phase=phase,
36
+ fn=fn,
37
+ order=order,
38
+ when=when,
39
+ name=name,
40
+ description=description,
41
+ )
42
+
43
+
44
+ __all__ = ["hook", "hook_spec"]
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"]