concurrent-c-node 0.22.1__tar.gz → 0.23.0__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.
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/PKG-INFO +25 -14
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/README.md +24 -13
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/__init__.py +75 -20
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/magics.py +7 -7
- concurrent_c_node-0.23.0/cc_node/stdio_line.cjs +13 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/concurrent_c_node.egg-info/PKG-INFO +25 -14
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/concurrent_c_node.egg-info/SOURCES.txt +1 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/pyproject.toml +2 -2
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/benchmarks/__init__.py +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/benchmarks/multi_domain.py +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/benchmarks/vs_alts.py +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/broker.cjs +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/examples/__init__.py +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/examples/bench_wire.py +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/cc_node/examples/use_node.py +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/concurrent_c_node.egg-info/dependency_links.txt +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/concurrent_c_node.egg-info/requires.txt +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/concurrent_c_node.egg-info/top_level.txt +0 -0
- {concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: concurrent-c-node
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: JavaScript and npm packages from Python over the Concurrent-C bridge: one spawned Node child per domain, host-controlled lifetime.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/sreekotay/concurrent-c
|
|
@@ -62,8 +62,9 @@ measurable wire. N domains = N processes.
|
|
|
62
62
|
in the child. No `{ async: true }`.
|
|
63
63
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
64
64
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
65
|
-
-
|
|
66
|
-
|
|
65
|
+
- `import cc_node` then `%%js` or `cc_node.get()` — one session, not a
|
|
66
|
+
child per cell. `%load_ext` still works (idempotent).
|
|
67
|
+
- `cc_node.require('path')` is `get().require('path')`.
|
|
67
68
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
68
69
|
cwd `require` once.
|
|
69
70
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -84,36 +85,45 @@ python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / min
|
|
|
84
85
|
|
|
85
86
|
Colab and the usual Jupyter kernel are **Python** — this package. Same
|
|
86
87
|
calling convention as a script: a cell blocks until Node answers;
|
|
87
|
-
thenables wait in the child. Magics share **one**
|
|
88
|
-
kernel, not a spawn per cell (~28ms).
|
|
88
|
+
thenables wait in the child. Magics and `get()` share **one** session
|
|
89
|
+
for the kernel, not a spawn per cell (~28ms). Child `console.log`
|
|
90
|
+
lands in the cell (inherited stdio, line-buffered).
|
|
89
91
|
|
|
90
92
|
```python
|
|
91
93
|
%pip install concurrent-c-node
|
|
92
94
|
# if `node` is missing (typical Colab):
|
|
93
95
|
!apt-get install -y nodejs
|
|
94
|
-
%load_ext cc_node
|
|
95
96
|
|
|
97
|
+
import cc_node # magics register; no %load_ext
|
|
98
|
+
path = cc_node.require('path')
|
|
99
|
+
path.join('a', 'b') # 'a/b'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```python
|
|
96
103
|
%%js
|
|
97
|
-
|
|
98
|
-
path.join('a', 'b')
|
|
104
|
+
console.log('hi') # shows in the cell
|
|
105
|
+
require('path').join('a', 'b') # last expression comes back as Python
|
|
99
106
|
```
|
|
100
107
|
|
|
101
108
|
```python
|
|
102
109
|
xs = [1, 2, 3, 4]
|
|
103
110
|
|
|
104
111
|
%%js -b xs -t chunks
|
|
105
|
-
xs.map(x => x * 2)
|
|
112
|
+
xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
106
113
|
```
|
|
107
114
|
|
|
108
115
|
| | |
|
|
109
116
|
|---|---|
|
|
110
|
-
|
|
|
111
|
-
| `%
|
|
117
|
+
| `import cc_node` | registers magics; does **not** spawn until first `%%js` / `get()` / `require()` |
|
|
118
|
+
| `%load_ext cc_node` | same, idempotent |
|
|
119
|
+
| `%js 1+1` / `%%js` | eval on `cc_node.get()`; last expression is the result |
|
|
112
120
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
113
121
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
114
122
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
115
|
-
| `%js_reset` | `destroy()` the
|
|
116
|
-
| `cc_node.
|
|
123
|
+
| `%js_reset` / `cc_node.reset()` | `destroy()` the session child; `%reset` does this too |
|
|
124
|
+
| `cc_node.get()` | the session the magics use; `create()` is still a private child |
|
|
125
|
+
| `cc_node.kernel()` | alias of `get()` |
|
|
126
|
+
| `cc_node.require('fs')` | `get().require('fs')` |
|
|
117
127
|
| `JsHandle` display | cheap `JsHandle #3` — repr does not cross the wire |
|
|
118
128
|
|
|
119
129
|
`eval()` is unchanged (one RTT, no extra globals). `%%js` / `eval_cell`
|
|
@@ -184,6 +194,7 @@ vs pymport / ncp / pythonia.
|
|
|
184
194
|
`destroy()`; afterwards: `bridge is closed`.
|
|
185
195
|
- `eval_cell(src, bindings=)` is the notebook door (`%%js`): cwd
|
|
186
196
|
`require` once, optional `globalThis` binds; `eval()` stays one RTT.
|
|
197
|
+
`get()` / `require()` / `eval()` at module level share that session.
|
|
187
198
|
- Crash isolation, not a sandbox. `destroy()` is cooperative; an
|
|
188
199
|
in-flight CPU-bound call finishes or you kill the child
|
|
189
200
|
([`bridge_stress.md`](https://github.com/sreekotay/concurrent-c/blob/main/stress/bridge/bridge_stress.md)).
|
|
@@ -282,7 +293,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
282
293
|
|
|
283
294
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
284
295
|
`benchmarks.vs_alts`.
|
|
285
|
-
Jupyter: `%load_ext cc_node
|
|
296
|
+
Jupyter: `import cc_node` then `%%js` (or `%load_ext cc_node`).
|
|
286
297
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
287
298
|
Own hot path in C/CC → native module (40–90ns) —
|
|
288
299
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
@@ -47,8 +47,9 @@ measurable wire. N domains = N processes.
|
|
|
47
47
|
in the child. No `{ async: true }`.
|
|
48
48
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
49
49
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
50
|
-
-
|
|
51
|
-
|
|
50
|
+
- `import cc_node` then `%%js` or `cc_node.get()` — one session, not a
|
|
51
|
+
child per cell. `%load_ext` still works (idempotent).
|
|
52
|
+
- `cc_node.require('path')` is `get().require('path')`.
|
|
52
53
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
53
54
|
cwd `require` once.
|
|
54
55
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -69,36 +70,45 @@ python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / min
|
|
|
69
70
|
|
|
70
71
|
Colab and the usual Jupyter kernel are **Python** — this package. Same
|
|
71
72
|
calling convention as a script: a cell blocks until Node answers;
|
|
72
|
-
thenables wait in the child. Magics share **one**
|
|
73
|
-
kernel, not a spawn per cell (~28ms).
|
|
73
|
+
thenables wait in the child. Magics and `get()` share **one** session
|
|
74
|
+
for the kernel, not a spawn per cell (~28ms). Child `console.log`
|
|
75
|
+
lands in the cell (inherited stdio, line-buffered).
|
|
74
76
|
|
|
75
77
|
```python
|
|
76
78
|
%pip install concurrent-c-node
|
|
77
79
|
# if `node` is missing (typical Colab):
|
|
78
80
|
!apt-get install -y nodejs
|
|
79
|
-
%load_ext cc_node
|
|
80
81
|
|
|
82
|
+
import cc_node # magics register; no %load_ext
|
|
83
|
+
path = cc_node.require('path')
|
|
84
|
+
path.join('a', 'b') # 'a/b'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```python
|
|
81
88
|
%%js
|
|
82
|
-
|
|
83
|
-
path.join('a', 'b')
|
|
89
|
+
console.log('hi') # shows in the cell
|
|
90
|
+
require('path').join('a', 'b') # last expression comes back as Python
|
|
84
91
|
```
|
|
85
92
|
|
|
86
93
|
```python
|
|
87
94
|
xs = [1, 2, 3, 4]
|
|
88
95
|
|
|
89
96
|
%%js -b xs -t chunks
|
|
90
|
-
xs.map(x => x * 2)
|
|
97
|
+
xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
91
98
|
```
|
|
92
99
|
|
|
93
100
|
| | |
|
|
94
101
|
|---|---|
|
|
95
|
-
|
|
|
96
|
-
| `%
|
|
102
|
+
| `import cc_node` | registers magics; does **not** spawn until first `%%js` / `get()` / `require()` |
|
|
103
|
+
| `%load_ext cc_node` | same, idempotent |
|
|
104
|
+
| `%js 1+1` / `%%js` | eval on `cc_node.get()`; last expression is the result |
|
|
97
105
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
98
106
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
99
107
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
100
|
-
| `%js_reset` | `destroy()` the
|
|
101
|
-
| `cc_node.
|
|
108
|
+
| `%js_reset` / `cc_node.reset()` | `destroy()` the session child; `%reset` does this too |
|
|
109
|
+
| `cc_node.get()` | the session the magics use; `create()` is still a private child |
|
|
110
|
+
| `cc_node.kernel()` | alias of `get()` |
|
|
111
|
+
| `cc_node.require('fs')` | `get().require('fs')` |
|
|
102
112
|
| `JsHandle` display | cheap `JsHandle #3` — repr does not cross the wire |
|
|
103
113
|
|
|
104
114
|
`eval()` is unchanged (one RTT, no extra globals). `%%js` / `eval_cell`
|
|
@@ -169,6 +179,7 @@ vs pymport / ncp / pythonia.
|
|
|
169
179
|
`destroy()`; afterwards: `bridge is closed`.
|
|
170
180
|
- `eval_cell(src, bindings=)` is the notebook door (`%%js`): cwd
|
|
171
181
|
`require` once, optional `globalThis` binds; `eval()` stays one RTT.
|
|
182
|
+
`get()` / `require()` / `eval()` at module level share that session.
|
|
172
183
|
- Crash isolation, not a sandbox. `destroy()` is cooperative; an
|
|
173
184
|
in-flight CPU-bound call finishes or you kill the child
|
|
174
185
|
([`bridge_stress.md`](https://github.com/sreekotay/concurrent-c/blob/main/stress/bridge/bridge_stress.md)).
|
|
@@ -267,7 +278,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
267
278
|
|
|
268
279
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
269
280
|
`benchmarks.vs_alts`.
|
|
270
|
-
Jupyter: `%load_ext cc_node
|
|
281
|
+
Jupyter: `import cc_node` then `%%js` (or `%load_ext cc_node`).
|
|
271
282
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
272
283
|
Own hot path in C/CC → native module (40–90ns) —
|
|
273
284
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
@@ -19,8 +19,8 @@ Handles never cross domains; every door after destroy() answers
|
|
|
19
19
|
articulately; destroy is idempotent and `with cc_node.create() as js:`
|
|
20
20
|
scopes it.
|
|
21
21
|
|
|
22
|
-
Notebooks (Jupyter / Colab):
|
|
23
|
-
|
|
22
|
+
Notebooks (Jupyter / Colab): `import cc_node` then `%%js` or
|
|
23
|
+
`cc_node.get()` — one session domain, same calling convention.
|
|
24
24
|
"""
|
|
25
25
|
import array
|
|
26
26
|
import atexit
|
|
@@ -36,10 +36,12 @@ import sys
|
|
|
36
36
|
import tempfile
|
|
37
37
|
|
|
38
38
|
__all__ = [
|
|
39
|
-
"create", "
|
|
39
|
+
"create", "get", "reset", "kernel", "reset_kernel",
|
|
40
|
+
"require", "eval", "eval_cell", "import_module", "stats",
|
|
41
|
+
"JsError", "JsHandle",
|
|
40
42
|
"load_ipython_extension", "unload_ipython_extension", "__version__",
|
|
41
43
|
]
|
|
42
|
-
__version__ = "0.
|
|
44
|
+
__version__ = "0.23.0"
|
|
43
45
|
|
|
44
46
|
_NO_NODE = (
|
|
45
47
|
"cc-node: no node executable (install Node, or set CC_NODE_BIN). "
|
|
@@ -161,12 +163,14 @@ class Bridge:
|
|
|
161
163
|
# numbers in the child, so the broker learns them from the env.
|
|
162
164
|
req_r, req_w = os.pipe()
|
|
163
165
|
resp_r, resp_w = os.pipe()
|
|
166
|
+
preload = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
167
|
+
"stdio_line.cjs")
|
|
164
168
|
env = dict(os.environ,
|
|
165
169
|
CC_WIRE_IN=str(req_r), CC_WIRE_OUT=str(resp_w),
|
|
166
170
|
CC_NODE_SHM_DIR=self._shm_dir)
|
|
167
171
|
try:
|
|
168
172
|
self._p = subprocess.Popen(
|
|
169
|
-
[node, broker],
|
|
173
|
+
[node, "-r", preload, broker],
|
|
170
174
|
pass_fds=(req_r, resp_w),
|
|
171
175
|
env=env,
|
|
172
176
|
cwd=os.getcwd(),
|
|
@@ -645,19 +649,19 @@ def create(node=None):
|
|
|
645
649
|
return Bridge(node=node)
|
|
646
650
|
|
|
647
651
|
|
|
648
|
-
|
|
652
|
+
_session = None
|
|
649
653
|
|
|
650
654
|
|
|
651
|
-
def
|
|
652
|
-
"""
|
|
653
|
-
|
|
654
|
-
global
|
|
655
|
-
b =
|
|
655
|
+
def get(node=None):
|
|
656
|
+
"""Session domain: one child for this process (the Jupyter kernel).
|
|
657
|
+
Lazy. Magics share it. `create()` is still a private child."""
|
|
658
|
+
global _session
|
|
659
|
+
b = _session
|
|
656
660
|
if b is not None and not b.closed:
|
|
657
661
|
if node is not None and node != b._node_bin:
|
|
658
662
|
raise JsError(
|
|
659
|
-
"cc-node:
|
|
660
|
-
"
|
|
663
|
+
"cc-node: get() already live with a different node; "
|
|
664
|
+
"reset() / %js_reset first")
|
|
661
665
|
return b
|
|
662
666
|
b = create(node=node)
|
|
663
667
|
try:
|
|
@@ -665,15 +669,15 @@ def kernel(node=None):
|
|
|
665
669
|
except Exception:
|
|
666
670
|
b.destroy()
|
|
667
671
|
raise
|
|
668
|
-
|
|
672
|
+
_session = b
|
|
669
673
|
return b
|
|
670
674
|
|
|
671
675
|
|
|
672
|
-
def
|
|
673
|
-
"""Destroy the
|
|
674
|
-
global
|
|
675
|
-
b =
|
|
676
|
-
|
|
676
|
+
def reset():
|
|
677
|
+
"""Destroy the session domain. Next get() / %%js spawns."""
|
|
678
|
+
global _session
|
|
679
|
+
b = _session
|
|
680
|
+
_session = None
|
|
677
681
|
if b is not None:
|
|
678
682
|
try:
|
|
679
683
|
b.destroy()
|
|
@@ -681,10 +685,61 @@ def reset_kernel():
|
|
|
681
685
|
pass
|
|
682
686
|
|
|
683
687
|
|
|
688
|
+
kernel = get
|
|
689
|
+
reset_kernel = reset
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
def require(name):
|
|
693
|
+
return get().require(name)
|
|
694
|
+
|
|
695
|
+
|
|
696
|
+
def eval(src): # noqa: A001 — session eval, pythonia-shaped
|
|
697
|
+
return get().eval(src)
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
def eval_cell(src, bindings=None):
|
|
701
|
+
return get().eval_cell(src, bindings)
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def import_module(name):
|
|
705
|
+
return get().import_module(name)
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
def stats():
|
|
709
|
+
return get().stats()
|
|
710
|
+
|
|
711
|
+
|
|
684
712
|
def load_ipython_extension(ip):
|
|
685
713
|
from .magics import load_ipython_extension as _load
|
|
686
714
|
_load(ip)
|
|
715
|
+
if getattr(ip, "_cc_node_lifecycle", False):
|
|
716
|
+
return
|
|
717
|
+
ip._cc_node_lifecycle = True
|
|
718
|
+
orig = getattr(ip, "reset", None)
|
|
719
|
+
if orig is not None:
|
|
720
|
+
def _reset(*args, **kwargs):
|
|
721
|
+
reset()
|
|
722
|
+
return orig(*args, **kwargs)
|
|
723
|
+
ip.reset = _reset
|
|
687
724
|
|
|
688
725
|
|
|
689
726
|
def unload_ipython_extension(ip):
|
|
690
|
-
|
|
727
|
+
reset()
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _boot_ipython():
|
|
731
|
+
# Already in a kernel: IPython is in sys.modules. Do not import it
|
|
732
|
+
# just because the extra is installed — scripts stay spawn-free.
|
|
733
|
+
if "IPython" not in sys.modules:
|
|
734
|
+
return
|
|
735
|
+
try:
|
|
736
|
+
from IPython import get_ipython
|
|
737
|
+
except ImportError:
|
|
738
|
+
return
|
|
739
|
+
ip = get_ipython()
|
|
740
|
+
if ip is None:
|
|
741
|
+
return
|
|
742
|
+
load_ipython_extension(ip)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
_boot_ipython()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from IPython.core.magic import Magics, line_cell_magic, line_magic, magics_class
|
|
3
3
|
from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring
|
|
4
4
|
|
|
5
|
-
from . import JsError,
|
|
5
|
+
from . import JsError, get, reset
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def _bind_map(names, ns):
|
|
@@ -30,7 +30,7 @@ class CcNodeMagics(Magics):
|
|
|
30
30
|
@argument("code", nargs="*", help="JS source (line magic)")
|
|
31
31
|
@line_cell_magic
|
|
32
32
|
def js(self, line, cell=None):
|
|
33
|
-
"""Eval JavaScript on the
|
|
33
|
+
"""Eval JavaScript on the session domain (`cc_node.get()`).
|
|
34
34
|
|
|
35
35
|
Line: %js 1 + 1
|
|
36
36
|
Cell: %%js then a body; last expression is the result.
|
|
@@ -45,20 +45,20 @@ class CcNodeMagics(Magics):
|
|
|
45
45
|
raise JsError("cc-node: empty %js / %%js")
|
|
46
46
|
ns = self.shell.user_ns
|
|
47
47
|
bindings = _bind_map(args.bind, ns)
|
|
48
|
-
result =
|
|
48
|
+
result = get().eval_cell(src, bindings or None)
|
|
49
49
|
if args.to:
|
|
50
50
|
ns[args.to] = result
|
|
51
51
|
return result
|
|
52
52
|
|
|
53
53
|
@line_magic
|
|
54
54
|
def js_reset(self, line):
|
|
55
|
-
"""Destroy the
|
|
56
|
-
|
|
55
|
+
"""Destroy the session Node child. Next %%js / get() spawns again."""
|
|
56
|
+
reset()
|
|
57
57
|
|
|
58
58
|
@line_magic
|
|
59
59
|
def js_stats(self, line):
|
|
60
|
-
"""Handle-table size of the
|
|
61
|
-
return
|
|
60
|
+
"""Handle-table size of the session domain (spawns if needed)."""
|
|
61
|
+
return get().stats()
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
def load_ipython_extension(ip):
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* Long-lived child: inherited stdout is often a pipe (Jupyter, nohup).
|
|
3
|
+
* Node then block-buffers console.log until the buffer fills or exit.
|
|
4
|
+
* Blocking writes make print land in the cell as it happens. TTY is
|
|
5
|
+
* already blocking — this is a no-op there. */
|
|
6
|
+
function block(s) {
|
|
7
|
+
try {
|
|
8
|
+
if (s && s._handle && typeof s._handle.setBlocking === 'function')
|
|
9
|
+
s._handle.setBlocking(true);
|
|
10
|
+
} catch (e) { /* ignore */ }
|
|
11
|
+
}
|
|
12
|
+
block(process.stdout);
|
|
13
|
+
block(process.stderr);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: concurrent-c-node
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: JavaScript and npm packages from Python over the Concurrent-C bridge: one spawned Node child per domain, host-controlled lifetime.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/sreekotay/concurrent-c
|
|
@@ -62,8 +62,9 @@ measurable wire. N domains = N processes.
|
|
|
62
62
|
in the child. No `{ async: true }`.
|
|
63
63
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
64
64
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
65
|
-
-
|
|
66
|
-
|
|
65
|
+
- `import cc_node` then `%%js` or `cc_node.get()` — one session, not a
|
|
66
|
+
child per cell. `%load_ext` still works (idempotent).
|
|
67
|
+
- `cc_node.require('path')` is `get().require('path')`.
|
|
67
68
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
68
69
|
cwd `require` once.
|
|
69
70
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -84,36 +85,45 @@ python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / min
|
|
|
84
85
|
|
|
85
86
|
Colab and the usual Jupyter kernel are **Python** — this package. Same
|
|
86
87
|
calling convention as a script: a cell blocks until Node answers;
|
|
87
|
-
thenables wait in the child. Magics share **one**
|
|
88
|
-
kernel, not a spawn per cell (~28ms).
|
|
88
|
+
thenables wait in the child. Magics and `get()` share **one** session
|
|
89
|
+
for the kernel, not a spawn per cell (~28ms). Child `console.log`
|
|
90
|
+
lands in the cell (inherited stdio, line-buffered).
|
|
89
91
|
|
|
90
92
|
```python
|
|
91
93
|
%pip install concurrent-c-node
|
|
92
94
|
# if `node` is missing (typical Colab):
|
|
93
95
|
!apt-get install -y nodejs
|
|
94
|
-
%load_ext cc_node
|
|
95
96
|
|
|
97
|
+
import cc_node # magics register; no %load_ext
|
|
98
|
+
path = cc_node.require('path')
|
|
99
|
+
path.join('a', 'b') # 'a/b'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```python
|
|
96
103
|
%%js
|
|
97
|
-
|
|
98
|
-
path.join('a', 'b')
|
|
104
|
+
console.log('hi') # shows in the cell
|
|
105
|
+
require('path').join('a', 'b') # last expression comes back as Python
|
|
99
106
|
```
|
|
100
107
|
|
|
101
108
|
```python
|
|
102
109
|
xs = [1, 2, 3, 4]
|
|
103
110
|
|
|
104
111
|
%%js -b xs -t chunks
|
|
105
|
-
xs.map(x => x * 2)
|
|
112
|
+
xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
106
113
|
```
|
|
107
114
|
|
|
108
115
|
| | |
|
|
109
116
|
|---|---|
|
|
110
|
-
|
|
|
111
|
-
| `%
|
|
117
|
+
| `import cc_node` | registers magics; does **not** spawn until first `%%js` / `get()` / `require()` |
|
|
118
|
+
| `%load_ext cc_node` | same, idempotent |
|
|
119
|
+
| `%js 1+1` / `%%js` | eval on `cc_node.get()`; last expression is the result |
|
|
112
120
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
113
121
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
114
122
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
115
|
-
| `%js_reset` | `destroy()` the
|
|
116
|
-
| `cc_node.
|
|
123
|
+
| `%js_reset` / `cc_node.reset()` | `destroy()` the session child; `%reset` does this too |
|
|
124
|
+
| `cc_node.get()` | the session the magics use; `create()` is still a private child |
|
|
125
|
+
| `cc_node.kernel()` | alias of `get()` |
|
|
126
|
+
| `cc_node.require('fs')` | `get().require('fs')` |
|
|
117
127
|
| `JsHandle` display | cheap `JsHandle #3` — repr does not cross the wire |
|
|
118
128
|
|
|
119
129
|
`eval()` is unchanged (one RTT, no extra globals). `%%js` / `eval_cell`
|
|
@@ -184,6 +194,7 @@ vs pymport / ncp / pythonia.
|
|
|
184
194
|
`destroy()`; afterwards: `bridge is closed`.
|
|
185
195
|
- `eval_cell(src, bindings=)` is the notebook door (`%%js`): cwd
|
|
186
196
|
`require` once, optional `globalThis` binds; `eval()` stays one RTT.
|
|
197
|
+
`get()` / `require()` / `eval()` at module level share that session.
|
|
187
198
|
- Crash isolation, not a sandbox. `destroy()` is cooperative; an
|
|
188
199
|
in-flight CPU-bound call finishes or you kill the child
|
|
189
200
|
([`bridge_stress.md`](https://github.com/sreekotay/concurrent-c/blob/main/stress/bridge/bridge_stress.md)).
|
|
@@ -282,7 +293,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
282
293
|
|
|
283
294
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
284
295
|
`benchmarks.vs_alts`.
|
|
285
|
-
Jupyter: `%load_ext cc_node
|
|
296
|
+
Jupyter: `import cc_node` then `%%js` (or `%load_ext cc_node`).
|
|
286
297
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
287
298
|
Own hot path in C/CC → native module (40–90ns) —
|
|
288
299
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "concurrent-c-node"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.23.0"
|
|
8
8
|
description = "JavaScript and npm packages from Python over the Concurrent-C bridge: one spawned Node child per domain, host-controlled lifetime."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -26,6 +26,6 @@ Documentation = "https://github.com/sreekotay/concurrent-c/blob/main/pypi/cc-nod
|
|
|
26
26
|
packages = ["cc_node", "cc_node.examples", "cc_node.benchmarks"]
|
|
27
27
|
|
|
28
28
|
[tool.setuptools.package-data]
|
|
29
|
-
cc_node = ["broker.cjs"]
|
|
29
|
+
cc_node = ["broker.cjs", "stdio_line.cjs"]
|
|
30
30
|
"cc_node.examples" = ["*.py"]
|
|
31
31
|
"cc_node.benchmarks" = ["*.py"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/concurrent_c_node.egg-info/requires.txt
RENAMED
|
File without changes
|
{concurrent_c_node-0.22.1 → concurrent_c_node-0.23.0}/concurrent_c_node.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|