tywrap 0.1.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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +263 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/index.d.ts +41 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +234 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/core/analyzer.d.ts +63 -0
  12. package/dist/core/analyzer.d.ts.map +1 -0
  13. package/dist/core/analyzer.js +676 -0
  14. package/dist/core/analyzer.js.map +1 -0
  15. package/dist/core/discovery.d.ts +85 -0
  16. package/dist/core/discovery.d.ts.map +1 -0
  17. package/dist/core/discovery.js +344 -0
  18. package/dist/core/discovery.js.map +1 -0
  19. package/dist/core/generator.d.ts +37 -0
  20. package/dist/core/generator.d.ts.map +1 -0
  21. package/dist/core/generator.js +368 -0
  22. package/dist/core/generator.js.map +1 -0
  23. package/dist/core/mapper.d.ts +40 -0
  24. package/dist/core/mapper.d.ts.map +1 -0
  25. package/dist/core/mapper.js +401 -0
  26. package/dist/core/mapper.js.map +1 -0
  27. package/dist/core/validation.d.ts +79 -0
  28. package/dist/core/validation.d.ts.map +1 -0
  29. package/dist/core/validation.js +486 -0
  30. package/dist/core/validation.js.map +1 -0
  31. package/dist/index.d.ts +26 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +29 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/runtime/base.d.ts +12 -0
  36. package/dist/runtime/base.d.ts.map +1 -0
  37. package/dist/runtime/base.js +6 -0
  38. package/dist/runtime/base.js.map +1 -0
  39. package/dist/runtime/errors.d.ts +17 -0
  40. package/dist/runtime/errors.d.ts.map +1 -0
  41. package/dist/runtime/errors.js +21 -0
  42. package/dist/runtime/errors.js.map +1 -0
  43. package/dist/runtime/http.d.ts +22 -0
  44. package/dist/runtime/http.d.ts.map +1 -0
  45. package/dist/runtime/http.js +79 -0
  46. package/dist/runtime/http.js.map +1 -0
  47. package/dist/runtime/index.d.ts +8 -0
  48. package/dist/runtime/index.d.ts.map +1 -0
  49. package/dist/runtime/index.js +17 -0
  50. package/dist/runtime/index.js.map +1 -0
  51. package/dist/runtime/node.d.ts +48 -0
  52. package/dist/runtime/node.d.ts.map +1 -0
  53. package/dist/runtime/node.js +328 -0
  54. package/dist/runtime/node.js.map +1 -0
  55. package/dist/runtime/optimized-node.d.ts +131 -0
  56. package/dist/runtime/optimized-node.d.ts.map +1 -0
  57. package/dist/runtime/optimized-node.js +622 -0
  58. package/dist/runtime/optimized-node.js.map +1 -0
  59. package/dist/runtime/protocol.d.ts +3 -0
  60. package/dist/runtime/protocol.d.ts.map +1 -0
  61. package/dist/runtime/protocol.js +3 -0
  62. package/dist/runtime/protocol.js.map +1 -0
  63. package/dist/runtime/pyodide.d.ts +27 -0
  64. package/dist/runtime/pyodide.d.ts.map +1 -0
  65. package/dist/runtime/pyodide.js +191 -0
  66. package/dist/runtime/pyodide.js.map +1 -0
  67. package/dist/tools/python_suite.js +173 -0
  68. package/dist/tools/python_suite.js.map +1 -0
  69. package/dist/types/index.d.ts +299 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +5 -0
  72. package/dist/types/index.js.map +1 -0
  73. package/dist/tywrap.d.ts +29 -0
  74. package/dist/tywrap.d.ts.map +1 -0
  75. package/dist/tywrap.js +589 -0
  76. package/dist/tywrap.js.map +1 -0
  77. package/dist/utils/bundle-optimizer.d.ts +182 -0
  78. package/dist/utils/bundle-optimizer.d.ts.map +1 -0
  79. package/dist/utils/bundle-optimizer.js +680 -0
  80. package/dist/utils/bundle-optimizer.js.map +1 -0
  81. package/dist/utils/cache.d.ts +149 -0
  82. package/dist/utils/cache.d.ts.map +1 -0
  83. package/dist/utils/cache.js +492 -0
  84. package/dist/utils/cache.js.map +1 -0
  85. package/dist/utils/codec.d.ts +94 -0
  86. package/dist/utils/codec.d.ts.map +1 -0
  87. package/dist/utils/codec.js +150 -0
  88. package/dist/utils/codec.js.map +1 -0
  89. package/dist/utils/logger.d.ts +42 -0
  90. package/dist/utils/logger.d.ts.map +1 -0
  91. package/dist/utils/logger.js +140 -0
  92. package/dist/utils/logger.js.map +1 -0
  93. package/dist/utils/memory-profiler.d.ts +123 -0
  94. package/dist/utils/memory-profiler.d.ts.map +1 -0
  95. package/dist/utils/memory-profiler.js +426 -0
  96. package/dist/utils/memory-profiler.js.map +1 -0
  97. package/dist/utils/parallel-processor.d.ts +146 -0
  98. package/dist/utils/parallel-processor.d.ts.map +1 -0
  99. package/dist/utils/parallel-processor.js +636 -0
  100. package/dist/utils/parallel-processor.js.map +1 -0
  101. package/dist/utils/python.d.ts +8 -0
  102. package/dist/utils/python.d.ts.map +1 -0
  103. package/dist/utils/python.js +56 -0
  104. package/dist/utils/python.js.map +1 -0
  105. package/dist/utils/runtime.d.ts +153 -0
  106. package/dist/utils/runtime.d.ts.map +1 -0
  107. package/dist/utils/runtime.js +519 -0
  108. package/dist/utils/runtime.js.map +1 -0
  109. package/package.json +136 -0
  110. package/runtime/python_bridge.py +476 -0
  111. package/src/cli.ts +331 -0
  112. package/src/config/index.ts +289 -0
  113. package/src/core/analyzer.ts +788 -0
  114. package/src/core/discovery.ts +415 -0
  115. package/src/core/generator.ts +447 -0
  116. package/src/core/mapper.ts +504 -0
  117. package/src/core/validation.ts +693 -0
  118. package/src/index.ts +83 -0
  119. package/src/runtime/base.ts +32 -0
  120. package/src/runtime/errors.ts +19 -0
  121. package/src/runtime/http.ts +132 -0
  122. package/src/runtime/index.ts +24 -0
  123. package/src/runtime/node.ts +431 -0
  124. package/src/runtime/optimized-node.ts +838 -0
  125. package/src/runtime/protocol.ts +2 -0
  126. package/src/runtime/pyodide.ts +228 -0
  127. package/src/types/global.d.ts +60 -0
  128. package/src/types/index.ts +410 -0
  129. package/src/tywrap.ts +669 -0
  130. package/src/utils/bundle-optimizer.ts +876 -0
  131. package/src/utils/cache.ts +634 -0
  132. package/src/utils/codec.ts +275 -0
  133. package/src/utils/logger.ts +201 -0
  134. package/src/utils/memory-profiler.ts +582 -0
  135. package/src/utils/parallel-processor.ts +879 -0
  136. package/src/utils/python.ts +79 -0
  137. package/src/utils/runtime.ts +616 -0
