tigrbl-kernel 0.4.2.dev4__py3-none-any.whl → 0.4.3.dev4__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_kernel/__init__.py +1 -19
- tigrbl_kernel/_build.py +1 -1
- tigrbl_kernel/_compile.py +100 -1
- tigrbl_kernel/channel_taxonomy.py +142 -0
- tigrbl_kernel/cross_transport.py +167 -0
- tigrbl_kernel/dispatch_taxonomy.py +47 -0
- tigrbl_kernel/events.py +10 -0
- tigrbl_kernel/helpers.py +2 -2
- tigrbl_kernel/loop_modes.py +19 -1
- tigrbl_kernel/measure.py +1 -1
- tigrbl_kernel/ordering.py +1 -0
- tigrbl_kernel/packed_access.py +131 -0
- tigrbl_kernel/packed_selectors.py +239 -0
- tigrbl_kernel/protocol_anchors.py +49 -0
- tigrbl_kernel/protocol_bindings.py +26 -7
- tigrbl_kernel/protocol_chains/http_stream.py +35 -2
- tigrbl_kernel/protocol_streams.py +470 -0
- tigrbl_kernel/resume_policy.py +78 -0
- tigrbl_kernel/runtime_contracts.py +254 -0
- tigrbl_kernel/rust_compile.py +11 -27
- tigrbl_kernel/rust_plan.py +9 -3
- tigrbl_kernel/rust_spec.py +15 -398
- tigrbl_kernel/transport_events.py +5 -2
- tigrbl_kernel/types.py +1 -0
- tigrbl_kernel/webtransport_events.py +65 -3
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/METADATA +74 -5
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/RECORD +30 -21
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/WHEEL +0 -0
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/licenses/LICENSE +0 -0
- {tigrbl_kernel-0.4.2.dev4.dist-info → tigrbl_kernel-0.4.3.dev4.dist-info}/licenses/NOTICE +0 -0
tigrbl_kernel/rust_spec.py
CHANGED
|
@@ -1,415 +1,32 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from
|
|
5
|
-
from types import SimpleNamespace
|
|
6
|
-
from typing import Any
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Any, NoReturn
|
|
7
5
|
|
|
8
|
-
from tigrbl_core.config.constants import (
|
|
9
|
-
TIGRBL_DEFAULT_ROOT_ALIAS,
|
|
10
|
-
TIGRBL_DEFAULT_ROOT_METHOD,
|
|
11
|
-
TIGRBL_DEFAULT_ROOT_PATH,
|
|
12
|
-
)
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
tables, (str, bytes, bytearray)
|
|
21
|
-
):
|
|
22
|
-
return tuple(tables)
|
|
23
|
-
return ()
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _opspecs(model: Any) -> tuple[Any, ...]:
|
|
27
|
-
ops = getattr(getattr(model, "opspecs", SimpleNamespace()), "all", ()) or ()
|
|
28
|
-
if ops:
|
|
29
|
-
return tuple(ops)
|
|
30
|
-
|
|
31
|
-
table_ops = getattr(model, "ops", None)
|
|
32
|
-
if table_ops is not None:
|
|
33
|
-
by_alias = getattr(table_ops, "by_alias", None)
|
|
34
|
-
if isinstance(by_alias, Mapping) and by_alias:
|
|
35
|
-
flattened: list[Any] = []
|
|
36
|
-
for specs in by_alias.values():
|
|
37
|
-
if specs is None:
|
|
38
|
-
continue
|
|
39
|
-
if isinstance(specs, Sequence) and not isinstance(
|
|
40
|
-
specs, (str, bytes, bytearray)
|
|
41
|
-
):
|
|
42
|
-
flattened.extend(tuple(specs))
|
|
43
|
-
continue
|
|
44
|
-
alias = getattr(specs, "alias", None)
|
|
45
|
-
target = getattr(specs, "target", None)
|
|
46
|
-
if alias is not None and target is not None:
|
|
47
|
-
flattened.append(specs)
|
|
48
|
-
if flattened:
|
|
49
|
-
return tuple(flattened)
|
|
50
|
-
all_ops = getattr(table_ops, "all", ()) or ()
|
|
51
|
-
if all_ops:
|
|
52
|
-
return tuple(all_ops)
|
|
53
|
-
if isinstance(table_ops, Sequence) and not isinstance(
|
|
54
|
-
table_ops, (str, bytes, bytearray)
|
|
55
|
-
):
|
|
56
|
-
return tuple(table_ops)
|
|
57
|
-
|
|
58
|
-
declared_ops = getattr(model, "__tigrbl_ops__", ()) or ()
|
|
59
|
-
if declared_ops:
|
|
60
|
-
return tuple(declared_ops)
|
|
61
|
-
|
|
62
|
-
return ()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _as_mapping(app: Any) -> dict[str, Any]:
|
|
66
|
-
if isinstance(app, str):
|
|
67
|
-
payload = json.loads(app)
|
|
68
|
-
if not isinstance(payload, dict):
|
|
69
|
-
raise TypeError("rust runtime expects a mapping or JSON object")
|
|
70
|
-
return payload
|
|
71
|
-
if isinstance(app, Mapping):
|
|
72
|
-
return dict(app)
|
|
73
|
-
return {
|
|
74
|
-
"name": getattr(
|
|
75
|
-
app,
|
|
76
|
-
"name",
|
|
77
|
-
getattr(
|
|
78
|
-
app,
|
|
79
|
-
"title",
|
|
80
|
-
getattr(app, "TITLE", app.__class__.__name__),
|
|
81
|
-
),
|
|
82
|
-
),
|
|
83
|
-
"title": getattr(
|
|
84
|
-
app,
|
|
85
|
-
"title",
|
|
86
|
-
getattr(
|
|
87
|
-
app,
|
|
88
|
-
"name",
|
|
89
|
-
getattr(app, "TITLE", app.__class__.__name__),
|
|
90
|
-
),
|
|
91
|
-
),
|
|
92
|
-
"version": getattr(app, "version", getattr(app, "VERSION", "0.1.0")),
|
|
93
|
-
"jsonrpc_prefix": getattr(app, "jsonrpc_prefix", "/rpc"),
|
|
94
|
-
"system_prefix": getattr(app, "system_prefix", "/system"),
|
|
95
|
-
"bindings": list(getattr(app, "bindings", []) or []),
|
|
96
|
-
"tables": list(getattr(app, "tables", []) or []),
|
|
97
|
-
"engines": list(getattr(app, "engines", []) or []),
|
|
98
|
-
"callbacks": list(getattr(app, "callbacks", []) or []),
|
|
99
|
-
"runtime": dict(getattr(app, "runtime_config", {}) or {}),
|
|
100
|
-
"metadata": dict(getattr(app, "metadata", {}) or {}),
|
|
101
|
-
"engine": getattr(app, "engine", None),
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
def _machine_name(source: dict[str, Any], app: Any) -> str:
|
|
106
|
-
raw = (
|
|
107
|
-
source.get("name")
|
|
108
|
-
or getattr(app, "name", None)
|
|
109
|
-
or source.get("title")
|
|
110
|
-
or getattr(app, "title", None)
|
|
111
|
-
or getattr(app, "TITLE", None)
|
|
112
|
-
or app.__class__.__name__
|
|
113
|
-
)
|
|
114
|
-
return str(raw).strip()
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _table_name(table: Any) -> str:
|
|
118
|
-
for attr in ("__tablename__", "__resource__", "resource_name", "name"):
|
|
119
|
-
value = getattr(table, attr, None)
|
|
120
|
-
if callable(value):
|
|
121
|
-
try:
|
|
122
|
-
value = value()
|
|
123
|
-
except Exception:
|
|
124
|
-
value = None
|
|
125
|
-
if isinstance(value, str) and value:
|
|
126
|
-
return value
|
|
127
|
-
model = getattr(table, "model", None)
|
|
128
|
-
if isinstance(model, type):
|
|
129
|
-
return _table_name(model)
|
|
130
|
-
if isinstance(model, str) and model:
|
|
131
|
-
return model.rsplit(":", 1)[-1].rsplit(".", 1)[-1].lower()
|
|
132
|
-
model_ref = getattr(table, "model_ref", None)
|
|
133
|
-
if isinstance(model_ref, str) and model_ref:
|
|
134
|
-
return model_ref.rsplit(":", 1)[-1].rsplit(".", 1)[-1].lower()
|
|
135
|
-
return getattr(table, "__name__", table.__class__.__name__).lower()
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def _iter_tables(source: dict[str, Any], app: Any) -> tuple[Any, ...]:
|
|
139
|
-
raw = source.get("tables")
|
|
140
|
-
if raw is None:
|
|
141
|
-
return _table_iter(app)
|
|
142
|
-
if isinstance(raw, Mapping):
|
|
143
|
-
return tuple(raw.values())
|
|
144
|
-
if isinstance(raw, Sequence) and not isinstance(raw, (str, bytes, bytearray)):
|
|
145
|
-
return tuple(raw)
|
|
146
|
-
raise TypeError("rust runtime expects app.tables to be a mapping or sequence")
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _normalize_transport(proto: str) -> str:
|
|
150
|
-
mapping = {
|
|
151
|
-
"http.rest": "rest",
|
|
152
|
-
"https.rest": "rest",
|
|
153
|
-
"http.jsonrpc": "jsonrpc",
|
|
154
|
-
"https.jsonrpc": "jsonrpc",
|
|
155
|
-
"http.stream": "stream",
|
|
156
|
-
"https.stream": "stream",
|
|
157
|
-
"http.sse": "sse",
|
|
158
|
-
"https.sse": "sse",
|
|
159
|
-
"ws": "ws",
|
|
160
|
-
"wss": "ws",
|
|
161
|
-
"webtransport": "webtransport",
|
|
162
|
-
}
|
|
163
|
-
return mapping.get(proto, proto or "rest")
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def _normalize_path(path: Any) -> str:
|
|
167
|
-
return str(path or "/").rstrip("/") or "/"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def _is_default_root_binding(binding: Mapping[str, Any]) -> bool:
|
|
171
|
-
return (
|
|
172
|
-
binding.get("transport") == "rest"
|
|
173
|
-
and _normalize_path(binding.get("path")) == "/"
|
|
7
|
+
def _raise_deprecated(action: str) -> NoReturn:
|
|
8
|
+
warnings.warn(
|
|
9
|
+
"tigrbl_kernel Rust spec serialization is deprecated; "
|
|
10
|
+
"Tigrbl runtime execution is Python-only.",
|
|
11
|
+
DeprecationWarning,
|
|
12
|
+
stacklevel=3,
|
|
174
13
|
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def _has_root_binding(bindings: list[dict[str, Any]]) -> bool:
|
|
178
|
-
return any(_is_default_root_binding(binding) for binding in bindings)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def _default_root_binding() -> dict[str, Any]:
|
|
182
|
-
return {
|
|
183
|
-
"alias": TIGRBL_DEFAULT_ROOT_ALIAS,
|
|
184
|
-
"transport": "rest",
|
|
185
|
-
"family": "rest",
|
|
186
|
-
"framing": None,
|
|
187
|
-
"path": TIGRBL_DEFAULT_ROOT_PATH,
|
|
188
|
-
"methods": (TIGRBL_DEFAULT_ROOT_METHOD,),
|
|
189
|
-
"op": {
|
|
190
|
-
"name": TIGRBL_DEFAULT_ROOT_ALIAS,
|
|
191
|
-
"kind": "read",
|
|
192
|
-
"route": TIGRBL_DEFAULT_ROOT_PATH,
|
|
193
|
-
"exchange": "request_response",
|
|
194
|
-
"tx_scope": "inherit",
|
|
195
|
-
"subevents": [],
|
|
196
|
-
},
|
|
197
|
-
"table": {"name": TIGRBL_DEFAULT_ROOT_ALIAS},
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def _binding_path(binding: Any, *, jsonrpc_prefix: str) -> str:
|
|
202
|
-
path = getattr(binding, "path", None)
|
|
203
|
-
if isinstance(path, str) and path:
|
|
204
|
-
return path
|
|
205
|
-
rpc_method = getattr(binding, "rpc_method", None)
|
|
206
|
-
if isinstance(rpc_method, str) and rpc_method:
|
|
207
|
-
prefix = jsonrpc_prefix.rstrip("/") or "/rpc"
|
|
208
|
-
return f"{prefix}/{rpc_method}"
|
|
209
|
-
return "/"
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def _binding_methods(binding: Any, target: str) -> tuple[str, ...]:
|
|
213
|
-
methods = tuple(
|
|
214
|
-
str(item).upper() for item in (getattr(binding, "methods", ()) or ())
|
|
215
|
-
)
|
|
216
|
-
if methods:
|
|
217
|
-
return methods
|
|
218
|
-
default = {
|
|
219
|
-
"create": ("POST",),
|
|
220
|
-
"read": ("GET",),
|
|
221
|
-
"list": ("GET",),
|
|
222
|
-
"delete": ("DELETE",),
|
|
223
|
-
"replace": ("PUT",),
|
|
224
|
-
"update": ("PATCH",),
|
|
225
|
-
"merge": ("PATCH",),
|
|
226
|
-
}
|
|
227
|
-
return default.get(target, ("POST",))
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def _lower_declared_bindings(source: dict[str, Any], app: Any) -> list[dict[str, Any]]:
|
|
231
|
-
raw = source.get("bindings")
|
|
232
|
-
if raw is not None:
|
|
233
|
-
if not (
|
|
234
|
-
isinstance(raw, Sequence) and not isinstance(raw, (str, bytes, bytearray))
|
|
235
|
-
):
|
|
236
|
-
raise TypeError("rust runtime expects app.bindings to be a sequence")
|
|
237
|
-
if raw and all(isinstance(item, Mapping) for item in raw):
|
|
238
|
-
payload: list[dict[str, Any]] = [dict(item) for item in raw]
|
|
239
|
-
if not _has_root_binding(payload):
|
|
240
|
-
payload.append(_default_root_binding())
|
|
241
|
-
return payload
|
|
242
|
-
|
|
243
|
-
payload: list[dict[str, Any]] = []
|
|
244
|
-
jsonrpc_prefix = str(source.get("jsonrpc_prefix", "/rpc") or "/rpc")
|
|
245
|
-
|
|
246
|
-
for table in _iter_tables(source, app):
|
|
247
|
-
table_name = _table_name(table)
|
|
248
|
-
for spec in _opspecs(table):
|
|
249
|
-
alias = str(getattr(spec, "alias", getattr(spec, "target", "")) or "")
|
|
250
|
-
target = str(getattr(spec, "target", alias) or alias)
|
|
251
|
-
exchange = str(
|
|
252
|
-
getattr(spec, "exchange", "request_response") or "request_response"
|
|
253
|
-
)
|
|
254
|
-
tx_scope = str(getattr(spec, "tx_scope", "inherit") or "inherit")
|
|
255
|
-
subevents = [
|
|
256
|
-
str(item) for item in tuple(getattr(spec, "subevents", ()) or ())
|
|
257
|
-
]
|
|
258
|
-
for binding in tuple(getattr(spec, "bindings", ()) or ()):
|
|
259
|
-
proto = str(getattr(binding, "proto", "") or "")
|
|
260
|
-
transport = _normalize_transport(proto)
|
|
261
|
-
path = _binding_path(binding, jsonrpc_prefix=jsonrpc_prefix)
|
|
262
|
-
methods = _binding_methods(binding, target)
|
|
263
|
-
payload.append(
|
|
264
|
-
{
|
|
265
|
-
"alias": alias,
|
|
266
|
-
"transport": transport,
|
|
267
|
-
"family": "bidirectional"
|
|
268
|
-
if transport in {"ws", "webtransport"}
|
|
269
|
-
else transport,
|
|
270
|
-
"framing": getattr(binding, "framing", None),
|
|
271
|
-
"path": path,
|
|
272
|
-
"methods": methods,
|
|
273
|
-
"op": {
|
|
274
|
-
"name": alias,
|
|
275
|
-
"kind": target,
|
|
276
|
-
"route": path,
|
|
277
|
-
"exchange": str(
|
|
278
|
-
getattr(binding, "exchange", exchange) or exchange
|
|
279
|
-
),
|
|
280
|
-
"tx_scope": tx_scope,
|
|
281
|
-
"subevents": subevents,
|
|
282
|
-
},
|
|
283
|
-
"table": {"name": table_name},
|
|
284
|
-
}
|
|
285
|
-
)
|
|
286
|
-
if not _has_root_binding(payload):
|
|
287
|
-
payload.append(_default_root_binding())
|
|
288
|
-
return payload
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def _lower_declared_tables(source: dict[str, Any], app: Any) -> list[dict[str, str]]:
|
|
292
|
-
raw = source.get("tables")
|
|
293
|
-
if raw is not None:
|
|
294
|
-
if not (
|
|
295
|
-
isinstance(raw, Sequence) and not isinstance(raw, (str, bytes, bytearray))
|
|
296
|
-
):
|
|
297
|
-
raise TypeError("rust runtime expects app.tables to be a sequence")
|
|
298
|
-
if raw and all(isinstance(item, Mapping) for item in raw):
|
|
299
|
-
return [dict(item) for item in raw]
|
|
300
|
-
return [{"name": _table_name(table)} for table in _iter_tables(source, app)]
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def _lower_declared_engines(source: dict[str, Any], app: Any) -> list[dict[str, Any]]:
|
|
304
|
-
raw = source.get("engines")
|
|
305
|
-
if raw is not None:
|
|
306
|
-
if not (
|
|
307
|
-
isinstance(raw, Sequence) and not isinstance(raw, (str, bytes, bytearray))
|
|
308
|
-
):
|
|
309
|
-
raise TypeError("rust runtime expects app.engines to be a sequence")
|
|
310
|
-
if raw:
|
|
311
|
-
return [
|
|
312
|
-
dict(item)
|
|
313
|
-
if isinstance(item, Mapping)
|
|
314
|
-
else {"name": "default", "kind": str(item)}
|
|
315
|
-
for item in raw
|
|
316
|
-
]
|
|
317
|
-
|
|
318
|
-
engine = source.get("engine", getattr(app, "engine", None))
|
|
319
|
-
if engine is None:
|
|
320
|
-
return [{"name": "default", "kind": "inmemory", "language": "rust"}]
|
|
321
|
-
|
|
322
|
-
try:
|
|
323
|
-
from tigrbl_core._spec.engine_spec import EngineSpec
|
|
324
|
-
|
|
325
|
-
spec = EngineSpec.from_any(engine)
|
|
326
|
-
except Exception:
|
|
327
|
-
spec = None
|
|
328
|
-
|
|
329
|
-
kind = "inmemory"
|
|
330
|
-
options: dict[str, Any] = {}
|
|
331
|
-
if spec is not None and getattr(spec, "kind", None):
|
|
332
|
-
if spec.kind == "sqlite" and getattr(spec, "memory", False):
|
|
333
|
-
kind = "inmemory"
|
|
334
|
-
else:
|
|
335
|
-
kind = str(spec.kind)
|
|
336
|
-
path = getattr(spec, "path", None)
|
|
337
|
-
if isinstance(path, str) and path:
|
|
338
|
-
options["path"] = path
|
|
339
|
-
dsn = getattr(spec, "dsn", None)
|
|
340
|
-
if isinstance(dsn, str) and dsn:
|
|
341
|
-
options["dsn"] = dsn
|
|
342
|
-
options["memory"] = bool(getattr(spec, "memory", False))
|
|
343
|
-
|
|
344
|
-
return [{"name": "default", "kind": kind, "language": "rust", "options": options}]
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
def _callable_name(value: Any) -> str:
|
|
348
|
-
module = getattr(value, "__module__", None)
|
|
349
|
-
qualname = getattr(value, "__qualname__", getattr(value, "__name__", None))
|
|
350
|
-
if module and qualname:
|
|
351
|
-
return f"{module}.{qualname}"
|
|
352
|
-
return str(value)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
def _lower_callbacks(source: dict[str, Any]) -> list[dict[str, Any]]:
|
|
356
|
-
raw = source.get("callbacks", ()) or ()
|
|
357
|
-
if not (
|
|
358
|
-
isinstance(raw, Sequence) and not isinstance(raw, (str, bytes, bytearray))
|
|
359
|
-
):
|
|
360
|
-
raise TypeError("rust runtime expects app.callbacks to be a sequence")
|
|
361
|
-
payload: list[dict[str, Any]] = []
|
|
362
|
-
for item in raw:
|
|
363
|
-
if isinstance(item, Mapping):
|
|
364
|
-
payload.append(dict(item))
|
|
365
|
-
continue
|
|
366
|
-
payload.append(
|
|
367
|
-
{
|
|
368
|
-
"name": _callable_name(item),
|
|
369
|
-
"kind": "callback",
|
|
370
|
-
"language": "python",
|
|
371
|
-
}
|
|
372
|
-
)
|
|
373
|
-
return payload
|
|
14
|
+
raise RuntimeError(f"{action} is unavailable; Tigrbl runtime execution is Python-only.")
|
|
374
15
|
|
|
375
16
|
|
|
376
17
|
def build_rust_app_spec(app: Any) -> dict[str, Any]:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
payload: dict[str, Any] = {
|
|
380
|
-
"name": name,
|
|
381
|
-
"title": str(source.get("title", name)).strip(),
|
|
382
|
-
"version": str(source.get("version", "0.1.0")).strip(),
|
|
383
|
-
"jsonrpc_prefix": str(source.get("jsonrpc_prefix", "/rpc")).strip() or "/rpc",
|
|
384
|
-
"system_prefix": str(source.get("system_prefix", "/system")).strip()
|
|
385
|
-
or "/system",
|
|
386
|
-
"metadata": dict(source.get("metadata", {}) or {}),
|
|
387
|
-
"runtime": dict(source.get("runtime", {}) or {}),
|
|
388
|
-
"dependencies": source.get("dependencies", {}) or {},
|
|
389
|
-
"security": source.get("security", {}) or {},
|
|
390
|
-
"bindings": _lower_declared_bindings(source, app),
|
|
391
|
-
"tables": _lower_declared_tables(source, app),
|
|
392
|
-
"engines": _lower_declared_engines(source, app),
|
|
393
|
-
"callbacks": _lower_callbacks(source),
|
|
394
|
-
}
|
|
395
|
-
if not payload["name"]:
|
|
396
|
-
raise ValueError("rust runtime requires a non-empty app name")
|
|
397
|
-
return payload
|
|
18
|
+
del app
|
|
19
|
+
_raise_deprecated("build_rust_app_spec")
|
|
398
20
|
|
|
399
21
|
|
|
400
22
|
def coerce_rust_spec_dict(app: Any) -> dict[str, Any]:
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if not isinstance(payload, dict):
|
|
404
|
-
raise TypeError("rust runtime expects a mapping or JSON object")
|
|
405
|
-
return payload
|
|
406
|
-
if isinstance(app, Mapping):
|
|
407
|
-
return build_rust_app_spec(dict(app))
|
|
408
|
-
return build_rust_app_spec(app)
|
|
23
|
+
del app
|
|
24
|
+
_raise_deprecated("coerce_rust_spec_dict")
|
|
409
25
|
|
|
410
26
|
|
|
411
27
|
def coerce_rust_spec_json(app: Any) -> str:
|
|
412
|
-
|
|
28
|
+
del app
|
|
29
|
+
_raise_deprecated("coerce_rust_spec_json")
|
|
413
30
|
|
|
414
31
|
|
|
415
32
|
__all__ = [
|
|
@@ -46,11 +46,14 @@ _KNOWN_TRANSPORTS = {
|
|
|
46
46
|
},
|
|
47
47
|
"callback": {
|
|
48
48
|
"scope_type": None,
|
|
49
|
-
"events": (
|
|
49
|
+
"events": (
|
|
50
|
+
("callback.delivery.emit", "outbound", False, "message.emit"),
|
|
51
|
+
("callback.delivery.emit_complete", "outbound", False, "message.processed"),
|
|
52
|
+
),
|
|
50
53
|
},
|
|
51
54
|
"webhook": {
|
|
52
55
|
"scope_type": "http",
|
|
53
|
-
"events": (("webhook.
|
|
56
|
+
"events": (("webhook.receive", "inbound", False, "request.received"),),
|
|
54
57
|
},
|
|
55
58
|
}
|
|
56
59
|
|
tigrbl_kernel/types.py
CHANGED
|
@@ -37,6 +37,7 @@ EFFECT_BY_ATOM_NAME = {
|
|
|
37
37
|
"sys.handler_tail": EFFECT_WIRE,
|
|
38
38
|
"sys.handler_upload": EFFECT_WIRE,
|
|
39
39
|
"sys.handler_download": EFFECT_WIRE,
|
|
40
|
+
"sys.handler_well_known": EFFECT_WIRE,
|
|
40
41
|
"sys.handler_append_chunk": EFFECT_WIRE,
|
|
41
42
|
"sys.handler_send_datagram": EFFECT_WIRE,
|
|
42
43
|
"sys.handler_checkpoint": EFFECT_WIRE,
|
|
@@ -109,6 +109,15 @@ _STREAM_DIRECTIONS = {
|
|
|
109
109
|
"client_to_server": "unidi_client_stream",
|
|
110
110
|
"server_to_client": "unidi_server_stream",
|
|
111
111
|
}
|
|
112
|
+
_STREAM_DIRECTION_METADATA = {
|
|
113
|
+
"bidi": "bidirectional",
|
|
114
|
+
"client_to_server": "client_to_server",
|
|
115
|
+
"server_to_client": "server_to_client",
|
|
116
|
+
}
|
|
117
|
+
_UNIDI_INITIATORS = {
|
|
118
|
+
"client_to_server": "client",
|
|
119
|
+
"server_to_client": "server",
|
|
120
|
+
}
|
|
112
121
|
|
|
113
122
|
|
|
114
123
|
def validate_webtransport_event_payload(
|
|
@@ -141,7 +150,15 @@ def _validate_session_payload(
|
|
|
141
150
|
expected_channel = "receive" if event in {"webtransport.connect", "webtransport.disconnect"} else "send"
|
|
142
151
|
if channel != expected_channel:
|
|
143
152
|
raise ValueError(f"{event} is only valid on {expected_channel}")
|
|
144
|
-
_forbid(
|
|
153
|
+
_forbid(
|
|
154
|
+
payload,
|
|
155
|
+
"stream_id",
|
|
156
|
+
"stream_direction",
|
|
157
|
+
"stream_initiator",
|
|
158
|
+
"lane_id",
|
|
159
|
+
"datagram_id",
|
|
160
|
+
"framing",
|
|
161
|
+
)
|
|
145
162
|
return {"family": "session", "lane": "session", "exchange": "request_response"}
|
|
146
163
|
|
|
147
164
|
|
|
@@ -166,6 +183,11 @@ def _validate_stream_payload(
|
|
|
166
183
|
else:
|
|
167
184
|
direction = str(direction) if direction in _STREAM_DIRECTIONS else "bidi"
|
|
168
185
|
lane = _STREAM_DIRECTIONS[str(direction)]
|
|
186
|
+
stream_initiator = _stream_initiator(
|
|
187
|
+
payload.get("stream_initiator"),
|
|
188
|
+
stream_direction=str(direction),
|
|
189
|
+
channel=channel,
|
|
190
|
+
)
|
|
169
191
|
if channel == "receive" and lane == "unidi_server_stream":
|
|
170
192
|
raise ValueError("server_to_client unidirectional streams cannot be receive events")
|
|
171
193
|
if channel == "send" and lane == "unidi_client_stream":
|
|
@@ -174,7 +196,7 @@ def _validate_stream_payload(
|
|
|
174
196
|
lane=lane,
|
|
175
197
|
inner_framing=payload.get("framing"),
|
|
176
198
|
)
|
|
177
|
-
|
|
199
|
+
projection = {
|
|
178
200
|
"family": "stream",
|
|
179
201
|
"lane": lane,
|
|
180
202
|
"exchange": {
|
|
@@ -182,7 +204,15 @@ def _validate_stream_payload(
|
|
|
182
204
|
"unidi_client_stream": "client_stream",
|
|
183
205
|
"unidi_server_stream": "server_stream",
|
|
184
206
|
}[lane],
|
|
207
|
+
"stream_direction": str(direction),
|
|
208
|
+
"direction": _STREAM_DIRECTION_METADATA[str(direction)],
|
|
209
|
+
"stream_initiator": stream_initiator,
|
|
185
210
|
}
|
|
211
|
+
_copy_int_field(payload, projection, "lane_id")
|
|
212
|
+
_copy_int_field(payload, projection, "stream_ordinal")
|
|
213
|
+
if payload.get("stream_id_width") is not None:
|
|
214
|
+
projection["stream_id_width"] = str(payload["stream_id_width"])
|
|
215
|
+
return projection
|
|
186
216
|
|
|
187
217
|
|
|
188
218
|
def _validate_datagram_payload(
|
|
@@ -195,7 +225,7 @@ def _validate_datagram_payload(
|
|
|
195
225
|
if channel != expected_channel:
|
|
196
226
|
raise ValueError(f"{event} is only valid on {expected_channel}")
|
|
197
227
|
_require(payload, "datagram_id")
|
|
198
|
-
_forbid(payload, "stream_id", "stream_direction")
|
|
228
|
+
_forbid(payload, "stream_id", "stream_direction", "stream_initiator", "lane_id")
|
|
199
229
|
validate_webtransport_inner_framing(
|
|
200
230
|
lane="datagram",
|
|
201
231
|
inner_framing=payload.get("framing"),
|
|
@@ -216,6 +246,38 @@ def _forbid(payload: dict[str, Any], *fields: str) -> None:
|
|
|
216
246
|
raise ValueError(f"WebTransport payload field not valid for event: {joined}")
|
|
217
247
|
|
|
218
248
|
|
|
249
|
+
def _stream_initiator(
|
|
250
|
+
value: object,
|
|
251
|
+
*,
|
|
252
|
+
stream_direction: str,
|
|
253
|
+
channel: str,
|
|
254
|
+
) -> str:
|
|
255
|
+
if stream_direction in _UNIDI_INITIATORS:
|
|
256
|
+
expected = _UNIDI_INITIATORS[stream_direction]
|
|
257
|
+
if value is not None and str(value) != expected:
|
|
258
|
+
raise ValueError(f"{stream_direction} stream_initiator must be {expected}")
|
|
259
|
+
return expected
|
|
260
|
+
if value is None:
|
|
261
|
+
return "client"
|
|
262
|
+
token = str(value)
|
|
263
|
+
if token not in {"client", "server"}:
|
|
264
|
+
raise ValueError("WebTransport stream_initiator must be client or server")
|
|
265
|
+
return token
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _copy_int_field(
|
|
269
|
+
payload: dict[str, Any],
|
|
270
|
+
projection: dict[str, object],
|
|
271
|
+
field: str,
|
|
272
|
+
) -> None:
|
|
273
|
+
value = payload.get(field)
|
|
274
|
+
if value is None:
|
|
275
|
+
return
|
|
276
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
277
|
+
raise ValueError(f"WebTransport payload {field} must be an integer")
|
|
278
|
+
projection[field] = value
|
|
279
|
+
|
|
280
|
+
|
|
219
281
|
__all__ = [
|
|
220
282
|
"compile_webtransport_chain",
|
|
221
283
|
"compile_webtransport_events",
|