agentixx 0.2.2__tar.gz → 0.2.4__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 (81) hide show
  1. {agentixx-0.2.2 → agentixx-0.2.4}/PKG-INFO +1 -1
  2. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/__init__.py +1 -1
  3. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/cli/build.py +38 -19
  4. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/nix/wrapper.nix.tmpl +7 -1
  5. agentixx-0.2.4/agentix/runtime/client/_sio_facade.py +87 -0
  6. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/server/worker/client.py +16 -1
  7. {agentixx-0.2.2 → agentixx-0.2.4}/pyproject.toml +1 -1
  8. {agentixx-0.2.2 → agentixx-0.2.4}/tests/_namespace_target.py +9 -0
  9. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_namespace_roundtrip.py +47 -0
  10. agentixx-0.2.2/agentix/runtime/client/_sio_facade.py +0 -47
  11. {agentixx-0.2.2 → agentixx-0.2.4}/.claude/scheduled_tasks.lock +0 -0
  12. {agentixx-0.2.2 → agentixx-0.2.4}/.claude/settings.local.json +0 -0
  13. {agentixx-0.2.2 → agentixx-0.2.4}/.claude/skills/agent-integration/SKILL.md +0 -0
  14. {agentixx-0.2.2 → agentixx-0.2.4}/.github/workflows/docs.yml +0 -0
  15. {agentixx-0.2.2 → agentixx-0.2.4}/.github/workflows/test.yml +0 -0
  16. {agentixx-0.2.2 → agentixx-0.2.4}/.gitignore +0 -0
  17. {agentixx-0.2.2 → agentixx-0.2.4}/ARCHITECTURE.md +0 -0
  18. {agentixx-0.2.2 → agentixx-0.2.4}/CLAUDE.md +0 -0
  19. {agentixx-0.2.2 → agentixx-0.2.4}/LICENSE +0 -0
  20. {agentixx-0.2.2 → agentixx-0.2.4}/README.md +0 -0
  21. {agentixx-0.2.2 → agentixx-0.2.4}/ROADMAP.md +0 -0
  22. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/cli/__init__.py +0 -0
  23. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/cli/__main__.py +0 -0
  24. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/cli/_resolve.py +0 -0
  25. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/deployment/__init__.py +0 -0
  26. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/deployment/_plugin.py +0 -0
  27. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/deployment/base.py +0 -0
  28. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/log/__init__.py +0 -0
  29. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/log/_bridge.py +0 -0
  30. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/nix/builder.nix +0 -0
  31. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/nix/flake.lock +0 -0
  32. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/nix/flake.nix +0 -0
  33. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/PROTOCOL.md +0 -0
  34. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/__init__.py +0 -0
  35. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/client/__init__.py +0 -0
  36. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/client/client.py +0 -0
  37. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/server/__init__.py +0 -0
  38. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/server/app.py +0 -0
  39. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/server/sio.py +0 -0
  40. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/server/worker/__init__.py +0 -0
  41. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/server/worker/__main__.py +0 -0
  42. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/server/worker/invoker.py +0 -0
  43. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/server/worker/process.py +0 -0
  44. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/shared/__init__.py +0 -0
  45. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/shared/callables.py +0 -0
  46. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/shared/codec.py +0 -0
  47. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/shared/framing.py +0 -0
  48. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/shared/idents.py +0 -0
  49. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/runtime/shared/models.py +0 -0
  50. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/sio.py +0 -0
  51. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/trace/__init__.py +0 -0
  52. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/trace/_bridge.py +0 -0
  53. {agentixx-0.2.2 → agentixx-0.2.4}/agentix/trace/processors.py +0 -0
  54. {agentixx-0.2.2 → agentixx-0.2.4}/docs/DEPLOY.md +0 -0
  55. {agentixx-0.2.2 → agentixx-0.2.4}/docs/concepts/bundles.mdx +0 -0
  56. {agentixx-0.2.2 → agentixx-0.2.4}/docs/concepts/remote-calls.mdx +0 -0
  57. {agentixx-0.2.2 → agentixx-0.2.4}/docs/deployment.mdx +0 -0
  58. {agentixx-0.2.2 → agentixx-0.2.4}/docs/development.mdx +0 -0
  59. {agentixx-0.2.2 → agentixx-0.2.4}/docs/docs.json +0 -0
  60. {agentixx-0.2.2 → agentixx-0.2.4}/docs/index.mdx +0 -0
  61. {agentixx-0.2.2 → agentixx-0.2.4}/docs/integrate-agent.mdx +0 -0
  62. {agentixx-0.2.2 → agentixx-0.2.4}/docs/integrate-dataset.mdx +0 -0
  63. {agentixx-0.2.2 → agentixx-0.2.4}/docs/quickstart.mdx +0 -0
  64. {agentixx-0.2.2 → agentixx-0.2.4}/docs/reference/architecture.mdx +0 -0
  65. {agentixx-0.2.2 → agentixx-0.2.4}/docs/reference/cli.mdx +0 -0
  66. {agentixx-0.2.2 → agentixx-0.2.4}/tests/__init__.py +0 -0
  67. {agentixx-0.2.2 → agentixx-0.2.4}/tests/_concurrent_target.py +0 -0
  68. {agentixx-0.2.2 → agentixx-0.2.4}/tests/_rpc_helpers.py +0 -0
  69. {agentixx-0.2.2 → agentixx-0.2.4}/tests/_trace_target.py +0 -0
  70. {agentixx-0.2.2 → agentixx-0.2.4}/tests/_user_app_target.py +0 -0
  71. {agentixx-0.2.2 → agentixx-0.2.4}/tests/_worker_target.py +0 -0
  72. {agentixx-0.2.2 → agentixx-0.2.4}/tests/conftest.py +0 -0
  73. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_concurrent_remote.py +0 -0
  74. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_cross_process_trace.py +0 -0
  75. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_models.py +0 -0
  76. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_plugin_axes.py +0 -0
  77. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_plugin_registry.py +0 -0
  78. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_remote_importable_module.py +0 -0
  79. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_rpc_protocol.py +0 -0
  80. {agentixx-0.2.2 → agentixx-0.2.4}/tests/test_worker_subprocess.py +0 -0
  81. {agentixx-0.2.2 → agentixx-0.2.4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentixx
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Sandboxed rollouts you call like typed Python (import as `agentix`)
5
5
  Project-URL: Homepage, https://github.com/Agentiix/Agentix
6
6
  Author: Agentiix
@@ -24,7 +24,7 @@ from agentix.runtime.client import RemoteCallError, RuntimeClient
24
24
  from agentix.runtime.client._sio_facade import AsyncClientNamespace
25
25
  from agentix.sio import Namespace, RemoteSioError, register_namespace
26
26
 
27
- __version__ = "0.2.2"
27
+ __version__ = "0.2.4"
28
28
 
29
29
  __all__ = [
30
30
  "AsyncClientNamespace",
@@ -87,13 +87,23 @@ def _stage_builder(dest: Path) -> None:
87
87
  (dest / fname).write_bytes(src.read_bytes())
88
88
 
89
89
 
90
- def _discover_plugin_nix(stage_plugin_dir: Path) -> list[str]:
91
- """Find every `agentix.<short>/default.nix` shipped by installed wheels.
92
-
93
- Returns the list of nix-relative paths (one per plugin) ready to drop
94
- into the generated wrapper flake. Each plugin's default.nix is copied
95
- into `stage_plugin_dir/<short>.nix` so the flake context is self-
96
- contained Nix won't follow absolute paths outside the flake root.
90
+ def _discover_plugin_nix(stage_plugin_dir: Path, project_src: Path) -> list[str]:
91
+ """Find every `default.nix` that contributes system binaries to the bundle.
92
+
93
+ Two sources, in order:
94
+
95
+ 1. Plugin wheels installed under the `agentix` namespace
96
+ (`agentix.<short>/default.nix`). These ship via `pip install
97
+ agentix-<plugin>` and are how runtime extensions add their CLI
98
+ deps (e.g. `agentix-runtime-basic` adds `bash`).
99
+ 2. The **user project root**'s own `default.nix`, if present —
100
+ lets a project pull in extra binaries (`claude`, `ffmpeg`, ...)
101
+ without packaging a fake plugin.
102
+
103
+ Each discovered file is copied into `stage_plugin_dir/<name>.nix`
104
+ so the flake context is self-contained; Nix won't follow absolute
105
+ paths outside the flake root. Returns the list of nix-relative
106
+ paths ready to drop into the generated wrapper flake.
97
107
  """
98
108
  stage_plugin_dir.mkdir(parents=True)
99
109
  nix_paths: list[str] = []
@@ -101,18 +111,27 @@ def _discover_plugin_nix(stage_plugin_dir: Path) -> list[str]:
101
111
  try:
102
112
  agentix_root = resources.files("agentix")
103
113
  except (ModuleNotFoundError, FileNotFoundError):
104
- return nix_paths
114
+ agentix_root = None
115
+
116
+ if agentix_root is not None:
117
+ for entry in agentix_root.iterdir():
118
+ if not entry.is_dir():
119
+ continue
120
+ nix_file = entry / "default.nix"
121
+ if not nix_file.is_file():
122
+ continue
123
+ short = entry.name
124
+ target = stage_plugin_dir / f"{short}.nix"
125
+ target.write_bytes(nix_file.read_bytes())
126
+ nix_paths.append(f"./plugins/{short}.nix")
127
+
128
+ # Project root default.nix, when present.
129
+ project_nix = project_src / "default.nix"
130
+ if project_nix.is_file():
131
+ target = stage_plugin_dir / "project.nix"
132
+ target.write_bytes(project_nix.read_bytes())
133
+ nix_paths.append("./plugins/project.nix")
105
134
 
106
- for entry in agentix_root.iterdir():
107
- if not entry.is_dir():
108
- continue
109
- nix_file = entry / "default.nix"
110
- if not nix_file.is_file():
111
- continue
112
- short = entry.name
113
- target = stage_plugin_dir / f"{short}.nix"
114
- target.write_bytes(nix_file.read_bytes())
115
- nix_paths.append(f"./plugins/{short}.nix")
116
135
  return nix_paths
117
136
 
118
137
 
@@ -274,7 +293,7 @@ def main(argv: Sequence[str] | None = None) -> int:
274
293
  def _stage(stage: Path) -> None:
275
294
  _stage_builder(stage / "_builder")
276
295
  _stage_project(src, stage / "project")
277
- plugin_paths = _discover_plugin_nix(stage / "plugins")
296
+ plugin_paths = _discover_plugin_nix(stage / "plugins", src)
278
297
  wrapper = _render_wrapper(
279
298
  name=name,
280
299
  tag=tag,
@@ -6,7 +6,13 @@
6
6
  outputs = { self, agentix }:
7
7
  let
8
8
  system = "@SYSTEM@";
9
- pkgs = agentix.inputs.nixpkgs.legacyPackages.${system};
9
+ # Re-import nixpkgs with `config.allowUnfree = true` so plugin
10
+ # `default.nix` files can pull unfree binaries (e.g. claude CLI).
11
+ # Plugins that don't need unfree packages are unaffected.
12
+ pkgs = import agentix.inputs.nixpkgs {
13
+ inherit system;
14
+ config.allowUnfree = true;
15
+ };
10
16
  in {
11
17
  packages.${system}.bundle = agentix.lib.mkBundle {
12
18
  inherit pkgs;
@@ -0,0 +1,87 @@
1
+ """Host-side namespace helpers.
2
+
3
+ `AsyncClientNamespace` is a thin subclass of `socketio.AsyncClientNamespace`
4
+ that msgpack-wraps event payloads — so plugin authors write
5
+ `await self.emit("x", {"a": 1})` and `async def on_x(self, data)`, and
6
+ the bytes/msgpack wire format stays internal.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import asyncio
12
+ import logging
13
+ from typing import Any
14
+
15
+ import socketio
16
+
17
+ from agentix.runtime.shared.codec import pack, unpack
18
+
19
+ logger = logging.getLogger("agentix.runtime.client.sio")
20
+
21
+
22
+ def _decode(raw: Any) -> Any:
23
+ if isinstance(raw, memoryview):
24
+ raw = raw.tobytes()
25
+ elif isinstance(raw, bytearray):
26
+ raw = bytes(raw)
27
+ if isinstance(raw, bytes):
28
+ return unpack(raw)
29
+ return raw
30
+
31
+
32
+ # Socket.IO lifecycle events run inline (they're cheap and ordering
33
+ # matters); everything else is a user data event and is detached.
34
+ _LIFECYCLE_EVENTS = frozenset({"connect", "disconnect", "connect_error"})
35
+
36
+
37
+ class AsyncClientNamespace(socketio.AsyncClientNamespace):
38
+ """`socketio.AsyncClientNamespace` with msgpack at the boundary.
39
+
40
+ Override `on_<event>` for inbound; call `await self.emit(...)` for
41
+ outbound. Data is plain Python — packing happens automatically.
42
+
43
+ Data-event handlers are dispatched as **detached tasks**, never
44
+ awaited inline. `socketio.AsyncClient` awaits `trigger_event` inside
45
+ its single websocket receive loop — so a slow handler (e.g. one
46
+ that calls a slow LLM) would stall *every* inbound event on the
47
+ connection, including unrelated `c.remote` results. Detaching keeps
48
+ the receive loop free; handler ordering per event is still
49
+ preserved by the order tasks are created.
50
+ """
51
+
52
+ _detached_tasks: set[asyncio.Task]
53
+
54
+ async def emit(self, event: str, data: Any = None, **kwargs: Any) -> Any:
55
+ return await super().emit(event, pack(data), **kwargs)
56
+
57
+ async def trigger_event(self, event: str, *args: Any) -> Any:
58
+ if event in _LIFECYCLE_EVENTS:
59
+ # Lifecycle: run inline. No msgpack payload to unwrap.
60
+ return await super().trigger_event(event, *args)
61
+
62
+ # Data event: unwrap the msgpack payload, then dispatch detached.
63
+ if args and isinstance(args[0], (bytes, bytearray, memoryview)):
64
+ args = (_decode(args[0]),) + args[1:]
65
+
66
+ handler = getattr(self, "on_" + event, None)
67
+ if handler is None:
68
+ return None
69
+
70
+ result = handler(*args)
71
+ if asyncio.iscoroutine(result):
72
+ if not hasattr(self, "_detached_tasks"):
73
+ self._detached_tasks = set()
74
+ task = asyncio.create_task(self._guard(result, event))
75
+ self._detached_tasks.add(task)
76
+ task.add_done_callback(self._detached_tasks.discard)
77
+ return None
78
+
79
+ @staticmethod
80
+ async def _guard(coro: Any, event: str) -> None:
81
+ try:
82
+ await coro
83
+ except Exception:
84
+ logger.exception("namespace handler for %r raised", event)
85
+
86
+
87
+ __all__ = ["AsyncClientNamespace"]
@@ -26,6 +26,11 @@ logger = logging.getLogger("agentix.runtime.server.worker.client")
26
26
 
27
27
  _WORKER_START_TIMEOUT = 15.0
28
28
  _DEFAULT_WORKER_PATH = "/usr/local/bin:/usr/bin:/bin"
29
+ # Plugin `default.nix` derivations are symlink-joined into this path
30
+ # inside the bundle image (see `agentix/nix/builder.nix`). Worker code
31
+ # (`subprocess.run("claude", ...)`, `c.remote(cc.run, ...)`, ...) must
32
+ # be able to find those binaries by bare name.
33
+ _RUNTIME_BIN_PATH = "/nix/runtime/bin"
29
34
  _STRIPPED_ENV = {
30
35
  "LD_LIBRARY_PATH",
31
36
  "LD_PRELOAD",
@@ -43,7 +48,17 @@ def _clean_worker_env(runtime_bin_dir: Path | None) -> dict[str, str]:
43
48
  for key, value in os.environ.items()
44
49
  if key not in _STRIPPED_ENV and not any(key.startswith(prefix) for prefix in _STRIPPED_ENV_PREFIXES)
45
50
  }
46
- env["PATH"] = f"{runtime_bin_dir}:{_DEFAULT_WORKER_PATH}" if runtime_bin_dir is not None else _DEFAULT_WORKER_PATH
51
+ # Build PATH from: the venv's bin (`runtime_bin_dir`), the bundle's
52
+ # symlink-join (`/nix/runtime/bin`), then a minimal system fallback.
53
+ # Inside the bundle image the first two are siblings and both must
54
+ # be searchable; outside the bundle, only the first one exists.
55
+ parts: list[str] = []
56
+ if runtime_bin_dir is not None:
57
+ parts.append(str(runtime_bin_dir))
58
+ if _RUNTIME_BIN_PATH not in parts:
59
+ parts.append(_RUNTIME_BIN_PATH)
60
+ parts.append(_DEFAULT_WORKER_PATH)
61
+ env["PATH"] = ":".join(parts)
47
62
  return env
48
63
 
49
64
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentixx"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "Sandboxed rollouts you call like typed Python (import as `agentix`)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -27,6 +27,15 @@ async def echo_via_namespace(payload: dict) -> dict:
27
27
  return await svc.request("echo", payload, timeout=10.0)
28
28
 
29
29
 
30
+ async def fire_namespace_event(payload: dict) -> str:
31
+ """Emit `slow` on `/plugin-test` and return immediately — does NOT
32
+ wait for the host handler. Used to prove a slow host handler does
33
+ not stall the runtime's other traffic."""
34
+ svc = _get()
35
+ await svc.emit("slow", payload)
36
+ return "fired"
37
+
38
+
30
39
  async def emit_log_line(message: str, level: str = "INFO") -> None:
31
40
  """Log via stdlib logging; the worker's log bridge ships it to host."""
32
41
  import logging
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
 
6
6
  import asyncio
7
7
  import logging
8
+ import time
8
9
 
9
10
  import pytest
10
11
 
@@ -15,6 +16,7 @@ from tests._namespace_target import (
15
16
  emit_log_line,
16
17
  emit_log_with_exception,
17
18
  emit_log_with_extra,
19
+ fire_namespace_event,
18
20
  )
19
21
 
20
22
 
@@ -49,6 +51,51 @@ async def test_plugin_namespace_round_trip(live_server):
49
51
  assert host_ns.seen[0]["data"] == {"hello": 1}
50
52
 
51
53
 
54
+ class _SlowHost(AsyncClientNamespace):
55
+ """Host namespace whose `slow` handler blocks for a long time."""
56
+
57
+ def __init__(self, hold: float) -> None:
58
+ super().__init__("/plugin-test")
59
+ self._hold = hold
60
+ self.started = False
61
+ self.finished = False
62
+
63
+ async def on_slow(self, data):
64
+ self.started = True
65
+ await asyncio.sleep(self._hold)
66
+ self.finished = True
67
+
68
+
69
+ @pytest.mark.asyncio
70
+ async def test_slow_namespace_handler_does_not_block_runtime(live_server):
71
+ """A slow plugin handler must not stall the SIO receive loop —
72
+ otherwise unrelated `c.remote` results queue up behind it.
73
+
74
+ Regression: `socketio.AsyncClient` awaits `trigger_event` inline in
75
+ its single websocket receive loop. `AsyncClientNamespace` detaches
76
+ data-event handlers so a slow one can't freeze the connection.
77
+ """
78
+ base_url = await live_server()
79
+ slow_host = _SlowHost(hold=30.0)
80
+
81
+ client = RuntimeClient(base_url)
82
+ client.register_namespace(slow_host)
83
+ async with client as c:
84
+ # Fire the event whose host handler sleeps 30s.
85
+ await c.remote(fire_namespace_event, {"k": "v"})
86
+
87
+ # Immediately do a normal RPC. If the slow handler blocked the
88
+ # receive loop, this `call:result` would be stuck behind it for
89
+ # ~30s. With the fix it returns near-instantly.
90
+ t0 = time.perf_counter()
91
+ result = await asyncio.wait_for(c.remote(abs, -5), timeout=10)
92
+ elapsed = time.perf_counter() - t0
93
+
94
+ assert result == 5
95
+ assert elapsed < 8.0, f"runtime stalled behind slow handler: {elapsed:.1f}s"
96
+ assert slow_host.started, "slow handler never ran"
97
+
98
+
52
99
  @pytest.mark.asyncio
53
100
  async def test_log_records_arrive_on_host(live_server):
54
101
  """Verify the full /log experience: plain messages, %-format args,
@@ -1,47 +0,0 @@
1
- """Host-side namespace helpers.
2
-
3
- `AsyncClientNamespace` is a thin subclass of `socketio.AsyncClientNamespace`
4
- that msgpack-wraps event payloads — so plugin authors write
5
- `await self.emit("x", {"a": 1})` and `async def on_x(self, data)`, and
6
- the bytes/msgpack wire format stays internal.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import Any
12
-
13
- import socketio
14
-
15
- from agentix.runtime.shared.codec import pack, unpack
16
-
17
-
18
- def _decode(raw: Any) -> Any:
19
- if isinstance(raw, memoryview):
20
- raw = raw.tobytes()
21
- elif isinstance(raw, bytearray):
22
- raw = bytes(raw)
23
- if isinstance(raw, bytes):
24
- return unpack(raw)
25
- return raw
26
-
27
-
28
- class AsyncClientNamespace(socketio.AsyncClientNamespace):
29
- """`socketio.AsyncClientNamespace` with msgpack at the boundary.
30
-
31
- Override `on_<event>` for inbound; call `await self.emit(...)` for
32
- outbound. Data is plain Python — packing happens automatically.
33
- """
34
-
35
- async def emit(self, event: str, data: Any = None, **kwargs: Any) -> Any:
36
- return await super().emit(event, pack(data), **kwargs)
37
-
38
- async def trigger_event(self, event: str, *args: Any) -> Any:
39
- # Unpack the single data payload (socketio always emits one arg
40
- # for a bytes event). Lifecycle events (`connect`, `disconnect`)
41
- # come with no data and we let them pass through untouched.
42
- if args and isinstance(args[0], (bytes, bytearray, memoryview)):
43
- args = (_decode(args[0]),) + args[1:]
44
- return await super().trigger_event(event, *args)
45
-
46
-
47
- __all__ = ["AsyncClientNamespace"]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes