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.
Files changed (107) hide show
  1. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/PKG-INFO +14 -5
  2. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/README.md +13 -4
  3. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/pyproject.toml +1 -1
  4. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/__init__.py +25 -0
  5. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/webhooks.py → tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/callbacks.py +12 -12
  6. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_completion.py +33 -0
  7. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_context.py +96 -0
  8. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_jsonrpc.py +38 -0
  9. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_receive.py +63 -0
  10. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_scope.py +75 -0
  11. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_send.py +242 -0
  12. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/_asgi_webtransport.py +335 -0
  13. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/channel/asgi.py +77 -0
  14. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/__init__.py +2 -0
  15. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/_invoke_support.py +193 -0
  16. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/__init__.py +15 -0
  17. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/context.py +392 -0
  18. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/hot.py +35 -0
  19. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/hot_namespaces.py +421 -0
  20. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/ctx/hot_state.py +226 -0
  21. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/invoke.py +70 -174
  22. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/kernel_executor.py +1 -1
  23. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/packed.py +154 -603
  24. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/executors/types.py +39 -0
  25. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/handle.py +19 -0
  26. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/__init__.py +0 -7
  27. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/runtime/exceptions.py +8 -0
  28. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/runtime/runtime.py +155 -0
  29. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/system.py +2 -2
  30. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/__init__.py +5 -12
  31. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/_fallback.py +73 -0
  32. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/_load_rust.py +3 -9
  33. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/backend.py +25 -1
  34. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/callbacks.py +43 -0
  35. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/codec.py +27 -0
  36. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/compile.py +15 -0
  37. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/errors.py +27 -0
  38. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/request.py +8 -0
  39. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/rust/response.py +8 -0
  40. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/runtime.py +62 -0
  41. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/rust/trace.py +29 -0
  42. tigrbl_runtime-0.4.3.dev4/tigrbl_runtime/semantics.py +319 -0
  43. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/__init__.py +0 -52
  44. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/callbacks.py +0 -15
  45. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/channel/asgi.py +0 -865
  46. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/channel/state.py +0 -11
  47. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/executors/helpers.py +0 -3
  48. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/executors/loop_regions.py +0 -46
  49. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/executors/types.py +0 -975
  50. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/handle.py +0 -11
  51. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/__init__.py +0 -19
  52. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/_iterators.py +0 -3
  53. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/anchors.py +0 -38
  54. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/app_frame_codec.py +0 -111
  55. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/completion_fence.py +0 -41
  56. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/dispatch_atoms.py +0 -46
  57. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/framing_atoms.py +0 -50
  58. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/http_stream.py +0 -3
  59. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/http_unary.py +0 -8
  60. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/lifespan_chain.py +0 -3
  61. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/loop_modes.py +0 -24
  62. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/scope_schemas.py +0 -63
  63. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/sse.py +0 -3
  64. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/static_files.py +0 -3
  65. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/subevent_handlers.py +0 -3
  66. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/transport_atoms.py +0 -3
  67. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/websocket.py +0 -3
  68. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/webtransport.py +0 -9
  69. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/protocol/webtransport_session.py +0 -129
  70. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/events.py +0 -97
  71. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/exceptions.py +0 -18
  72. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/executor/__init__.py +0 -6
  73. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/executor/invoke.py +0 -72
  74. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/kernel.py +0 -35
  75. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/labels.py +0 -3
  76. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/runtime.py +0 -229
  77. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/__init__.py +0 -1
  78. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/converters.py +0 -1
  79. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/exceptions.py +0 -1
  80. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/mappings.py +0 -1
  81. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/runtime/status/utils.py +0 -1
  82. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/_fallback.py +0 -320
  83. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/_parity_contract.py +0 -161
  84. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/callbacks.py +0 -60
  85. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/codec.py +0 -14
  86. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/compile.py +0 -35
  87. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/errors.py +0 -2
  88. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/parity.py +0 -72
  89. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/runtime.py +0 -127
  90. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/rust/trace.py +0 -64
  91. tigrbl_runtime-0.4.2.dev4/tigrbl_runtime/transactions.py +0 -3
  92. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/LICENSE +0 -0
  93. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/NOTICE +0 -0
  94. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/channel/__init__.py +0 -0
  95. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/channel/capabilities.py +0 -0
  96. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/channel/websocket.py +0 -0
  97. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/config/__init__.py +0 -0
  98. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/config/constants.py +0 -0
  99. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/base.py +0 -0
  100. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/numba_packed.py +0 -0
  101. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/executors/phase.py +0 -0
  102. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/_typing_aliases.py +0 -0
  103. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/base.py +0 -0
  104. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/channel.py +0 -0
  105. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/hook_types.py +0 -0
  106. {tigrbl_runtime-0.4.2.dev4 → tigrbl_runtime-0.4.3.dev4}/tigrbl_runtime/runtime/response.py +0 -0
  107. {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.2.dev4
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/, rust/, transactions, webhooks
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: `ExecutionBackend`, `RustBackendConfig`, `RustBindingsUnavailableError`.
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 ExecutionBackend
356
+ from tigrbl_runtime import Runtime
348
357
 
349
- print(ExecutionBackend)
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/, rust/, transactions, webhooks
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: `ExecutionBackend`, `RustBackendConfig`, `RustBindingsUnavailableError`.
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 ExecutionBackend
116
+ from tigrbl_runtime import Runtime
108
117
 
109
- print(ExecutionBackend)
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.2.dev4"
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 compile_webhook_delivery_plan(
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": "webhook.delivery.emit_complete",
22
+ "completion_subevent": "callback.delivery.emit_complete",
23
23
  }
24
24
 
25
25
 
26
- def build_webhook_payload(
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 sign_webhook_payload(payload: Mapping[str, Any], *, secret: str) -> dict[str, object]:
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 deliver_webhook(
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("webhook.delivery.emit")
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": "webhook.delivery.emit",
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("webhook.delivery.emit_complete")
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
- "build_webhook_payload",
113
- "compile_webhook_delivery_plan",
114
- "deliver_webhook",
115
- "sign_webhook_payload",
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
+ )