tywrap 0.3.0 → 0.5.0
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.
- package/README.md +40 -5
- package/dist/cli.js +24 -6
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -13
- package/dist/config/index.js.map +1 -1
- package/dist/core/analyzer.d.ts.map +1 -1
- package/dist/core/analyzer.js +0 -1
- package/dist/core/analyzer.js.map +1 -1
- package/dist/dev.d.ts +57 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +743 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/runtime/base.d.ts +7 -7
- package/dist/runtime/base.js +8 -8
- package/dist/runtime/bounded-context.d.ts +15 -31
- package/dist/runtime/bounded-context.d.ts.map +1 -1
- package/dist/runtime/bounded-context.js +16 -15
- package/dist/runtime/bounded-context.js.map +1 -1
- package/dist/runtime/http.d.ts +24 -5
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js +57 -12
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/node.d.ts +33 -7
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +144 -120
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +5 -4
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +5 -4
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +2 -18
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +2 -29
- package/dist/runtime/pooled-transport.js.map +1 -1
- package/dist/runtime/process-io.d.ts +2 -18
- package/dist/runtime/process-io.d.ts.map +1 -1
- package/dist/runtime/process-io.js +3 -30
- package/dist/runtime/process-io.js.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.d.ts +12 -0
- package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -0
- package/dist/runtime/pyodide-bootstrap-core.generated.js +12 -0
- package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -0
- package/dist/runtime/pyodide-io.d.ts +7 -37
- package/dist/runtime/pyodide-io.d.ts.map +1 -1
- package/dist/runtime/pyodide-io.js +90 -214
- package/dist/runtime/pyodide-io.js.map +1 -1
- package/dist/runtime/pyodide.d.ts +25 -5
- package/dist/runtime/pyodide.d.ts.map +1 -1
- package/dist/runtime/pyodide.js +59 -10
- package/dist/runtime/pyodide.js.map +1 -1
- package/dist/runtime/{bridge-protocol.d.ts → rpc-client.d.ts} +59 -46
- package/dist/runtime/rpc-client.d.ts.map +1 -0
- package/dist/runtime/{bridge-protocol.js → rpc-client.js} +60 -42
- package/dist/runtime/rpc-client.js.map +1 -0
- package/dist/runtime/worker-pool.d.ts +8 -20
- package/dist/runtime/worker-pool.d.ts.map +1 -1
- package/dist/runtime/worker-pool.js +58 -59
- package/dist/runtime/worker-pool.js.map +1 -1
- package/dist/types/index.d.ts +8 -10
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +6 -0
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +10 -3
- package/dist/tywrap.js.map +1 -1
- package/package.json +22 -18
- package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
- package/runtime/python_bridge.py +85 -702
- package/runtime/safe_codec.py +10 -2
- package/runtime/tywrap_bridge_core.py +875 -0
- package/src/cli.ts +27 -6
- package/src/config/index.ts +28 -15
- package/src/core/analyzer.ts +1 -2
- package/src/dev.ts +983 -0
- package/src/index.ts +7 -7
- package/src/runtime/base.ts +8 -8
- package/src/runtime/bounded-context.ts +17 -56
- package/src/runtime/http.ts +85 -13
- package/src/runtime/node.ts +172 -178
- package/src/runtime/optimized-node.ts +5 -4
- package/src/runtime/pooled-transport.ts +2 -57
- package/src/runtime/process-io.ts +3 -55
- package/src/runtime/pyodide-bootstrap-core.generated.ts +12 -0
- package/src/runtime/pyodide-io.ts +92 -243
- package/src/runtime/pyodide.ts +87 -10
- package/src/runtime/{bridge-protocol.ts → rpc-client.ts} +76 -49
- package/src/runtime/worker-pool.ts +67 -88
- package/src/types/index.ts +20 -12
- package/src/tywrap.ts +17 -3
- package/dist/runtime/bridge-core.d.ts +0 -65
- package/dist/runtime/bridge-core.d.ts.map +0 -1
- package/dist/runtime/bridge-core.js +0 -379
- package/dist/runtime/bridge-core.js.map +0 -1
- package/dist/runtime/bridge-protocol.d.ts.map +0 -1
- package/dist/runtime/bridge-protocol.js.map +0 -1
- package/src/runtime/bridge-core.ts +0 -494
package/runtime/python_bridge.py
CHANGED
|
@@ -1,18 +1,62 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Reference tywrap Python bridge server (subprocess + HTTP transports).
|
|
4
|
+
|
|
5
|
+
This module owns the I/O concerns and process identity for the Node/Bun/Deno
|
|
6
|
+
subprocess transport and the HTTP transport:
|
|
7
|
+
- the stdin/stdout JSONL request/response loop (main())
|
|
8
|
+
- env-var size guards (TYWRAP_CODEC_MAX_BYTES / TYWRAP_REQUEST_MAX_BYTES)
|
|
9
|
+
- TYWRAP_CODEC_FALLBACK=json marker mode and TYWRAP_TORCH_ALLOW_COPY
|
|
10
|
+
- the real OS pid and bridge='python-subprocess' identity
|
|
11
|
+
- the final SafeCodec.encode wrapper (NaN rejection + numpy scalar coercion +
|
|
12
|
+
the explicit byte-size limit error message)
|
|
13
|
+
|
|
14
|
+
The protocol dispatch, request deserialization, and the 6 __tywrap__ marker
|
|
15
|
+
serializers live in the shared, pure module tywrap_bridge_core (so the in-WASM
|
|
16
|
+
Pyodide server can run the SAME code). Those names are re-exported below for
|
|
17
|
+
backward compatibility, since this package ships runtime/ and external importers
|
|
18
|
+
may reference e.g. serialize() or dispatch_request().
|
|
19
|
+
"""
|
|
2
20
|
import sys
|
|
3
21
|
import json
|
|
4
|
-
import importlib
|
|
5
|
-
import importlib.util
|
|
6
22
|
import os
|
|
7
|
-
import
|
|
8
|
-
import base64
|
|
9
|
-
import datetime as dt
|
|
10
|
-
import decimal
|
|
11
|
-
import uuid
|
|
12
|
-
from pathlib import Path, PurePath
|
|
23
|
+
import importlib # noqa: F401 (re-exported for compat / used by handlers via core)
|
|
13
24
|
|
|
14
25
|
from safe_codec import SafeCodec, CodecError
|
|
15
26
|
|
|
27
|
+
import tywrap_bridge_core as core
|
|
28
|
+
|
|
29
|
+
# Re-export the shared protocol/serialization surface so existing importers of
|
|
30
|
+
# python_bridge keep working after the extraction (codex-flagged: runtime/ ships).
|
|
31
|
+
from tywrap_bridge_core import ( # noqa: F401
|
|
32
|
+
PROTOCOL,
|
|
33
|
+
PROTOCOL_VERSION,
|
|
34
|
+
CODEC_VERSION,
|
|
35
|
+
ProtocolError,
|
|
36
|
+
InstanceHandleError,
|
|
37
|
+
deserialize,
|
|
38
|
+
arrow_available,
|
|
39
|
+
module_available,
|
|
40
|
+
is_numpy_array,
|
|
41
|
+
is_pandas_dataframe,
|
|
42
|
+
is_pandas_series,
|
|
43
|
+
is_scipy_sparse,
|
|
44
|
+
is_torch_tensor,
|
|
45
|
+
is_sklearn_estimator,
|
|
46
|
+
serialize_ndarray_json,
|
|
47
|
+
serialize_dataframe_json,
|
|
48
|
+
serialize_series_json,
|
|
49
|
+
serialize_sparse_matrix,
|
|
50
|
+
serialize_sklearn_estimator,
|
|
51
|
+
serialize_pydantic,
|
|
52
|
+
serialize_stdlib,
|
|
53
|
+
require_protocol,
|
|
54
|
+
require_str,
|
|
55
|
+
coerce_list,
|
|
56
|
+
coerce_dict,
|
|
57
|
+
build_error_payload,
|
|
58
|
+
)
|
|
59
|
+
|
|
16
60
|
# Ensure the working directory is importable so local modules can be resolved when
|
|
17
61
|
# the bridge is launched as a script from a different directory.
|
|
18
62
|
try:
|
|
@@ -29,11 +73,8 @@ except (OSError, ValueError, TypeError, AttributeError) as exc:
|
|
|
29
73
|
instances = {}
|
|
30
74
|
|
|
31
75
|
FALLBACK_JSON = os.environ.get('TYWRAP_CODEC_FALLBACK', '').lower() == 'json'
|
|
32
|
-
|
|
33
|
-
PROTOCOL_VERSION = 1
|
|
76
|
+
TORCH_ALLOW_COPY = os.environ.get('TYWRAP_TORCH_ALLOW_COPY', '').lower() in ('1', 'true', 'yes')
|
|
34
77
|
BRIDGE_NAME = 'python-subprocess'
|
|
35
|
-
# Why: include a stable version in envelopes so decoders can reject incompatible changes.
|
|
36
|
-
CODEC_VERSION = 1
|
|
37
78
|
|
|
38
79
|
|
|
39
80
|
class CodecConfigError(ValueError):
|
|
@@ -134,71 +175,15 @@ def get_request_max_bytes():
|
|
|
134
175
|
REQUEST_MAX_BYTES = get_request_max_bytes()
|
|
135
176
|
|
|
136
177
|
|
|
137
|
-
|
|
138
|
-
pass
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
class InstanceHandleError(ValueError):
|
|
142
|
-
"""Raised when an instance handle is unknown or no longer valid."""
|
|
143
|
-
|
|
144
|
-
_NO_DESERIALIZE = object()
|
|
145
|
-
_ERR_BYTES_MISSING_B64 = 'Invalid bytes envelope: missing b64'
|
|
146
|
-
_ERR_BYTES_MISSING_DATA = 'Invalid bytes envelope: missing data'
|
|
147
|
-
_ERR_BYTES_INVALID_BASE64 = 'Invalid bytes envelope: invalid base64'
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
def _deserialize_bytes_envelope(value) -> object:
|
|
151
|
-
"""
|
|
152
|
-
Decode base64-encoded bytes envelopes from JS into Python bytes.
|
|
153
|
-
|
|
154
|
-
Supported shapes:
|
|
155
|
-
- { "__tywrap_bytes__": true, "b64": "..." } (JS SafeCodec.encodeRequest)
|
|
156
|
-
- { "__type__": "bytes", "encoding": "base64", "data": "..." } (legacy/compat)
|
|
157
|
-
|
|
158
|
-
Why: TS SafeCodec encodes Uint8Array/ArrayBuffer as base64 objects, but
|
|
159
|
-
Python handlers expect real bytes/bytearray to preserve behavior (e.g., len()).
|
|
160
|
-
"""
|
|
161
|
-
if not isinstance(value, dict):
|
|
162
|
-
return _NO_DESERIALIZE
|
|
163
|
-
|
|
164
|
-
if value.get('__tywrap_bytes__') is True:
|
|
165
|
-
b64 = value.get('b64')
|
|
166
|
-
if not isinstance(b64, str):
|
|
167
|
-
raise ProtocolError(_ERR_BYTES_MISSING_B64)
|
|
168
|
-
try:
|
|
169
|
-
return base64.b64decode(b64, validate=True)
|
|
170
|
-
except Exception as exc:
|
|
171
|
-
raise ProtocolError(_ERR_BYTES_INVALID_BASE64) from exc
|
|
172
|
-
|
|
173
|
-
if value.get('__type__') == 'bytes' and value.get('encoding') == 'base64':
|
|
174
|
-
data = value.get('data')
|
|
175
|
-
if not isinstance(data, str):
|
|
176
|
-
raise ProtocolError(_ERR_BYTES_MISSING_DATA)
|
|
177
|
-
try:
|
|
178
|
-
return base64.b64decode(data, validate=True)
|
|
179
|
-
except Exception as exc:
|
|
180
|
-
raise ProtocolError(_ERR_BYTES_INVALID_BASE64) from exc
|
|
181
|
-
|
|
182
|
-
return _NO_DESERIALIZE
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def deserialize(value):
|
|
178
|
+
def serialize(obj):
|
|
186
179
|
"""
|
|
187
|
-
|
|
180
|
+
Backward-compatible result serializer (subprocess identity).
|
|
188
181
|
|
|
189
|
-
|
|
190
|
-
|
|
182
|
+
Threads the env-derived TYWRAP_CODEC_FALLBACK / TYWRAP_TORCH_ALLOW_COPY flags
|
|
183
|
+
into the shared core serializer. Kept as a module-level function so existing
|
|
184
|
+
importers of python_bridge.serialize keep working.
|
|
191
185
|
"""
|
|
192
|
-
|
|
193
|
-
if decoded is not _NO_DESERIALIZE:
|
|
194
|
-
return decoded
|
|
195
|
-
|
|
196
|
-
if isinstance(value, list):
|
|
197
|
-
return [deserialize(item) for item in value]
|
|
198
|
-
if isinstance(value, dict):
|
|
199
|
-
# Preserve dict shape while decoding nested values.
|
|
200
|
-
return {k: deserialize(v) for k, v in value.items()}
|
|
201
|
-
return value
|
|
186
|
+
return core.serialize(obj, force_json_markers=FALLBACK_JSON, torch_allow_copy=TORCH_ALLOW_COPY)
|
|
202
187
|
|
|
203
188
|
|
|
204
189
|
_PROTOCOL_DIAGNOSTIC_MAX = 2048
|
|
@@ -222,644 +207,42 @@ def emit_protocol_diagnostic(message: str) -> None:
|
|
|
222
207
|
pass
|
|
223
208
|
|
|
224
209
|
|
|
225
|
-
def arrow_available():
|
|
226
|
-
"""
|
|
227
|
-
Return True when pyarrow can be imported.
|
|
228
|
-
|
|
229
|
-
Why: advertise Arrow capability to the TS side without crashing startup when
|
|
230
|
-
pyarrow is optional or missing.
|
|
231
|
-
"""
|
|
232
|
-
try:
|
|
233
|
-
import pyarrow
|
|
234
|
-
except (ImportError, OSError):
|
|
235
|
-
return False
|
|
236
|
-
return True
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def module_available(module_name: str) -> bool:
|
|
240
|
-
"""
|
|
241
|
-
Lightweight feature detection for optional codec dependencies.
|
|
242
|
-
|
|
243
|
-
Why: exposes availability in bridge metadata without importing heavy modules or triggering
|
|
244
|
-
side effects, so the TS side can decide when to rely on optional codecs. These flags are
|
|
245
|
-
best-effort hints; serialization still performs its own import checks for correctness.
|
|
246
|
-
"""
|
|
247
|
-
try:
|
|
248
|
-
return importlib.util.find_spec(module_name) is not None
|
|
249
|
-
except (ImportError, AttributeError, TypeError, ValueError):
|
|
250
|
-
# Why: guard against unusual importlib edge cases without masking other failures.
|
|
251
|
-
return False
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
def is_numpy_array(obj):
|
|
255
|
-
"""
|
|
256
|
-
Detect numpy arrays when NumPy is installed.
|
|
257
|
-
|
|
258
|
-
Why: keep NumPy optional while enabling ndarray serialization.
|
|
259
|
-
"""
|
|
260
|
-
try:
|
|
261
|
-
import numpy as np # noqa: F401
|
|
262
|
-
except Exception:
|
|
263
|
-
return False
|
|
264
|
-
return isinstance(obj, np.ndarray)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def is_pandas_dataframe(obj):
|
|
268
|
-
"""
|
|
269
|
-
Detect pandas DataFrame instances when pandas is installed.
|
|
270
|
-
|
|
271
|
-
Why: avoid hard pandas dependency while enabling dataframe encoding.
|
|
272
|
-
"""
|
|
273
|
-
try:
|
|
274
|
-
import pandas as pd # noqa: F401
|
|
275
|
-
except Exception:
|
|
276
|
-
return False
|
|
277
|
-
return isinstance(obj, pd.DataFrame)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
def is_pandas_series(obj):
|
|
281
|
-
"""
|
|
282
|
-
Detect pandas Series instances when pandas is installed.
|
|
283
|
-
|
|
284
|
-
Why: avoid hard pandas dependency while enabling series encoding.
|
|
285
|
-
"""
|
|
286
|
-
try:
|
|
287
|
-
import pandas as pd # noqa: F401
|
|
288
|
-
except Exception:
|
|
289
|
-
return False
|
|
290
|
-
return isinstance(obj, pd.Series)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def is_scipy_sparse(obj):
|
|
294
|
-
"""
|
|
295
|
-
Detect scipy sparse matrices when scipy is installed.
|
|
296
|
-
|
|
297
|
-
Why: allow sparse matrix encoding without importing scipy in all environments.
|
|
298
|
-
"""
|
|
299
|
-
try:
|
|
300
|
-
import scipy.sparse as sp # noqa: F401
|
|
301
|
-
except Exception:
|
|
302
|
-
return False
|
|
303
|
-
try:
|
|
304
|
-
return sp.issparse(obj)
|
|
305
|
-
except Exception:
|
|
306
|
-
return False
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def is_torch_tensor(obj):
|
|
310
|
-
"""
|
|
311
|
-
Detect torch tensors when torch is installed.
|
|
312
|
-
|
|
313
|
-
Why: allow tensor encoding without a hard torch dependency.
|
|
314
|
-
"""
|
|
315
|
-
try:
|
|
316
|
-
import torch # noqa: F401
|
|
317
|
-
except Exception:
|
|
318
|
-
return False
|
|
319
|
-
try:
|
|
320
|
-
return torch.is_tensor(obj)
|
|
321
|
-
except Exception:
|
|
322
|
-
return False
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
def is_sklearn_estimator(obj):
|
|
326
|
-
"""
|
|
327
|
-
Detect sklearn estimators for metadata-only serialization.
|
|
328
|
-
|
|
329
|
-
Why: allow feature-gated estimator metadata without importing sklearn by default.
|
|
330
|
-
"""
|
|
331
|
-
try:
|
|
332
|
-
from sklearn.base import BaseEstimator # noqa: F401
|
|
333
|
-
except Exception:
|
|
334
|
-
return False
|
|
335
|
-
return isinstance(obj, BaseEstimator)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
def serialize_ndarray(obj):
|
|
339
|
-
"""
|
|
340
|
-
Encode a NumPy ndarray for transport over the JSONL bridge.
|
|
341
|
-
|
|
342
|
-
Why: Arrow IPC gives a compact, lossless binary payload that the JS side can decode as a
|
|
343
|
-
Table. If JSON fallback is explicitly requested, honor it even when pyarrow is installed so
|
|
344
|
-
callers don't unexpectedly need an Arrow decoder on the TypeScript side.
|
|
345
|
-
|
|
346
|
-
Note: PyArrow's pa.array() only handles 1D arrays. For multi-dimensional arrays, we flatten
|
|
347
|
-
before encoding and include shape metadata for reconstruction on the JS side. This maintains
|
|
348
|
-
Arrow's binary efficiency while working with the current arrow-js implementation (which
|
|
349
|
-
doesn't yet support FixedShapeTensorArray). See: https://github.com/apache/arrow-js/issues/115
|
|
350
|
-
"""
|
|
351
|
-
if FALLBACK_JSON:
|
|
352
|
-
return serialize_ndarray_json(obj)
|
|
353
|
-
try:
|
|
354
|
-
import pyarrow as pa # type: ignore
|
|
355
|
-
except Exception as exc:
|
|
356
|
-
raise RuntimeError(
|
|
357
|
-
'Arrow encoding unavailable for ndarray; install pyarrow or set TYWRAP_CODEC_FALLBACK=json to enable JSON fallback'
|
|
358
|
-
) from exc
|
|
359
|
-
try:
|
|
360
|
-
# Flatten multi-dimensional arrays for Arrow compatibility
|
|
361
|
-
# pa.array() only handles 1D arrays; we preserve shape for JS-side reconstruction
|
|
362
|
-
original_shape = list(obj.shape) if hasattr(obj, 'shape') else None
|
|
363
|
-
flat = obj.flatten() if hasattr(obj, 'ndim') and obj.ndim > 1 else obj
|
|
364
|
-
arr = pa.array(flat)
|
|
365
|
-
table = pa.Table.from_arrays([arr], names=['value'])
|
|
366
|
-
sink = pa.BufferOutputStream()
|
|
367
|
-
with pa.ipc.new_stream(sink, table.schema) as writer:
|
|
368
|
-
writer.write_table(table)
|
|
369
|
-
buf = sink.getvalue()
|
|
370
|
-
b64 = base64.b64encode(buf.to_pybytes()).decode('ascii')
|
|
371
|
-
return {
|
|
372
|
-
'__tywrap__': 'ndarray',
|
|
373
|
-
'codecVersion': CODEC_VERSION,
|
|
374
|
-
'encoding': 'arrow',
|
|
375
|
-
'b64': b64,
|
|
376
|
-
'shape': original_shape,
|
|
377
|
-
'dtype': str(obj.dtype) if hasattr(obj, 'dtype') else None,
|
|
378
|
-
}
|
|
379
|
-
except Exception as exc:
|
|
380
|
-
if FALLBACK_JSON:
|
|
381
|
-
return serialize_ndarray_json(obj)
|
|
382
|
-
raise RuntimeError('Arrow encoding failed for ndarray') from exc
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
def serialize_ndarray_json(obj):
|
|
386
|
-
"""
|
|
387
|
-
JSON fallback for ndarray encoding.
|
|
388
|
-
|
|
389
|
-
Why: this keeps the bridge usable in environments without pyarrow/Arrow decoding, at the
|
|
390
|
-
cost of larger payloads and potential dtype loss.
|
|
391
|
-
"""
|
|
392
|
-
try:
|
|
393
|
-
data = obj.tolist()
|
|
394
|
-
except Exception as exc:
|
|
395
|
-
raise RuntimeError('JSON fallback failed for ndarray') from exc
|
|
396
|
-
return {
|
|
397
|
-
'__tywrap__': 'ndarray',
|
|
398
|
-
'codecVersion': CODEC_VERSION,
|
|
399
|
-
'encoding': 'json',
|
|
400
|
-
'data': data,
|
|
401
|
-
'shape': getattr(obj, 'shape', None),
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
def serialize_dataframe(obj):
|
|
406
|
-
"""
|
|
407
|
-
Encode a pandas DataFrame for transport.
|
|
408
|
-
|
|
409
|
-
Why: we emit Feather (Arrow IPC file) as *uncompressed* because the JS apache-arrow reader
|
|
410
|
-
does not implement record batch compression. Keeping this uncompressed makes Arrow mode
|
|
411
|
-
work out-of-the-box for Node decoders.
|
|
412
|
-
"""
|
|
413
|
-
if FALLBACK_JSON:
|
|
414
|
-
return serialize_dataframe_json(obj)
|
|
415
|
-
try:
|
|
416
|
-
import pyarrow as pa # type: ignore
|
|
417
|
-
import pyarrow.feather as feather # type: ignore
|
|
418
|
-
except Exception as exc:
|
|
419
|
-
raise RuntimeError(
|
|
420
|
-
'Arrow encoding unavailable for pandas.DataFrame; install pyarrow or set TYWRAP_CODEC_FALLBACK=json to enable JSON fallback'
|
|
421
|
-
) from exc
|
|
422
|
-
try:
|
|
423
|
-
table = pa.Table.from_pandas(obj) # type: ignore
|
|
424
|
-
sink = pa.BufferOutputStream()
|
|
425
|
-
# Use explicit uncompressed payloads so JS decoders (apache-arrow) can read them
|
|
426
|
-
# without optional compression dependencies.
|
|
427
|
-
feather.write_feather(table, sink, compression='uncompressed')
|
|
428
|
-
buf = sink.getvalue()
|
|
429
|
-
b64 = base64.b64encode(buf.to_pybytes()).decode('ascii')
|
|
430
|
-
return {
|
|
431
|
-
'__tywrap__': 'dataframe',
|
|
432
|
-
'codecVersion': CODEC_VERSION,
|
|
433
|
-
'encoding': 'arrow',
|
|
434
|
-
'b64': b64,
|
|
435
|
-
}
|
|
436
|
-
except Exception as exc:
|
|
437
|
-
if FALLBACK_JSON:
|
|
438
|
-
return serialize_dataframe_json(obj)
|
|
439
|
-
raise RuntimeError('Arrow encoding failed for pandas.DataFrame') from exc
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
def serialize_dataframe_json(obj):
|
|
443
|
-
"""
|
|
444
|
-
JSON fallback for DataFrame encoding.
|
|
445
|
-
|
|
446
|
-
Why: this keeps the example/runtime working without Arrow; it is easy to inspect but larger
|
|
447
|
-
than Arrow and may not preserve all dtypes exactly.
|
|
448
|
-
"""
|
|
449
|
-
try:
|
|
450
|
-
data = obj.to_dict(orient='records')
|
|
451
|
-
except Exception as exc:
|
|
452
|
-
raise RuntimeError('JSON fallback failed for pandas.DataFrame') from exc
|
|
453
|
-
return {
|
|
454
|
-
'__tywrap__': 'dataframe',
|
|
455
|
-
'codecVersion': CODEC_VERSION,
|
|
456
|
-
'encoding': 'json',
|
|
457
|
-
'data': data,
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
def serialize_series(obj):
|
|
462
|
-
"""
|
|
463
|
-
Encode a pandas Series for transport.
|
|
464
|
-
|
|
465
|
-
Why: encode as a single-column Arrow Table stream (not a raw Array schema) because the JS
|
|
466
|
-
decoder contract is "table-like" and pyarrow's IPC writer expects a Schema, not a DataType.
|
|
467
|
-
"""
|
|
468
|
-
if FALLBACK_JSON:
|
|
469
|
-
return serialize_series_json(obj)
|
|
470
|
-
try:
|
|
471
|
-
import pyarrow as pa # type: ignore
|
|
472
|
-
except Exception as exc:
|
|
473
|
-
raise RuntimeError(
|
|
474
|
-
'Arrow encoding unavailable for pandas.Series; install pyarrow or set TYWRAP_CODEC_FALLBACK=json to enable JSON fallback'
|
|
475
|
-
) from exc
|
|
476
|
-
try:
|
|
477
|
-
arr = pa.Array.from_pandas(obj) # type: ignore
|
|
478
|
-
table = pa.Table.from_arrays([arr], names=['value'])
|
|
479
|
-
sink = pa.BufferOutputStream()
|
|
480
|
-
with pa.ipc.new_stream(sink, table.schema) as writer:
|
|
481
|
-
writer.write_table(table)
|
|
482
|
-
buf = sink.getvalue()
|
|
483
|
-
b64 = base64.b64encode(buf.to_pybytes()).decode('ascii')
|
|
484
|
-
return {
|
|
485
|
-
'__tywrap__': 'series',
|
|
486
|
-
'codecVersion': CODEC_VERSION,
|
|
487
|
-
'encoding': 'arrow',
|
|
488
|
-
'b64': b64,
|
|
489
|
-
'name': getattr(obj, 'name', None),
|
|
490
|
-
}
|
|
491
|
-
except Exception as exc:
|
|
492
|
-
if FALLBACK_JSON:
|
|
493
|
-
return serialize_series_json(obj)
|
|
494
|
-
raise RuntimeError('Arrow encoding failed for pandas.Series') from exc
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
def serialize_series_json(obj):
|
|
498
|
-
"""
|
|
499
|
-
JSON fallback for Series encoding.
|
|
500
|
-
|
|
501
|
-
Why: avoids requiring Arrow decoding support, at the cost of potentially lossy dtype/NA
|
|
502
|
-
representation compared to Arrow.
|
|
503
|
-
"""
|
|
504
|
-
try:
|
|
505
|
-
data = obj.to_list() # type: ignore
|
|
506
|
-
except Exception:
|
|
507
|
-
try:
|
|
508
|
-
data = obj.to_dict() # type: ignore
|
|
509
|
-
except Exception as exc:
|
|
510
|
-
raise RuntimeError('JSON fallback failed for pandas.Series') from exc
|
|
511
|
-
return {
|
|
512
|
-
'__tywrap__': 'series',
|
|
513
|
-
'codecVersion': CODEC_VERSION,
|
|
514
|
-
'encoding': 'json',
|
|
515
|
-
'data': data,
|
|
516
|
-
'name': getattr(obj, 'name', None),
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
def serialize_sparse_matrix(obj):
|
|
521
|
-
"""
|
|
522
|
-
Serialize scipy sparse matrices into structured JSON envelopes.
|
|
523
|
-
|
|
524
|
-
Why: preserve sparsity and matrix shape without implicit dense conversion, keeping
|
|
525
|
-
failures explicit when unsupported formats or dtypes are encountered.
|
|
526
|
-
"""
|
|
527
|
-
try:
|
|
528
|
-
fmt = obj.getformat()
|
|
529
|
-
except Exception as exc:
|
|
530
|
-
raise RuntimeError('Failed to inspect scipy sparse matrix format') from exc
|
|
531
|
-
|
|
532
|
-
if fmt not in ('csr', 'csc', 'coo'):
|
|
533
|
-
raise RuntimeError(f'Unsupported scipy sparse format: {fmt}')
|
|
534
|
-
|
|
535
|
-
dtype = None
|
|
536
|
-
try:
|
|
537
|
-
dtype = str(obj.dtype)
|
|
538
|
-
except Exception:
|
|
539
|
-
dtype = None
|
|
540
|
-
if getattr(obj.dtype, 'kind', None) == 'c':
|
|
541
|
-
raise RuntimeError('Complex sparse matrices are not supported by JSON codec')
|
|
542
|
-
|
|
543
|
-
if fmt in ('csr', 'csc'):
|
|
544
|
-
data = obj.data.tolist()
|
|
545
|
-
indices = obj.indices.tolist()
|
|
546
|
-
indptr = obj.indptr.tolist()
|
|
547
|
-
return {
|
|
548
|
-
'__tywrap__': 'scipy.sparse',
|
|
549
|
-
'codecVersion': CODEC_VERSION,
|
|
550
|
-
'encoding': 'json',
|
|
551
|
-
'format': fmt,
|
|
552
|
-
'shape': list(obj.shape),
|
|
553
|
-
'data': data,
|
|
554
|
-
'indices': indices,
|
|
555
|
-
'indptr': indptr,
|
|
556
|
-
'dtype': dtype,
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
# coo
|
|
560
|
-
data = obj.data.tolist()
|
|
561
|
-
row = obj.row.tolist()
|
|
562
|
-
col = obj.col.tolist()
|
|
563
|
-
return {
|
|
564
|
-
'__tywrap__': 'scipy.sparse',
|
|
565
|
-
'codecVersion': CODEC_VERSION,
|
|
566
|
-
'encoding': 'json',
|
|
567
|
-
'format': fmt,
|
|
568
|
-
'shape': list(obj.shape),
|
|
569
|
-
'data': data,
|
|
570
|
-
'row': row,
|
|
571
|
-
'col': col,
|
|
572
|
-
'dtype': dtype,
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
def serialize_torch_tensor(obj):
|
|
577
|
-
"""
|
|
578
|
-
Serialize torch.Tensor values via the ndarray envelope.
|
|
579
|
-
|
|
580
|
-
Why: ensure CPU-only transport by default and make device/copy behavior explicit to callers.
|
|
581
|
-
"""
|
|
582
|
-
allow_copy = os.environ.get('TYWRAP_TORCH_ALLOW_COPY', '').lower() in ('1', 'true', 'yes')
|
|
583
|
-
tensor = obj.detach()
|
|
584
|
-
if getattr(tensor, 'device', None) is not None and tensor.device.type != 'cpu':
|
|
585
|
-
if not allow_copy:
|
|
586
|
-
raise RuntimeError(
|
|
587
|
-
'Torch tensor is on a non-CPU device; set TYWRAP_TORCH_ALLOW_COPY=1 to allow CPU transfer'
|
|
588
|
-
)
|
|
589
|
-
tensor = tensor.to('cpu')
|
|
590
|
-
if hasattr(tensor, 'is_contiguous') and not tensor.is_contiguous():
|
|
591
|
-
if not allow_copy:
|
|
592
|
-
raise RuntimeError(
|
|
593
|
-
'Torch tensor is not contiguous; set TYWRAP_TORCH_ALLOW_COPY=1 to allow contiguous copy'
|
|
594
|
-
)
|
|
595
|
-
tensor = tensor.contiguous()
|
|
596
|
-
try:
|
|
597
|
-
arr = tensor.numpy()
|
|
598
|
-
except Exception as exc:
|
|
599
|
-
raise RuntimeError('Failed to convert torch.Tensor to numpy') from exc
|
|
600
|
-
|
|
601
|
-
return {
|
|
602
|
-
'__tywrap__': 'torch.tensor',
|
|
603
|
-
'codecVersion': CODEC_VERSION,
|
|
604
|
-
'encoding': 'ndarray',
|
|
605
|
-
'value': serialize_ndarray(arr),
|
|
606
|
-
'shape': list(tensor.shape),
|
|
607
|
-
'dtype': str(tensor.dtype),
|
|
608
|
-
'device': str(tensor.device),
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
def serialize_sklearn_estimator(obj):
|
|
613
|
-
"""
|
|
614
|
-
Serialize sklearn estimators as metadata only.
|
|
615
|
-
|
|
616
|
-
Why: avoid unsafe pickling while still exposing model identity and params to TypeScript.
|
|
617
|
-
"""
|
|
618
|
-
try:
|
|
619
|
-
import sklearn # noqa: F401
|
|
620
|
-
except Exception as exc:
|
|
621
|
-
raise RuntimeError('scikit-learn is not available') from exc
|
|
622
|
-
|
|
623
|
-
params = obj.get_params(deep=False)
|
|
624
|
-
try:
|
|
625
|
-
json.dumps(params)
|
|
626
|
-
except Exception as exc:
|
|
627
|
-
raise RuntimeError(
|
|
628
|
-
'scikit-learn estimator params are not JSON-serializable; avoid returning estimators or sanitize params'
|
|
629
|
-
) from exc
|
|
630
|
-
|
|
631
|
-
return {
|
|
632
|
-
'__tywrap__': 'sklearn.estimator',
|
|
633
|
-
'codecVersion': CODEC_VERSION,
|
|
634
|
-
'encoding': 'json',
|
|
635
|
-
'className': obj.__class__.__name__,
|
|
636
|
-
'module': obj.__class__.__module__,
|
|
637
|
-
'version': getattr(sklearn, '__version__', None),
|
|
638
|
-
'params': params,
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
_NO_PYDANTIC = object()
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
def serialize_pydantic(obj):
|
|
646
|
-
"""
|
|
647
|
-
Serialize Pydantic v2 models without importing Pydantic.
|
|
648
|
-
|
|
649
|
-
Why: returning BaseModel instances is common in typed Python APIs. Converting via
|
|
650
|
-
`model_dump` keeps Python type hints accurate (return the model), while the bridge still
|
|
651
|
-
emits a JSON-serializable payload. We default to `by_alias=True` so alias_generator-based
|
|
652
|
-
camelCase schemas round-trip cleanly to TypeScript.
|
|
653
|
-
"""
|
|
654
|
-
|
|
655
|
-
model_dump = getattr(obj, 'model_dump', None)
|
|
656
|
-
if not callable(model_dump):
|
|
657
|
-
return _NO_PYDANTIC
|
|
658
|
-
try:
|
|
659
|
-
try:
|
|
660
|
-
return model_dump(by_alias=True, mode='json')
|
|
661
|
-
except TypeError:
|
|
662
|
-
# Older Pydantic versions may not support `mode=...`.
|
|
663
|
-
return model_dump(by_alias=True)
|
|
664
|
-
except Exception as exc:
|
|
665
|
-
raise RuntimeError(f'model_dump failed: {exc}') from exc
|
|
666
|
-
|
|
667
|
-
def serialize(obj):
|
|
668
|
-
if is_numpy_array(obj):
|
|
669
|
-
return serialize_ndarray(obj)
|
|
670
|
-
if is_pandas_dataframe(obj):
|
|
671
|
-
return serialize_dataframe(obj)
|
|
672
|
-
if is_pandas_series(obj):
|
|
673
|
-
return serialize_series(obj)
|
|
674
|
-
if is_scipy_sparse(obj):
|
|
675
|
-
return serialize_sparse_matrix(obj)
|
|
676
|
-
if is_torch_tensor(obj):
|
|
677
|
-
return serialize_torch_tensor(obj)
|
|
678
|
-
if is_sklearn_estimator(obj):
|
|
679
|
-
return serialize_sklearn_estimator(obj)
|
|
680
|
-
pydantic_value = serialize_pydantic(obj)
|
|
681
|
-
if pydantic_value is not _NO_PYDANTIC:
|
|
682
|
-
return pydantic_value
|
|
683
|
-
stdlib_value = serialize_stdlib(obj)
|
|
684
|
-
if stdlib_value is not None:
|
|
685
|
-
return stdlib_value
|
|
686
|
-
return obj
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
def serialize_stdlib(obj):
|
|
690
|
-
if isinstance(obj, dt.datetime):
|
|
691
|
-
return obj.isoformat()
|
|
692
|
-
if isinstance(obj, dt.date):
|
|
693
|
-
return obj.isoformat()
|
|
694
|
-
if isinstance(obj, dt.time):
|
|
695
|
-
return obj.isoformat()
|
|
696
|
-
if isinstance(obj, dt.timedelta):
|
|
697
|
-
return obj.total_seconds()
|
|
698
|
-
if isinstance(obj, decimal.Decimal):
|
|
699
|
-
return str(obj)
|
|
700
|
-
if isinstance(obj, uuid.UUID):
|
|
701
|
-
return str(obj)
|
|
702
|
-
if isinstance(obj, (Path, PurePath)):
|
|
703
|
-
return str(obj)
|
|
704
|
-
return None
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
def handle_call(params):
|
|
708
|
-
module_name = require_str(params, 'module')
|
|
709
|
-
function_name = require_str(params, 'functionName')
|
|
710
|
-
args = deserialize(coerce_list(params.get('args'), 'args'))
|
|
711
|
-
kwargs = deserialize(coerce_dict(params.get('kwargs'), 'kwargs'))
|
|
712
|
-
mod = importlib.import_module(module_name)
|
|
713
|
-
func = getattr(mod, function_name)
|
|
714
|
-
res = func(*args, **kwargs)
|
|
715
|
-
return serialize(res)
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
def handle_instantiate(params):
|
|
719
|
-
module_name = require_str(params, 'module')
|
|
720
|
-
class_name = require_str(params, 'className')
|
|
721
|
-
args = deserialize(coerce_list(params.get('args'), 'args'))
|
|
722
|
-
kwargs = deserialize(coerce_dict(params.get('kwargs'), 'kwargs'))
|
|
723
|
-
mod = importlib.import_module(module_name)
|
|
724
|
-
cls = getattr(mod, class_name)
|
|
725
|
-
obj = cls(*args, **kwargs)
|
|
726
|
-
handle_id = str(id(obj))
|
|
727
|
-
instances[handle_id] = obj
|
|
728
|
-
return handle_id
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
def handle_call_method(params):
|
|
732
|
-
handle_id = require_str(params, 'handle')
|
|
733
|
-
method_name = require_str(params, 'methodName')
|
|
734
|
-
args = deserialize(coerce_list(params.get('args'), 'args'))
|
|
735
|
-
kwargs = deserialize(coerce_dict(params.get('kwargs'), 'kwargs'))
|
|
736
|
-
if handle_id not in instances:
|
|
737
|
-
raise InstanceHandleError(f'Unknown instance handle: {handle_id}')
|
|
738
|
-
obj = instances[handle_id]
|
|
739
|
-
func = getattr(obj, method_name)
|
|
740
|
-
res = func(*args, **kwargs)
|
|
741
|
-
return serialize(res)
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
def handle_dispose_instance(params):
|
|
745
|
-
handle_id = require_str(params, 'handle')
|
|
746
|
-
if handle_id not in instances:
|
|
747
|
-
return False
|
|
748
|
-
del instances[handle_id]
|
|
749
|
-
return True
|
|
750
|
-
|
|
751
|
-
|
|
752
210
|
def handle_meta():
|
|
753
211
|
"""
|
|
754
|
-
Return bridge metadata for capability detection.
|
|
212
|
+
Return bridge metadata for capability detection (subprocess identity).
|
|
755
213
|
|
|
756
214
|
Why: the Node side uses this to decide whether optional codecs can be used.
|
|
215
|
+
Kept for backward compatibility; delegates to the shared core builder with the
|
|
216
|
+
real pid and bridge='python-subprocess'.
|
|
757
217
|
"""
|
|
758
|
-
return
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
'
|
|
764
|
-
|
|
765
|
-
'arrowAvailable': arrow_available(),
|
|
766
|
-
'scipyAvailable': module_available('scipy'),
|
|
767
|
-
'torchAvailable': module_available('torch'),
|
|
768
|
-
'sklearnAvailable': module_available('sklearn'),
|
|
769
|
-
'instances': len(instances),
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
def require_protocol(msg):
|
|
774
|
-
if not isinstance(msg, dict):
|
|
775
|
-
raise ProtocolError('Invalid request payload')
|
|
776
|
-
proto = msg.get('protocol')
|
|
777
|
-
if proto != PROTOCOL:
|
|
778
|
-
raise ProtocolError(f'Invalid protocol: {proto}')
|
|
779
|
-
mid = msg.get('id')
|
|
780
|
-
if not isinstance(mid, int):
|
|
781
|
-
raise ProtocolError(f'Invalid request id: {mid}')
|
|
782
|
-
return mid
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
def require_str(params, key):
|
|
786
|
-
"""
|
|
787
|
-
Return a required string parameter from a request.
|
|
788
|
-
|
|
789
|
-
Why: keep request schema validation centralized and explicit for clearer errors.
|
|
790
|
-
"""
|
|
791
|
-
value = params.get(key)
|
|
792
|
-
if not isinstance(value, str) or not value:
|
|
793
|
-
raise ProtocolError(f'Missing {key}')
|
|
794
|
-
return value
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
def coerce_list(value, key):
|
|
798
|
-
"""
|
|
799
|
-
Coerce an optional list parameter into a list.
|
|
800
|
-
|
|
801
|
-
Why: normalize args inputs while rejecting invalid shapes early.
|
|
802
|
-
"""
|
|
803
|
-
if value is None:
|
|
804
|
-
return []
|
|
805
|
-
if not isinstance(value, list):
|
|
806
|
-
raise ProtocolError(f'Invalid {key}')
|
|
807
|
-
return value
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
def coerce_dict(value, key):
|
|
811
|
-
"""
|
|
812
|
-
Coerce an optional dict parameter into a dict.
|
|
813
|
-
|
|
814
|
-
Why: normalize kwargs inputs while rejecting invalid shapes early.
|
|
815
|
-
"""
|
|
816
|
-
if value is None:
|
|
817
|
-
return {}
|
|
818
|
-
if not isinstance(value, dict):
|
|
819
|
-
raise ProtocolError(f'Invalid {key}')
|
|
820
|
-
return value
|
|
218
|
+
return core.build_meta(
|
|
219
|
+
instances,
|
|
220
|
+
bridge=BRIDGE_NAME,
|
|
221
|
+
pid=os.getpid(),
|
|
222
|
+
python_version=sys.version.split()[0],
|
|
223
|
+
codec_fallback='json' if FALLBACK_JSON else 'none',
|
|
224
|
+
)
|
|
821
225
|
|
|
822
226
|
|
|
823
227
|
def dispatch_request(msg):
|
|
824
228
|
"""
|
|
825
|
-
Dispatch a validated request to the correct handler.
|
|
826
|
-
|
|
827
|
-
Why: keep the main loop focused on I/O while this function handles validation and routing.
|
|
828
|
-
"""
|
|
829
|
-
mid = require_protocol(msg)
|
|
830
|
-
method = msg.get('method')
|
|
831
|
-
if not isinstance(method, str):
|
|
832
|
-
raise ProtocolError('Missing method')
|
|
833
|
-
params = coerce_dict(msg.get('params'), 'params')
|
|
834
|
-
if method == 'call':
|
|
835
|
-
result = handle_call(params)
|
|
836
|
-
elif method == 'instantiate':
|
|
837
|
-
result = handle_instantiate(params)
|
|
838
|
-
elif method == 'call_method':
|
|
839
|
-
result = handle_call_method(params)
|
|
840
|
-
elif method == 'dispose_instance':
|
|
841
|
-
result = handle_dispose_instance(params)
|
|
842
|
-
elif method == 'meta':
|
|
843
|
-
result = handle_meta()
|
|
844
|
-
else:
|
|
845
|
-
raise ProtocolError(f'Unknown method: {method}')
|
|
846
|
-
return mid, result
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
def build_error_payload(mid, exc, *, include_traceback):
|
|
850
|
-
"""
|
|
851
|
-
Build a protocol error response.
|
|
229
|
+
Dispatch a validated request to the correct handler (subprocess identity).
|
|
852
230
|
|
|
853
|
-
Why:
|
|
231
|
+
Why: keep main() focused on I/O. Delegates routing/validation/serialization to
|
|
232
|
+
the shared core, supplying the subprocess pid/bridge and the env-derived flags.
|
|
233
|
+
Returns (mid, result) to preserve the historical signature for any importer.
|
|
854
234
|
"""
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
235
|
+
out = core.dispatch_request(
|
|
236
|
+
msg,
|
|
237
|
+
instances,
|
|
238
|
+
bridge=BRIDGE_NAME,
|
|
239
|
+
pid=os.getpid(),
|
|
240
|
+
force_json_markers=FALLBACK_JSON,
|
|
241
|
+
allow_nan=False,
|
|
242
|
+
python_version=sys.version.split()[0],
|
|
243
|
+
torch_allow_copy=TORCH_ALLOW_COPY,
|
|
244
|
+
)
|
|
245
|
+
return out['id'], out['result']
|
|
863
246
|
|
|
864
247
|
|
|
865
248
|
def encode_response(out):
|
|
@@ -915,7 +298,7 @@ def main():
|
|
|
915
298
|
mid = req_id
|
|
916
299
|
try:
|
|
917
300
|
mid, result = dispatch_request(msg)
|
|
918
|
-
out = {
|
|
301
|
+
out = {'id': mid, 'protocol': PROTOCOL, 'result': result}
|
|
919
302
|
except ProtocolError as e:
|
|
920
303
|
emit_protocol_diagnostic(str(e))
|
|
921
304
|
out = build_error_payload(mid, e, include_traceback=False)
|