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.
- tigrbl/README.md +94 -0
- tigrbl/__init__.py +139 -14
- tigrbl/api/__init__.py +6 -0
- tigrbl/api/_api.py +97 -0
- tigrbl/api/api_spec.py +30 -0
- tigrbl/api/mro_collect.py +43 -0
- tigrbl/api/shortcuts.py +56 -0
- tigrbl/api/tigrbl_api.py +291 -0
- tigrbl/app/__init__.py +0 -0
- tigrbl/app/_app.py +86 -0
- tigrbl/app/_model_registry.py +41 -0
- tigrbl/app/app_spec.py +42 -0
- tigrbl/app/mro_collect.py +67 -0
- tigrbl/app/shortcuts.py +65 -0
- tigrbl/app/tigrbl_app.py +319 -0
- tigrbl/bindings/__init__.py +73 -0
- tigrbl/bindings/api/__init__.py +12 -0
- tigrbl/bindings/api/common.py +109 -0
- tigrbl/bindings/api/include.py +256 -0
- tigrbl/bindings/api/resource_proxy.py +149 -0
- tigrbl/bindings/api/rpc.py +111 -0
- tigrbl/bindings/columns.py +49 -0
- tigrbl/bindings/handlers/__init__.py +11 -0
- tigrbl/bindings/handlers/builder.py +119 -0
- tigrbl/bindings/handlers/ctx.py +74 -0
- tigrbl/bindings/handlers/identifiers.py +228 -0
- tigrbl/bindings/handlers/namespaces.py +51 -0
- tigrbl/bindings/handlers/steps.py +276 -0
- tigrbl/bindings/hooks.py +311 -0
- tigrbl/bindings/model.py +194 -0
- tigrbl/bindings/model_helpers.py +139 -0
- tigrbl/bindings/model_registry.py +77 -0
- tigrbl/bindings/rest/__init__.py +7 -0
- tigrbl/bindings/rest/attach.py +34 -0
- tigrbl/bindings/rest/collection.py +286 -0
- tigrbl/bindings/rest/common.py +120 -0
- tigrbl/bindings/rest/fastapi.py +76 -0
- tigrbl/bindings/rest/helpers.py +119 -0
- tigrbl/bindings/rest/io.py +317 -0
- tigrbl/bindings/rest/io_headers.py +49 -0
- tigrbl/bindings/rest/member.py +386 -0
- tigrbl/bindings/rest/router.py +296 -0
- tigrbl/bindings/rest/routing.py +153 -0
- tigrbl/bindings/rpc.py +364 -0
- tigrbl/bindings/schemas/__init__.py +11 -0
- tigrbl/bindings/schemas/builder.py +348 -0
- tigrbl/bindings/schemas/defaults.py +260 -0
- tigrbl/bindings/schemas/utils.py +193 -0
- tigrbl/column/README.md +62 -0
- tigrbl/column/__init__.py +72 -0
- tigrbl/column/_column.py +96 -0
- tigrbl/column/column_spec.py +40 -0
- tigrbl/column/field_spec.py +31 -0
- tigrbl/column/infer/__init__.py +25 -0
- tigrbl/column/infer/core.py +92 -0
- tigrbl/column/infer/jsonhints.py +44 -0
- tigrbl/column/infer/planning.py +133 -0
- tigrbl/column/infer/types.py +102 -0
- tigrbl/column/infer/utils.py +59 -0
- tigrbl/column/io_spec.py +136 -0
- tigrbl/column/mro_collect.py +59 -0
- tigrbl/column/shortcuts.py +89 -0
- tigrbl/column/storage_spec.py +65 -0
- tigrbl/config/__init__.py +19 -0
- tigrbl/config/constants.py +224 -0
- tigrbl/config/defaults.py +29 -0
- tigrbl/config/resolver.py +295 -0
- tigrbl/core/__init__.py +47 -0
- tigrbl/core/crud/__init__.py +36 -0
- tigrbl/core/crud/bulk.py +168 -0
- tigrbl/core/crud/helpers/__init__.py +76 -0
- tigrbl/core/crud/helpers/db.py +92 -0
- tigrbl/core/crud/helpers/enum.py +86 -0
- tigrbl/core/crud/helpers/filters.py +162 -0
- tigrbl/core/crud/helpers/model.py +123 -0
- tigrbl/core/crud/helpers/normalize.py +99 -0
- tigrbl/core/crud/ops.py +235 -0
- tigrbl/ddl/__init__.py +344 -0
- tigrbl/decorators.py +17 -0
- tigrbl/deps/__init__.py +20 -0
- tigrbl/deps/fastapi.py +45 -0
- tigrbl/deps/favicon.svg +4 -0
- tigrbl/deps/jinja.py +27 -0
- tigrbl/deps/pydantic.py +10 -0
- tigrbl/deps/sqlalchemy.py +94 -0
- tigrbl/deps/starlette.py +36 -0
- tigrbl/engine/__init__.py +45 -0
- tigrbl/engine/_engine.py +144 -0
- tigrbl/engine/bind.py +33 -0
- tigrbl/engine/builders.py +236 -0
- tigrbl/engine/capabilities.py +29 -0
- tigrbl/engine/collect.py +111 -0
- tigrbl/engine/decorators.py +110 -0
- tigrbl/engine/docs/PLUGINS.md +49 -0
- tigrbl/engine/engine_spec.py +355 -0
- tigrbl/engine/plugins.py +52 -0
- tigrbl/engine/registry.py +36 -0
- tigrbl/engine/resolver.py +224 -0
- tigrbl/engine/shortcuts.py +216 -0
- tigrbl/hook/__init__.py +21 -0
- tigrbl/hook/_hook.py +22 -0
- tigrbl/hook/decorators.py +28 -0
- tigrbl/hook/hook_spec.py +24 -0
- tigrbl/hook/mro_collect.py +98 -0
- tigrbl/hook/shortcuts.py +44 -0
- tigrbl/hook/types.py +76 -0
- tigrbl/op/__init__.py +50 -0
- tigrbl/op/_op.py +31 -0
- tigrbl/op/canonical.py +31 -0
- tigrbl/op/collect.py +11 -0
- tigrbl/op/decorators.py +238 -0
- tigrbl/op/model_registry.py +301 -0
- tigrbl/op/mro_collect.py +99 -0
- tigrbl/op/resolver.py +216 -0
- tigrbl/op/types.py +136 -0
- tigrbl/orm/__init__.py +1 -0
- tigrbl/orm/mixins/_RowBound.py +83 -0
- tigrbl/orm/mixins/__init__.py +95 -0
- tigrbl/orm/mixins/bootstrappable.py +113 -0
- tigrbl/orm/mixins/bound.py +47 -0
- tigrbl/orm/mixins/edges.py +40 -0
- tigrbl/orm/mixins/fields.py +165 -0
- tigrbl/orm/mixins/hierarchy.py +54 -0
- tigrbl/orm/mixins/key_digest.py +44 -0
- tigrbl/orm/mixins/lifecycle.py +115 -0
- tigrbl/orm/mixins/locks.py +51 -0
- tigrbl/orm/mixins/markers.py +16 -0
- tigrbl/orm/mixins/operations.py +57 -0
- tigrbl/orm/mixins/ownable.py +337 -0
- tigrbl/orm/mixins/principals.py +98 -0
- tigrbl/orm/mixins/tenant_bound.py +301 -0
- tigrbl/orm/mixins/upsertable.py +118 -0
- tigrbl/orm/mixins/utils.py +49 -0
- tigrbl/orm/tables/__init__.py +72 -0
- tigrbl/orm/tables/_base.py +8 -0
- tigrbl/orm/tables/audit.py +56 -0
- tigrbl/orm/tables/client.py +25 -0
- tigrbl/orm/tables/group.py +29 -0
- tigrbl/orm/tables/org.py +30 -0
- tigrbl/orm/tables/rbac.py +76 -0
- tigrbl/orm/tables/status.py +106 -0
- tigrbl/orm/tables/tenant.py +22 -0
- tigrbl/orm/tables/user.py +39 -0
- tigrbl/response/README.md +34 -0
- tigrbl/response/__init__.py +33 -0
- tigrbl/response/bind.py +12 -0
- tigrbl/response/decorators.py +37 -0
- tigrbl/response/resolver.py +83 -0
- tigrbl/response/shortcuts.py +171 -0
- tigrbl/response/types.py +49 -0
- tigrbl/rest/__init__.py +27 -0
- tigrbl/runtime/README.md +129 -0
- tigrbl/runtime/__init__.py +20 -0
- tigrbl/runtime/atoms/__init__.py +102 -0
- tigrbl/runtime/atoms/emit/__init__.py +42 -0
- tigrbl/runtime/atoms/emit/paired_post.py +158 -0
- tigrbl/runtime/atoms/emit/paired_pre.py +106 -0
- tigrbl/runtime/atoms/emit/readtime_alias.py +120 -0
- tigrbl/runtime/atoms/out/__init__.py +38 -0
- tigrbl/runtime/atoms/out/masking.py +135 -0
- tigrbl/runtime/atoms/refresh/__init__.py +38 -0
- tigrbl/runtime/atoms/refresh/demand.py +130 -0
- tigrbl/runtime/atoms/resolve/__init__.py +40 -0
- tigrbl/runtime/atoms/resolve/assemble.py +167 -0
- tigrbl/runtime/atoms/resolve/paired_gen.py +147 -0
- tigrbl/runtime/atoms/response/__init__.py +19 -0
- tigrbl/runtime/atoms/response/headers_from_payload.py +57 -0
- tigrbl/runtime/atoms/response/negotiate.py +30 -0
- tigrbl/runtime/atoms/response/negotiation.py +43 -0
- tigrbl/runtime/atoms/response/render.py +36 -0
- tigrbl/runtime/atoms/response/renderer.py +116 -0
- tigrbl/runtime/atoms/response/template.py +44 -0
- tigrbl/runtime/atoms/response/templates.py +88 -0
- tigrbl/runtime/atoms/schema/__init__.py +40 -0
- tigrbl/runtime/atoms/schema/collect_in.py +21 -0
- tigrbl/runtime/atoms/schema/collect_out.py +21 -0
- tigrbl/runtime/atoms/storage/__init__.py +38 -0
- tigrbl/runtime/atoms/storage/to_stored.py +167 -0
- tigrbl/runtime/atoms/wire/__init__.py +45 -0
- tigrbl/runtime/atoms/wire/build_in.py +166 -0
- tigrbl/runtime/atoms/wire/build_out.py +87 -0
- tigrbl/runtime/atoms/wire/dump.py +206 -0
- tigrbl/runtime/atoms/wire/validate_in.py +227 -0
- tigrbl/runtime/context.py +206 -0
- tigrbl/runtime/errors/__init__.py +61 -0
- tigrbl/runtime/errors/converters.py +214 -0
- tigrbl/runtime/errors/exceptions.py +124 -0
- tigrbl/runtime/errors/mappings.py +71 -0
- tigrbl/runtime/errors/utils.py +150 -0
- tigrbl/runtime/events.py +209 -0
- tigrbl/runtime/executor/__init__.py +6 -0
- tigrbl/runtime/executor/guards.py +132 -0
- tigrbl/runtime/executor/helpers.py +88 -0
- tigrbl/runtime/executor/invoke.py +150 -0
- tigrbl/runtime/executor/types.py +84 -0
- tigrbl/runtime/kernel.py +644 -0
- tigrbl/runtime/labels.py +353 -0
- tigrbl/runtime/opview.py +89 -0
- tigrbl/runtime/ordering.py +256 -0
- tigrbl/runtime/system.py +279 -0
- tigrbl/runtime/trace.py +330 -0
- tigrbl/schema/__init__.py +38 -0
- tigrbl/schema/_schema.py +27 -0
- tigrbl/schema/builder/__init__.py +17 -0
- tigrbl/schema/builder/build_schema.py +209 -0
- tigrbl/schema/builder/cache.py +24 -0
- tigrbl/schema/builder/compat.py +16 -0
- tigrbl/schema/builder/extras.py +85 -0
- tigrbl/schema/builder/helpers.py +51 -0
- tigrbl/schema/builder/list_params.py +117 -0
- tigrbl/schema/builder/strip_parent_fields.py +70 -0
- tigrbl/schema/collect.py +79 -0
- tigrbl/schema/decorators.py +68 -0
- tigrbl/schema/get_schema.py +86 -0
- tigrbl/schema/schema_spec.py +20 -0
- tigrbl/schema/shortcuts.py +42 -0
- tigrbl/schema/types.py +34 -0
- tigrbl/schema/utils.py +143 -0
- tigrbl/session/README.md +14 -0
- tigrbl/session/__init__.py +28 -0
- tigrbl/session/abc.py +76 -0
- tigrbl/session/base.py +151 -0
- tigrbl/session/decorators.py +43 -0
- tigrbl/session/default.py +118 -0
- tigrbl/session/shortcuts.py +50 -0
- tigrbl/session/spec.py +112 -0
- tigrbl/shortcuts.py +22 -0
- tigrbl/specs.py +44 -0
- tigrbl/system/__init__.py +13 -0
- tigrbl/system/diagnostics/__init__.py +24 -0
- tigrbl/system/diagnostics/compat.py +31 -0
- tigrbl/system/diagnostics/healthz.py +41 -0
- tigrbl/system/diagnostics/hookz.py +51 -0
- tigrbl/system/diagnostics/kernelz.py +20 -0
- tigrbl/system/diagnostics/methodz.py +43 -0
- tigrbl/system/diagnostics/router.py +73 -0
- tigrbl/system/diagnostics/utils.py +43 -0
- tigrbl/system/uvicorn.py +60 -0
- tigrbl/table/__init__.py +9 -0
- tigrbl/table/_base.py +260 -0
- tigrbl/table/_table.py +54 -0
- tigrbl/table/mro_collect.py +69 -0
- tigrbl/table/shortcuts.py +57 -0
- tigrbl/table/table_spec.py +28 -0
- tigrbl/transport/__init__.py +74 -0
- tigrbl/transport/jsonrpc/__init__.py +19 -0
- tigrbl/transport/jsonrpc/dispatcher.py +352 -0
- tigrbl/transport/jsonrpc/helpers.py +115 -0
- tigrbl/transport/jsonrpc/models.py +41 -0
- tigrbl/transport/rest/__init__.py +25 -0
- tigrbl/transport/rest/aggregator.py +132 -0
- tigrbl/types/__init__.py +170 -0
- tigrbl/types/allow_anon_provider.py +19 -0
- tigrbl/types/authn_abc.py +30 -0
- tigrbl/types/nested_path_provider.py +22 -0
- tigrbl/types/op.py +35 -0
- tigrbl/types/op_config_provider.py +17 -0
- tigrbl/types/op_verb_alias_provider.py +33 -0
- tigrbl/types/request_extras_provider.py +22 -0
- tigrbl/types/response_extras_provider.py +22 -0
- tigrbl/types/table_config_provider.py +13 -0
- tigrbl/types/uuid.py +55 -0
- tigrbl-0.3.0.dist-info/METADATA +516 -0
- tigrbl-0.3.0.dist-info/RECORD +266 -0
- {tigrbl-0.0.1.dev1.dist-info → tigrbl-0.3.0.dist-info}/WHEEL +1 -1
- tigrbl-0.3.0.dist-info/licenses/LICENSE +201 -0
- tigrbl/ExampleAgent.py +0 -1
- tigrbl-0.0.1.dev1.dist-info/METADATA +0 -18
- tigrbl-0.0.1.dev1.dist-info/RECORD +0 -5
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from .spec import SessionSpec, SessionCfg
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _normalize(cfg: Optional[SessionCfg] = None, **kw: Any) -> SessionSpec:
|
|
8
|
+
if cfg is not None and kw:
|
|
9
|
+
raise ValueError("Pass either a mapping/spec or keyword args, not both")
|
|
10
|
+
return SessionSpec.from_any(cfg or kw) or SessionSpec()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def session_ctx(cfg: Optional[SessionCfg] = None, /, **kw: Any):
|
|
14
|
+
"""
|
|
15
|
+
Attach a SessionSpec to an App, API, Model/Table, or op handler.
|
|
16
|
+
|
|
17
|
+
Precedence is evaluated by the resolver using:
|
|
18
|
+
op > model > api > app
|
|
19
|
+
(Resolver is part of the runtime/engine layer and is independent of this decorator.)
|
|
20
|
+
"""
|
|
21
|
+
spec = _normalize(cfg, **kw)
|
|
22
|
+
|
|
23
|
+
def _apply(obj: Any) -> Any:
|
|
24
|
+
setattr(obj, "__tigrbl_session_ctx__", spec)
|
|
25
|
+
return obj
|
|
26
|
+
|
|
27
|
+
return _apply
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def read_only_session(obj: Any = None, /, *, isolation: Optional[str] = None):
|
|
31
|
+
"""
|
|
32
|
+
Convenience decorator for read-only sessions.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def _wrap(o: Any) -> Any:
|
|
36
|
+
setattr(
|
|
37
|
+
o,
|
|
38
|
+
"__tigrbl_session_ctx__",
|
|
39
|
+
SessionSpec(read_only=True, isolation=isolation),
|
|
40
|
+
)
|
|
41
|
+
return o
|
|
42
|
+
|
|
43
|
+
return _wrap(obj) if obj is not None else _wrap
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from typing import Any, Callable, Optional
|
|
5
|
+
|
|
6
|
+
from .base import TigrblSessionBase
|
|
7
|
+
from .spec import SessionSpec
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DefaultSession(TigrblSessionBase):
|
|
11
|
+
"""
|
|
12
|
+
Delegating session that wraps an underlying native session object
|
|
13
|
+
(sync or async) and exposes the Tigrbl Session ABC.
|
|
14
|
+
|
|
15
|
+
No third-party imports: we rely on duck-typed methods on the underlying
|
|
16
|
+
object (begin/commit/rollback, add/delete/flush/refresh/get/execute/close).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, underlying: Any, spec: Optional[SessionSpec] = None) -> None:
|
|
20
|
+
super().__init__(spec)
|
|
21
|
+
self._u = underlying
|
|
22
|
+
|
|
23
|
+
# ---- async marker ----
|
|
24
|
+
async def run_sync(self, fn: Callable[[Any], Any]) -> Any:
|
|
25
|
+
rv = fn(self._u)
|
|
26
|
+
if inspect.isawaitable(rv):
|
|
27
|
+
return await rv
|
|
28
|
+
return rv
|
|
29
|
+
|
|
30
|
+
# ---- TX primitives ----
|
|
31
|
+
async def _tx_begin_impl(self) -> None:
|
|
32
|
+
fn = getattr(self._u, "begin", None)
|
|
33
|
+
if not callable(fn):
|
|
34
|
+
raise RuntimeError("underlying session does not support begin()")
|
|
35
|
+
rv = fn()
|
|
36
|
+
if inspect.isawaitable(rv):
|
|
37
|
+
await rv
|
|
38
|
+
|
|
39
|
+
async def _tx_commit_impl(self) -> None:
|
|
40
|
+
fn = getattr(self._u, "commit", None)
|
|
41
|
+
if not callable(fn):
|
|
42
|
+
raise RuntimeError("underlying session does not support commit()")
|
|
43
|
+
rv = fn()
|
|
44
|
+
if inspect.isawaitable(rv):
|
|
45
|
+
await rv
|
|
46
|
+
|
|
47
|
+
async def _tx_rollback_impl(self) -> None:
|
|
48
|
+
fn = getattr(self._u, "rollback", None)
|
|
49
|
+
if not callable(fn):
|
|
50
|
+
raise RuntimeError("underlying session does not support rollback()")
|
|
51
|
+
rv = fn()
|
|
52
|
+
if inspect.isawaitable(rv):
|
|
53
|
+
await rv
|
|
54
|
+
|
|
55
|
+
def in_transaction(self) -> bool:
|
|
56
|
+
it = getattr(self._u, "in_transaction", None)
|
|
57
|
+
if callable(it):
|
|
58
|
+
try:
|
|
59
|
+
return bool(it())
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
return super().in_transaction()
|
|
63
|
+
|
|
64
|
+
# ---- CRUD primitives ----
|
|
65
|
+
def _add_impl(self, obj: Any) -> Any:
|
|
66
|
+
fn = getattr(self._u, "add", None)
|
|
67
|
+
if not callable(fn):
|
|
68
|
+
raise NotImplementedError("underlying session does not implement add(obj)")
|
|
69
|
+
return fn(obj)
|
|
70
|
+
|
|
71
|
+
async def _delete_impl(self, obj: Any) -> None:
|
|
72
|
+
fn = getattr(self._u, "delete", None)
|
|
73
|
+
if not callable(fn):
|
|
74
|
+
raise NotImplementedError(
|
|
75
|
+
"underlying session does not implement delete(obj)"
|
|
76
|
+
)
|
|
77
|
+
rv = fn(obj)
|
|
78
|
+
if inspect.isawaitable(rv):
|
|
79
|
+
await rv
|
|
80
|
+
|
|
81
|
+
async def _flush_impl(self) -> None:
|
|
82
|
+
fn = getattr(self._u, "flush", None)
|
|
83
|
+
if callable(fn):
|
|
84
|
+
rv = fn()
|
|
85
|
+
if inspect.isawaitable(rv):
|
|
86
|
+
await rv
|
|
87
|
+
|
|
88
|
+
async def _refresh_impl(self, obj: Any) -> None:
|
|
89
|
+
fn = getattr(self._u, "refresh", None)
|
|
90
|
+
if callable(fn):
|
|
91
|
+
rv = fn(obj)
|
|
92
|
+
if inspect.isawaitable(rv):
|
|
93
|
+
await rv
|
|
94
|
+
|
|
95
|
+
async def _get_impl(self, model: type, ident: Any) -> Any | None:
|
|
96
|
+
fn = getattr(self._u, "get", None)
|
|
97
|
+
if not callable(fn):
|
|
98
|
+
raise NotImplementedError(
|
|
99
|
+
"underlying session does not implement get(model, ident)"
|
|
100
|
+
)
|
|
101
|
+
rv = fn(model, ident)
|
|
102
|
+
return await rv if inspect.isawaitable(rv) else rv
|
|
103
|
+
|
|
104
|
+
async def _execute_impl(self, stmt: Any) -> Any:
|
|
105
|
+
fn = getattr(self._u, "execute", None)
|
|
106
|
+
if not callable(fn):
|
|
107
|
+
raise NotImplementedError(
|
|
108
|
+
"underlying session does not implement execute(stmt)"
|
|
109
|
+
)
|
|
110
|
+
rv = fn(stmt)
|
|
111
|
+
return await rv if inspect.isawaitable(rv) else rv
|
|
112
|
+
|
|
113
|
+
async def _close_impl(self) -> None:
|
|
114
|
+
fn = getattr(self._u, "close", None)
|
|
115
|
+
if callable(fn):
|
|
116
|
+
rv = fn()
|
|
117
|
+
if inspect.isawaitable(rv):
|
|
118
|
+
await rv
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Optional
|
|
4
|
+
|
|
5
|
+
from .default import DefaultSession
|
|
6
|
+
from .spec import SessionSpec, SessionCfg
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def session_spec(cfg: SessionCfg = None, /, **kw: Any) -> SessionSpec:
|
|
10
|
+
"""
|
|
11
|
+
Build a SessionSpec from either a mapping/spec or kwargs.
|
|
12
|
+
"""
|
|
13
|
+
if cfg is not None and kw:
|
|
14
|
+
raise ValueError("Provide either a mapping/spec or kwargs, not both")
|
|
15
|
+
return SessionSpec.from_any(cfg or kw) or SessionSpec()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Isolation presets
|
|
19
|
+
def tx_read_committed(*, read_only: Optional[bool] = None) -> SessionSpec:
|
|
20
|
+
return SessionSpec(isolation="read_committed", read_only=read_only)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def tx_repeatable_read(*, read_only: Optional[bool] = None) -> SessionSpec:
|
|
24
|
+
return SessionSpec(isolation="repeatable_read", read_only=read_only)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def tx_serializable(*, read_only: Optional[bool] = None) -> SessionSpec:
|
|
28
|
+
return SessionSpec(isolation="serializable", read_only=read_only)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def readonly() -> SessionSpec:
|
|
32
|
+
return SessionSpec(read_only=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Provider/sessionmaker wrapper
|
|
36
|
+
def wrap_sessionmaker(
|
|
37
|
+
maker: Callable[[], Any], spec: SessionSpec
|
|
38
|
+
) -> Callable[[], DefaultSession]:
|
|
39
|
+
"""
|
|
40
|
+
Wrap any provider's session factory to yield DefaultSession instances that
|
|
41
|
+
enforce the Tigrbl Session ABC and policy.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def _mk() -> DefaultSession:
|
|
45
|
+
underlying = maker()
|
|
46
|
+
s = DefaultSession(underlying, spec)
|
|
47
|
+
s.apply_spec(spec)
|
|
48
|
+
return s
|
|
49
|
+
|
|
50
|
+
return _mk
|
tigrbl/session/spec.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, fields
|
|
4
|
+
from typing import Any, Mapping, MutableMapping, Optional, Union
|
|
5
|
+
|
|
6
|
+
SessionCfg = Union["SessionSpec", Mapping[str, Any], None]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class SessionSpec:
|
|
11
|
+
"""
|
|
12
|
+
Per-session policy for Tigrbl sessions.
|
|
13
|
+
|
|
14
|
+
These fields are backend-agnostic hints and constraints. Adapters should
|
|
15
|
+
validate and apply them where supported; critical ones (like isolation and
|
|
16
|
+
read_only) SHOULD be validated and enforced.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# Transaction policy
|
|
20
|
+
isolation: Optional[str] = (
|
|
21
|
+
None # "read_committed" | "repeatable_read" | "snapshot" | "serializable"
|
|
22
|
+
)
|
|
23
|
+
read_only: Optional[bool] = None
|
|
24
|
+
autobegin: Optional[bool] = True
|
|
25
|
+
expire_on_commit: Optional[bool] = None
|
|
26
|
+
|
|
27
|
+
# Retries & backoff
|
|
28
|
+
retry_on_conflict: Optional[bool] = None
|
|
29
|
+
max_retries: int = 0
|
|
30
|
+
backoff_ms: int = 0
|
|
31
|
+
backoff_jitter: bool = True
|
|
32
|
+
|
|
33
|
+
# Timeouts / resources
|
|
34
|
+
statement_timeout_ms: Optional[int] = None
|
|
35
|
+
lock_timeout_ms: Optional[int] = None
|
|
36
|
+
fetch_rows: Optional[int] = None
|
|
37
|
+
stream_chunk_rows: Optional[int] = None
|
|
38
|
+
|
|
39
|
+
# Consistency coordinates
|
|
40
|
+
min_lsn: Optional[str] = None
|
|
41
|
+
as_of_ts: Optional[str] = None
|
|
42
|
+
consistency: Optional[str] = None # "strong" | "bounded_staleness" | "eventual"
|
|
43
|
+
staleness_ms: Optional[int] = None
|
|
44
|
+
|
|
45
|
+
# Tenancy & security
|
|
46
|
+
tenant_id: Optional[str] = None
|
|
47
|
+
role: Optional[str] = None
|
|
48
|
+
rls_context: Mapping[str, str] = None
|
|
49
|
+
|
|
50
|
+
# Observability
|
|
51
|
+
trace_id: Optional[str] = None
|
|
52
|
+
query_tag: Optional[str] = None
|
|
53
|
+
tag: Optional[str] = None
|
|
54
|
+
tracing_sample: Optional[float] = None
|
|
55
|
+
|
|
56
|
+
# Cache / index hints
|
|
57
|
+
cache_read: Optional[bool] = None
|
|
58
|
+
cache_write: Optional[bool] = None
|
|
59
|
+
namespace: Optional[str] = None
|
|
60
|
+
|
|
61
|
+
# Data protection / compliance
|
|
62
|
+
kms_key_alias: Optional[str] = None
|
|
63
|
+
classification: Optional[str] = None
|
|
64
|
+
audit: Optional[bool] = None
|
|
65
|
+
|
|
66
|
+
# Idempotency & pagination
|
|
67
|
+
idempotency_key: Optional[str] = None
|
|
68
|
+
page_snapshot: Optional[str] = None
|
|
69
|
+
|
|
70
|
+
def merge(self, higher: "SessionSpec | Mapping[str, Any] | None") -> "SessionSpec":
|
|
71
|
+
"""
|
|
72
|
+
Overlay another spec on top of this one (non-None fields take precedence).
|
|
73
|
+
Use to implement op > model > api > app precedence.
|
|
74
|
+
"""
|
|
75
|
+
if higher is None:
|
|
76
|
+
return self
|
|
77
|
+
h = higher if isinstance(higher, SessionSpec) else SessionSpec.from_any(higher)
|
|
78
|
+
if h is None:
|
|
79
|
+
return self
|
|
80
|
+
vals: MutableMapping[str, Any] = {
|
|
81
|
+
f.name: getattr(self, f.name) for f in fields(SessionSpec)
|
|
82
|
+
}
|
|
83
|
+
for f in fields(SessionSpec):
|
|
84
|
+
hv = getattr(h, f.name)
|
|
85
|
+
if hv is not None:
|
|
86
|
+
vals[f.name] = hv
|
|
87
|
+
return SessionSpec(**vals) # type: ignore[arg-type]
|
|
88
|
+
|
|
89
|
+
def to_kwargs(self) -> dict[str, Any]:
|
|
90
|
+
"""Return only non-None items as a plain dict (adapters can **kwargs this)."""
|
|
91
|
+
return {
|
|
92
|
+
f.name: getattr(self, f.name)
|
|
93
|
+
for f in fields(SessionSpec)
|
|
94
|
+
if getattr(self, f.name) is not None
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def from_any(x: SessionCfg) -> Optional["SessionSpec"]:
|
|
99
|
+
if x is None:
|
|
100
|
+
return None
|
|
101
|
+
if isinstance(x, SessionSpec):
|
|
102
|
+
return x
|
|
103
|
+
if isinstance(x, Mapping):
|
|
104
|
+
m = dict(x)
|
|
105
|
+
# aliases
|
|
106
|
+
if "readonly" in m and "read_only" not in m:
|
|
107
|
+
m["read_only"] = bool(m.pop("readonly"))
|
|
108
|
+
if "iso" in m and "isolation" not in m:
|
|
109
|
+
m["isolation"] = str(m.pop("iso"))
|
|
110
|
+
allowed = {f.name for f in fields(SessionSpec)}
|
|
111
|
+
return SessionSpec(**{k: v for k, v in m.items() if k in allowed})
|
|
112
|
+
raise TypeError(f"Unsupported SessionSpec input: {type(x)}")
|
tigrbl/shortcuts.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# tigrbl/tigrbl/v3/shortcuts.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from .app import shortcuts as app_shortcuts
|
|
5
|
+
from .api import shortcuts as api_shortcuts
|
|
6
|
+
from .engine import shortcuts as engine_shortcuts
|
|
7
|
+
from .specs import shortcuts as specs_shortcuts
|
|
8
|
+
from .table import shortcuts as table_shortcuts
|
|
9
|
+
|
|
10
|
+
from .app.shortcuts import * # noqa: F401,F403
|
|
11
|
+
from .api.shortcuts import * # noqa: F401,F403
|
|
12
|
+
from .engine.shortcuts import * # noqa: F401,F403
|
|
13
|
+
from .specs.shortcuts import * # noqa: F401,F403
|
|
14
|
+
from .table.shortcuts import * # noqa: F401,F403
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
*app_shortcuts.__all__,
|
|
18
|
+
*api_shortcuts.__all__,
|
|
19
|
+
*engine_shortcuts.__all__,
|
|
20
|
+
*specs_shortcuts.__all__,
|
|
21
|
+
*table_shortcuts.__all__,
|
|
22
|
+
]
|
tigrbl/specs.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# tigrbl/v3/specs.py
|
|
2
|
+
"""Compatibility layer that re-exports column specs.
|
|
3
|
+
|
|
4
|
+
Importing from ``tigrbl.specs`` remains supported but the
|
|
5
|
+
implementation now lives under :mod:`tigrbl.column`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from .column import * # noqa: F401,F403
|
|
13
|
+
from .column import (
|
|
14
|
+
__all__ as _all,
|
|
15
|
+
column_spec as _column_spec,
|
|
16
|
+
field_spec as _field_spec,
|
|
17
|
+
infer as _infer,
|
|
18
|
+
io_spec as _io_spec,
|
|
19
|
+
shortcuts as _shortcuts,
|
|
20
|
+
storage_spec as _storage_spec,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = list(_all)
|
|
24
|
+
|
|
25
|
+
# Allow submodule imports like ``tigrbl.specs.storage_spec``
|
|
26
|
+
__path__: list[str] = []
|
|
27
|
+
_mod = sys.modules[__name__]
|
|
28
|
+
_mod.column_spec = _column_spec
|
|
29
|
+
_mod.field_spec = _field_spec
|
|
30
|
+
_mod.infer = _infer
|
|
31
|
+
_mod.io_spec = _io_spec
|
|
32
|
+
_mod.shortcuts = _shortcuts
|
|
33
|
+
_mod.storage_spec = _storage_spec
|
|
34
|
+
|
|
35
|
+
sys.modules[f"{__name__}.column_spec"] = _column_spec
|
|
36
|
+
sys.modules[f"{__name__}.field_spec"] = _field_spec
|
|
37
|
+
sys.modules[f"{__name__}.infer"] = _infer
|
|
38
|
+
sys.modules[f"{__name__}.io_spec"] = _io_spec
|
|
39
|
+
sys.modules[f"{__name__}.shortcuts"] = _shortcuts
|
|
40
|
+
sys.modules[f"{__name__}.storage_spec"] = _storage_spec
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def __dir__() -> list[str]:
|
|
44
|
+
return sorted(__all__)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# tigrbl/v3/system/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Tigrbl v3 – System/Diagnostics helpers.
|
|
4
|
+
|
|
5
|
+
- mount_diagnostics(api, *, get_db=None) -> Router
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .diagnostics import mount_diagnostics
|
|
11
|
+
from .uvicorn import stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
__all__ = ["mount_diagnostics", "stop_uvicorn_server"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from ...runtime.kernel import _default_kernel, build_phase_chains
|
|
2
|
+
from .router import mount_diagnostics
|
|
3
|
+
from .methodz import build_methodz_endpoint as _build_methodz_endpoint
|
|
4
|
+
from .hookz import build_hookz_endpoint as _build_hookz_endpoint
|
|
5
|
+
from .kernelz import build_kernelz_endpoint as _build_kernelz_endpoint
|
|
6
|
+
from .utils import (
|
|
7
|
+
model_iter as _model_iter,
|
|
8
|
+
opspecs as _opspecs,
|
|
9
|
+
label_callable as _label_callable,
|
|
10
|
+
label_hook as _label_hook,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"mount_diagnostics",
|
|
15
|
+
"_build_methodz_endpoint",
|
|
16
|
+
"_build_hookz_endpoint",
|
|
17
|
+
"_build_kernelz_endpoint",
|
|
18
|
+
"_model_iter",
|
|
19
|
+
"_opspecs",
|
|
20
|
+
"_label_callable",
|
|
21
|
+
"_label_hook",
|
|
22
|
+
"build_phase_chains",
|
|
23
|
+
"_default_kernel",
|
|
24
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Sequence
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from ...types import Router, Request, Depends
|
|
8
|
+
from fastapi.responses import JSONResponse
|
|
9
|
+
except Exception: # pragma: no cover
|
|
10
|
+
|
|
11
|
+
class Router: # type: ignore
|
|
12
|
+
def __init__(self, *a, **kw):
|
|
13
|
+
self.routes = []
|
|
14
|
+
|
|
15
|
+
def add_api_route(
|
|
16
|
+
self, path: str, endpoint: Callable, methods: Sequence[str], **opts
|
|
17
|
+
):
|
|
18
|
+
self.routes.append((path, methods, endpoint, opts))
|
|
19
|
+
|
|
20
|
+
class Request: # type: ignore
|
|
21
|
+
def __init__(self, scope=None):
|
|
22
|
+
self.scope = scope or {}
|
|
23
|
+
self.state = SimpleNamespace()
|
|
24
|
+
self.query_params = {}
|
|
25
|
+
|
|
26
|
+
class JSONResponse(dict): # type: ignore
|
|
27
|
+
def __init__(self, content: Any, status_code: int = 200):
|
|
28
|
+
super().__init__(content=content, status_code=status_code)
|
|
29
|
+
|
|
30
|
+
def Depends(fn: Callable[..., Any]): # type: ignore
|
|
31
|
+
return fn
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Callable, Optional
|
|
5
|
+
|
|
6
|
+
from .compat import Depends, JSONResponse, Request
|
|
7
|
+
from .utils import maybe_execute
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_healthz_endpoint(dep: Optional[Callable[..., Any]]):
|
|
13
|
+
"""
|
|
14
|
+
Returns a FastAPI endpoint function for /healthz.
|
|
15
|
+
If `dep` is provided, it's used as a dependency to supply `db`.
|
|
16
|
+
Otherwise, we try request.state.db.
|
|
17
|
+
"""
|
|
18
|
+
if dep is not None:
|
|
19
|
+
|
|
20
|
+
async def _healthz(db: Any = Depends(dep)):
|
|
21
|
+
try:
|
|
22
|
+
await maybe_execute(db, "SELECT 1")
|
|
23
|
+
return {"ok": True}
|
|
24
|
+
except Exception as e: # pragma: no cover
|
|
25
|
+
logger.exception("/healthz failed")
|
|
26
|
+
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
|
27
|
+
|
|
28
|
+
return _healthz
|
|
29
|
+
|
|
30
|
+
async def _healthz(request: Request):
|
|
31
|
+
db = getattr(request.state, "db", None)
|
|
32
|
+
if db is None:
|
|
33
|
+
return {"ok": True, "warning": "no-db"}
|
|
34
|
+
try:
|
|
35
|
+
await maybe_execute(db, "SELECT 1")
|
|
36
|
+
return {"ok": True}
|
|
37
|
+
except Exception as e: # pragma: no cover
|
|
38
|
+
logger.exception("/healthz failed")
|
|
39
|
+
return JSONResponse({"ok": False, "error": str(e)}, status_code=500)
|
|
40
|
+
|
|
41
|
+
return _healthz
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
from ...op.types import PHASES
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def build_hookz_endpoint(api: Any):
|
|
10
|
+
cache: Optional[Dict[str, Dict[str, Dict[str, List[str]]]]] = None
|
|
11
|
+
|
|
12
|
+
async def _hookz():
|
|
13
|
+
nonlocal cache
|
|
14
|
+
"""
|
|
15
|
+
Expose hook execution order for each method.
|
|
16
|
+
|
|
17
|
+
Phases appear in runner order; error phases trail.
|
|
18
|
+
Within each phase, hooks are listed in execution order: global (None) hooks,
|
|
19
|
+
then method-specific hooks.
|
|
20
|
+
"""
|
|
21
|
+
if cache is not None:
|
|
22
|
+
return cache
|
|
23
|
+
|
|
24
|
+
from . import _model_iter, _opspecs, _label_callable
|
|
25
|
+
|
|
26
|
+
out: Dict[str, Dict[str, Dict[str, List[str]]]] = {}
|
|
27
|
+
for model in _model_iter(api):
|
|
28
|
+
mname = getattr(model, "__name__", "Model")
|
|
29
|
+
hooks_root = getattr(model, "hooks", SimpleNamespace())
|
|
30
|
+
alias_sources = set()
|
|
31
|
+
rpc_ns = getattr(model, "rpc", SimpleNamespace())
|
|
32
|
+
alias_sources.update(getattr(rpc_ns, "__dict__", {}).keys())
|
|
33
|
+
for sp in _opspecs(model):
|
|
34
|
+
alias_sources.add(sp.alias)
|
|
35
|
+
|
|
36
|
+
model_map: Dict[str, Dict[str, List[str]]] = {}
|
|
37
|
+
for alias in sorted(alias_sources):
|
|
38
|
+
alias_ns = getattr(hooks_root, alias, None) or SimpleNamespace()
|
|
39
|
+
phase_map: Dict[str, List[str]] = {}
|
|
40
|
+
for ph in PHASES:
|
|
41
|
+
steps = list(getattr(alias_ns, ph, []) or [])
|
|
42
|
+
if steps:
|
|
43
|
+
phase_map[ph] = [_label_callable(fn) for fn in steps]
|
|
44
|
+
if phase_map:
|
|
45
|
+
model_map[alias] = phase_map
|
|
46
|
+
if model_map:
|
|
47
|
+
out[mname] = model_map
|
|
48
|
+
cache = out
|
|
49
|
+
return cache
|
|
50
|
+
|
|
51
|
+
return _hookz
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Diagnostic endpoint exposing kernel phase plans."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from ...runtime.kernel import _default_kernel as K
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def build_kernelz_endpoint(api: Any):
|
|
11
|
+
"""Return an async handler that serves the Kernel's cached plan."""
|
|
12
|
+
|
|
13
|
+
async def _kernelz():
|
|
14
|
+
K.ensure_primed(api)
|
|
15
|
+
return K.kernelz_payload(api)
|
|
16
|
+
|
|
17
|
+
return _kernelz
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = ["build_kernelz_endpoint"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def build_methodz_endpoint(api: Any):
|
|
7
|
+
cache: Optional[Dict[str, List[Dict[str, Any]]]] = None
|
|
8
|
+
|
|
9
|
+
async def _methodz():
|
|
10
|
+
nonlocal cache
|
|
11
|
+
"""Ordered, canonical operation list."""
|
|
12
|
+
if cache is not None:
|
|
13
|
+
return cache
|
|
14
|
+
|
|
15
|
+
from . import _model_iter, _opspecs
|
|
16
|
+
|
|
17
|
+
methods: List[Dict[str, Any]] = []
|
|
18
|
+
for model in _model_iter(api):
|
|
19
|
+
mname = getattr(model, "__name__", "Model")
|
|
20
|
+
for sp in _opspecs(model):
|
|
21
|
+
if not getattr(sp, "expose_rpc", True):
|
|
22
|
+
continue
|
|
23
|
+
methods.append(
|
|
24
|
+
{
|
|
25
|
+
"method": f"{mname}.{sp.alias}",
|
|
26
|
+
"model": mname,
|
|
27
|
+
"alias": sp.alias,
|
|
28
|
+
"target": sp.target,
|
|
29
|
+
"arity": sp.arity,
|
|
30
|
+
"persist": sp.persist,
|
|
31
|
+
"request_model": getattr(sp, "request_model", None) is not None,
|
|
32
|
+
"response_model": getattr(sp, "response_model", None)
|
|
33
|
+
is not None,
|
|
34
|
+
"routes": bool(getattr(sp, "expose_routes", True)),
|
|
35
|
+
"rpc": bool(getattr(sp, "expose_rpc", True)),
|
|
36
|
+
"tags": list(getattr(sp, "tags", ()) or (mname,)),
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
methods.sort(key=lambda x: (x["model"], x["alias"]))
|
|
40
|
+
cache = {"methods": methods}
|
|
41
|
+
return cache
|
|
42
|
+
|
|
43
|
+
return _methodz
|