tigrbl-runtime 0.4.2.dev4__tar.gz → 0.4.3.dev4__tar.gz
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_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/PKG-INFO +14 -5
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/README.md +13 -4
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/pyproject.toml +1 -1
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/__init__.py +25 -0
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/webhooks.py → tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/callbacks.py +12 -12
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_completion.py +33 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_context.py +96 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_jsonrpc.py +38 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_receive.py +63 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_scope.py +75 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_send.py +242 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_webtransport.py +335 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/asgi.py +77 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/__init__.py +2 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/_invoke_support.py +193 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/__init__.py +15 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/context.py +392 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/hot.py +35 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/hot_namespaces.py +421 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/hot_state.py +226 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/invoke.py +70 -174
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/kernel_executor.py +1 -1
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/packed.py +154 -603
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/types.py +39 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/handle.py +19 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/__init__.py +0 -7
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/runtime/exceptions.py +8 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/runtime/runtime.py +155 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/system.py +2 -2
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/__init__.py +5 -12
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/_fallback.py +73 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/_load_rust.py +3 -9
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/backend.py +25 -1
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/callbacks.py +43 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/codec.py +27 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/compile.py +15 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/errors.py +27 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/request.py +8 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/response.py +8 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/runtime.py +62 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/trace.py +29 -0
- tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/semantics.py +319 -0
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/__init__.py +0 -52
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/callbacks.py +0 -15
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/channel/asgi.py +0 -865
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/channel/state.py +0 -11
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/executors/helpers.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/executors/loop_regions.py +0 -46
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/executors/types.py +0 -975
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/handle.py +0 -11
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/__init__.py +0 -19
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/_iterators.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/anchors.py +0 -38
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/app_frame_codec.py +0 -111
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/completion_fence.py +0 -41
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/dispatch_atoms.py +0 -46
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/framing_atoms.py +0 -50
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/http_stream.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/http_unary.py +0 -8
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/lifespan_chain.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/loop_modes.py +0 -24
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/scope_schemas.py +0 -63
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/sse.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/static_files.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/subevent_handlers.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/transport_atoms.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/websocket.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/webtransport.py +0 -9
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/webtransport_session.py +0 -129
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/events.py +0 -97
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/exceptions.py +0 -18
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/executor/__init__.py +0 -6
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/executor/invoke.py +0 -72
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/kernel.py +0 -35
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/labels.py +0 -3
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/runtime.py +0 -229
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/__init__.py +0 -1
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/converters.py +0 -1
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/exceptions.py +0 -1
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/mappings.py +0 -1
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/utils.py +0 -1
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/_fallback.py +0 -320
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/_parity_contract.py +0 -161
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/callbacks.py +0 -60
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/codec.py +0 -14
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/compile.py +0 -35
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/errors.py +0 -2
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/parity.py +0 -72
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/runtime.py +0 -127
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/trace.py +0 -64
- tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/transactions.py +0 -3
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/LICENSE +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/NOTICE +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/channel/__init__.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/channel/capabilities.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/channel/websocket.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/config/__init__.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/config/constants.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/base.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/numba_packed.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/phase.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/_typing_aliases.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/base.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/channel.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/hook_types.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/response.py +0 -0
- {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/availability.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrbl-runtime
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3.dev4
|
|
4
4
|
Summary: Runtime pipeline helpers and execution bridge surfaces for Tigrbl ASGI applications, transports, and operation dispatch.
|
|
5
5
|
License: Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -311,12 +311,21 @@ pip install tigrbl-runtime
|
|
|
311
311
|
`tigrbl-runtime` 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.
|
|
312
312
|
|
|
313
313
|
Implementation orientation:
|
|
314
|
-
- `tigrbl_runtime`: callbacks, channel/, config/, executors/, handle, protocol/, runtime/,
|
|
314
|
+
- `tigrbl_runtime`: callbacks, channel/, config/, executors/, handle, protocol/, runtime/, transactions, webhooks
|
|
315
|
+
- `tigrbl_runtime/rust/`: deprecated compatibility shims only; Rust execution is unavailable.
|
|
316
|
+
|
|
317
|
+
Runtime authoring BCP:
|
|
318
|
+
- Do use this package for runtime-owned routing, request execution, transport-unit execution, framing atoms, transport channels, transaction helpers, and kernel integration.
|
|
319
|
+
- Do keep handler invocation, transaction progression, error handling, and transport emission aligned with compiled plans.
|
|
320
|
+
- Do not make application route handlers, FastAPI/Starlette objects, direct SQLAlchemy session calls, or ad-hoc engine construction the runtime contract.
|
|
321
|
+
- Do not bypass kernel plans or lifecycle phases when adding REST, JSON-RPC, stream, SSE, WebSocket, or WebTransport behavior.
|
|
322
|
+
- Avoid hiding behavior in transport wrappers that diagnostics and compiled plans cannot inspect.
|
|
315
323
|
|
|
316
324
|
## Public API and Import Surface
|
|
317
325
|
|
|
318
326
|
- Import roots: `tigrbl_runtime`.
|
|
319
|
-
- Public symbols: `
|
|
327
|
+
- Public symbols: `Runtime`, `RuntimeBase`.
|
|
328
|
+
- Rust-named symbols live only under `tigrbl_runtime.rust` as deprecated compatibility shims and must not be used for execution.
|
|
320
329
|
- Workspace dependencies: [`tigrbl-typing`](https://pypi.org/project/tigrbl-typing/), [`tigrbl-kernel`](https://pypi.org/project/tigrbl-kernel/), [`tigrbl-atoms`](https://pypi.org/project/tigrbl-atoms/), [`tigrbl-base`](https://pypi.org/project/tigrbl-base/), [`tigrbl-core`](https://pypi.org/project/tigrbl-core/).
|
|
321
330
|
- External runtime dependencies: `numba>=0.61.2`.
|
|
322
331
|
|
|
@@ -344,9 +353,9 @@ print(module.__name__)
|
|
|
344
353
|
### Import a public symbol
|
|
345
354
|
|
|
346
355
|
```python
|
|
347
|
-
from tigrbl_runtime import
|
|
356
|
+
from tigrbl_runtime import Runtime
|
|
348
357
|
|
|
349
|
-
print(
|
|
358
|
+
print(Runtime)
|
|
350
359
|
```
|
|
351
360
|
|
|
352
361
|
### Use with the facade when building applications
|
|
@@ -71,12 +71,21 @@ pip install tigrbl-runtime
|
|
|
71
71
|
`tigrbl-runtime` 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.
|
|
72
72
|
|
|
73
73
|
Implementation orientation:
|
|
74
|
-
- `tigrbl_runtime`: callbacks, channel/, config/, executors/, handle, protocol/, runtime/,
|
|
74
|
+
- `tigrbl_runtime`: callbacks, channel/, config/, executors/, handle, protocol/, runtime/, transactions, webhooks
|
|
75
|
+
- `tigrbl_runtime/rust/`: deprecated compatibility shims only; Rust execution is unavailable.
|
|
76
|
+
|
|
77
|
+
Runtime authoring BCP:
|
|
78
|
+
- Do use this package for runtime-owned routing, request execution, transport-unit execution, framing atoms, transport channels, transaction helpers, and kernel integration.
|
|
79
|
+
- Do keep handler invocation, transaction progression, error handling, and transport emission aligned with compiled plans.
|
|
80
|
+
- Do not make application route handlers, FastAPI/Starlette objects, direct SQLAlchemy session calls, or ad-hoc engine construction the runtime contract.
|
|
81
|
+
- Do not bypass kernel plans or lifecycle phases when adding REST, JSON-RPC, stream, SSE, WebSocket, or WebTransport behavior.
|
|
82
|
+
- Avoid hiding behavior in transport wrappers that diagnostics and compiled plans cannot inspect.
|
|
75
83
|
|
|
76
84
|
## Public API and Import Surface
|
|
77
85
|
|
|
78
86
|
- Import roots: `tigrbl_runtime`.
|
|
79
|
-
- Public symbols: `
|
|
87
|
+
- Public symbols: `Runtime`, `RuntimeBase`.
|
|
88
|
+
- Rust-named symbols live only under `tigrbl_runtime.rust` as deprecated compatibility shims and must not be used for execution.
|
|
80
89
|
- Workspace dependencies: [`tigrbl-typing`](https://pypi.org/project/tigrbl-typing/), [`tigrbl-kernel`](https://pypi.org/project/tigrbl-kernel/), [`tigrbl-atoms`](https://pypi.org/project/tigrbl-atoms/), [`tigrbl-base`](https://pypi.org/project/tigrbl-base/), [`tigrbl-core`](https://pypi.org/project/tigrbl-core/).
|
|
81
90
|
- External runtime dependencies: `numba>=0.61.2`.
|
|
82
91
|
|
|
@@ -104,9 +113,9 @@ print(module.__name__)
|
|
|
104
113
|
### Import a public symbol
|
|
105
114
|
|
|
106
115
|
```python
|
|
107
|
-
from tigrbl_runtime import
|
|
116
|
+
from tigrbl_runtime import Runtime
|
|
108
117
|
|
|
109
|
-
print(
|
|
118
|
+
print(Runtime)
|
|
110
119
|
```
|
|
111
120
|
|
|
112
121
|
### Use with the facade when building applications
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tigrbl-runtime"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.3.dev4"
|
|
4
4
|
description = "Runtime pipeline helpers and execution bridge surfaces for Tigrbl ASGI applications, transports, and operation dispatch."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Public exports for ``tigrbl_runtime`` with lazy loading."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from importlib import import_module
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
_LAZY_EXPORTS = {
|
|
9
|
+
"Runtime": "runtime",
|
|
10
|
+
"RuntimeBase": "runtime",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
*_LAZY_EXPORTS,
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def __getattr__(name: str) -> Any:
|
|
19
|
+
module_name = _LAZY_EXPORTS.get(name)
|
|
20
|
+
if module_name is None:
|
|
21
|
+
raise AttributeError(name)
|
|
22
|
+
module = import_module(f"{__name__}.{module_name}")
|
|
23
|
+
value = getattr(module, name)
|
|
24
|
+
globals()[name] = value
|
|
25
|
+
return value
|
|
@@ -7,7 +7,7 @@ from collections.abc import Callable, Mapping
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def
|
|
10
|
+
def compile_callback_delivery_plan(
|
|
11
11
|
*,
|
|
12
12
|
event: str,
|
|
13
13
|
endpoint: str,
|
|
@@ -19,11 +19,11 @@ def compile_webhook_delivery_plan(
|
|
|
19
19
|
"endpoint": endpoint,
|
|
20
20
|
"signing": {"algorithm": "hmac-sha256", "secret_ref": signing_secret_ref},
|
|
21
21
|
"retry": dict(retry or {"max_attempts": 1}),
|
|
22
|
-
"completion_subevent": "
|
|
22
|
+
"completion_subevent": "callback.delivery.emit_complete",
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def build_callback_payload(
|
|
27
27
|
*, event: str, data: Mapping[str, Any], idempotency_key: str
|
|
28
28
|
) -> dict[str, object]:
|
|
29
29
|
return {
|
|
@@ -33,13 +33,13 @@ def build_webhook_payload(
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def
|
|
36
|
+
def sign_callback_payload(payload: Mapping[str, Any], *, secret: str) -> dict[str, object]:
|
|
37
37
|
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
38
38
|
digest = hmac.new(secret.encode("utf-8"), canonical, hashlib.sha256).hexdigest()
|
|
39
39
|
return {"payload": dict(payload), "signature": f"sha256={digest}"}
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def
|
|
42
|
+
def deliver_callback(
|
|
43
43
|
payload: Mapping[str, Any],
|
|
44
44
|
*,
|
|
45
45
|
send: Callable[[Mapping[str, Any]], Mapping[str, Any]],
|
|
@@ -54,7 +54,7 @@ def deliver_webhook(
|
|
|
54
54
|
for attempt in range(1, max_attempts + 1):
|
|
55
55
|
attempts = attempt
|
|
56
56
|
if trace and attempt == 1:
|
|
57
|
-
trace("
|
|
57
|
+
trace("callback.delivery.emit")
|
|
58
58
|
try:
|
|
59
59
|
response = send(payload)
|
|
60
60
|
except Exception as exc:
|
|
@@ -65,7 +65,7 @@ def deliver_webhook(
|
|
|
65
65
|
"attempts": attempts,
|
|
66
66
|
"completed": False,
|
|
67
67
|
"error_ctx": {
|
|
68
|
-
"subevent": "
|
|
68
|
+
"subevent": "callback.delivery.emit",
|
|
69
69
|
"message": str(exc),
|
|
70
70
|
},
|
|
71
71
|
}
|
|
@@ -73,7 +73,7 @@ def deliver_webhook(
|
|
|
73
73
|
status_code = int(response.get("status_code", 0))
|
|
74
74
|
if 200 <= status_code < 300:
|
|
75
75
|
if trace:
|
|
76
|
-
trace("
|
|
76
|
+
trace("callback.delivery.emit_complete")
|
|
77
77
|
return {
|
|
78
78
|
"status": "delivered",
|
|
79
79
|
"attempts": attempts,
|
|
@@ -109,8 +109,8 @@ def _is_transient(status_code: int) -> bool:
|
|
|
109
109
|
|
|
110
110
|
|
|
111
111
|
__all__ = [
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
112
|
+
"build_callback_payload",
|
|
113
|
+
"compile_callback_delivery_plan",
|
|
114
|
+
"deliver_callback",
|
|
115
|
+
"sign_callback_payload",
|
|
116
116
|
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from tigrbl_atoms.atoms.transport.asgi_channel import (
|
|
6
|
+
complete_channel_state as _complete_channel_state,
|
|
7
|
+
)
|
|
8
|
+
from tigrbl_typing.channel import OpChannel
|
|
9
|
+
|
|
10
|
+
from ._asgi_scope import build_asgi_channel
|
|
11
|
+
from ._asgi_send import send_transport_via_channel
|
|
12
|
+
from .websocket import RuntimeWebSocket
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def channel_senders():
|
|
16
|
+
from tigrbl_atoms.atoms.egress.asgi_send import _send_json
|
|
17
|
+
|
|
18
|
+
return _send_json, send_transport_via_channel
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def complete_channel(env: Any, ctx: Any) -> None:
|
|
22
|
+
channel = ctx.get("channel")
|
|
23
|
+
if channel is None:
|
|
24
|
+
channel = build_asgi_channel(env)
|
|
25
|
+
ctx["channel"] = channel
|
|
26
|
+
if isinstance(getattr(channel, "state", None), dict):
|
|
27
|
+
_complete_channel_state(channel.state)
|
|
28
|
+
ctx["transport_completed"] = True
|
|
29
|
+
ctx["current_phase"] = "POST_EMIT"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def websocket_adapter(channel: OpChannel) -> RuntimeWebSocket:
|
|
33
|
+
return RuntimeWebSocket(channel)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
from tigrbl_kernel.channel_taxonomy import normalize_exchange
|
|
7
|
+
from tigrbl_typing.channel import OpChannel
|
|
8
|
+
|
|
9
|
+
from ._asgi_jsonrpc import _resolve_jsonrpc_endpoint
|
|
10
|
+
from ._asgi_receive import _receive_session_message
|
|
11
|
+
from ._asgi_scope import _scheme, build_asgi_channel
|
|
12
|
+
from ._asgi_webtransport import (
|
|
13
|
+
_receive_webtransport_session_messages,
|
|
14
|
+
_webtransport_scope_state,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def prepare_channel_context(env: Any, ctx: Any) -> OpChannel:
|
|
19
|
+
temp = ctx.get("temp")
|
|
20
|
+
if not isinstance(temp, dict):
|
|
21
|
+
ctx["temp"] = {}
|
|
22
|
+
temp = ctx["temp"]
|
|
23
|
+
|
|
24
|
+
route = temp.setdefault("route", {})
|
|
25
|
+
exchange = str(
|
|
26
|
+
route.get("exchange")
|
|
27
|
+
or getattr(ctx, "tigrbl_exchange", None)
|
|
28
|
+
or "request_response"
|
|
29
|
+
)
|
|
30
|
+
exchange = normalize_exchange(exchange)
|
|
31
|
+
protocol = str(route.get("protocol") or _scheme(getattr(env, "scope", {}) or {}))
|
|
32
|
+
framing = route.get("framing")
|
|
33
|
+
channel = build_asgi_channel(
|
|
34
|
+
env,
|
|
35
|
+
exchange=exchange,
|
|
36
|
+
protocol=protocol or None,
|
|
37
|
+
framing=str(framing) if isinstance(framing, str) else None,
|
|
38
|
+
)
|
|
39
|
+
ctx["channel"] = channel
|
|
40
|
+
ctx["path"] = channel.path
|
|
41
|
+
ctx["method"] = channel.method or channel.protocol.upper()
|
|
42
|
+
|
|
43
|
+
dispatch = temp.setdefault("dispatch", {})
|
|
44
|
+
if isinstance(dispatch, dict):
|
|
45
|
+
dispatch.setdefault("channel_protocol", channel.protocol)
|
|
46
|
+
dispatch.setdefault("channel_selector", channel.selector)
|
|
47
|
+
dispatch.setdefault("path_params", dict(channel.path_params))
|
|
48
|
+
endpoint = _resolve_jsonrpc_endpoint(ctx, channel)
|
|
49
|
+
if endpoint:
|
|
50
|
+
dispatch.setdefault("endpoint", endpoint)
|
|
51
|
+
|
|
52
|
+
scope = getattr(env, "scope", {}) or {}
|
|
53
|
+
scope_type = str(scope.get("type") or "http")
|
|
54
|
+
if scope_type == "webtransport":
|
|
55
|
+
await _receive_webtransport_session_messages(env, channel, ctx)
|
|
56
|
+
wt_state = _webtransport_scope_state(env)
|
|
57
|
+
trace = wt_state.get("trace")
|
|
58
|
+
if isinstance(trace, list):
|
|
59
|
+
ctx["webtransport_trace"] = trace
|
|
60
|
+
channel.state["webtransport_trace"] = trace
|
|
61
|
+
hook_trace = wt_state.get("hook_trace")
|
|
62
|
+
if isinstance(hook_trace, list):
|
|
63
|
+
ctx["webtransport_hook_trace"] = hook_trace
|
|
64
|
+
channel.state["webtransport_hook_trace"] = hook_trace
|
|
65
|
+
route.setdefault("protocol", dispatch.get("binding_protocol"))
|
|
66
|
+
route.setdefault("selector", channel.path)
|
|
67
|
+
route.setdefault("path_params", dict(channel.path_params))
|
|
68
|
+
route.setdefault("endpoint", dispatch.get("endpoint"))
|
|
69
|
+
elif scope_type == "websocket":
|
|
70
|
+
await _receive_session_message(
|
|
71
|
+
env,
|
|
72
|
+
channel,
|
|
73
|
+
ctx,
|
|
74
|
+
connect_type="websocket.connect",
|
|
75
|
+
receive_type="websocket.receive",
|
|
76
|
+
disconnect_type="websocket.disconnect",
|
|
77
|
+
eager_payload_after_connect=False,
|
|
78
|
+
)
|
|
79
|
+
message = ctx.get("channel_message")
|
|
80
|
+
if isinstance(message, Mapping) and message.get("text") is not None:
|
|
81
|
+
try:
|
|
82
|
+
parsed = json.loads(str(message.get("text")))
|
|
83
|
+
except Exception:
|
|
84
|
+
parsed = None
|
|
85
|
+
if isinstance(parsed, Mapping) and parsed.get("jsonrpc") == "2.0":
|
|
86
|
+
dispatch["binding_protocol"] = (
|
|
87
|
+
"wss.jsonrpc" if channel.protocol == "wss" else "ws.jsonrpc"
|
|
88
|
+
)
|
|
89
|
+
dispatch["rpc"] = dict(parsed)
|
|
90
|
+
dispatch["rpc_method"] = parsed.get("method")
|
|
91
|
+
route.setdefault("protocol", dispatch.get("binding_protocol"))
|
|
92
|
+
route.setdefault("selector", channel.path)
|
|
93
|
+
route.setdefault("path_params", dict(channel.path_params))
|
|
94
|
+
route.setdefault("endpoint", dispatch.get("endpoint"))
|
|
95
|
+
|
|
96
|
+
return channel
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping
|
|
4
|
+
|
|
5
|
+
from tigrbl_core.config.constants import __JSONRPC_DEFAULT_ENDPOINT_MAPPINGS__
|
|
6
|
+
from tigrbl_typing.channel import OpChannel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _normalize_path(path: str) -> str:
|
|
10
|
+
return path.rstrip("/") or "/"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _resolve_jsonrpc_endpoint(ctx: Any, channel: OpChannel) -> str | None:
|
|
14
|
+
if channel.kind != "http" or str(channel.method or "").upper() != "POST":
|
|
15
|
+
return None
|
|
16
|
+
|
|
17
|
+
path = _normalize_path(channel.path)
|
|
18
|
+
route = {}
|
|
19
|
+
temp = ctx.get("temp")
|
|
20
|
+
if isinstance(temp, dict):
|
|
21
|
+
route = temp.setdefault("route", {})
|
|
22
|
+
if isinstance(route, Mapping):
|
|
23
|
+
endpoint = route.get("endpoint")
|
|
24
|
+
if isinstance(endpoint, str) and endpoint:
|
|
25
|
+
return endpoint
|
|
26
|
+
|
|
27
|
+
for owner_key in ("router", "app"):
|
|
28
|
+
owner = ctx.get(owner_key)
|
|
29
|
+
mounts = getattr(owner, "_jsonrpc_endpoint_mounts", None)
|
|
30
|
+
if isinstance(mounts, Mapping):
|
|
31
|
+
endpoint = mounts.get(path) or mounts.get(channel.path)
|
|
32
|
+
if isinstance(endpoint, str) and endpoint:
|
|
33
|
+
return endpoint
|
|
34
|
+
|
|
35
|
+
for endpoint, mapped_path in __JSONRPC_DEFAULT_ENDPOINT_MAPPINGS__.items():
|
|
36
|
+
if path == _normalize_path(mapped_path):
|
|
37
|
+
return endpoint
|
|
38
|
+
return None
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import deque
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tigrbl_typing.channel import OpChannel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def _receive_session_message(
|
|
10
|
+
env: Any,
|
|
11
|
+
channel: OpChannel,
|
|
12
|
+
ctx: Any,
|
|
13
|
+
*,
|
|
14
|
+
connect_type: str,
|
|
15
|
+
receive_type: str | tuple[str, ...],
|
|
16
|
+
disconnect_type: str,
|
|
17
|
+
eager_payload_after_connect: bool = True,
|
|
18
|
+
) -> None:
|
|
19
|
+
receive = getattr(env, "receive", None)
|
|
20
|
+
if not callable(receive):
|
|
21
|
+
return
|
|
22
|
+
message = await receive()
|
|
23
|
+
state = channel.state
|
|
24
|
+
state["last_event"] = message
|
|
25
|
+
if message.get("type") == connect_type:
|
|
26
|
+
state["connected"] = True
|
|
27
|
+
if message.get("session_id") is not None:
|
|
28
|
+
state["session_id"] = message.get("session_id")
|
|
29
|
+
if not eager_payload_after_connect:
|
|
30
|
+
ctx["channel_message"] = message
|
|
31
|
+
return
|
|
32
|
+
message = await receive()
|
|
33
|
+
state["last_event"] = message
|
|
34
|
+
receive_types = (receive_type,) if isinstance(receive_type, str) else receive_type
|
|
35
|
+
if message.get("type") in receive_types:
|
|
36
|
+
queue = state.get("receive_queue")
|
|
37
|
+
if isinstance(queue, deque):
|
|
38
|
+
queue.append(message)
|
|
39
|
+
else:
|
|
40
|
+
next_queue = deque()
|
|
41
|
+
if isinstance(queue, list):
|
|
42
|
+
next_queue.extend(queue)
|
|
43
|
+
next_queue.append(message)
|
|
44
|
+
state["receive_queue"] = next_queue
|
|
45
|
+
payload = message.get("bytes")
|
|
46
|
+
if payload is None:
|
|
47
|
+
payload = message.get("data")
|
|
48
|
+
if payload is None and message.get("text") is not None:
|
|
49
|
+
payload = str(message.get("text")).encode("utf-8")
|
|
50
|
+
ctx["body"] = payload
|
|
51
|
+
ctx["channel_message"] = message
|
|
52
|
+
for key in (
|
|
53
|
+
"session_id",
|
|
54
|
+
"stream_id",
|
|
55
|
+
"stream_direction",
|
|
56
|
+
"datagram_id",
|
|
57
|
+
"framing",
|
|
58
|
+
):
|
|
59
|
+
if message.get(key) is not None:
|
|
60
|
+
state[key] = message.get(key)
|
|
61
|
+
elif message.get("type") == disconnect_type:
|
|
62
|
+
state["disconnected"] = True
|
|
63
|
+
ctx["channel_message"] = message
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping
|
|
4
|
+
from urllib.parse import parse_qs
|
|
5
|
+
|
|
6
|
+
from tigrbl_kernel.channel_taxonomy import (
|
|
7
|
+
channel_family as _channel_family,
|
|
8
|
+
channel_kind as _channel_kind,
|
|
9
|
+
channel_subevents as _subevents,
|
|
10
|
+
normalize_exchange,
|
|
11
|
+
)
|
|
12
|
+
from tigrbl_typing.channel import OpChannel
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _headers(scope: Mapping[str, Any]) -> dict[str, str]:
|
|
16
|
+
pairs = scope.get("headers", ())
|
|
17
|
+
out: dict[str, str] = {}
|
|
18
|
+
for key, value in pairs or ():
|
|
19
|
+
try:
|
|
20
|
+
out[bytes(key).decode("latin-1").lower()] = bytes(value).decode("latin-1")
|
|
21
|
+
except Exception:
|
|
22
|
+
continue
|
|
23
|
+
return out
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _query(scope: Mapping[str, Any]) -> dict[str, list[str]]:
|
|
27
|
+
raw = scope.get("query_string", b"")
|
|
28
|
+
if isinstance(raw, str):
|
|
29
|
+
raw = raw.encode("utf-8")
|
|
30
|
+
if not isinstance(raw, (bytes, bytearray)):
|
|
31
|
+
return {}
|
|
32
|
+
return {
|
|
33
|
+
key: [str(item) for item in values]
|
|
34
|
+
for key, values in parse_qs(
|
|
35
|
+
bytes(raw).decode("utf-8"), keep_blank_values=True
|
|
36
|
+
).items()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _scheme(scope: Mapping[str, Any]) -> str:
|
|
41
|
+
return str(scope.get("scheme") or "").lower()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def build_asgi_channel(
|
|
45
|
+
env: Any,
|
|
46
|
+
*,
|
|
47
|
+
exchange: str = "request_response",
|
|
48
|
+
protocol: str | None = None,
|
|
49
|
+
framing: str | None = None,
|
|
50
|
+
) -> OpChannel:
|
|
51
|
+
exchange = normalize_exchange(exchange)
|
|
52
|
+
scope = getattr(env, "scope", {}) or {}
|
|
53
|
+
scope_type = str(scope.get("type") or "http")
|
|
54
|
+
path = str(scope.get("path") or "/")
|
|
55
|
+
method = scope.get("method")
|
|
56
|
+
protocol_name = protocol or _scheme(scope) or scope_type
|
|
57
|
+
selector = path
|
|
58
|
+
if scope_type == "http" and isinstance(method, str):
|
|
59
|
+
selector = f"{method.upper()} {path}"
|
|
60
|
+
return OpChannel(
|
|
61
|
+
kind=_channel_kind(scope_type, exchange),
|
|
62
|
+
family=_channel_family(scope_type, exchange),
|
|
63
|
+
exchange=exchange,
|
|
64
|
+
protocol=protocol_name,
|
|
65
|
+
path=path,
|
|
66
|
+
method=str(method).upper() if isinstance(method, str) else None,
|
|
67
|
+
selector=selector,
|
|
68
|
+
framing=framing,
|
|
69
|
+
subevents=_subevents(scope_type, exchange),
|
|
70
|
+
headers=_headers(scope),
|
|
71
|
+
query=_query(scope),
|
|
72
|
+
path_params=scope.get("path_params", {}) or {},
|
|
73
|
+
send=getattr(env, "send", None),
|
|
74
|
+
receive=getattr(env, "receive", None),
|
|
75
|
+
)
|