tigrbl-kernel 0.4.1.dev6__py3-none-any.whl → 0.4.2.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/_build.py +5 -0
- tigrbl_kernel/contract_classification.py +186 -0
- tigrbl_kernel/protocol_bindings.py +162 -32
- tigrbl_kernel/subevent_taxonomy.py +14 -5
- tigrbl_kernel/webtransport_events.py +177 -4
- {tigrbl_kernel-0.4.1.dev6.dist-info → tigrbl_kernel-0.4.2.dev4.dist-info}/METADATA +142 -109
- {tigrbl_kernel-0.4.1.dev6.dist-info → tigrbl_kernel-0.4.2.dev4.dist-info}/RECORD +10 -8
- {tigrbl_kernel-0.4.1.dev6.dist-info → tigrbl_kernel-0.4.2.dev4.dist-info}/WHEEL +1 -1
- tigrbl_kernel-0.4.2.dev4.dist-info/licenses/NOTICE +7 -0
- {tigrbl_kernel-0.4.1.dev6.dist-info → tigrbl_kernel-0.4.2.dev4.dist-info}/licenses/LICENSE +0 -0
tigrbl_kernel/_build.py
CHANGED
|
@@ -597,6 +597,11 @@ def _program_has_exact_http_like_no_input_binding(
|
|
|
597
597
|
|
|
598
598
|
|
|
599
599
|
def _step_has_route_binding(step: StepFn) -> bool:
|
|
600
|
+
for candidate in (step, getattr(step, "__tigrbl_direct_run", None)):
|
|
601
|
+
endpoint = getattr(candidate, "__tigrbl_websocket_endpoint__", None)
|
|
602
|
+
path = getattr(candidate, "__tigrbl_websocket_path__", None)
|
|
603
|
+
if callable(endpoint) and isinstance(path, str) and path:
|
|
604
|
+
return True
|
|
600
605
|
closure = getattr(step, "__closure__", None)
|
|
601
606
|
if not closure:
|
|
602
607
|
return False
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tigrbl_core._spec.binding_spec import normalize_exchange
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
CONTRACT_CHANNELS: tuple[str, ...] = ("receive", "send")
|
|
10
|
+
CONTRACT_SCOPE_TYPES: tuple[str, ...] = ("http", "websocket", "webtransport")
|
|
11
|
+
CONTRACT_DIRECTIONS: tuple[str, ...] = (
|
|
12
|
+
"client_to_server",
|
|
13
|
+
"server_to_client",
|
|
14
|
+
"app_to_server",
|
|
15
|
+
"server_to_app",
|
|
16
|
+
"system",
|
|
17
|
+
)
|
|
18
|
+
CONTRACT_FAMILIES: tuple[str, ...] = (
|
|
19
|
+
"request",
|
|
20
|
+
"session",
|
|
21
|
+
"message",
|
|
22
|
+
"stream",
|
|
23
|
+
"datagram",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
CONTRACT_BINDING_ALIASES: dict[str, tuple[str, ...]] = {
|
|
27
|
+
"rest": ("http.rest", "https.rest"),
|
|
28
|
+
"jsonrpc": ("http.jsonrpc", "https.jsonrpc"),
|
|
29
|
+
"http.stream": ("http.stream", "https.stream"),
|
|
30
|
+
"sse": ("http.sse", "https.sse"),
|
|
31
|
+
"websocket": ("ws", "wss"),
|
|
32
|
+
"webtransport": ("webtransport",),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
CONTRACT_EXCHANGE_ALIASES: dict[str, str] = {
|
|
36
|
+
"unary": "request_response",
|
|
37
|
+
"duplex": "bidirectional_stream",
|
|
38
|
+
"client_stream": "client_stream",
|
|
39
|
+
"server_stream": "server_stream",
|
|
40
|
+
"fire_and_forget": "fire_and_forget",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
CANONICAL_CONTRACT_EVENTS: tuple[str, ...] = (
|
|
44
|
+
"http.request",
|
|
45
|
+
"http.disconnect",
|
|
46
|
+
"http.response.start",
|
|
47
|
+
"http.response.body",
|
|
48
|
+
"http.response.pathsend",
|
|
49
|
+
"websocket.connect",
|
|
50
|
+
"websocket.receive",
|
|
51
|
+
"websocket.disconnect",
|
|
52
|
+
"websocket.accept",
|
|
53
|
+
"websocket.send",
|
|
54
|
+
"websocket.close",
|
|
55
|
+
"webtransport.connect",
|
|
56
|
+
"webtransport.accept",
|
|
57
|
+
"webtransport.stream.receive",
|
|
58
|
+
"webtransport.stream.send",
|
|
59
|
+
"webtransport.stream.close",
|
|
60
|
+
"webtransport.stream.reset",
|
|
61
|
+
"webtransport.stream.stop_sending",
|
|
62
|
+
"webtransport.datagram.receive",
|
|
63
|
+
"webtransport.datagram.send",
|
|
64
|
+
"webtransport.disconnect",
|
|
65
|
+
"webtransport.close",
|
|
66
|
+
"transport.emit.complete",
|
|
67
|
+
"transport.emit.failed",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
_EVENT_SCOPE_PREFIXES: dict[str, str] = {
|
|
71
|
+
"http.": "http",
|
|
72
|
+
"websocket.": "websocket",
|
|
73
|
+
"webtransport.": "webtransport",
|
|
74
|
+
"transport.emit.": "webtransport",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True, slots=True)
|
|
79
|
+
class ContractClassificationProjection:
|
|
80
|
+
event: str
|
|
81
|
+
channel: str
|
|
82
|
+
scope_type: str
|
|
83
|
+
binding: str
|
|
84
|
+
local_binding_kinds: tuple[str, ...]
|
|
85
|
+
family: str
|
|
86
|
+
contract_exchange: str
|
|
87
|
+
local_exchange: str
|
|
88
|
+
direction: str
|
|
89
|
+
allowed_framings: tuple[str, ...]
|
|
90
|
+
required_payload_fields: tuple[str, ...]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def project_contract_classification(row: dict[str, Any]) -> ContractClassificationProjection:
|
|
94
|
+
if "subsurface" in row:
|
|
95
|
+
raise ValueError("contract classifications must not use subsurface")
|
|
96
|
+
|
|
97
|
+
event = _required_str(row, "event")
|
|
98
|
+
channel = _required_str(row, "channel")
|
|
99
|
+
scope_type = _required_str(row, "scope_type")
|
|
100
|
+
binding = _required_str(row, "binding")
|
|
101
|
+
family = _required_str(row, "family")
|
|
102
|
+
exchange = _required_str(row, "exchange")
|
|
103
|
+
direction = _required_str(row, "direction")
|
|
104
|
+
|
|
105
|
+
if event not in CANONICAL_CONTRACT_EVENTS:
|
|
106
|
+
raise ValueError(f"unsupported contract event {event!r}")
|
|
107
|
+
if channel not in CONTRACT_CHANNELS:
|
|
108
|
+
raise ValueError(f"unsupported contract channel {channel!r}")
|
|
109
|
+
if scope_type not in CONTRACT_SCOPE_TYPES:
|
|
110
|
+
raise ValueError(f"unsupported contract scope_type {scope_type!r}")
|
|
111
|
+
if not _event_matches_scope(event=event, scope_type=scope_type):
|
|
112
|
+
raise ValueError(f"contract event {event!r} does not match scope_type {scope_type!r}")
|
|
113
|
+
if family not in CONTRACT_FAMILIES:
|
|
114
|
+
raise ValueError(f"unsupported contract family {family!r}")
|
|
115
|
+
if direction not in CONTRACT_DIRECTIONS:
|
|
116
|
+
raise ValueError(f"unsupported contract direction {direction!r}")
|
|
117
|
+
if binding not in CONTRACT_BINDING_ALIASES:
|
|
118
|
+
raise ValueError(f"unsupported contract binding {binding!r}")
|
|
119
|
+
if scope_type == "webtransport" and family == "message":
|
|
120
|
+
raise ValueError("WebTransport message is not a native transport family")
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
local_exchange = normalize_exchange(CONTRACT_EXCHANGE_ALIASES[exchange])
|
|
124
|
+
except KeyError as exc:
|
|
125
|
+
raise ValueError(f"unsupported contract exchange {exchange!r}") from exc
|
|
126
|
+
|
|
127
|
+
return ContractClassificationProjection(
|
|
128
|
+
event=event,
|
|
129
|
+
channel=channel,
|
|
130
|
+
scope_type=scope_type,
|
|
131
|
+
binding=binding,
|
|
132
|
+
local_binding_kinds=CONTRACT_BINDING_ALIASES[binding],
|
|
133
|
+
family=family,
|
|
134
|
+
contract_exchange=exchange,
|
|
135
|
+
local_exchange=local_exchange,
|
|
136
|
+
direction=direction,
|
|
137
|
+
allowed_framings=_string_tuple(row.get("allowed_framings", ())),
|
|
138
|
+
required_payload_fields=_string_tuple(row.get("required_payload_fields", ())),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def is_supported_contract_classification(row: dict[str, Any]) -> bool:
|
|
143
|
+
try:
|
|
144
|
+
project_contract_classification(row)
|
|
145
|
+
except ValueError:
|
|
146
|
+
return False
|
|
147
|
+
return True
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _required_str(row: dict[str, Any], key: str) -> str:
|
|
151
|
+
value = row.get(key)
|
|
152
|
+
if not isinstance(value, str) or not value:
|
|
153
|
+
raise ValueError(f"contract classification requires string {key!r}")
|
|
154
|
+
return value
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _string_tuple(value: Any) -> tuple[str, ...]:
|
|
158
|
+
if value is None:
|
|
159
|
+
return ()
|
|
160
|
+
if not isinstance(value, (list, tuple)):
|
|
161
|
+
raise ValueError("contract classification list fields must be sequences")
|
|
162
|
+
result = tuple(str(item) for item in value)
|
|
163
|
+
if any(not item for item in result):
|
|
164
|
+
raise ValueError("contract classification list fields must not contain blanks")
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _event_matches_scope(*, event: str, scope_type: str) -> bool:
|
|
169
|
+
for prefix, expected_scope_type in _EVENT_SCOPE_PREFIXES.items():
|
|
170
|
+
if event.startswith(prefix):
|
|
171
|
+
return scope_type == expected_scope_type
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
__all__ = [
|
|
176
|
+
"CANONICAL_CONTRACT_EVENTS",
|
|
177
|
+
"CONTRACT_BINDING_ALIASES",
|
|
178
|
+
"CONTRACT_CHANNELS",
|
|
179
|
+
"CONTRACT_DIRECTIONS",
|
|
180
|
+
"CONTRACT_EXCHANGE_ALIASES",
|
|
181
|
+
"CONTRACT_FAMILIES",
|
|
182
|
+
"CONTRACT_SCOPE_TYPES",
|
|
183
|
+
"ContractClassificationProjection",
|
|
184
|
+
"is_supported_contract_classification",
|
|
185
|
+
"project_contract_classification",
|
|
186
|
+
]
|
|
@@ -3,26 +3,43 @@ from __future__ import annotations
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
+
from tigrbl_core._spec.binding_spec import (
|
|
7
|
+
validate_app_framing_for_binding,
|
|
8
|
+
validate_binding_profile_exchange,
|
|
9
|
+
validate_webtransport_inner_framing,
|
|
10
|
+
validate_webtransport_lane_exchange,
|
|
11
|
+
webtransport_lane_for_profile,
|
|
12
|
+
webtransport_runtime_family,
|
|
13
|
+
)
|
|
14
|
+
|
|
6
15
|
|
|
7
16
|
def _unsupported(message: str) -> ValueError:
|
|
8
17
|
return ValueError(f"binding protocol unsupported before runtime: {message}")
|
|
9
18
|
|
|
10
19
|
|
|
11
20
|
def compile_binding_protocol_plan(op_id: str, binding: Mapping[str, Any]) -> dict[str, object]:
|
|
12
|
-
kind = binding.get("kind")
|
|
21
|
+
kind = binding.get("kind") or binding.get("proto")
|
|
13
22
|
if not kind:
|
|
14
23
|
raise ValueError(
|
|
15
24
|
"BindingSpec binding source is required; transport guessing is ambiguous"
|
|
16
25
|
)
|
|
17
26
|
|
|
18
27
|
kind = str(kind)
|
|
28
|
+
profile = binding.get("profile")
|
|
29
|
+
if kind in {"http", "https"} and profile:
|
|
30
|
+
kind = f"{kind}.{profile}"
|
|
31
|
+
elif kind == "websocket":
|
|
32
|
+
kind = str(binding.get("proto") or "ws")
|
|
19
33
|
framing = binding.get("framing")
|
|
20
34
|
rows: tuple[dict[str, str], ...]
|
|
21
35
|
|
|
22
|
-
if kind
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
if kind in {"http.rest", "https.rest"}:
|
|
37
|
+
validate_binding_profile_exchange(
|
|
38
|
+
binding_kind=kind,
|
|
39
|
+
exchange=str(binding.get("exchange") or "request_response"),
|
|
40
|
+
)
|
|
41
|
+
validate_app_framing_for_binding(binding_kind=kind, framing=str(framing or "json"))
|
|
42
|
+
family = "request"
|
|
26
43
|
framing = "json"
|
|
27
44
|
anchors = (
|
|
28
45
|
"ingress.receive",
|
|
@@ -31,13 +48,18 @@ def compile_binding_protocol_plan(op_id: str, binding: Mapping[str, Any]) -> dic
|
|
|
31
48
|
"transport.emit_complete",
|
|
32
49
|
)
|
|
33
50
|
rows = (
|
|
34
|
-
{"family": "
|
|
35
|
-
{"family": "
|
|
51
|
+
{"family": "request", "subevent": "request.received"},
|
|
52
|
+
{"family": "request", "subevent": "response.emit"},
|
|
36
53
|
)
|
|
37
|
-
elif kind
|
|
54
|
+
elif kind in {"http.jsonrpc", "https.jsonrpc"}:
|
|
38
55
|
if not binding.get("rpc_method"):
|
|
39
56
|
raise _unsupported("http.jsonrpc requires rpc_method")
|
|
40
|
-
|
|
57
|
+
validate_binding_profile_exchange(
|
|
58
|
+
binding_kind=kind,
|
|
59
|
+
exchange=str(binding.get("exchange") or "request_response"),
|
|
60
|
+
)
|
|
61
|
+
validate_app_framing_for_binding(binding_kind=kind, framing=str(framing or "jsonrpc"))
|
|
62
|
+
family = "request"
|
|
41
63
|
framing = "jsonrpc"
|
|
42
64
|
anchors = (
|
|
43
65
|
"framing.decode",
|
|
@@ -46,18 +68,28 @@ def compile_binding_protocol_plan(op_id: str, binding: Mapping[str, Any]) -> dic
|
|
|
46
68
|
"framing.encode",
|
|
47
69
|
)
|
|
48
70
|
rows = (
|
|
49
|
-
{"family": "
|
|
50
|
-
{"family": "
|
|
71
|
+
{"family": "request", "subevent": "request.received"},
|
|
72
|
+
{"family": "request", "subevent": "response.emit"},
|
|
51
73
|
)
|
|
52
|
-
elif kind
|
|
74
|
+
elif kind in {"http.stream", "https.stream"}:
|
|
75
|
+
validate_binding_profile_exchange(
|
|
76
|
+
binding_kind=kind,
|
|
77
|
+
exchange=str(binding.get("exchange") or "server_stream"),
|
|
78
|
+
)
|
|
79
|
+
validate_app_framing_for_binding(binding_kind=kind, framing=str(framing or "stream"))
|
|
53
80
|
family = "stream"
|
|
54
|
-
framing = "stream"
|
|
81
|
+
framing = str(framing or "stream")
|
|
55
82
|
anchors = ("handler.invoke", "transport.emit", "transport.emit_complete")
|
|
56
83
|
rows = (
|
|
57
84
|
{"family": "stream", "subevent": "stream.chunk"},
|
|
58
85
|
{"family": "stream", "subevent": "stream.close"},
|
|
59
86
|
)
|
|
60
|
-
elif kind
|
|
87
|
+
elif kind in {"http.sse", "https.sse"}:
|
|
88
|
+
validate_binding_profile_exchange(
|
|
89
|
+
binding_kind=kind,
|
|
90
|
+
exchange=str(binding.get("exchange") or "server_stream"),
|
|
91
|
+
)
|
|
92
|
+
validate_app_framing_for_binding(binding_kind=kind, framing=str(framing or "sse"))
|
|
61
93
|
family = "stream"
|
|
62
94
|
framing = "sse"
|
|
63
95
|
anchors = (
|
|
@@ -67,15 +99,25 @@ def compile_binding_protocol_plan(op_id: str, binding: Mapping[str, Any]) -> dic
|
|
|
67
99
|
"transport.emit_complete",
|
|
68
100
|
)
|
|
69
101
|
rows = (
|
|
70
|
-
{"family": "
|
|
71
|
-
{"family": "
|
|
102
|
+
{"family": "stream", "subevent": "message.encoded"},
|
|
103
|
+
{"family": "stream", "subevent": "message.emit"},
|
|
72
104
|
{"family": "stream", "subevent": "stream.close"},
|
|
73
105
|
)
|
|
74
|
-
elif kind in {"ws", "websocket"}:
|
|
106
|
+
elif kind in {"ws", "wss", "websocket"}:
|
|
75
107
|
if binding.get("methods"):
|
|
76
108
|
raise _unsupported("websocket bindings do not accept HTTP methods")
|
|
77
109
|
family = "message"
|
|
78
110
|
framing = str(framing or "text")
|
|
111
|
+
subprotocols = tuple(str(item).lower() for item in binding.get("subprotocols", ()))
|
|
112
|
+
validate_binding_profile_exchange(
|
|
113
|
+
binding_kind="wss" if kind == "wss" else "ws",
|
|
114
|
+
exchange=str(binding.get("exchange") or "bidirectional_stream"),
|
|
115
|
+
)
|
|
116
|
+
validate_app_framing_for_binding(
|
|
117
|
+
binding_kind="wss" if kind == "wss" else "ws",
|
|
118
|
+
framing=framing,
|
|
119
|
+
subprotocols=subprotocols,
|
|
120
|
+
)
|
|
79
121
|
anchors = (
|
|
80
122
|
"transport.accept",
|
|
81
123
|
"framing.decode",
|
|
@@ -91,32 +133,120 @@ def compile_binding_protocol_plan(op_id: str, binding: Mapping[str, Any]) -> dic
|
|
|
91
133
|
elif kind == "webtransport":
|
|
92
134
|
if binding.get("exchange") == "request_response":
|
|
93
135
|
raise _unsupported("webtransport request_response exchange")
|
|
94
|
-
|
|
95
|
-
framing
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"
|
|
99
|
-
"handler.invoke",
|
|
100
|
-
"transport.close",
|
|
136
|
+
validate_app_framing_for_binding(binding_kind=kind, framing=str(framing or "webtransport"))
|
|
137
|
+
if framing and str(framing) != "webtransport":
|
|
138
|
+
raise _unsupported("webtransport outer framing must remain webtransport")
|
|
139
|
+
lane = webtransport_lane_for_profile(
|
|
140
|
+
binding.get("lane") or binding.get("profile") or "webtransport"
|
|
101
141
|
)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
142
|
+
validate_webtransport_lane_exchange(
|
|
143
|
+
lane=lane,
|
|
144
|
+
exchange=str(binding.get("exchange") or {
|
|
145
|
+
"session": "bidirectional_stream",
|
|
146
|
+
"bidi_stream": "bidirectional_stream",
|
|
147
|
+
"unidi_client_stream": "client_stream",
|
|
148
|
+
"unidi_server_stream": "server_stream",
|
|
149
|
+
"datagram": "bidirectional_stream",
|
|
150
|
+
}[lane]),
|
|
151
|
+
)
|
|
152
|
+
inner_framing = validate_webtransport_inner_framing(
|
|
153
|
+
lane=lane,
|
|
154
|
+
inner_framing=binding.get("inner_framing"),
|
|
105
155
|
)
|
|
156
|
+
family = webtransport_runtime_family(lane)
|
|
157
|
+
framing = "webtransport"
|
|
158
|
+
if lane == "session":
|
|
159
|
+
anchors = (
|
|
160
|
+
"transport.accept",
|
|
161
|
+
"dispatch.subevent.derive",
|
|
162
|
+
"handler.invoke",
|
|
163
|
+
"transport.close",
|
|
164
|
+
)
|
|
165
|
+
rows = (
|
|
166
|
+
{"family": "session", "subevent": "session.open"},
|
|
167
|
+
{"family": "session", "subevent": "session.close"},
|
|
168
|
+
)
|
|
169
|
+
elif lane == "bidi_stream":
|
|
170
|
+
anchors = (
|
|
171
|
+
"transport.accept",
|
|
172
|
+
"transport.receive",
|
|
173
|
+
"framing.decode",
|
|
174
|
+
"dispatch.subevent.derive",
|
|
175
|
+
"handler.invoke",
|
|
176
|
+
"framing.encode",
|
|
177
|
+
"transport.emit",
|
|
178
|
+
"transport.close",
|
|
179
|
+
)
|
|
180
|
+
rows = (
|
|
181
|
+
{"family": "stream", "subevent": "stream.open"},
|
|
182
|
+
{"family": "stream", "subevent": "stream.chunk.received"},
|
|
183
|
+
{"family": "stream", "subevent": "stream.chunk.emit"},
|
|
184
|
+
{"family": "stream", "subevent": "stream.close"},
|
|
185
|
+
)
|
|
186
|
+
elif lane == "unidi_client_stream":
|
|
187
|
+
anchors = (
|
|
188
|
+
"transport.accept",
|
|
189
|
+
"transport.receive",
|
|
190
|
+
"framing.decode",
|
|
191
|
+
"dispatch.subevent.derive",
|
|
192
|
+
"handler.invoke",
|
|
193
|
+
"transport.close",
|
|
194
|
+
)
|
|
195
|
+
rows = (
|
|
196
|
+
{"family": "stream", "subevent": "stream.open"},
|
|
197
|
+
{"family": "stream", "subevent": "stream.chunk.received"},
|
|
198
|
+
{"family": "stream", "subevent": "stream.close"},
|
|
199
|
+
)
|
|
200
|
+
elif lane == "unidi_server_stream":
|
|
201
|
+
anchors = (
|
|
202
|
+
"transport.accept",
|
|
203
|
+
"handler.invoke",
|
|
204
|
+
"framing.encode",
|
|
205
|
+
"transport.emit",
|
|
206
|
+
"transport.close",
|
|
207
|
+
)
|
|
208
|
+
rows = (
|
|
209
|
+
{"family": "stream", "subevent": "stream.open"},
|
|
210
|
+
{"family": "stream", "subevent": "stream.chunk.emit"},
|
|
211
|
+
{"family": "stream", "subevent": "stream.emit_complete"},
|
|
212
|
+
{"family": "stream", "subevent": "stream.close"},
|
|
213
|
+
)
|
|
214
|
+
elif lane == "datagram":
|
|
215
|
+
anchors = (
|
|
216
|
+
"transport.accept",
|
|
217
|
+
"transport.receive",
|
|
218
|
+
"framing.decode",
|
|
219
|
+
"dispatch.subevent.derive",
|
|
220
|
+
"handler.invoke",
|
|
221
|
+
"framing.encode",
|
|
222
|
+
"transport.emit",
|
|
223
|
+
)
|
|
224
|
+
rows = (
|
|
225
|
+
{"family": "datagram", "subevent": "datagram.received"},
|
|
226
|
+
{"family": "datagram", "subevent": "datagram.emit"},
|
|
227
|
+
{"family": "datagram", "subevent": "datagram.emit_complete"},
|
|
228
|
+
)
|
|
229
|
+
else: # pragma: no cover - guarded by webtransport_lane_for_profile
|
|
230
|
+
raise _unsupported(f"webtransport lane {lane}")
|
|
106
231
|
else:
|
|
107
232
|
raise _unsupported(kind)
|
|
108
233
|
|
|
234
|
+
event_key_inputs = {
|
|
235
|
+
"family": family,
|
|
236
|
+
"binding": kind,
|
|
237
|
+
"framing": framing,
|
|
238
|
+
}
|
|
239
|
+
if kind == "webtransport":
|
|
240
|
+
event_key_inputs["lane"] = lane
|
|
241
|
+
event_key_inputs["inner_framing"] = inner_framing
|
|
242
|
+
|
|
109
243
|
return {
|
|
110
244
|
"op_id": op_id,
|
|
111
245
|
"binding_kind": kind,
|
|
112
246
|
"family": family,
|
|
113
247
|
"framing": framing,
|
|
114
248
|
"atom_anchors": anchors,
|
|
115
|
-
"event_key_inputs":
|
|
116
|
-
"family": family,
|
|
117
|
-
"binding": kind,
|
|
118
|
-
"framing": framing,
|
|
119
|
-
},
|
|
249
|
+
"event_key_inputs": event_key_inputs,
|
|
120
250
|
"capability_requirements": {
|
|
121
251
|
"required_mask": _required_mask(kind=kind, family=family, framing=str(framing)),
|
|
122
252
|
},
|
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
|
|
4
4
|
_FAMILIES = {
|
|
5
5
|
"request": ("request.received", "request.body.received"),
|
|
6
|
-
"response": ("response.emit", "response.emit_complete"),
|
|
7
6
|
"session": ("session.open", "session.ready", "session.close"),
|
|
8
7
|
"message": ("message.received", "message.decoded", "message.emit", "message.emit_complete"),
|
|
9
8
|
"stream": ("stream.open", "stream.chunk.received", "stream.chunk.emit", "stream.close"),
|
|
@@ -11,19 +10,29 @@ _FAMILIES = {
|
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
_BINDING_FAMILY = {
|
|
14
|
-
"http.rest": "
|
|
15
|
-
"
|
|
13
|
+
"http.rest": "request",
|
|
14
|
+
"https.rest": "request",
|
|
15
|
+
"http.jsonrpc": "request",
|
|
16
|
+
"https.jsonrpc": "request",
|
|
16
17
|
"http.stream": "stream",
|
|
18
|
+
"https.stream": "stream",
|
|
17
19
|
"http.sse": "stream",
|
|
20
|
+
"https.sse": "stream",
|
|
18
21
|
"ws": "message",
|
|
22
|
+
"wss": "message",
|
|
19
23
|
"websocket": "message",
|
|
24
|
+
"webtransport": "session",
|
|
25
|
+
"webtransport.session": "session",
|
|
26
|
+
"webtransport.bidi_stream": "stream",
|
|
27
|
+
"webtransport.unidi_client_stream": "stream",
|
|
28
|
+
"webtransport.unidi_server_stream": "stream",
|
|
20
29
|
"webtransport.datagram": "datagram",
|
|
21
30
|
}
|
|
22
31
|
|
|
23
32
|
_ALIASES = {
|
|
24
33
|
"receive": {"message": "message.received", "request": "request.received"},
|
|
25
|
-
"emit": {"
|
|
26
|
-
"complete": {"
|
|
34
|
+
"emit": {"message": "message.emit", "stream": "stream.chunk.emit", "datagram": "datagram.emit"},
|
|
35
|
+
"complete": {"message": "message.emit_complete", "stream": "stream.close", "datagram": "datagram.emit_complete"},
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Sequence
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tigrbl_core._spec.binding_spec import validate_webtransport_inner_framing
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
def _event(subevent: str, atom: str, family: str) -> dict[str, str]:
|
|
@@ -23,6 +26,26 @@ def compile_webtransport_events(surface: str) -> list[dict[str, str]]:
|
|
|
23
26
|
_event("stream.emit_complete", "transport.emit", "stream"),
|
|
24
27
|
_event("stream.close", "transport.close", "stream"),
|
|
25
28
|
]
|
|
29
|
+
if surface == "bidi_stream":
|
|
30
|
+
return [
|
|
31
|
+
_event("stream.open", "transport.accept", "stream"),
|
|
32
|
+
_event("stream.chunk.received", "transport.receive", "stream"),
|
|
33
|
+
_event("stream.chunk.emit", "transport.emit", "stream"),
|
|
34
|
+
_event("stream.close", "transport.close", "stream"),
|
|
35
|
+
]
|
|
36
|
+
if surface == "unidi_client_stream":
|
|
37
|
+
return [
|
|
38
|
+
_event("stream.open", "transport.accept", "stream"),
|
|
39
|
+
_event("stream.chunk.received", "transport.receive", "stream"),
|
|
40
|
+
_event("stream.close", "transport.close", "stream"),
|
|
41
|
+
]
|
|
42
|
+
if surface == "unidi_server_stream":
|
|
43
|
+
return [
|
|
44
|
+
_event("stream.open", "transport.accept", "stream"),
|
|
45
|
+
_event("stream.chunk.emit", "transport.emit", "stream"),
|
|
46
|
+
_event("stream.emit_complete", "transport.emit", "stream"),
|
|
47
|
+
_event("stream.close", "transport.close", "stream"),
|
|
48
|
+
]
|
|
26
49
|
if surface == "datagram":
|
|
27
50
|
return [
|
|
28
51
|
_event("datagram.received", "transport.receive", "datagram"),
|
|
@@ -31,10 +54,12 @@ def compile_webtransport_events(surface: str) -> list[dict[str, str]]:
|
|
|
31
54
|
]
|
|
32
55
|
if surface == "app_frame":
|
|
33
56
|
return [
|
|
34
|
-
_event("app_frame.decode", "framing.decode", "
|
|
35
|
-
_event("app_frame.emit", "transport.emit", "
|
|
36
|
-
_event("app_frame.encode", "framing.encode", "
|
|
57
|
+
_event("app_frame.decode", "framing.decode", "stream"),
|
|
58
|
+
_event("app_frame.emit", "transport.emit", "stream"),
|
|
59
|
+
_event("app_frame.encode", "framing.encode", "stream"),
|
|
37
60
|
]
|
|
61
|
+
if surface == "message":
|
|
62
|
+
raise ValueError("WebTransport does not expose message as a native surface")
|
|
38
63
|
raise ValueError(f"unsupported WebTransport surface: {surface}")
|
|
39
64
|
|
|
40
65
|
|
|
@@ -48,4 +73,152 @@ def compile_webtransport_chain(*, include_stream: bool = True, include_datagram:
|
|
|
48
73
|
return {"binding": "webtransport", "subevents": tuple(subevents)}
|
|
49
74
|
|
|
50
75
|
|
|
51
|
-
|
|
76
|
+
def compile_webtransport_native_lanes() -> dict[str, tuple[str, ...]]:
|
|
77
|
+
return {
|
|
78
|
+
"session": tuple(event["subevent"] for event in compile_webtransport_events("session")),
|
|
79
|
+
"bidi_stream": tuple(event["subevent"] for event in compile_webtransport_events("bidi_stream")),
|
|
80
|
+
"unidi_client_stream": tuple(
|
|
81
|
+
event["subevent"] for event in compile_webtransport_events("unidi_client_stream")
|
|
82
|
+
),
|
|
83
|
+
"unidi_server_stream": tuple(
|
|
84
|
+
event["subevent"] for event in compile_webtransport_events("unidi_server_stream")
|
|
85
|
+
),
|
|
86
|
+
"datagram": tuple(event["subevent"] for event in compile_webtransport_events("datagram")),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
_STREAM_EVENTS = {
|
|
91
|
+
"webtransport.stream.receive",
|
|
92
|
+
"webtransport.stream.send",
|
|
93
|
+
"webtransport.stream.close",
|
|
94
|
+
"webtransport.stream.reset",
|
|
95
|
+
"webtransport.stream.stop_sending",
|
|
96
|
+
}
|
|
97
|
+
_DATAGRAM_EVENTS = {
|
|
98
|
+
"webtransport.datagram.receive",
|
|
99
|
+
"webtransport.datagram.send",
|
|
100
|
+
}
|
|
101
|
+
_SESSION_EVENTS = {
|
|
102
|
+
"webtransport.connect",
|
|
103
|
+
"webtransport.accept",
|
|
104
|
+
"webtransport.disconnect",
|
|
105
|
+
"webtransport.close",
|
|
106
|
+
}
|
|
107
|
+
_STREAM_DIRECTIONS = {
|
|
108
|
+
"bidi": "bidi_stream",
|
|
109
|
+
"client_to_server": "unidi_client_stream",
|
|
110
|
+
"server_to_client": "unidi_server_stream",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def validate_webtransport_event_payload(
|
|
115
|
+
*,
|
|
116
|
+
event: str,
|
|
117
|
+
channel: str,
|
|
118
|
+
payload: dict[str, Any],
|
|
119
|
+
) -> dict[str, object]:
|
|
120
|
+
if not isinstance(payload, dict):
|
|
121
|
+
raise ValueError("WebTransport event payload must be a mapping")
|
|
122
|
+
if channel not in {"receive", "send"}:
|
|
123
|
+
raise ValueError("WebTransport event channel must be receive or send")
|
|
124
|
+
if event in _SESSION_EVENTS:
|
|
125
|
+
return _validate_session_payload(event=event, channel=channel, payload=payload)
|
|
126
|
+
if event in _STREAM_EVENTS:
|
|
127
|
+
return _validate_stream_payload(event=event, channel=channel, payload=payload)
|
|
128
|
+
if event in _DATAGRAM_EVENTS:
|
|
129
|
+
return _validate_datagram_payload(event=event, channel=channel, payload=payload)
|
|
130
|
+
if event.startswith("webtransport.message"):
|
|
131
|
+
raise ValueError("WebTransport message is not a native transport lane")
|
|
132
|
+
raise ValueError(f"unsupported WebTransport event {event!r}")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _validate_session_payload(
|
|
136
|
+
*,
|
|
137
|
+
event: str,
|
|
138
|
+
channel: str,
|
|
139
|
+
payload: dict[str, Any],
|
|
140
|
+
) -> dict[str, object]:
|
|
141
|
+
expected_channel = "receive" if event in {"webtransport.connect", "webtransport.disconnect"} else "send"
|
|
142
|
+
if channel != expected_channel:
|
|
143
|
+
raise ValueError(f"{event} is only valid on {expected_channel}")
|
|
144
|
+
_forbid(payload, "stream_id", "stream_direction", "datagram_id", "framing")
|
|
145
|
+
return {"family": "session", "lane": "session", "exchange": "request_response"}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _validate_stream_payload(
|
|
149
|
+
*,
|
|
150
|
+
event: str,
|
|
151
|
+
channel: str,
|
|
152
|
+
payload: dict[str, Any],
|
|
153
|
+
) -> dict[str, object]:
|
|
154
|
+
expected_channel = "receive" if event == "webtransport.stream.receive" else "send"
|
|
155
|
+
if channel != expected_channel:
|
|
156
|
+
raise ValueError(f"{event} is only valid on {expected_channel}")
|
|
157
|
+
_require(payload, "stream_id")
|
|
158
|
+
_forbid(payload, "datagram_id")
|
|
159
|
+
direction = payload.get("stream_direction")
|
|
160
|
+
if event in {
|
|
161
|
+
"webtransport.stream.receive",
|
|
162
|
+
"webtransport.stream.send",
|
|
163
|
+
}:
|
|
164
|
+
if not isinstance(direction, str) or direction not in _STREAM_DIRECTIONS:
|
|
165
|
+
raise ValueError("WebTransport stream payload requires valid stream_direction")
|
|
166
|
+
else:
|
|
167
|
+
direction = str(direction) if direction in _STREAM_DIRECTIONS else "bidi"
|
|
168
|
+
lane = _STREAM_DIRECTIONS[str(direction)]
|
|
169
|
+
if channel == "receive" and lane == "unidi_server_stream":
|
|
170
|
+
raise ValueError("server_to_client unidirectional streams cannot be receive events")
|
|
171
|
+
if channel == "send" and lane == "unidi_client_stream":
|
|
172
|
+
raise ValueError("client_to_server unidirectional streams cannot be send events")
|
|
173
|
+
validate_webtransport_inner_framing(
|
|
174
|
+
lane=lane,
|
|
175
|
+
inner_framing=payload.get("framing"),
|
|
176
|
+
)
|
|
177
|
+
return {
|
|
178
|
+
"family": "stream",
|
|
179
|
+
"lane": lane,
|
|
180
|
+
"exchange": {
|
|
181
|
+
"bidi_stream": "bidirectional_stream",
|
|
182
|
+
"unidi_client_stream": "client_stream",
|
|
183
|
+
"unidi_server_stream": "server_stream",
|
|
184
|
+
}[lane],
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _validate_datagram_payload(
|
|
189
|
+
*,
|
|
190
|
+
event: str,
|
|
191
|
+
channel: str,
|
|
192
|
+
payload: dict[str, Any],
|
|
193
|
+
) -> dict[str, object]:
|
|
194
|
+
expected_channel = "receive" if event == "webtransport.datagram.receive" else "send"
|
|
195
|
+
if channel != expected_channel:
|
|
196
|
+
raise ValueError(f"{event} is only valid on {expected_channel}")
|
|
197
|
+
_require(payload, "datagram_id")
|
|
198
|
+
_forbid(payload, "stream_id", "stream_direction")
|
|
199
|
+
validate_webtransport_inner_framing(
|
|
200
|
+
lane="datagram",
|
|
201
|
+
inner_framing=payload.get("framing"),
|
|
202
|
+
)
|
|
203
|
+
return {"family": "datagram", "lane": "datagram", "exchange": "bidirectional_stream"}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _require(payload: dict[str, Any], field: str) -> None:
|
|
207
|
+
value = payload.get(field)
|
|
208
|
+
if value is None or value == "":
|
|
209
|
+
raise ValueError(f"WebTransport payload requires {field}")
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _forbid(payload: dict[str, Any], *fields: str) -> None:
|
|
213
|
+
present = [field for field in fields if field in payload and payload[field] is not None]
|
|
214
|
+
if present:
|
|
215
|
+
joined = ", ".join(present)
|
|
216
|
+
raise ValueError(f"WebTransport payload field not valid for event: {joined}")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
__all__ = [
|
|
220
|
+
"compile_webtransport_chain",
|
|
221
|
+
"compile_webtransport_events",
|
|
222
|
+
"compile_webtransport_native_lanes",
|
|
223
|
+
"validate_webtransport_event_payload",
|
|
224
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrbl-kernel
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2.dev4
|
|
4
4
|
Summary: Kernel orchestration for composing Tigrbl runtime plans, bindings, operation dispatch, and optimized ASGI execution.
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -204,6 +204,7 @@ License: Apache License
|
|
|
204
204
|
See the License for the specific language governing permissions and
|
|
205
205
|
limitations under the License.
|
|
206
206
|
License-File: LICENSE
|
|
207
|
+
License-File: NOTICE
|
|
207
208
|
Keywords: tigrbl,asgi,api,json-rpc,rest,sqlalchemy,pydantic,kernel,orchestration,dispatch,openapi,openrpc,schema-first
|
|
208
209
|
Author: Jacob Stewart
|
|
209
210
|
Author-email: jacob@swarmauri.com
|
|
@@ -234,129 +235,161 @@ Project-URL: Organization, https://github.com/tigrbl
|
|
|
234
235
|
Project-URL: Repository, https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_kernel
|
|
235
236
|
Description-Content-Type: text/markdown
|
|
236
237
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
##
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
-
|
|
277
|
-
- [
|
|
278
|
-
- [
|
|
279
|
-
-
|
|
280
|
-
-
|
|
281
|
-
- [`tigrbl-ops-oltp`](https://pypi.org/project/tigrbl-ops-oltp/) - Transactional OLTP operation handlers for Tigrbl CRUD, bulk, REST, JSON-RPC, and database-backed workloads.
|
|
282
|
-
- [`tigrbl-ops-realtime`](https://pypi.org/project/tigrbl-ops-realtime/) - Realtime, streaming, datagram, websocket, and event operation handlers for Tigrbl ASGI runtimes.
|
|
283
|
-
- [`tigrbl-orm`](https://pypi.org/project/tigrbl-orm/) - SQLAlchemy ORM tables, mixins, columns, model helpers, and persistence primitives for Tigrbl applications.
|
|
284
|
-
- [`tigrbl-runtime`](https://pypi.org/project/tigrbl-runtime/) - Runtime pipeline helpers and execution bridge surfaces for Tigrbl ASGI applications, transports, and operation dispatch.
|
|
285
|
-
- [`tigrbl_spec`](https://pypi.org/project/tigrbl_spec/) - Shared Tigrbl interfaces, protocol definitions, compatibility targets, and specification artifacts for framework integration.
|
|
286
|
-
- [`tigrbl_tests`](https://pypi.org/project/tigrbl_tests/) - Reusable Tigrbl pytest fixtures, conformance assertions, integration helpers, and package test utilities.
|
|
287
|
-
- [`tigrbl-typing`](https://pypi.org/project/tigrbl-typing/) - Typing protocols, aliases, generics, and shared type helpers for Tigrbl framework packages and extensions.
|
|
288
|
-
|
|
289
|
-
Engine packages:
|
|
290
|
-
- [`tigrbl_engine_bigquery`](https://pypi.org/project/tigrbl_engine_bigquery/) - BigQuery engine plugin for Google BigQuery warehouse sessions, analytics workloads, and Tigrbl engine registration.
|
|
291
|
-
- [`tigrbl_engine_clickhouse`](https://pypi.org/project/tigrbl_engine_clickhouse/) - ClickHouse engine plugin for analytical database sessions, warehouse workloads, and Tigrbl engine registration.
|
|
292
|
-
- [`tigrbl_engine_csv`](https://pypi.org/project/tigrbl_engine_csv/) - CSV engine plugin for file-backed tables, pandas DataFrames, and lightweight Tigrbl data workflows.
|
|
293
|
-
- [`tigrbl_engine_dataframe`](https://pypi.org/project/tigrbl_engine_dataframe/) - DataFrame engine plugin for transactional pandas sessions and in-process Tigrbl analytics workloads.
|
|
294
|
-
- [`tigrbl_engine_duckdb`](https://pypi.org/project/tigrbl_engine_duckdb/) - DuckDB engine plugin for embedded analytical database sessions, OLAP workloads, and Tigrbl engine registration.
|
|
295
|
-
- [`tigrbl_engine_inmemcache`](https://pypi.org/project/tigrbl_engine_inmemcache/) - In-memory cache engine plugin for process-local TTL, LRU, and fast Tigrbl cache workflows.
|
|
296
|
-
- [`tigrbl_engine_inmemory`](https://pypi.org/project/tigrbl_engine_inmemory/) - In-memory database engine plugin for process-local transactional storage, copy-on-write snapshots, and Tigrbl testing.
|
|
297
|
-
- [`tigrbl_engine_membloom`](https://pypi.org/project/tigrbl_engine_membloom/) - In-memory Bloom filter engine plugin for membership checks, rotating TTL windows, and Tigrbl API workflows.
|
|
298
|
-
- [`tigrbl_engine_memdedupe`](https://pypi.org/project/tigrbl_engine_memdedupe/) - In-memory dedupe engine plugin for idempotency tracking, duplicate suppression, and Tigrbl workflow coordination.
|
|
299
|
-
- [`tigrbl_engine_memkv`](https://pypi.org/project/tigrbl_engine_memkv/) - In-memory key-value engine plugin for process-local KV storage, cache workflows, and lightweight Tigrbl services.
|
|
300
|
-
- [`tigrbl_engine_memlru`](https://pypi.org/project/tigrbl_engine_memlru/) - In-memory LRU engine plugin for least-recently-used cache behavior and process-local Tigrbl data workflows.
|
|
301
|
-
- [`tigrbl_engine_mempubsub`](https://pypi.org/project/tigrbl_engine_mempubsub/) - In-memory pub/sub engine plugin for process-local publish-subscribe channels, events, and Tigrbl realtime workflows.
|
|
302
|
-
- [`tigrbl_engine_memqueue`](https://pypi.org/project/tigrbl_engine_memqueue/) - In-memory queue engine plugin for process-local tasks, message workflows, and Tigrbl runtime coordination.
|
|
303
|
-
- [`tigrbl_engine_memrate`](https://pypi.org/project/tigrbl_engine_memrate/) - In-memory rate-limit engine plugin for API quotas, counters, windows, and Tigrbl governance workflows.
|
|
304
|
-
- [`tigrbl_engine_numpy`](https://pypi.org/project/tigrbl_engine_numpy/) - NumPy engine plugin for array-to-table helpers, analytical workflows, and Tigrbl data integration.
|
|
305
|
-
- [`tigrbl_engine_pandas`](https://pypi.org/project/tigrbl_engine_pandas/) - Pandas engine plugin for transactional DataFrame sessions, tabular workflows, and Tigrbl data integration.
|
|
306
|
-
- [`tigrbl_engine_pgsqli_wal`](https://pypi.org/project/tigrbl_engine_pgsqli_wal/) - PostgreSQL and SQLite WAL engine plugin for transactional Tigrbl workflows and database-backed engine registration.
|
|
307
|
-
- [`tigrbl_engine_postgres`](https://pypi.org/project/tigrbl_engine_postgres/) - PostgreSQL engine plugin for SQLAlchemy sessions, async database workflows, and Tigrbl application persistence.
|
|
308
|
-
- [`tigrbl_engine_pyspark`](https://pypi.org/project/tigrbl_engine_pyspark/) - PySpark engine plugin for distributed DataFrame integration, analytics workloads, and Tigrbl data workflows.
|
|
309
|
-
- [`tigrbl_engine_redis`](https://pypi.org/project/tigrbl_engine_redis/) - Redis engine plugin for cache, data structures, and Tigrbl engine workflows backed by Redis.
|
|
310
|
-
- [`tigrbl_engine_rediscachethrough`](https://pypi.org/project/tigrbl_engine_rediscachethrough/) - Redis cache-through engine plugin for Redis, PostgreSQL, and Tigrbl data-access acceleration workflows.
|
|
311
|
-
- [`tigrbl_engine_snowflake`](https://pypi.org/project/tigrbl_engine_snowflake/) - Snowflake engine plugin for warehouse sessions, analytical workloads, and Tigrbl engine registration.
|
|
312
|
-
- [`tigrbl_engine_sqlite`](https://pypi.org/project/tigrbl_engine_sqlite/) - SQLite engine plugin for SQLAlchemy sessions, local transactional storage, and Tigrbl application persistence.
|
|
313
|
-
- [`tigrbl_engine_xlsx`](https://pypi.org/project/tigrbl_engine_xlsx/) - XLSX engine plugin for Excel workbook-backed tables, worksheet data access, and Tigrbl tabular workflows.
|
|
314
|
-
|
|
315
|
-
Application packages:
|
|
316
|
-
- [`tigrbl_acme_ca`](https://pypi.org/project/tigrbl_acme_ca/) - ACME v2 certificate authority app for Tigrbl tables, certificate automation, TLS workflows, and API surfaces.
|
|
317
|
-
- [`tigrbl_spiffe`](https://pypi.org/project/tigrbl_spiffe/) - SPIFFE and SPIRE identity app for Tigrbl with workload identity tables, UDS transport, and HTTP API surfaces.
|
|
318
|
-
|
|
319
|
-
Source-tree links remain available from each package identity section; this ecosystem section is intentionally PyPI-first for package discovery and installation routing.
|
|
238
|
+
<div align="center">
|
|
239
|
+
<h1>tigrbl-kernel</h1>
|
|
240
|
+
<img src="https://raw.githubusercontent.com/swarmauri/swarmauri-sdk/master/assets/tigrbl_full_logo.png" alt="Tigrbl logo" width="140"/>
|
|
241
|
+
<p><strong>Kernel orchestration for composing Tigrbl runtime plans, bindings, operation dispatch, and optimized ASGI execution.</strong></p>
|
|
242
|
+
<a href="https://pypi.org/project/tigrbl-kernel/"><img src="https://img.shields.io/pypi/v/tigrbl-kernel?label=PyPI" alt="PyPI version for tigrbl-kernel"/></a>
|
|
243
|
+
<a href="https://pypi.org/project/tigrbl-kernel/"><img src="https://static.pepy.tech/badge/tigrbl-kernel" alt="Downloads for tigrbl-kernel"/></a>
|
|
244
|
+
<a href="https://discord.gg/K4YTAPapjR"><img src="https://img.shields.io/badge/Discord-Join%20chat-5865F2?logo=discord&logoColor=white" alt="Discord community for tigrbl-kernel"/></a>
|
|
245
|
+
<a href="https://github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_kernel/README.md"><img src="https://hits.sh/github.com/tigrbl/tigrbl/blob/master/pkgs/core/tigrbl_kernel/README.md.svg?label=hits" alt="Repository hits for tigrbl-kernel README"/></a>
|
|
246
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-Apache%202.0-525252" alt="Apache 2.0 license"/></a>
|
|
247
|
+
<a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-3776ab" alt="Python versions 3.10 | 3.11 | 3.12 | 3.13 | 3.14 for tigrbl-kernel"/></a>
|
|
248
|
+
<a href="https://github.com/tigrbl/tigrbl/blob/master/docs/README.md"><img src="https://img.shields.io/badge/workspace-core-1f6feb" alt="Workspace group for tigrbl-kernel"/></a>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
## What is tigrbl-kernel?
|
|
252
|
+
|
|
253
|
+
Kernel orchestration for composing Tigrbl runtime plans, bindings, operation dispatch, and optimized ASGI execution.
|
|
254
|
+
|
|
255
|
+
## Why use tigrbl-kernel?
|
|
256
|
+
|
|
257
|
+
Use it when you need this foundational Tigrbl layer directly as a small, focused dependency.
|
|
258
|
+
|
|
259
|
+
## When should I install tigrbl-kernel?
|
|
260
|
+
|
|
261
|
+
Install it for extension packages, package-local tests, or internals that need this boundary without the whole facade.
|
|
262
|
+
|
|
263
|
+
## Who is tigrbl-kernel for?
|
|
264
|
+
|
|
265
|
+
Framework maintainers, extension authors, and advanced users composing Tigrbl from split packages.
|
|
266
|
+
|
|
267
|
+
## Where does tigrbl-kernel fit?
|
|
268
|
+
|
|
269
|
+
`tigrbl-kernel` lives at `pkgs/core/tigrbl_kernel` and serves a focused layer in the split Tigrbl framework.
|
|
270
|
+
|
|
271
|
+
## How does tigrbl-kernel work?
|
|
272
|
+
|
|
273
|
+
It owns a narrow layer in the split workspace and is consumed by higher-level packages through explicit dependencies.
|
|
274
|
+
|
|
275
|
+
## Certification Status
|
|
276
|
+
|
|
277
|
+
- Package status: governed package in the `tigrbl/tigrbl` workspace.
|
|
278
|
+
- Governance source: [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json).
|
|
279
|
+
- Release evidence: [publish workflow](https://github.com/tigrbl/tigrbl/actions/workflows/publish.yml) validates package builds, tests, GitHub release assets, and PyPI publication for managed packages.
|
|
280
|
+
- Local certification guard: `pkgs/core/tigrbl_tests/tests/unit/test_package_badges_and_notices.py` verifies every package README keeps the Discord badge, Apache 2.0 badge, explicit Python-version badge, `LICENSE`, and `NOTICE`.
|
|
281
|
+
- Scope note: this README documents the package boundary. Runtime feature support remains governed by `.ssot/` entities and the conformance docs linked below.
|
|
320
282
|
|
|
321
283
|
## Install
|
|
322
284
|
|
|
285
|
+
```bash
|
|
286
|
+
uv add tigrbl-kernel
|
|
287
|
+
```
|
|
288
|
+
|
|
323
289
|
```bash
|
|
324
290
|
pip install tigrbl-kernel
|
|
325
291
|
```
|
|
326
292
|
|
|
327
|
-
##
|
|
293
|
+
## Surface Coverage
|
|
294
|
+
|
|
295
|
+
| Surface | Value |
|
|
296
|
+
|---|---|
|
|
297
|
+
| PyPI package | [`tigrbl-kernel`](https://pypi.org/project/tigrbl-kernel/) |
|
|
298
|
+
| Repository path | [`pkgs/core/tigrbl_kernel`](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_kernel) |
|
|
299
|
+
| Python import root | `tigrbl_kernel` |
|
|
300
|
+
| Console scripts | none declared |
|
|
301
|
+
| Entry points | none declared |
|
|
302
|
+
| Optional extras | none declared |
|
|
303
|
+
| Legal files | `LICENSE`, `NOTICE` |
|
|
304
|
+
| Supported Python | `3.10 | 3.11 | 3.12 | 3.13 | 3.14` |
|
|
305
|
+
|
|
306
|
+
## What It Owns
|
|
307
|
+
|
|
308
|
+
`tigrbl-kernel` owns the `foundational framework package` boundary. It should be installed when you need this package's focused responsibility without assuming every other Tigrbl workspace package is present.
|
|
309
|
+
|
|
310
|
+
Implementation orientation:
|
|
311
|
+
- `tigrbl_kernel`: _build, _compile, atoms, cache, callbacks, contract_classification, core, eventkey, eventkey_hooks, events, helpers, hook_types
|
|
312
|
+
|
|
313
|
+
## Public API and Import Surface
|
|
314
|
+
|
|
315
|
+
- Import roots: `tigrbl_kernel`.
|
|
316
|
+
- Public symbols: `BatchOpPlan`, `ExecutionBackend`, `Kernel`, `OpView`, `PackedKernel`, `RustBackendConfig`, `RustPlan`, `SchemaIn`, `SchemaOut`, `build_kernel_plan`, `build_packed_kernel`, `build_packed_kernel_measurement_view`.
|
|
317
|
+
- Workspace dependencies: [`tigrbl-typing`](https://pypi.org/project/tigrbl-typing/), [`tigrbl-atoms`](https://pypi.org/project/tigrbl-atoms/), [`tigrbl-core`](https://pypi.org/project/tigrbl-core/).
|
|
318
|
+
- External runtime dependencies: none declared.
|
|
319
|
+
|
|
320
|
+
## Usage Examples
|
|
321
|
+
|
|
322
|
+
### Verify the installed package
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
python -m pip show tigrbl-kernel
|
|
326
|
+
python - <<'PY'
|
|
327
|
+
from importlib.metadata import version
|
|
328
|
+
print(version("tigrbl-kernel"))
|
|
329
|
+
PY
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Import the package boundary
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
import importlib
|
|
336
|
+
|
|
337
|
+
module = importlib.import_module("tigrbl_kernel")
|
|
338
|
+
print(module.__name__)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Import a public symbol
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from tigrbl_kernel import BatchOpPlan
|
|
345
|
+
|
|
346
|
+
print(BatchOpPlan)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Use with the facade when building applications
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
uv add tigrbl tigrbl-kernel
|
|
353
|
+
python - <<'PY'
|
|
354
|
+
import tigrbl
|
|
355
|
+
print(tigrbl.__name__)
|
|
356
|
+
PY
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## How To Choose This Package
|
|
360
|
+
|
|
361
|
+
Choose `tigrbl-kernel` when the quick-answer table matches your use case. Choose [`tigrbl`](https://pypi.org/project/tigrbl/) instead when you want the full public facade. Choose a lower-level package such as [`tigrbl-core`](https://pypi.org/project/tigrbl-core/), [`tigrbl-base`](https://pypi.org/project/tigrbl-base/), or [`tigrbl-runtime`](https://pypi.org/project/tigrbl-runtime/) when you are building framework extensions or testing a specific internal boundary.
|
|
328
362
|
|
|
329
|
-
|
|
363
|
+
## Related Packages
|
|
330
364
|
|
|
331
|
-
|
|
365
|
+
- [`tigrbl-typing`](https://pypi.org/project/tigrbl-typing/)
|
|
366
|
+
- [`tigrbl-atoms`](https://pypi.org/project/tigrbl-atoms/)
|
|
367
|
+
- [`tigrbl-core`](https://pypi.org/project/tigrbl-core/)
|
|
368
|
+
- [`tigrbl`](https://pypi.org/project/tigrbl/)
|
|
369
|
+
- [`tigrbl-base`](https://pypi.org/project/tigrbl-base/)
|
|
370
|
+
- [`tigrbl-runtime`](https://pypi.org/project/tigrbl-runtime/)
|
|
332
371
|
|
|
333
|
-
|
|
372
|
+
## Documentation Links
|
|
334
373
|
|
|
335
|
-
|
|
374
|
+
- [Workspace docs](https://github.com/tigrbl/tigrbl/blob/master/docs/README.md)
|
|
375
|
+
- [Package catalog](https://github.com/tigrbl/tigrbl/blob/master/docs/developer/PACKAGE_CATALOG.md)
|
|
376
|
+
- [Package layout](https://github.com/tigrbl/tigrbl/blob/master/docs/developer/PACKAGE_LAYOUT.md)
|
|
377
|
+
- [Current target](https://github.com/tigrbl/tigrbl/blob/master/docs/conformance/CURRENT_TARGET.md)
|
|
378
|
+
- [Current state](https://github.com/tigrbl/tigrbl/blob/master/docs/conformance/CURRENT_STATE.md)
|
|
379
|
+
- [SSOT registry](https://github.com/tigrbl/tigrbl/blob/master/.ssot/registry.json)
|
|
380
|
+
- [Release workflow](https://github.com/tigrbl/tigrbl/actions/workflows/publish.yml)
|
|
336
381
|
|
|
337
|
-
|
|
338
|
-
It is not the authoritative location for repository governance, current target status, current state reporting, certification claims, or release evidence.
|
|
382
|
+
## Support
|
|
339
383
|
|
|
340
|
-
|
|
384
|
+
- Community: [Discord](https://discord.gg/K4YTAPapjR).
|
|
385
|
+
- Issues: [GitHub Issues](https://github.com/tigrbl/tigrbl/issues).
|
|
386
|
+
- Repository: [pkgs/core/tigrbl_kernel](https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_kernel).
|
|
341
387
|
|
|
342
|
-
-
|
|
343
|
-
- `docs/README.md`
|
|
344
|
-
- `docs/conformance/CURRENT_TARGET.md`
|
|
345
|
-
- `docs/conformance/CURRENT_STATE.md`
|
|
346
|
-
- `docs/conformance/NEXT_STEPS.md`
|
|
347
|
-
- `docs/governance/DOC_POINTERS.md`
|
|
348
|
-
- `docs/developer/PACKAGE_CATALOG.md`
|
|
349
|
-
- `docs/developer/PACKAGE_LAYOUT.md`
|
|
388
|
+
## Package-local Boundary
|
|
350
389
|
|
|
351
|
-
|
|
390
|
+
This README is the package-local distribution entry point for `tigrbl-kernel`. It answers install, usage, API, ownership, and certification-orientation questions for this package. Broader architectural decisions, release status, and cross-package proof chains remain in the repository-level docs and SSOT registry.
|
|
352
391
|
|
|
353
|
-
|
|
354
|
-
- organization: `https://github.com/tigrbl`
|
|
355
|
-
- social: `https://discord.gg/K4YTAPapjR`
|
|
356
|
-
- package path: `https://github.com/tigrbl/tigrbl/tree/master/pkgs/core/tigrbl_kernel`
|
|
357
|
-
- workspace path: `pkgs/core/tigrbl_kernel`
|
|
358
|
-
- workspace class: core Python package
|
|
359
|
-
- implementation layout: `tigrbl_kernel/`
|
|
392
|
+
## License
|
|
360
393
|
|
|
361
|
-
|
|
394
|
+
Licensed under the Apache License, Version 2.0. See `LICENSE`, `NOTICE`, and the official [Apache 2.0 license text](https://www.apache.org/licenses/LICENSE-2.0).
|
|
362
395
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
tigrbl_kernel/__init__.py,sha256=9hH7UWbqXuR5F0o0doSOjwLiy6KCHB6qZr5sS_O6Kr0,2813
|
|
2
|
-
tigrbl_kernel/_build.py,sha256=
|
|
2
|
+
tigrbl_kernel/_build.py,sha256=xLaLHsVWSeY3mefyCEnGYEGzHlyMU_1SGgyr7T6hUeY,50369
|
|
3
3
|
tigrbl_kernel/_compile.py,sha256=hQ3UscZ_zrCodWZfTe7mZ7I3MBMJzIxqF1SZLycRL9A,7771
|
|
4
4
|
tigrbl_kernel/atoms.py,sha256=ZNGj2PBUMu1BvPjNYlz4_vu4vhx4gD20C7zNE_GKfe8,12253
|
|
5
5
|
tigrbl_kernel/cache.py,sha256=NxuqvH03xYwcQoY86ie1hmtDeA36XdiLmNyx2ImLQ6k,2596
|
|
6
6
|
tigrbl_kernel/callbacks.py,sha256=r_ZI19nbAoDM-uf-FMUmWWAF9WaEKJK3Fpg-58ui8a4,772
|
|
7
|
+
tigrbl_kernel/contract_classification.py,sha256=5ZbhtJr88VtIPAtPWD5eL4BKKQU734kZeMEvEqh9cvU,6009
|
|
7
8
|
tigrbl_kernel/core.py,sha256=3lR3VrlHOFiP-POLluEpBKWeGDdaqX8tJYHQ0eRluP0,6667
|
|
8
9
|
tigrbl_kernel/eventkey.py,sha256=aTwzBkAjeC-kaEUItL1Mcfcq_Ur1fUbZDY6XxKdwWdg,1980
|
|
9
10
|
tigrbl_kernel/eventkey_hooks.py,sha256=Cbkev0GXgSH27dlFoVeaKC7XuQ9yJq4bI1Mlc3qB4Qs,2480
|
|
@@ -20,7 +21,7 @@ tigrbl_kernel/opchannel_capabilities.py,sha256=5zM9-1fc-W8BXyXF4VVWrIsmdwCH5xLra
|
|
|
20
21
|
tigrbl_kernel/opview_compiler.py,sha256=DbZ3E0GYl_JEgExELjaNE7v2W4hl9LqvAB-CE5TwP7o,5370
|
|
21
22
|
tigrbl_kernel/ordering.py,sha256=92elylml2LPDYiSnjj4QHNM3OR9utxN3Al9g-UJsyJ0,13542
|
|
22
23
|
tigrbl_kernel/payload.py,sha256=eyHo-cAUXLWQQ9-xZVOl_rjFudA-djMZji9hoK-v9IE,2167
|
|
23
|
-
tigrbl_kernel/protocol_bindings.py,sha256=
|
|
24
|
+
tigrbl_kernel/protocol_bindings.py,sha256=u3XcmqyX3MJqyUUkUgKSBEMvM8hXCwwP_eT-n_GqfEQ,9985
|
|
24
25
|
tigrbl_kernel/protocol_chains/__init__.py,sha256=iSnOw5R4ahGypm59bQgPpsBwFXbPcnKPozjSfc9N6GQ,151
|
|
25
26
|
tigrbl_kernel/protocol_chains/http_stream.py,sha256=5dY86DBJzcGcmrvOuDJo8M0cjLdeSmNAa_fo9zHuBdE,1123
|
|
26
27
|
tigrbl_kernel/protocol_chains/http_unary.py,sha256=7EABv0bUAJ5UI3TWP-S-8LtgwZJLj5pw-ZnZ_8qiD4g,1776
|
|
@@ -37,15 +38,16 @@ tigrbl_kernel/rust_plan.py,sha256=p4A46oOVtqamGJS_x_RGsGJo6IN81LKa1_KmGstyxoM,34
|
|
|
37
38
|
tigrbl_kernel/rust_spec.py,sha256=ICfIenmocRucLunvSvS91L0vli6YSMBGWnWSGN-9ppE,14668
|
|
38
39
|
tigrbl_kernel/segment_fusion.py,sha256=zbPNGd1eFJGFZHJxrfanaNMyrJjJw1aPRsl_jJfN4sM,1607
|
|
39
40
|
tigrbl_kernel/subevent_handlers.py,sha256=4j4780QjQiqY4qpRsyxrPh_znRK3SkLAFDN_x6eidwU,1779
|
|
40
|
-
tigrbl_kernel/subevent_taxonomy.py,sha256=
|
|
41
|
+
tigrbl_kernel/subevent_taxonomy.py,sha256=67RJ7ZkfJMLfa7evzVfTlFqtdWSnhp65zwA4qhNwSG8,2274
|
|
41
42
|
tigrbl_kernel/trace.py,sha256=dWILtq9seOj-WwCx__Pk_VZAbFIN1tqTbs0gIndYkU8,9816
|
|
42
43
|
tigrbl_kernel/transaction_units.py,sha256=_NeQHycjjDoLG1kmD74o10wDQpdA5qjs-Sd_fxPb0_E,863
|
|
43
44
|
tigrbl_kernel/transport_atoms.py,sha256=enfNCMuLgLMKi8zraMm-QOk7ys5d9kcP-WPr0-wK6KE,2136
|
|
44
45
|
tigrbl_kernel/transport_events.py,sha256=qogvEAAPPX_Y6V5evGuqvFCpIePYK4ryLmCDT9JHkFM,3777
|
|
45
46
|
tigrbl_kernel/types.py,sha256=BM_uPtF17MSI-q2ya93BWheAlPxOu-NaflwmxuicutA,2198
|
|
46
47
|
tigrbl_kernel/utils.py,sha256=iCcm0flQfAi4w4cbVEaq6yiPR-kAoXx2owiRpbBf-0o,6953
|
|
47
|
-
tigrbl_kernel/webtransport_events.py,sha256=
|
|
48
|
-
tigrbl_kernel-0.4.
|
|
49
|
-
tigrbl_kernel-0.4.
|
|
50
|
-
tigrbl_kernel-0.4.
|
|
51
|
-
tigrbl_kernel-0.4.
|
|
48
|
+
tigrbl_kernel/webtransport_events.py,sha256=MqWpnB4QvCbGgV8Sk-247EugbsXrfmDeY5YzLv84URo,8858
|
|
49
|
+
tigrbl_kernel-0.4.2.dev4.dist-info/METADATA,sha256=F2HrzGzOLA71P21WZmWg3oKEiaM1UNZ4ea8GLLxdSd8,22464
|
|
50
|
+
tigrbl_kernel-0.4.2.dev4.dist-info/WHEEL,sha256=eY7nduwzv-ldUxpzbRlxwvC693Hg6PX8bWDjEHjZ_dk,88
|
|
51
|
+
tigrbl_kernel-0.4.2.dev4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
52
|
+
tigrbl_kernel-0.4.2.dev4.dist-info/licenses/NOTICE,sha256=EvJMTshzsWz43LiK-DeN2ZuLtrP49cxvlrFlJ8F_buc,221
|
|
53
|
+
tigrbl_kernel-0.4.2.dev4.dist-info/RECORD,,
|
|
File without changes
|