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
tigrbl/runtime/system.py
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# tigrbl/v3/runtime/system.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import inspect
|
|
6
|
+
from typing import Any, Callable, Dict, Mapping, MutableMapping, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
from . import errors as _err
|
|
9
|
+
from .executor.helpers import _in_tx, _is_async_db
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
# Canonical anchors for system steps (ordering uses these symbolic anchors)
|
|
14
|
+
START_TX = "START_TX"
|
|
15
|
+
HANDLER = "HANDLER"
|
|
16
|
+
END_TX = "END_TX"
|
|
17
|
+
|
|
18
|
+
# Runner signature (matches atoms): (obj|None, ctx) -> None
|
|
19
|
+
SysRunFn = Callable[[Optional[object], Any], None]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
# Pluggable runners (adapters install real implementations at app startup)
|
|
24
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _Installed:
|
|
28
|
+
begin: Optional[Callable[[Any], None]] = None # (ctx) -> None
|
|
29
|
+
handler: Optional[Callable[[Optional[object], Any], None]] = (
|
|
30
|
+
None # (obj, ctx) -> None
|
|
31
|
+
)
|
|
32
|
+
commit: Optional[Callable[[Any], None]] = None # (ctx) -> None
|
|
33
|
+
rollback: Optional[Callable[[Any, BaseException | None], None]] = (
|
|
34
|
+
None # (ctx, err) -> None
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
INSTALLED = _Installed() # singleton container
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def install(
|
|
42
|
+
*,
|
|
43
|
+
begin: Optional[Callable[[Any], None]] = None,
|
|
44
|
+
handler: Optional[Callable[[Optional[object], Any], None]] = None,
|
|
45
|
+
commit: Optional[Callable[[Any], None]] = None,
|
|
46
|
+
rollback: Optional[Callable[[Any, BaseException | None], None]] = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Adapter entrypoint to install concrete system step functions.
|
|
50
|
+
|
|
51
|
+
Example (SQLAlchemy-ish pseudocode):
|
|
52
|
+
from tigrbl.runtime import system as sys
|
|
53
|
+
sys.install(
|
|
54
|
+
begin=lambda ctx: ctx.session.begin(),
|
|
55
|
+
handler=lambda obj, ctx: ctx.route_handler(obj, ctx),
|
|
56
|
+
commit=lambda ctx: ctx.session.commit(),
|
|
57
|
+
rollback=lambda ctx, err: ctx.session.rollback(),
|
|
58
|
+
)
|
|
59
|
+
"""
|
|
60
|
+
if begin is not None:
|
|
61
|
+
INSTALLED.begin = begin
|
|
62
|
+
if handler is not None:
|
|
63
|
+
INSTALLED.handler = handler
|
|
64
|
+
if commit is not None:
|
|
65
|
+
INSTALLED.commit = commit
|
|
66
|
+
if rollback is not None:
|
|
67
|
+
INSTALLED.rollback = rollback
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
71
|
+
# Default implementations (safe no-ops except handler, which errs if missing)
|
|
72
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _sys_tx_begin(_obj: Optional[object], ctx: Any) -> None:
|
|
76
|
+
"""
|
|
77
|
+
sys:txn:begin — open a transaction/savepoint if the adapter installed a runner.
|
|
78
|
+
Defaults to no-op; sets a small flag for diagnostics.
|
|
79
|
+
"""
|
|
80
|
+
log.debug("system: begin_tx enter")
|
|
81
|
+
_ensure_temp(ctx)
|
|
82
|
+
has_open = any(
|
|
83
|
+
callable(fn) for fn in (INSTALLED.begin, INSTALLED.commit, INSTALLED.rollback)
|
|
84
|
+
)
|
|
85
|
+
ctx.temp["__sys_tx_open__"] = has_open
|
|
86
|
+
try:
|
|
87
|
+
if callable(INSTALLED.begin):
|
|
88
|
+
INSTALLED.begin(ctx)
|
|
89
|
+
log.debug("system: begin_tx executed.")
|
|
90
|
+
else:
|
|
91
|
+
log.debug("system: begin_tx no-op (no adapter installed).")
|
|
92
|
+
except Exception as e: # escalate as typed error
|
|
93
|
+
ctx.temp["__sys_tx_open__"] = False
|
|
94
|
+
raise _err.SystemStepError("Failed to begin transaction.", cause=e)
|
|
95
|
+
finally:
|
|
96
|
+
log.debug("system: begin_tx exit")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _sys_handler_crud(obj: Optional[object], ctx: Any) -> None:
|
|
100
|
+
"""
|
|
101
|
+
sys:handler:crud — invoke the model/router handler.
|
|
102
|
+
Resolution order:
|
|
103
|
+
1) Installed adapter runner (INSTALLED.handler)
|
|
104
|
+
2) ctx.temp['handler'] if callable
|
|
105
|
+
3) getattr(ctx, 'handler')
|
|
106
|
+
4) getattr(ctx.model, 'runtime').handler or ctx.model.handler
|
|
107
|
+
On total miss, raises SystemStepError.
|
|
108
|
+
"""
|
|
109
|
+
try:
|
|
110
|
+
# 1) Adapter-installed
|
|
111
|
+
if callable(INSTALLED.handler):
|
|
112
|
+
return INSTALLED.handler(obj, ctx)
|
|
113
|
+
|
|
114
|
+
# 2) From ctx.temp (explicitly staged by adapter)
|
|
115
|
+
h = _get_temp(ctx).get("handler")
|
|
116
|
+
if callable(h):
|
|
117
|
+
return h(obj, ctx)
|
|
118
|
+
|
|
119
|
+
# 3) Directly on ctx (adapters may set this)
|
|
120
|
+
h = getattr(ctx, "handler", None)
|
|
121
|
+
if callable(h):
|
|
122
|
+
return h(obj, ctx)
|
|
123
|
+
|
|
124
|
+
# 4) On the model (common pattern: Model.runtime.handler / Model.handler)
|
|
125
|
+
mdl = getattr(ctx, "model", None)
|
|
126
|
+
if mdl is not None:
|
|
127
|
+
r = getattr(getattr(mdl, "runtime", None), "handler", None)
|
|
128
|
+
if callable(r):
|
|
129
|
+
return r(obj, ctx)
|
|
130
|
+
r = getattr(mdl, "handler", None)
|
|
131
|
+
if callable(r):
|
|
132
|
+
return r(obj, ctx)
|
|
133
|
+
|
|
134
|
+
# No handler found
|
|
135
|
+
raise _err.SystemStepError(
|
|
136
|
+
"No handler is installed or discoverable for this operation."
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
except _err.TigrblError:
|
|
140
|
+
# Pass through typed errors intact
|
|
141
|
+
raise
|
|
142
|
+
except Exception as e:
|
|
143
|
+
raise _err.SystemStepError("Handler execution failed.", cause=e)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def _sys_tx_commit(_obj: Optional[object], ctx: Any) -> None:
|
|
147
|
+
"""
|
|
148
|
+
sys:txn:commit — commit the transaction if begin ran and adapter installed commit.
|
|
149
|
+
Defaults to no-op; clears the 'open' flag.
|
|
150
|
+
"""
|
|
151
|
+
log.debug("system: commit_tx enter")
|
|
152
|
+
_ensure_temp(ctx)
|
|
153
|
+
db = getattr(ctx, "db", None)
|
|
154
|
+
open_flag = bool(ctx.temp.get("__sys_tx_open__")) or (db is not None and _in_tx(db))
|
|
155
|
+
try:
|
|
156
|
+
if open_flag:
|
|
157
|
+
if callable(INSTALLED.commit):
|
|
158
|
+
rv = INSTALLED.commit(ctx)
|
|
159
|
+
if inspect.isawaitable(rv):
|
|
160
|
+
await rv # type: ignore[func-returns-value]
|
|
161
|
+
log.debug("system: commit_tx executed.")
|
|
162
|
+
else:
|
|
163
|
+
log.debug("system: commit_tx no-op (no adapter commit).")
|
|
164
|
+
|
|
165
|
+
if db is not None and _in_tx(db):
|
|
166
|
+
log.debug("system: commit_tx fallback commit executing.")
|
|
167
|
+
commit = getattr(db, "commit", None)
|
|
168
|
+
if callable(commit):
|
|
169
|
+
try:
|
|
170
|
+
if _is_async_db(db):
|
|
171
|
+
await commit() # type: ignore[misc]
|
|
172
|
+
else:
|
|
173
|
+
commit()
|
|
174
|
+
log.debug("system: commit_tx fallback commit succeeded.")
|
|
175
|
+
except Exception as e: # pragma: no cover - defensive safeguard
|
|
176
|
+
log.exception("system: commit_tx fallback commit failed: %s", e)
|
|
177
|
+
else:
|
|
178
|
+
log.debug(
|
|
179
|
+
"system: commit_tx fallback commit not possible (no commit attr)."
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
log.debug("system: commit_tx no-op (open=%s).", open_flag)
|
|
183
|
+
except Exception as e:
|
|
184
|
+
raise _err.SystemStepError("Failed to commit transaction.", cause=e)
|
|
185
|
+
finally:
|
|
186
|
+
ctx.temp["__sys_tx_open__"] = False
|
|
187
|
+
log.debug("system: commit_tx exit")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def run_rollback(ctx: Any, err: BaseException | None = None) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Execute rollback logic when the kernel catches an exception.
|
|
193
|
+
Always safe to call (even if no begin was executed).
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
if callable(INSTALLED.rollback):
|
|
197
|
+
INSTALLED.rollback(ctx, err)
|
|
198
|
+
log.debug("system: rollback executed.")
|
|
199
|
+
else:
|
|
200
|
+
# Best-effort fallback: try common session attribute
|
|
201
|
+
sess = getattr(ctx, "session", None)
|
|
202
|
+
rb = getattr(sess, "rollback", None)
|
|
203
|
+
if callable(rb):
|
|
204
|
+
rb()
|
|
205
|
+
log.debug("system: rollback via ctx.session.rollback().")
|
|
206
|
+
except Exception as e: # Never mask the original error; log only.
|
|
207
|
+
log.exception("system: rollback failed: %s", e)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
211
|
+
# Registry & dispatch (mirrors atoms registry shape for consistency)
|
|
212
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
REGISTRY: Dict[Tuple[str, str], Tuple[str, SysRunFn]] = {
|
|
215
|
+
("txn", "begin"): (START_TX, _sys_tx_begin),
|
|
216
|
+
("handler", "crud"): (HANDLER, _sys_handler_crud),
|
|
217
|
+
("txn", "commit"): (END_TX, _sys_tx_commit),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def get(domain: str, subject: str) -> Tuple[str, SysRunFn]:
|
|
222
|
+
"""
|
|
223
|
+
Return (anchor, runner) for a given system step, e.g., get('txn','begin').
|
|
224
|
+
"""
|
|
225
|
+
key = (domain, subject)
|
|
226
|
+
if key not in REGISTRY:
|
|
227
|
+
raise KeyError(f"Unknown system step: {domain}:{subject}")
|
|
228
|
+
return REGISTRY[key]
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def subjects(domain: str | None = None) -> Tuple[Tuple[str, str], ...]:
|
|
232
|
+
"""
|
|
233
|
+
Return the available (domain, subject) tuples, optionally filtered by domain.
|
|
234
|
+
"""
|
|
235
|
+
items = tuple(sorted(REGISTRY.keys()))
|
|
236
|
+
if domain is None:
|
|
237
|
+
return items
|
|
238
|
+
return tuple(k for k in items if k[0] == domain)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def all_items() -> Tuple[Tuple[Tuple[str, str], Tuple[str, SysRunFn]], ...]:
|
|
242
|
+
"""Return the registry items as a sorted tuple (deterministic iteration)."""
|
|
243
|
+
return tuple(sorted(REGISTRY.items(), key=lambda kv: (kv[0][0], kv[0][1])))
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
247
|
+
# Internals
|
|
248
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _ensure_temp(ctx: Any) -> MutableMapping[str, Any]:
|
|
252
|
+
tmp = getattr(ctx, "temp", None)
|
|
253
|
+
if not isinstance(tmp, dict):
|
|
254
|
+
tmp = {}
|
|
255
|
+
setattr(ctx, "temp", tmp)
|
|
256
|
+
return tmp
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _get_temp(ctx: Any) -> Mapping[str, Any]:
|
|
260
|
+
tmp = getattr(ctx, "temp", None)
|
|
261
|
+
return tmp if isinstance(tmp, Mapping) else {}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
__all__ = [
|
|
265
|
+
# anchors
|
|
266
|
+
"START_TX",
|
|
267
|
+
"HANDLER",
|
|
268
|
+
"END_TX",
|
|
269
|
+
# install surface
|
|
270
|
+
"install",
|
|
271
|
+
"INSTALLED",
|
|
272
|
+
# rollback
|
|
273
|
+
"run_rollback",
|
|
274
|
+
# registry facade
|
|
275
|
+
"REGISTRY",
|
|
276
|
+
"get",
|
|
277
|
+
"subjects",
|
|
278
|
+
"all_items",
|
|
279
|
+
]
|
tigrbl/runtime/trace.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# tigrbl/v3/runtime/trace.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import datetime as _dt
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any, Dict, Mapping, MutableMapping, Optional, Sequence, Tuple
|
|
10
|
+
|
|
11
|
+
# Public status constants
|
|
12
|
+
OK = "ok"
|
|
13
|
+
ERROR = "error"
|
|
14
|
+
SKIPPED = "skipped"
|
|
15
|
+
|
|
16
|
+
# Soft caps to avoid unbounded growth
|
|
17
|
+
_MAX_STEPS = 5000
|
|
18
|
+
_MAX_EVENTS = 2000
|
|
19
|
+
_MAX_KV_KEYS = 16
|
|
20
|
+
_MAX_SCALAR_LEN = 256
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class _TraceState:
|
|
25
|
+
enabled: bool = True
|
|
26
|
+
sampled: bool = True
|
|
27
|
+
started_at: _dt.datetime = field(
|
|
28
|
+
default_factory=lambda: _dt.datetime.now(_dt.timezone.utc)
|
|
29
|
+
)
|
|
30
|
+
t0: float = field(default_factory=time.perf_counter)
|
|
31
|
+
seq: int = 0
|
|
32
|
+
steps: list[Dict[str, Any]] = field(default_factory=list) # closed steps
|
|
33
|
+
events: list[Dict[str, Any]] = field(default_factory=list) # loose events
|
|
34
|
+
open: Dict[int, Tuple[str, float, Dict[str, Any]]] = field(
|
|
35
|
+
default_factory=dict
|
|
36
|
+
) # seq -> (label, t_start, base_kv)
|
|
37
|
+
plan_labels: Tuple[
|
|
38
|
+
str, ...
|
|
39
|
+
] = () # optional: the full ordered plan (for diagnostics)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
43
|
+
# Public API
|
|
44
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def init(ctx: Any, *, plan_labels: Optional[Sequence[str]] = None) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Initialize tracing for this context. Called once by the kernel before executing the plan.
|
|
50
|
+
Respects cfg.trace.enabled and cfg.trace.sample_rate (0..1).
|
|
51
|
+
"""
|
|
52
|
+
st = _get_state(ctx, create=True)
|
|
53
|
+
|
|
54
|
+
# Read config (tolerant to missing cfg/attrs)
|
|
55
|
+
cfg = getattr(ctx, "cfg", None)
|
|
56
|
+
enabled = True
|
|
57
|
+
sample_rate = 1.0
|
|
58
|
+
tr = getattr(cfg, "trace", None)
|
|
59
|
+
if tr is not None:
|
|
60
|
+
en = getattr(tr, "enabled", None)
|
|
61
|
+
if isinstance(en, bool):
|
|
62
|
+
enabled = en
|
|
63
|
+
sr = getattr(tr, "sample_rate", None)
|
|
64
|
+
if isinstance(sr, (int, float)):
|
|
65
|
+
sample_rate = max(0.0, min(1.0, float(sr)))
|
|
66
|
+
|
|
67
|
+
st.enabled = bool(enabled)
|
|
68
|
+
st.sampled = (random.random() < sample_rate) if st.enabled else False
|
|
69
|
+
|
|
70
|
+
if plan_labels:
|
|
71
|
+
st.plan_labels = tuple(str(x) for x in plan_labels[:_MAX_STEPS])
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def set_enabled(ctx: Any, enabled: bool) -> None:
|
|
75
|
+
"""Force tracing on/off for this context."""
|
|
76
|
+
st = _get_state(ctx, create=True)
|
|
77
|
+
st.enabled = bool(enabled)
|
|
78
|
+
if not st.enabled:
|
|
79
|
+
st.sampled = False
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def start(ctx: Any, label: str, **kv: Any) -> int | None:
|
|
83
|
+
"""
|
|
84
|
+
Start a trace span for a plan label. Returns a step id (seq) you should pass to end().
|
|
85
|
+
If tracing is disabled or not sampled, returns None.
|
|
86
|
+
"""
|
|
87
|
+
st = _get_state(ctx)
|
|
88
|
+
if not _active(st):
|
|
89
|
+
return None
|
|
90
|
+
if len(st.steps) + len(st.open) >= _MAX_STEPS:
|
|
91
|
+
return None # soft drop
|
|
92
|
+
|
|
93
|
+
seq = st.seq = st.seq + 1
|
|
94
|
+
base = _base_entry(label)
|
|
95
|
+
base["seq"] = seq
|
|
96
|
+
base.update(_safe_kv(kv))
|
|
97
|
+
|
|
98
|
+
t_start = time.perf_counter()
|
|
99
|
+
st.open[seq] = (label, t_start, base)
|
|
100
|
+
return seq
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def end(
|
|
104
|
+
ctx: Any, seq: int | None, status: str = OK, **kv: Any
|
|
105
|
+
) -> Optional[Dict[str, Any]]:
|
|
106
|
+
"""
|
|
107
|
+
End a previously started span by its seq id. Returns the finalized step dict.
|
|
108
|
+
If seq is None or tracing is inactive, this is a no-op.
|
|
109
|
+
"""
|
|
110
|
+
st = _get_state(ctx)
|
|
111
|
+
if not _active(st) or seq is None:
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
opened = st.open.pop(seq, None)
|
|
115
|
+
if opened is None:
|
|
116
|
+
return None # already closed or never started
|
|
117
|
+
|
|
118
|
+
_label, t_start, base = opened
|
|
119
|
+
dur_ms = max(0.0, (time.perf_counter() - t_start) * 1000.0)
|
|
120
|
+
|
|
121
|
+
entry = dict(base)
|
|
122
|
+
entry["status"] = str(status or OK)
|
|
123
|
+
entry["dur_ms"] = round(dur_ms, 3)
|
|
124
|
+
# merge extra kv (sanitized), without clobbering base keys unintentionally
|
|
125
|
+
entry.update(_safe_kv(kv))
|
|
126
|
+
|
|
127
|
+
if len(st.steps) < _MAX_STEPS:
|
|
128
|
+
st.steps.append(entry)
|
|
129
|
+
return entry
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def event(ctx: Any, name: str, **kv: Any) -> None:
|
|
133
|
+
"""Record an ad-hoc event (not tied to a plan label)."""
|
|
134
|
+
st = _get_state(ctx)
|
|
135
|
+
if not _active(st) or len(st.events) >= _MAX_EVENTS:
|
|
136
|
+
return
|
|
137
|
+
st.events.append(
|
|
138
|
+
{
|
|
139
|
+
"ts": _iso_now(),
|
|
140
|
+
"name": str(name),
|
|
141
|
+
**_safe_kv(kv),
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def attach_error(ctx: Any, seq: int | None, exc: BaseException) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Attach a compact, non-sensitive error summary to an open/closed step.
|
|
149
|
+
If `seq` is None or cannot be found, emits a free-standing 'error' event instead.
|
|
150
|
+
"""
|
|
151
|
+
info = {
|
|
152
|
+
"err_type": exc.__class__.__name__,
|
|
153
|
+
"err_msg": _safe_scalar(getattr(exc, "detail", None)) or _safe_scalar(str(exc)),
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
st = _get_state(ctx)
|
|
157
|
+
if not _active(st):
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# Try to update an open step first
|
|
161
|
+
if seq is not None and seq in st.open:
|
|
162
|
+
label, t_start, base = st.open[seq]
|
|
163
|
+
base.setdefault("error", info)
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
# Try to update the last closed step with matching seq
|
|
167
|
+
found = None
|
|
168
|
+
if seq is not None:
|
|
169
|
+
for step in reversed(st.steps):
|
|
170
|
+
if step.get("seq") == seq:
|
|
171
|
+
found = step
|
|
172
|
+
break
|
|
173
|
+
if found is not None:
|
|
174
|
+
found.setdefault("error", info)
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
# Fallback: emit an event
|
|
178
|
+
event(ctx, "error", **info)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def snapshot(ctx: Any) -> Dict[str, Any]:
|
|
182
|
+
"""
|
|
183
|
+
Return a structured snapshot of the trace suitable for diagnostics endpoints.
|
|
184
|
+
"""
|
|
185
|
+
st = _get_state(ctx)
|
|
186
|
+
total_ms = max(0.0, (time.perf_counter() - st.t0) * 1000.0)
|
|
187
|
+
return {
|
|
188
|
+
"enabled": st.enabled,
|
|
189
|
+
"sampled": st.sampled,
|
|
190
|
+
"started_at": st.started_at.isoformat(),
|
|
191
|
+
"duration_ms": round(total_ms, 3),
|
|
192
|
+
"counts": {
|
|
193
|
+
"steps": len(st.steps),
|
|
194
|
+
"open": len(st.open),
|
|
195
|
+
"events": len(st.events),
|
|
196
|
+
},
|
|
197
|
+
"plan": st.plan_labels,
|
|
198
|
+
"steps": tuple(st.steps), # immutable copy for consumers
|
|
199
|
+
"events": tuple(st.events),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def clear(ctx: Any) -> None:
|
|
204
|
+
"""Erase all trace data for this context."""
|
|
205
|
+
tmp = _ensure_temp(ctx)
|
|
206
|
+
tmp.pop("__trace__", None)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@contextmanager
|
|
210
|
+
def span(ctx: Any, label: str, **kv: Any):
|
|
211
|
+
"""
|
|
212
|
+
Context-manager convenience for start()/end(). On exception, marks status=error
|
|
213
|
+
and attaches a compact error summary; then re-raises.
|
|
214
|
+
"""
|
|
215
|
+
seq = start(ctx, label, **kv)
|
|
216
|
+
try:
|
|
217
|
+
yield seq
|
|
218
|
+
end(ctx, seq, status=OK)
|
|
219
|
+
except Exception as e: # pragma: no cover (kernel normally handles)
|
|
220
|
+
attach_error(ctx, seq, e)
|
|
221
|
+
end(ctx, seq, status=ERROR)
|
|
222
|
+
raise
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
226
|
+
# Internals
|
|
227
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _ensure_temp(ctx: Any) -> MutableMapping[str, Any]:
|
|
231
|
+
tmp = getattr(ctx, "temp", None)
|
|
232
|
+
if not isinstance(tmp, dict):
|
|
233
|
+
tmp = {}
|
|
234
|
+
setattr(ctx, "temp", tmp)
|
|
235
|
+
return tmp
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _get_state(ctx: Any, *, create: bool = False) -> _TraceState:
|
|
239
|
+
tmp = _ensure_temp(ctx)
|
|
240
|
+
st = tmp.get("__trace__")
|
|
241
|
+
if isinstance(st, _TraceState):
|
|
242
|
+
return st
|
|
243
|
+
if create or st is None:
|
|
244
|
+
st = _TraceState()
|
|
245
|
+
tmp["__trace__"] = st
|
|
246
|
+
return st
|
|
247
|
+
# If someone stuffed a dict there, replace with a fresh state.
|
|
248
|
+
st = _TraceState()
|
|
249
|
+
tmp["__trace__"] = st
|
|
250
|
+
return st
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _active(st: _TraceState) -> bool:
|
|
254
|
+
return bool(st.enabled and st.sampled)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _iso_now() -> str:
|
|
258
|
+
return _dt.datetime.now(_dt.timezone.utc).isoformat()
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _parse_label(label: str) -> Dict[str, Any]:
|
|
262
|
+
"""
|
|
263
|
+
Parse step_kind:domain:subject@anchor#field into parts.
|
|
264
|
+
Tolerant: any segment may be missing.
|
|
265
|
+
"""
|
|
266
|
+
out = {"label": str(label)}
|
|
267
|
+
left, anchor = (label.split("@", 1) + [""])[:2]
|
|
268
|
+
field = None
|
|
269
|
+
if "#" in anchor:
|
|
270
|
+
anchor, field = anchor.split("#", 1)
|
|
271
|
+
parts = left.split(":")
|
|
272
|
+
if parts:
|
|
273
|
+
out["step_kind"] = parts[0] or None
|
|
274
|
+
if len(parts) > 1:
|
|
275
|
+
out["domain"] = parts[1] or None
|
|
276
|
+
if len(parts) > 2:
|
|
277
|
+
out["subject"] = ":".join(parts[2:]) or None # tolerate extra colons
|
|
278
|
+
out["anchor"] = anchor or None
|
|
279
|
+
out["field"] = field or None
|
|
280
|
+
return out
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _base_entry(label: str) -> Dict[str, Any]:
|
|
284
|
+
entry = _parse_label(label)
|
|
285
|
+
entry["ts"] = _iso_now()
|
|
286
|
+
return entry
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _safe_kv(kv: Mapping[str, Any]) -> Dict[str, Any]:
|
|
290
|
+
"""
|
|
291
|
+
Sanitize and cap key/values:
|
|
292
|
+
- keep at most _MAX_KV_KEYS keys
|
|
293
|
+
- value → scalar (bool/int/float/str<=N) or a short type tag
|
|
294
|
+
"""
|
|
295
|
+
out: Dict[str, Any] = {}
|
|
296
|
+
for i, (k, v) in enumerate(kv.items()):
|
|
297
|
+
if i >= _MAX_KV_KEYS:
|
|
298
|
+
out["_kv_truncated"] = True
|
|
299
|
+
break
|
|
300
|
+
out[str(k)] = _safe_scalar(v)
|
|
301
|
+
return out
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _safe_scalar(v: Any) -> Any:
|
|
305
|
+
if v is None:
|
|
306
|
+
return None
|
|
307
|
+
if isinstance(v, (bool, int, float)):
|
|
308
|
+
return v
|
|
309
|
+
if isinstance(v, (bytes, bytearray, memoryview)):
|
|
310
|
+
return f"<{type(v).__name__}:{len(v)}B>"
|
|
311
|
+
s = str(v)
|
|
312
|
+
if len(s) > _MAX_SCALAR_LEN:
|
|
313
|
+
s = s[: _MAX_SCALAR_LEN - 1] + "…"
|
|
314
|
+
return s
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
__all__ = [
|
|
318
|
+
"OK",
|
|
319
|
+
"ERROR",
|
|
320
|
+
"SKIPPED",
|
|
321
|
+
"init",
|
|
322
|
+
"set_enabled",
|
|
323
|
+
"start",
|
|
324
|
+
"end",
|
|
325
|
+
"event",
|
|
326
|
+
"attach_error",
|
|
327
|
+
"snapshot",
|
|
328
|
+
"clear",
|
|
329
|
+
"span",
|
|
330
|
+
]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# tigrbl/v3/schema/__init__.py
|
|
2
|
+
from .builder import _build_schema, _build_list_params
|
|
3
|
+
from .utils import (
|
|
4
|
+
namely_model,
|
|
5
|
+
_make_bulk_rows_model,
|
|
6
|
+
_make_bulk_rows_response_model,
|
|
7
|
+
_make_bulk_ids_model,
|
|
8
|
+
_make_deleted_response_model,
|
|
9
|
+
_make_pk_model,
|
|
10
|
+
)
|
|
11
|
+
from .get_schema import get_schema
|
|
12
|
+
from .decorators import schema_ctx
|
|
13
|
+
from .collect import collect_decorated_schemas
|
|
14
|
+
from ._schema import Schema
|
|
15
|
+
from .schema_spec import SchemaSpec
|
|
16
|
+
from .shortcuts import schema, schema_spec
|
|
17
|
+
from .types import SchemaRef, SchemaArg, SchemaKind
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"_build_schema",
|
|
21
|
+
"_build_list_params",
|
|
22
|
+
"namely_model",
|
|
23
|
+
"_make_bulk_rows_model",
|
|
24
|
+
"_make_bulk_rows_response_model",
|
|
25
|
+
"_make_bulk_ids_model",
|
|
26
|
+
"_make_deleted_response_model",
|
|
27
|
+
"_make_pk_model",
|
|
28
|
+
"get_schema",
|
|
29
|
+
"schema_ctx",
|
|
30
|
+
"collect_decorated_schemas",
|
|
31
|
+
"Schema",
|
|
32
|
+
"SchemaSpec",
|
|
33
|
+
"schema",
|
|
34
|
+
"schema_spec",
|
|
35
|
+
"SchemaRef",
|
|
36
|
+
"SchemaArg",
|
|
37
|
+
"SchemaKind",
|
|
38
|
+
]
|
tigrbl/schema/_schema.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# tigrbl/v3/schema/_schema.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Type
|
|
6
|
+
|
|
7
|
+
try: # pragma: no cover
|
|
8
|
+
from pydantic import BaseModel # type: ignore
|
|
9
|
+
except Exception: # pragma: no cover
|
|
10
|
+
|
|
11
|
+
class BaseModel: # minimal stub for typing only
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from .types import SchemaKind
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True, slots=True)
|
|
19
|
+
class Schema:
|
|
20
|
+
"""Concrete schema paired with its alias and kind."""
|
|
21
|
+
|
|
22
|
+
model: Type[BaseModel]
|
|
23
|
+
kind: SchemaKind = "out"
|
|
24
|
+
alias: str | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ["Schema"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Schema builder package for Tigrbl v3."""
|
|
2
|
+
|
|
3
|
+
from .cache import _SchemaCache, _SchemaVerb
|
|
4
|
+
from .extras import _merge_request_extras, _merge_response_extras
|
|
5
|
+
from .build_schema import _build_schema
|
|
6
|
+
from .list_params import _build_list_params
|
|
7
|
+
from .strip_parent_fields import _strip_parent_fields
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"_build_schema",
|
|
11
|
+
"_build_list_params",
|
|
12
|
+
"_strip_parent_fields",
|
|
13
|
+
"_merge_request_extras",
|
|
14
|
+
"_merge_response_extras",
|
|
15
|
+
"_SchemaCache",
|
|
16
|
+
"_SchemaVerb",
|
|
17
|
+
]
|