@@ -0,0 +1,476 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+ import json
4
+ import importlib
5
+ import os
6
+ import traceback
7
+ import base64
8
+ import datetime as dt
9
+ import decimal
10
+ import uuid
11
+ from pathlib import Path, PurePath
12
+
13
+ instances = {}
14
+
15
+ FALLBACK_JSON = os.environ.get('TYWRAP_CODEC_FALLBACK', '').lower() == 'json'
16
+ PROTOCOL = 'tywrap/1'
17
+ PROTOCOL_VERSION = 1
18
+ BRIDGE_NAME = 'python-subprocess'
19
+
20
+
21
+ class ProtocolError(Exception):
22
+ pass
23
+
24
+
25
+ def arrow_available():
26
+ try:
27
+ import pyarrow # noqa: F401
28
+ except Exception:
29
+ return False
30
+ return True
31
+
32
+
33
+ def is_numpy_array(obj):
34
+ try:
35
+ import numpy as np # noqa: F401
36
+ except Exception:
37
+ return False
38
+ return isinstance(obj, np.ndarray)
39
+
40
+
41
+ def is_pandas_dataframe(obj):
42
+ try:
43
+ import pandas as pd # noqa: F401
44
+ except Exception:
45
+ return False
46
+ return isinstance(obj, pd.DataFrame)
47
+
48
+
49
+ def is_pandas_series(obj):
50
+ try:
51
+ import pandas as pd # noqa: F401
52
+ except Exception:
53
+ return False
54
+ return isinstance(obj, pd.Series)
55
+
56
+
57
+ def is_scipy_sparse(obj):
58
+ try:
59
+ import scipy.sparse as sp # noqa: F401
60
+ except Exception:
61
+ return False
62
+ try:
63
+ return sp.issparse(obj)
64
+ except Exception:
65
+ return False
66
+
67
+
68
+ def is_torch_tensor(obj):
69
+ try:
70
+ import torch # noqa: F401
71
+ except Exception:
72
+ return False
73
+ try:
74
+ return torch.is_tensor(obj)
75
+ except Exception:
76
+ return False
77
+
78
+
79
+ def is_sklearn_estimator(obj):
80
+ try:
81
+ from sklearn.base import BaseEstimator # noqa: F401
82
+ except Exception:
83
+ return False
84
+ return isinstance(obj, BaseEstimator)
85
+
86
+
87
+ def serialize_ndarray(obj):
88
+ try:
89
+ import pyarrow as pa # type: ignore
90
+ except Exception as exc:
91
+ if FALLBACK_JSON:
92
+ return serialize_ndarray_json(obj)
93
+ raise RuntimeError(
94
+ 'Arrow encoding unavailable for ndarray; install pyarrow or set TYWRAP_CODEC_FALLBACK=json to enable JSON fallback'
95
+ ) from exc
96
+ try:
97
+ arr = pa.array(obj)
98
+ sink = pa.BufferOutputStream()
99
+ with pa.ipc.new_stream(sink, arr.type) as writer:
100
+ writer.write(arr)
101
+ buf = sink.getvalue()
102
+ b64 = base64.b64encode(buf.to_pybytes()).decode('ascii')
103
+ return {
104
+ '__tywrap__': 'ndarray',
105
+ 'encoding': 'arrow',
106
+ 'b64': b64,
107
+ 'shape': getattr(obj, 'shape', None),
108
+ }
109
+ except Exception as exc:
110
+ if FALLBACK_JSON:
111
+ return serialize_ndarray_json(obj)
112
+ raise RuntimeError('Arrow encoding failed for ndarray') from exc
113
+
114
+
115
+ def serialize_ndarray_json(obj):
116
+ try:
117
+ data = obj.tolist()
118
+ except Exception as exc:
119
+ raise RuntimeError('JSON fallback failed for ndarray') from exc
120
+ return {
121
+ '__tywrap__': 'ndarray',
122
+ 'encoding': 'json',
123
+ 'data': data,
124
+ 'shape': getattr(obj, 'shape', None),
125
+ }
126
+
127
+
128
+ def serialize_dataframe(obj):
129
+ try:
130
+ import pyarrow as pa # type: ignore
131
+ import pyarrow.feather as feather # type: ignore
132
+ except Exception as exc:
133
+ if FALLBACK_JSON:
134
+ return serialize_dataframe_json(obj)
135
+ raise RuntimeError(
136
+ 'Arrow encoding unavailable for pandas.DataFrame; install pyarrow or set TYWRAP_CODEC_FALLBACK=json to enable JSON fallback'
137
+ ) from exc
138
+ try:
139
+ table = pa.Table.from_pandas(obj) # type: ignore
140
+ sink = pa.BufferOutputStream()
141
+ feather.write_feather(table, sink)
142
+ buf = sink.getvalue()
143
+ b64 = base64.b64encode(buf.to_pybytes()).decode('ascii')
144
+ return {
145
+ '__tywrap__': 'dataframe',
146
+ 'encoding': 'arrow',
147
+ 'b64': b64,
148
+ }
149
+ except Exception as exc:
150
+ if FALLBACK_JSON:
151
+ return serialize_dataframe_json(obj)
152
+ raise RuntimeError('Arrow encoding failed for pandas.DataFrame') from exc
153
+
154
+
155
+ def serialize_dataframe_json(obj):
156
+ try:
157
+ data = obj.to_dict(orient='records')
158
+ except Exception as exc:
159
+ raise RuntimeError('JSON fallback failed for pandas.DataFrame') from exc
160
+ return {
161
+ '__tywrap__': 'dataframe',
162
+ 'encoding': 'json',
163
+ 'data': data,
164
+ }
165
+
166
+
167
+ def serialize_series(obj):
168
+ try:
169
+ import pyarrow as pa # type: ignore
170
+ except Exception as exc:
171
+ if FALLBACK_JSON:
172
+ return serialize_series_json(obj)
173
+ raise RuntimeError(
174
+ 'Arrow encoding unavailable for pandas.Series; install pyarrow or set TYWRAP_CODEC_FALLBACK=json to enable JSON fallback'
175
+ ) from exc
176
+ try:
177
+ arr = pa.Array.from_pandas(obj) # type: ignore
178
+ sink = pa.BufferOutputStream()
179
+ with pa.ipc.new_stream(sink, arr.type) as writer:
180
+ writer.write(arr)
181
+ buf = sink.getvalue()
182
+ b64 = base64.b64encode(buf.to_pybytes()).decode('ascii')
183
+ return {
184
+ '__tywrap__': 'series',
185
+ 'encoding': 'arrow',
186
+ 'b64': b64,
187
+ 'name': getattr(obj, 'name', None),
188
+ }
189
+ except Exception as exc:
190
+ if FALLBACK_JSON:
191
+ return serialize_series_json(obj)
192
+ raise RuntimeError('Arrow encoding failed for pandas.Series') from exc
193
+
194
+
195
+ def serialize_series_json(obj):
196
+ try:
197
+ data = obj.to_list() # type: ignore
198
+ except Exception:
199
+ try:
200
+ data = obj.to_dict() # type: ignore
201
+ except Exception as exc:
202
+ raise RuntimeError('JSON fallback failed for pandas.Series') from exc
203
+ return {
204
+ '__tywrap__': 'series',
205
+ 'encoding': 'json',
206
+ 'data': data,
207
+ 'name': getattr(obj, 'name', None),
208
+ }
209
+
210
+
211
+ def serialize_sparse_matrix(obj):
212
+ try:
213
+ fmt = obj.getformat()
214
+ except Exception as exc:
215
+ raise RuntimeError('Failed to inspect scipy sparse matrix format') from exc
216
+
217
+ if fmt not in ('csr', 'csc', 'coo'):
218
+ raise RuntimeError(f'Unsupported scipy sparse format: {fmt}')
219
+
220
+ dtype = None
221
+ try:
222
+ dtype = str(obj.dtype)
223
+ except Exception:
224
+ dtype = None
225
+ if getattr(obj.dtype, 'kind', None) == 'c':
226
+ raise RuntimeError('Complex sparse matrices are not supported by JSON codec')
227
+
228
+ if fmt in ('csr', 'csc'):
229
+ data = obj.data.tolist()
230
+ indices = obj.indices.tolist()
231
+ indptr = obj.indptr.tolist()
232
+ return {
233
+ '__tywrap__': 'scipy.sparse',
234
+ 'encoding': 'json',
235
+ 'format': fmt,
236
+ 'shape': list(obj.shape),
237
+ 'data': data,
238
+ 'indices': indices,
239
+ 'indptr': indptr,
240
+ 'dtype': dtype,
241
+ }
242
+
243
+ # coo
244
+ data = obj.data.tolist()
245
+ row = obj.row.tolist()
246
+ col = obj.col.tolist()
247
+ return {
248
+ '__tywrap__': 'scipy.sparse',
249
+ 'encoding': 'json',
250
+ 'format': fmt,
251
+ 'shape': list(obj.shape),
252
+ 'data': data,
253
+ 'row': row,
254
+ 'col': col,
255
+ 'dtype': dtype,
256
+ }
257
+
258
+
259
+ def serialize_torch_tensor(obj):
260
+ allow_copy = os.environ.get('TYWRAP_TORCH_ALLOW_COPY', '').lower() in ('1', 'true', 'yes')
261
+ tensor = obj.detach()
262
+ if getattr(tensor, 'device', None) is not None and tensor.device.type != 'cpu':
263
+ if not allow_copy:
264
+ raise RuntimeError(
265
+ 'Torch tensor is on a non-CPU device; set TYWRAP_TORCH_ALLOW_COPY=1 to allow CPU transfer'
266
+ )
267
+ tensor = tensor.to('cpu')
268
+ if hasattr(tensor, 'is_contiguous') and not tensor.is_contiguous():
269
+ if not allow_copy:
270
+ raise RuntimeError(
271
+ 'Torch tensor is not contiguous; set TYWRAP_TORCH_ALLOW_COPY=1 to allow contiguous copy'
272
+ )
273
+ tensor = tensor.contiguous()
274
+ try:
275
+ arr = tensor.numpy()
276
+ except Exception as exc:
277
+ raise RuntimeError('Failed to convert torch.Tensor to numpy') from exc
278
+
279
+ return {
280
+ '__tywrap__': 'torch.tensor',
281
+ 'encoding': 'ndarray',
282
+ 'value': serialize_ndarray(arr),
283
+ 'shape': list(tensor.shape),
284
+ 'dtype': str(tensor.dtype),
285
+ 'device': str(tensor.device),
286
+ }
287
+
288
+
289
+ def serialize_sklearn_estimator(obj):
290
+ try:
291
+ import sklearn # noqa: F401
292
+ except Exception as exc:
293
+ raise RuntimeError('scikit-learn is not available') from exc
294
+
295
+ params = obj.get_params(deep=False)
296
+ try:
297
+ json.dumps(params)
298
+ except Exception as exc:
299
+ raise RuntimeError(
300
+ 'scikit-learn estimator params are not JSON-serializable; avoid returning estimators or sanitize params'
301
+ ) from exc
302
+
303
+ return {
304
+ '__tywrap__': 'sklearn.estimator',
305
+ 'encoding': 'json',
306
+ 'className': obj.__class__.__name__,
307
+ 'module': obj.__class__.__module__,
308
+ 'version': getattr(sklearn, '__version__', None),
309
+ 'params': params,
310
+ }
311
+
312
+ def serialize(obj):
313
+ if is_numpy_array(obj):
314
+ return serialize_ndarray(obj)
315
+ if is_pandas_dataframe(obj):
316
+ return serialize_dataframe(obj)
317
+ if is_pandas_series(obj):
318
+ return serialize_series(obj)
319
+ if is_scipy_sparse(obj):
320
+ return serialize_sparse_matrix(obj)
321
+ if is_torch_tensor(obj):
322
+ return serialize_torch_tensor(obj)
323
+ if is_sklearn_estimator(obj):
324
+ return serialize_sklearn_estimator(obj)
325
+ stdlib_value = serialize_stdlib(obj)
326
+ if stdlib_value is not None:
327
+ return stdlib_value
328
+ return obj
329
+
330
+
331
+ def serialize_stdlib(obj):
332
+ if isinstance(obj, dt.datetime):
333
+ return obj.isoformat()
334
+ if isinstance(obj, dt.date):
335
+ return obj.isoformat()
336
+ if isinstance(obj, dt.time):
337
+ return obj.isoformat()
338
+ if isinstance(obj, dt.timedelta):
339
+ return obj.total_seconds()
340
+ if isinstance(obj, decimal.Decimal):
341
+ return str(obj)
342
+ if isinstance(obj, uuid.UUID):
343
+ return str(obj)
344
+ if isinstance(obj, (Path, PurePath)):
345
+ return str(obj)
346
+ return None
347
+
348
+
349
+ def handle_call(params):
350
+ module_name = params.get('module')
351
+ function_name = params.get('functionName')
352
+ args = params.get('args') or []
353
+ kwargs = params.get('kwargs') or {}
354
+ mod = importlib.import_module(module_name)
355
+ func = getattr(mod, function_name)
356
+ res = func(*args, **kwargs)
357
+ return serialize(res)
358
+
359
+
360
+ def handle_instantiate(params):
361
+ module_name = params.get('module')
362
+ class_name = params.get('className')
363
+ args = params.get('args') or []
364
+ kwargs = params.get('kwargs') or {}
365
+ mod = importlib.import_module(module_name)
366
+ cls = getattr(mod, class_name)
367
+ obj = cls(*args, **kwargs)
368
+ handle_id = str(id(obj))
369
+ instances[handle_id] = obj
370
+ return handle_id
371
+
372
+
373
+ def handle_call_method(params):
374
+ handle_id = params.get('handle')
375
+ method_name = params.get('methodName')
376
+ args = params.get('args') or []
377
+ kwargs = params.get('kwargs') or {}
378
+ if handle_id not in instances:
379
+ raise KeyError(f'Unknown handle: {handle_id}')
380
+ obj = instances[handle_id]
381
+ func = getattr(obj, method_name)
382
+ res = func(*args, **kwargs)
383
+ return serialize(res)
384
+
385
+
386
+ def handle_dispose_instance(params):
387
+ handle_id = params.get('handle')
388
+ if handle_id not in instances:
389
+ raise KeyError(f'Unknown handle: {handle_id}')
390
+ del instances[handle_id]
391
+ return True
392
+
393
+
394
+ def handle_meta():
395
+ return {
396
+ 'protocol': PROTOCOL,
397
+ 'protocolVersion': PROTOCOL_VERSION,
398
+ 'bridge': BRIDGE_NAME,
399
+ 'pythonVersion': sys.version.split()[0],
400
+ 'pid': os.getpid(),
401
+ 'codecFallback': 'json' if FALLBACK_JSON else 'none',
402
+ 'arrowAvailable': arrow_available(),
403
+ 'instances': len(instances),
404
+ }
405
+
406
+
407
+ def require_protocol(msg):
408
+ proto = msg.get('protocol')
409
+ if proto != PROTOCOL:
410
+ raise ProtocolError(f'Invalid protocol: {proto}')
411
+ mid = msg.get('id')
412
+ if not isinstance(mid, int):
413
+ raise ProtocolError(f'Invalid request id: {mid}')
414
+ return mid
415
+
416
+
417
+ def main():
418
+ for line in sys.stdin:
419
+ line = line.strip()
420
+ if not line:
421
+ continue
422
+ mid = None
423
+ out = None
424
+ try:
425
+ msg = json.loads(line)
426
+ mid = require_protocol(msg)
427
+ method = msg.get('method')
428
+ if not isinstance(method, str):
429
+ raise ProtocolError('Missing method')
430
+ params = msg.get('params') or {}
431
+ if not isinstance(params, dict):
432
+ raise ProtocolError('Invalid params')
433
+ try:
434
+ if method == 'call':
435
+ result = handle_call(params)
436
+ elif method == 'instantiate':
437
+ result = handle_instantiate(params)
438
+ elif method == 'call_method':
439
+ result = handle_call_method(params)
440
+ elif method == 'dispose_instance':
441
+ result = handle_dispose_instance(params)
442
+ elif method == 'meta':
443
+ result = handle_meta()
444
+ else:
445
+ raise ValueError('Unknown method')
446
+ out = { 'id': mid, 'protocol': PROTOCOL, 'result': result }
447
+ except Exception as e:
448
+ out = {
449
+ 'id': mid,
450
+ 'protocol': PROTOCOL,
451
+ 'error': { 'type': type(e).__name__, 'message': str(e), 'traceback': traceback.format_exc() }
452
+ }
453
+ except Exception as e:
454
+ out = {
455
+ 'id': mid if mid is not None else -1,
456
+ 'protocol': PROTOCOL,
457
+ 'error': { 'type': type(e).__name__, 'message': str(e) }
458
+ }
459
+
460
+ try:
461
+ sys.stdout.write(json.dumps(out) + '\n')
462
+ except Exception as e:
463
+ err_out = {
464
+ 'id': mid if mid is not None else -1,
465
+ 'protocol': PROTOCOL,
466
+ 'error': {
467
+ 'type': type(e).__name__,
468
+ 'message': f'Failed to serialize response: {e}'
469
+ }
470
+ }
471
+ sys.stdout.write(json.dumps(err_out) + '\n')
472
+ sys.stdout.flush()
473
+
474
+
475
+ if __name__ == '__main__':
476
+ main()