concurrent-c-node 0.23.0__tar.gz → 0.23.2__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.23.0 → concurrent_c_node-0.23.2}/PKG-INFO +64 -42
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/README.md +63 -41
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/__init__.py +8 -6
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/benchmarks/vs_alts.py +75 -2
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/concurrent_c_node.egg-info/PKG-INFO +64 -42
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/pyproject.toml +1 -1
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/benchmarks/__init__.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/benchmarks/multi_domain.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/broker.cjs +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/examples/__init__.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/examples/bench_wire.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/examples/use_node.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/magics.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/cc_node/stdio_line.cjs +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/concurrent_c_node.egg-info/SOURCES.txt +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/concurrent_c_node.egg-info/dependency_links.txt +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/concurrent_c_node.egg-info/requires.txt +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/concurrent_c_node.egg-info/top_level.txt +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: concurrent-c-node
|
|
3
|
-
Version: 0.23.
|
|
3
|
+
Version: 0.23.2
|
|
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
|
|
@@ -18,6 +18,13 @@ Requires-Dist: ipython>=7; extra == "jupyter"
|
|
|
18
18
|
Call Node (and npm packages) from Python.
|
|
19
19
|
Native types, exceptions, callbacks, and async all cross the boundary.
|
|
20
20
|
|
|
21
|
+
## Why use this
|
|
22
|
+
|
|
23
|
+
Call real Node from Python — `require`, native addons, npm — not a JS
|
|
24
|
+
engine in-process. One child, shm for bulk, `from cc_node import require`
|
|
25
|
+
in Jupyter/Colab with no `%load_ext`. pythonia is the packaged peer; this
|
|
26
|
+
is faster on the wire and honest about bulk and callbacks.
|
|
27
|
+
|
|
21
28
|
Part of [Concurrent-C](https://github.com/sreekotay/concurrent-c) — a
|
|
22
29
|
strict C11-superset preprocessor: `.ccs` lowers to plain C and compiles
|
|
23
30
|
with your host C compiler. (This bridge itself is pure Python stdlib —
|
|
@@ -28,15 +35,19 @@ bridge):
|
|
|
28
35
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
29
36
|
|
|
30
37
|
```python
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
js = cc_node.create() # always a child `node` process
|
|
34
|
-
_ = js.require('lodash') # cwd node_modules
|
|
38
|
+
from cc_node import require # one session child (lazy)
|
|
39
|
+
_ = require('lodash') # cwd node_modules
|
|
35
40
|
_.chunk([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
|
|
41
|
+
```
|
|
36
42
|
|
|
43
|
+
A private child (`create()`) is still there when you want N Nodes or
|
|
44
|
+
an explicit lifetime — not required for the first call.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import cc_node
|
|
48
|
+
js = cc_node.create() # a private Node, not the session
|
|
37
49
|
semver = js.require('semver')
|
|
38
50
|
semver.satisfies('1.2.3', '^1.0.0') # True
|
|
39
|
-
|
|
40
51
|
js.destroy() # or: with cc_node.create() as js:
|
|
41
52
|
```
|
|
42
53
|
|
|
@@ -44,12 +55,13 @@ The other direction (Python from Node):
|
|
|
44
55
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
45
56
|
(in-process by default; vs pymport / ncp / pythonia in that README).
|
|
46
57
|
|
|
47
|
-
Every `create()`
|
|
48
|
-
measurable wire. N domains = N processes.
|
|
58
|
+
Every `create()` is a separate Node — real addons, crash isolation,
|
|
59
|
+
measurable wire. N domains = N processes. `require()` / `get()` share
|
|
60
|
+
one session for the process (the Jupyter kernel).
|
|
49
61
|
|
|
50
62
|
| | this package | CC hosted (`cc_js_new(false, …)`) |
|
|
51
63
|
|---|---|---|
|
|
52
|
-
| API | `
|
|
64
|
+
| API | `require()` / `create()` | `.ccs` program |
|
|
53
65
|
| Where | child `node` | libnode in-process |
|
|
54
66
|
| Hot call | ~105µs RTT | sub-µs (needs libnode) |
|
|
55
67
|
| Bulk | shm (~9.5ms / 8MB) | in-process |
|
|
@@ -60,11 +72,12 @@ measurable wire. N domains = N processes.
|
|
|
60
72
|
|
|
61
73
|
- Always a child `node`. A call blocks until JS answers; thenables wait
|
|
62
74
|
in the child. No `{ async: true }`.
|
|
75
|
+
- `from cc_node import require` is the session. `create()` is a private
|
|
76
|
+
child. `reset()` / `%js_reset` / `%reset` / atexit tear the session down.
|
|
63
77
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
64
78
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
65
|
-
- `import cc_node`
|
|
66
|
-
|
|
67
|
-
- `cc_node.require('path')` is `get().require('path')`.
|
|
79
|
+
- Notebook: `import cc_node` registers `%%js` (no `%load_ext`). Same
|
|
80
|
+
session as `require()`.
|
|
68
81
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
69
82
|
cwd `require` once.
|
|
70
83
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -74,35 +87,36 @@ measurable wire. N domains = N processes.
|
|
|
74
87
|
|
|
75
88
|
```
|
|
76
89
|
pip install concurrent-c-node # needs node on PATH
|
|
77
|
-
pip install 'concurrent-c-node[jupyter]' #
|
|
90
|
+
pip install 'concurrent-c-node[jupyter]' # IPython (%%js); require() does not need this
|
|
78
91
|
python -m cc_node.examples.use_node
|
|
79
92
|
python -m cc_node.examples.bench_wire
|
|
80
93
|
python -m cc_node.benchmarks.multi_domain
|
|
81
|
-
python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / mini-racer
|
|
94
|
+
python -m cc_node.benchmarks.vs_alts # vs pythonia / DIY node / pythonmonkey / mini-racer
|
|
82
95
|
```
|
|
83
96
|
|
|
84
97
|
## Jupyter / Colab
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
for the kernel, not a spawn per cell (~28ms). Child `console.log`
|
|
90
|
-
lands in the cell (inherited stdio, line-buffered).
|
|
99
|
+
Same verb as pythonia: `require`. No `%load_ext`, no `create()`, no
|
|
100
|
+
`destroy()` for the happy path. One session child for the kernel;
|
|
101
|
+
`console.log` lands in the cell.
|
|
91
102
|
|
|
92
103
|
```python
|
|
93
104
|
%pip install concurrent-c-node
|
|
94
105
|
# if `node` is missing (typical Colab):
|
|
95
106
|
!apt-get install -y nodejs
|
|
96
107
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
path.join('a', 'b') # 'a/b'
|
|
108
|
+
from cc_node import require
|
|
109
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
100
110
|
```
|
|
101
111
|
|
|
112
|
+
`import cc_node` also registers `%%js` (IPython already running; the
|
|
113
|
+
`[jupyter]` extra is only if you want magics without IPython already
|
|
114
|
+
installed).
|
|
115
|
+
|
|
102
116
|
```python
|
|
103
117
|
%%js
|
|
104
118
|
console.log('hi') # shows in the cell
|
|
105
|
-
require('
|
|
119
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
106
120
|
```
|
|
107
121
|
|
|
108
122
|
```python
|
|
@@ -114,9 +128,10 @@ xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
|
114
128
|
|
|
115
129
|
| | |
|
|
116
130
|
|---|---|
|
|
117
|
-
| `import
|
|
118
|
-
|
|
|
119
|
-
| `%
|
|
131
|
+
| `from cc_node import require` | session `require`; spawns on first call |
|
|
132
|
+
| `import cc_node` | registers magics; does **not** spawn until first `require()` / `%%js` |
|
|
133
|
+
| `%load_ext cc_node` | same, idempotent; not required |
|
|
134
|
+
| `%js 1+1` / `%%js` | eval on the same session; last expression is the result |
|
|
120
135
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
121
136
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
122
137
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
@@ -161,25 +176,30 @@ Colab is not that. There, default `create()` blocks the kernel thread —
|
|
|
161
176
|
Wire: line-JSON on dedicated fds (stdio stays yours). Bulk spill: private
|
|
162
177
|
0700 dir, 0600 files, removed with the bridge.
|
|
163
178
|
|
|
164
|
-
### Vs pythonmonkey / mini-racer / DIY node
|
|
179
|
+
### Vs pythonia / pythonmonkey / mini-racer / DIY node
|
|
165
180
|
|
|
166
|
-
Most “JS from Python” libraries are **not Node**.
|
|
167
|
-
|
|
181
|
+
Most “JS from Python” libraries are **not Node**. pythonia (PyPI
|
|
182
|
+
[`javascript`](https://pypi.org/project/javascript/), JSPyBridge) is the
|
|
183
|
+
packaged peer that is: `require()` on import, one child. Bulk is a
|
|
184
|
+
**sum** over 1M floats (`.length` on an in-process wrapper is free and
|
|
185
|
+
lies). Snapshot:
|
|
168
186
|
[`cc_node_vs_alts_20260813.txt`](https://github.com/sreekotay/concurrent-c/blob/main/perf/baselines/cc_node_vs_alts_20260813.txt)
|
|
169
187
|
· harness: [`benchmarks/vs_alts.py`](https://github.com/sreekotay/concurrent-c/blob/main/pypi/cc-node/cc_node/benchmarks/vs_alts.py).
|
|
170
188
|
|
|
171
|
-
| | cc-node | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
172
|
-
|
|
173
|
-
| identity RTT | **20µs** |
|
|
174
|
-
| callback | **
|
|
175
|
-
| 8MB typed / list | **
|
|
176
|
-
| `require('fs')` | yes | yes | yes | no | no |
|
|
177
|
-
| process | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
189
|
+
| | cc-node | pythonia | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
190
|
+
|---|---|---|---|---|---|---|
|
|
191
|
+
| identity RTT | **20µs** | 42µs | 19µs | 23ms | **<1µs** | 104µs |
|
|
192
|
+
| callback | **40µs** | 106µs | — | — | 1µs | — |
|
|
193
|
+
| 8MB typed / list | **7.5ms shm** / 266ms | — / 481ms | — / 155ms | — | — / 609ms | — / 78ms |
|
|
194
|
+
| `require('fs')` | yes | yes | yes | yes | no | no |
|
|
195
|
+
| process | child `node` | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
178
196
|
|
|
179
197
|
Tiny scalars: pythonmonkey’s in-process SM beats a child. Real Node
|
|
180
198
|
(`require('fs')`, native addons, callbacks, stdout stays yours): this
|
|
181
|
-
package
|
|
182
|
-
|
|
199
|
+
package — ~2× pythonia on identity, shm for bulk, Python callables are
|
|
200
|
+
sync (pythonia’s JS side sees a Promise). Isolated `node -e` per call is
|
|
201
|
+
~1000× a persistent child. Optional engines SKIP if not importable —
|
|
202
|
+
not package deps.
|
|
183
203
|
|
|
184
204
|
The other direction (Python from Node):
|
|
185
205
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
@@ -233,9 +253,11 @@ total(array.array('d', range(1_000_000)))
|
|
|
233
253
|
## Common issues
|
|
234
254
|
|
|
235
255
|
**`Cannot find module`.** `require` / `import` resolve from the Python
|
|
236
|
-
process cwd (`node_modules` next to your program), not from
|
|
237
|
-
site-packages
|
|
238
|
-
|
|
256
|
+
process cwd (`node_modules` next to your notebook or program), not from
|
|
257
|
+
this wheel’s site-packages, and this package does **not** `npm install`
|
|
258
|
+
on a miss (pythonia does). `npm install lodash` in that directory is
|
|
259
|
+
the fix; or `create(node=…)` / `CC_NODE_BIN` when the wrong Node is on
|
|
260
|
+
`PATH`.
|
|
239
261
|
|
|
240
262
|
### Empty `{}` stays a handle
|
|
241
263
|
|
|
@@ -293,7 +315,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
293
315
|
|
|
294
316
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
295
317
|
`benchmarks.vs_alts`.
|
|
296
|
-
Jupyter: `
|
|
318
|
+
Jupyter: `from cc_node import require` (or `import cc_node` then `%%js`).
|
|
297
319
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
298
320
|
Own hot path in C/CC → native module (40–90ns) —
|
|
299
321
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
Call Node (and npm packages) from Python.
|
|
4
4
|
Native types, exceptions, callbacks, and async all cross the boundary.
|
|
5
5
|
|
|
6
|
+
## Why use this
|
|
7
|
+
|
|
8
|
+
Call real Node from Python — `require`, native addons, npm — not a JS
|
|
9
|
+
engine in-process. One child, shm for bulk, `from cc_node import require`
|
|
10
|
+
in Jupyter/Colab with no `%load_ext`. pythonia is the packaged peer; this
|
|
11
|
+
is faster on the wire and honest about bulk and callbacks.
|
|
12
|
+
|
|
6
13
|
Part of [Concurrent-C](https://github.com/sreekotay/concurrent-c) — a
|
|
7
14
|
strict C11-superset preprocessor: `.ccs` lowers to plain C and compiles
|
|
8
15
|
with your host C compiler. (This bridge itself is pure Python stdlib —
|
|
@@ -13,15 +20,19 @@ bridge):
|
|
|
13
20
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
14
21
|
|
|
15
22
|
```python
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
js = cc_node.create() # always a child `node` process
|
|
19
|
-
_ = js.require('lodash') # cwd node_modules
|
|
23
|
+
from cc_node import require # one session child (lazy)
|
|
24
|
+
_ = require('lodash') # cwd node_modules
|
|
20
25
|
_.chunk([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
|
|
26
|
+
```
|
|
21
27
|
|
|
28
|
+
A private child (`create()`) is still there when you want N Nodes or
|
|
29
|
+
an explicit lifetime — not required for the first call.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import cc_node
|
|
33
|
+
js = cc_node.create() # a private Node, not the session
|
|
22
34
|
semver = js.require('semver')
|
|
23
35
|
semver.satisfies('1.2.3', '^1.0.0') # True
|
|
24
|
-
|
|
25
36
|
js.destroy() # or: with cc_node.create() as js:
|
|
26
37
|
```
|
|
27
38
|
|
|
@@ -29,12 +40,13 @@ The other direction (Python from Node):
|
|
|
29
40
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
30
41
|
(in-process by default; vs pymport / ncp / pythonia in that README).
|
|
31
42
|
|
|
32
|
-
Every `create()`
|
|
33
|
-
measurable wire. N domains = N processes.
|
|
43
|
+
Every `create()` is a separate Node — real addons, crash isolation,
|
|
44
|
+
measurable wire. N domains = N processes. `require()` / `get()` share
|
|
45
|
+
one session for the process (the Jupyter kernel).
|
|
34
46
|
|
|
35
47
|
| | this package | CC hosted (`cc_js_new(false, …)`) |
|
|
36
48
|
|---|---|---|
|
|
37
|
-
| API | `
|
|
49
|
+
| API | `require()` / `create()` | `.ccs` program |
|
|
38
50
|
| Where | child `node` | libnode in-process |
|
|
39
51
|
| Hot call | ~105µs RTT | sub-µs (needs libnode) |
|
|
40
52
|
| Bulk | shm (~9.5ms / 8MB) | in-process |
|
|
@@ -45,11 +57,12 @@ measurable wire. N domains = N processes.
|
|
|
45
57
|
|
|
46
58
|
- Always a child `node`. A call blocks until JS answers; thenables wait
|
|
47
59
|
in the child. No `{ async: true }`.
|
|
60
|
+
- `from cc_node import require` is the session. `create()` is a private
|
|
61
|
+
child. `reset()` / `%js_reset` / `%reset` / atexit tear the session down.
|
|
48
62
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
49
63
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
50
|
-
- `import cc_node`
|
|
51
|
-
|
|
52
|
-
- `cc_node.require('path')` is `get().require('path')`.
|
|
64
|
+
- Notebook: `import cc_node` registers `%%js` (no `%load_ext`). Same
|
|
65
|
+
session as `require()`.
|
|
53
66
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
54
67
|
cwd `require` once.
|
|
55
68
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -59,35 +72,36 @@ measurable wire. N domains = N processes.
|
|
|
59
72
|
|
|
60
73
|
```
|
|
61
74
|
pip install concurrent-c-node # needs node on PATH
|
|
62
|
-
pip install 'concurrent-c-node[jupyter]' #
|
|
75
|
+
pip install 'concurrent-c-node[jupyter]' # IPython (%%js); require() does not need this
|
|
63
76
|
python -m cc_node.examples.use_node
|
|
64
77
|
python -m cc_node.examples.bench_wire
|
|
65
78
|
python -m cc_node.benchmarks.multi_domain
|
|
66
|
-
python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / mini-racer
|
|
79
|
+
python -m cc_node.benchmarks.vs_alts # vs pythonia / DIY node / pythonmonkey / mini-racer
|
|
67
80
|
```
|
|
68
81
|
|
|
69
82
|
## Jupyter / Colab
|
|
70
83
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
for the kernel, not a spawn per cell (~28ms). Child `console.log`
|
|
75
|
-
lands in the cell (inherited stdio, line-buffered).
|
|
84
|
+
Same verb as pythonia: `require`. No `%load_ext`, no `create()`, no
|
|
85
|
+
`destroy()` for the happy path. One session child for the kernel;
|
|
86
|
+
`console.log` lands in the cell.
|
|
76
87
|
|
|
77
88
|
```python
|
|
78
89
|
%pip install concurrent-c-node
|
|
79
90
|
# if `node` is missing (typical Colab):
|
|
80
91
|
!apt-get install -y nodejs
|
|
81
92
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
path.join('a', 'b') # 'a/b'
|
|
93
|
+
from cc_node import require
|
|
94
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
85
95
|
```
|
|
86
96
|
|
|
97
|
+
`import cc_node` also registers `%%js` (IPython already running; the
|
|
98
|
+
`[jupyter]` extra is only if you want magics without IPython already
|
|
99
|
+
installed).
|
|
100
|
+
|
|
87
101
|
```python
|
|
88
102
|
%%js
|
|
89
103
|
console.log('hi') # shows in the cell
|
|
90
|
-
require('
|
|
104
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
91
105
|
```
|
|
92
106
|
|
|
93
107
|
```python
|
|
@@ -99,9 +113,10 @@ xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
|
99
113
|
|
|
100
114
|
| | |
|
|
101
115
|
|---|---|
|
|
102
|
-
| `import
|
|
103
|
-
|
|
|
104
|
-
| `%
|
|
116
|
+
| `from cc_node import require` | session `require`; spawns on first call |
|
|
117
|
+
| `import cc_node` | registers magics; does **not** spawn until first `require()` / `%%js` |
|
|
118
|
+
| `%load_ext cc_node` | same, idempotent; not required |
|
|
119
|
+
| `%js 1+1` / `%%js` | eval on the same session; last expression is the result |
|
|
105
120
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
106
121
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
107
122
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
@@ -146,25 +161,30 @@ Colab is not that. There, default `create()` blocks the kernel thread —
|
|
|
146
161
|
Wire: line-JSON on dedicated fds (stdio stays yours). Bulk spill: private
|
|
147
162
|
0700 dir, 0600 files, removed with the bridge.
|
|
148
163
|
|
|
149
|
-
### Vs pythonmonkey / mini-racer / DIY node
|
|
164
|
+
### Vs pythonia / pythonmonkey / mini-racer / DIY node
|
|
150
165
|
|
|
151
|
-
Most “JS from Python” libraries are **not Node**.
|
|
152
|
-
|
|
166
|
+
Most “JS from Python” libraries are **not Node**. pythonia (PyPI
|
|
167
|
+
[`javascript`](https://pypi.org/project/javascript/), JSPyBridge) is the
|
|
168
|
+
packaged peer that is: `require()` on import, one child. Bulk is a
|
|
169
|
+
**sum** over 1M floats (`.length` on an in-process wrapper is free and
|
|
170
|
+
lies). Snapshot:
|
|
153
171
|
[`cc_node_vs_alts_20260813.txt`](https://github.com/sreekotay/concurrent-c/blob/main/perf/baselines/cc_node_vs_alts_20260813.txt)
|
|
154
172
|
· harness: [`benchmarks/vs_alts.py`](https://github.com/sreekotay/concurrent-c/blob/main/pypi/cc-node/cc_node/benchmarks/vs_alts.py).
|
|
155
173
|
|
|
156
|
-
| | cc-node | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
157
|
-
|
|
158
|
-
| identity RTT | **20µs** |
|
|
159
|
-
| callback | **
|
|
160
|
-
| 8MB typed / list | **
|
|
161
|
-
| `require('fs')` | yes | yes | yes | no | no |
|
|
162
|
-
| process | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
174
|
+
| | cc-node | pythonia | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
175
|
+
|---|---|---|---|---|---|---|
|
|
176
|
+
| identity RTT | **20µs** | 42µs | 19µs | 23ms | **<1µs** | 104µs |
|
|
177
|
+
| callback | **40µs** | 106µs | — | — | 1µs | — |
|
|
178
|
+
| 8MB typed / list | **7.5ms shm** / 266ms | — / 481ms | — / 155ms | — | — / 609ms | — / 78ms |
|
|
179
|
+
| `require('fs')` | yes | yes | yes | yes | no | no |
|
|
180
|
+
| process | child `node` | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
163
181
|
|
|
164
182
|
Tiny scalars: pythonmonkey’s in-process SM beats a child. Real Node
|
|
165
183
|
(`require('fs')`, native addons, callbacks, stdout stays yours): this
|
|
166
|
-
package
|
|
167
|
-
|
|
184
|
+
package — ~2× pythonia on identity, shm for bulk, Python callables are
|
|
185
|
+
sync (pythonia’s JS side sees a Promise). Isolated `node -e` per call is
|
|
186
|
+
~1000× a persistent child. Optional engines SKIP if not importable —
|
|
187
|
+
not package deps.
|
|
168
188
|
|
|
169
189
|
The other direction (Python from Node):
|
|
170
190
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
@@ -218,9 +238,11 @@ total(array.array('d', range(1_000_000)))
|
|
|
218
238
|
## Common issues
|
|
219
239
|
|
|
220
240
|
**`Cannot find module`.** `require` / `import` resolve from the Python
|
|
221
|
-
process cwd (`node_modules` next to your program), not from
|
|
222
|
-
site-packages
|
|
223
|
-
|
|
241
|
+
process cwd (`node_modules` next to your notebook or program), not from
|
|
242
|
+
this wheel’s site-packages, and this package does **not** `npm install`
|
|
243
|
+
on a miss (pythonia does). `npm install lodash` in that directory is
|
|
244
|
+
the fix; or `create(node=…)` / `CC_NODE_BIN` when the wrong Node is on
|
|
245
|
+
`PATH`.
|
|
224
246
|
|
|
225
247
|
### Empty `{}` stays a handle
|
|
226
248
|
|
|
@@ -278,7 +300,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
278
300
|
|
|
279
301
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
280
302
|
`benchmarks.vs_alts`.
|
|
281
|
-
Jupyter: `
|
|
303
|
+
Jupyter: `from cc_node import require` (or `import cc_node` then `%%js`).
|
|
282
304
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
283
305
|
Own hot path in C/CC → native module (40–90ns) —
|
|
284
306
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"""cc-node: JavaScript (and every npm package) from Python.
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
_ = js.require('lodash') # resolved from YOUR cwd's node_modules
|
|
3
|
+
from cc_node import require
|
|
4
|
+
_ = require('lodash') # session child; cwd node_modules
|
|
6
5
|
_.chunk([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
|
|
6
|
+
|
|
7
|
+
import cc_node
|
|
8
|
+
js = cc_node.create() # a private child, not the session
|
|
7
9
|
js.destroy()
|
|
8
10
|
|
|
9
11
|
The mirror of the cc-python bridge, same rules pointed the other way:
|
|
@@ -19,8 +21,8 @@ Handles never cross domains; every door after destroy() answers
|
|
|
19
21
|
articulately; destroy is idempotent and `with cc_node.create() as js:`
|
|
20
22
|
scopes it.
|
|
21
23
|
|
|
22
|
-
Notebooks (Jupyter / Colab): `
|
|
23
|
-
`cc_node
|
|
24
|
+
Notebooks (Jupyter / Colab): `from cc_node import require` or
|
|
25
|
+
`import cc_node` then `%%js` — one session, same calling convention.
|
|
24
26
|
"""
|
|
25
27
|
import array
|
|
26
28
|
import atexit
|
|
@@ -41,7 +43,7 @@ __all__ = [
|
|
|
41
43
|
"JsError", "JsHandle",
|
|
42
44
|
"load_ipython_extension", "unload_ipython_extension", "__version__",
|
|
43
45
|
]
|
|
44
|
-
__version__ = "0.23.
|
|
46
|
+
__version__ = "0.23.2"
|
|
45
47
|
|
|
46
48
|
_NO_NODE = (
|
|
47
49
|
"cc-node: no node executable (install Node, or set CC_NODE_BIN). "
|
|
@@ -6,8 +6,9 @@ Real Node (this package)
|
|
|
6
6
|
`require('fs')`, native addons, the npm in cwd, crash isolation.
|
|
7
7
|
Honest alternatives: drive a `node` child yourself. Spawn-per-call
|
|
8
8
|
(`node -e`) is what people write first. A persistent JSON-stdio loop
|
|
9
|
-
is the DIY that actually competes.
|
|
10
|
-
|
|
9
|
+
is the DIY that actually competes. pythonia (PyPI `javascript`,
|
|
10
|
+
JSPyBridge) is the packaged peer with the same DX — `require()` on
|
|
11
|
+
import, one child. PyExecJS was eval-a-string; abandoned.
|
|
11
12
|
|
|
12
13
|
A JS engine inside CPython (not Node)
|
|
13
14
|
pythonmonkey — SpiderMonkey + CommonJS `require` of *pure JS*.
|
|
@@ -218,6 +219,77 @@ def bench_cc_node():
|
|
|
218
219
|
js.destroy()
|
|
219
220
|
|
|
220
221
|
|
|
222
|
+
def bench_pythonia():
|
|
223
|
+
"""JSPyBridge: PyPI `javascript`, npm twin `pythonia`. Real Node child.
|
|
224
|
+
A Python callable the JS side sees is a Promise (await in the child)."""
|
|
225
|
+
try:
|
|
226
|
+
t0 = time.perf_counter()
|
|
227
|
+
from javascript import eval_js, require
|
|
228
|
+
eval_js("return 1")
|
|
229
|
+
_result("pythonia.spawn_ms", round((time.perf_counter() - t0) * 1000))
|
|
230
|
+
except ImportError:
|
|
231
|
+
_result("pythonia.rtt_us", "SKIP not installed")
|
|
232
|
+
_cap("pythonia.require_path", False, "not installed")
|
|
233
|
+
_cap("pythonia.require_fs", False, "not installed")
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
f = eval_js("return (x) => x")
|
|
237
|
+
f(1)
|
|
238
|
+
dt = _time_loop(500, f)
|
|
239
|
+
_result("pythonia.rtt_us", _us(dt, 500))
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
g = eval_js(
|
|
243
|
+
"return async (cb) => { const v = await cb(21); return v * 2 }")
|
|
244
|
+
if g(lambda x: x + 1) != 44:
|
|
245
|
+
raise RuntimeError("callback mismatch")
|
|
246
|
+
dt = _time_loop(200, lambda _i: g(lambda x: x + 1))
|
|
247
|
+
_result("pythonia.callback_roundtrip_us", _us(dt, 200))
|
|
248
|
+
_cap("pythonia.python_callback", True,
|
|
249
|
+
"JS sees a Promise; await in the child")
|
|
250
|
+
except Exception as e:
|
|
251
|
+
_result("pythonia.callback_roundtrip_us",
|
|
252
|
+
"SKIP %s" % type(e).__name__)
|
|
253
|
+
_cap("pythonia.python_callback", False, str(e)[:80])
|
|
254
|
+
|
|
255
|
+
_result("pythonia.bulk_8mb_shm_ms", "SKIP JSON IPC, no shm")
|
|
256
|
+
try:
|
|
257
|
+
biglist = _bulk_list()
|
|
258
|
+
sm = eval_js("return " + SUM_JS)
|
|
259
|
+
got = sm(biglist)
|
|
260
|
+
expect = _bulk_sum_expect(biglist)
|
|
261
|
+
if abs(float(got) - expect) > 1e-3:
|
|
262
|
+
raise RuntimeError("pythonia bulk sum mismatch")
|
|
263
|
+
t0 = time.perf_counter()
|
|
264
|
+
for _ in range(3):
|
|
265
|
+
sm(biglist)
|
|
266
|
+
_result("pythonia.bulk_8mb_json_list_ms",
|
|
267
|
+
round((time.perf_counter() - t0) / 3 * 1000))
|
|
268
|
+
except Exception as e:
|
|
269
|
+
_result("pythonia.bulk_8mb_json_list_ms",
|
|
270
|
+
"SKIP %s" % type(e).__name__)
|
|
271
|
+
|
|
272
|
+
_cap("pythonia.require_path",
|
|
273
|
+
require("path").join("a", "b") == "a/b")
|
|
274
|
+
fd, tmp = tempfile.mkstemp()
|
|
275
|
+
try:
|
|
276
|
+
os.write(fd, b"hi")
|
|
277
|
+
os.close(fd)
|
|
278
|
+
got = require("fs").readFileSync(tmp, "utf8")
|
|
279
|
+
_cap("pythonia.require_fs", got == "hi")
|
|
280
|
+
finally:
|
|
281
|
+
try:
|
|
282
|
+
os.unlink(tmp)
|
|
283
|
+
except OSError:
|
|
284
|
+
pass
|
|
285
|
+
try:
|
|
286
|
+
then = eval_js(
|
|
287
|
+
"return async (x) => { await Promise.resolve(); return x * 2 }")
|
|
288
|
+
_cap("pythonia.thenable", then(21) == 42)
|
|
289
|
+
except Exception as e:
|
|
290
|
+
_cap("pythonia.thenable", False, str(e)[:80])
|
|
291
|
+
|
|
292
|
+
|
|
221
293
|
def bench_diy_stdio():
|
|
222
294
|
t0 = time.perf_counter()
|
|
223
295
|
diy = DiyStdio()
|
|
@@ -408,6 +480,7 @@ def main():
|
|
|
408
480
|
print("SKIP need node on PATH")
|
|
409
481
|
return 1
|
|
410
482
|
bench_cc_node()
|
|
483
|
+
bench_pythonia()
|
|
411
484
|
bench_diy_stdio()
|
|
412
485
|
bench_spawn_each()
|
|
413
486
|
bench_pythonmonkey()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: concurrent-c-node
|
|
3
|
-
Version: 0.23.
|
|
3
|
+
Version: 0.23.2
|
|
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
|
|
@@ -18,6 +18,13 @@ Requires-Dist: ipython>=7; extra == "jupyter"
|
|
|
18
18
|
Call Node (and npm packages) from Python.
|
|
19
19
|
Native types, exceptions, callbacks, and async all cross the boundary.
|
|
20
20
|
|
|
21
|
+
## Why use this
|
|
22
|
+
|
|
23
|
+
Call real Node from Python — `require`, native addons, npm — not a JS
|
|
24
|
+
engine in-process. One child, shm for bulk, `from cc_node import require`
|
|
25
|
+
in Jupyter/Colab with no `%load_ext`. pythonia is the packaged peer; this
|
|
26
|
+
is faster on the wire and honest about bulk and callbacks.
|
|
27
|
+
|
|
21
28
|
Part of [Concurrent-C](https://github.com/sreekotay/concurrent-c) — a
|
|
22
29
|
strict C11-superset preprocessor: `.ccs` lowers to plain C and compiles
|
|
23
30
|
with your host C compiler. (This bridge itself is pure Python stdlib —
|
|
@@ -28,15 +35,19 @@ bridge):
|
|
|
28
35
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
29
36
|
|
|
30
37
|
```python
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
js = cc_node.create() # always a child `node` process
|
|
34
|
-
_ = js.require('lodash') # cwd node_modules
|
|
38
|
+
from cc_node import require # one session child (lazy)
|
|
39
|
+
_ = require('lodash') # cwd node_modules
|
|
35
40
|
_.chunk([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
|
|
41
|
+
```
|
|
36
42
|
|
|
43
|
+
A private child (`create()`) is still there when you want N Nodes or
|
|
44
|
+
an explicit lifetime — not required for the first call.
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import cc_node
|
|
48
|
+
js = cc_node.create() # a private Node, not the session
|
|
37
49
|
semver = js.require('semver')
|
|
38
50
|
semver.satisfies('1.2.3', '^1.0.0') # True
|
|
39
|
-
|
|
40
51
|
js.destroy() # or: with cc_node.create() as js:
|
|
41
52
|
```
|
|
42
53
|
|
|
@@ -44,12 +55,13 @@ The other direction (Python from Node):
|
|
|
44
55
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
45
56
|
(in-process by default; vs pymport / ncp / pythonia in that README).
|
|
46
57
|
|
|
47
|
-
Every `create()`
|
|
48
|
-
measurable wire. N domains = N processes.
|
|
58
|
+
Every `create()` is a separate Node — real addons, crash isolation,
|
|
59
|
+
measurable wire. N domains = N processes. `require()` / `get()` share
|
|
60
|
+
one session for the process (the Jupyter kernel).
|
|
49
61
|
|
|
50
62
|
| | this package | CC hosted (`cc_js_new(false, …)`) |
|
|
51
63
|
|---|---|---|
|
|
52
|
-
| API | `
|
|
64
|
+
| API | `require()` / `create()` | `.ccs` program |
|
|
53
65
|
| Where | child `node` | libnode in-process |
|
|
54
66
|
| Hot call | ~105µs RTT | sub-µs (needs libnode) |
|
|
55
67
|
| Bulk | shm (~9.5ms / 8MB) | in-process |
|
|
@@ -60,11 +72,12 @@ measurable wire. N domains = N processes.
|
|
|
60
72
|
|
|
61
73
|
- Always a child `node`. A call blocks until JS answers; thenables wait
|
|
62
74
|
in the child. No `{ async: true }`.
|
|
75
|
+
- `from cc_node import require` is the session. `create()` is a private
|
|
76
|
+
child. `reset()` / `%js_reset` / `%reset` / atexit tear the session down.
|
|
63
77
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
64
78
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
65
|
-
- `import cc_node`
|
|
66
|
-
|
|
67
|
-
- `cc_node.require('path')` is `get().require('path')`.
|
|
79
|
+
- Notebook: `import cc_node` registers `%%js` (no `%load_ext`). Same
|
|
80
|
+
session as `require()`.
|
|
68
81
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
69
82
|
cwd `require` once.
|
|
70
83
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -74,35 +87,36 @@ measurable wire. N domains = N processes.
|
|
|
74
87
|
|
|
75
88
|
```
|
|
76
89
|
pip install concurrent-c-node # needs node on PATH
|
|
77
|
-
pip install 'concurrent-c-node[jupyter]' #
|
|
90
|
+
pip install 'concurrent-c-node[jupyter]' # IPython (%%js); require() does not need this
|
|
78
91
|
python -m cc_node.examples.use_node
|
|
79
92
|
python -m cc_node.examples.bench_wire
|
|
80
93
|
python -m cc_node.benchmarks.multi_domain
|
|
81
|
-
python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / mini-racer
|
|
94
|
+
python -m cc_node.benchmarks.vs_alts # vs pythonia / DIY node / pythonmonkey / mini-racer
|
|
82
95
|
```
|
|
83
96
|
|
|
84
97
|
## Jupyter / Colab
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
for the kernel, not a spawn per cell (~28ms). Child `console.log`
|
|
90
|
-
lands in the cell (inherited stdio, line-buffered).
|
|
99
|
+
Same verb as pythonia: `require`. No `%load_ext`, no `create()`, no
|
|
100
|
+
`destroy()` for the happy path. One session child for the kernel;
|
|
101
|
+
`console.log` lands in the cell.
|
|
91
102
|
|
|
92
103
|
```python
|
|
93
104
|
%pip install concurrent-c-node
|
|
94
105
|
# if `node` is missing (typical Colab):
|
|
95
106
|
!apt-get install -y nodejs
|
|
96
107
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
path.join('a', 'b') # 'a/b'
|
|
108
|
+
from cc_node import require
|
|
109
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
100
110
|
```
|
|
101
111
|
|
|
112
|
+
`import cc_node` also registers `%%js` (IPython already running; the
|
|
113
|
+
`[jupyter]` extra is only if you want magics without IPython already
|
|
114
|
+
installed).
|
|
115
|
+
|
|
102
116
|
```python
|
|
103
117
|
%%js
|
|
104
118
|
console.log('hi') # shows in the cell
|
|
105
|
-
require('
|
|
119
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
106
120
|
```
|
|
107
121
|
|
|
108
122
|
```python
|
|
@@ -114,9 +128,10 @@ xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
|
114
128
|
|
|
115
129
|
| | |
|
|
116
130
|
|---|---|
|
|
117
|
-
| `import
|
|
118
|
-
|
|
|
119
|
-
| `%
|
|
131
|
+
| `from cc_node import require` | session `require`; spawns on first call |
|
|
132
|
+
| `import cc_node` | registers magics; does **not** spawn until first `require()` / `%%js` |
|
|
133
|
+
| `%load_ext cc_node` | same, idempotent; not required |
|
|
134
|
+
| `%js 1+1` / `%%js` | eval on the same session; last expression is the result |
|
|
120
135
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
121
136
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
122
137
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
@@ -161,25 +176,30 @@ Colab is not that. There, default `create()` blocks the kernel thread —
|
|
|
161
176
|
Wire: line-JSON on dedicated fds (stdio stays yours). Bulk spill: private
|
|
162
177
|
0700 dir, 0600 files, removed with the bridge.
|
|
163
178
|
|
|
164
|
-
### Vs pythonmonkey / mini-racer / DIY node
|
|
179
|
+
### Vs pythonia / pythonmonkey / mini-racer / DIY node
|
|
165
180
|
|
|
166
|
-
Most “JS from Python” libraries are **not Node**.
|
|
167
|
-
|
|
181
|
+
Most “JS from Python” libraries are **not Node**. pythonia (PyPI
|
|
182
|
+
[`javascript`](https://pypi.org/project/javascript/), JSPyBridge) is the
|
|
183
|
+
packaged peer that is: `require()` on import, one child. Bulk is a
|
|
184
|
+
**sum** over 1M floats (`.length` on an in-process wrapper is free and
|
|
185
|
+
lies). Snapshot:
|
|
168
186
|
[`cc_node_vs_alts_20260813.txt`](https://github.com/sreekotay/concurrent-c/blob/main/perf/baselines/cc_node_vs_alts_20260813.txt)
|
|
169
187
|
· harness: [`benchmarks/vs_alts.py`](https://github.com/sreekotay/concurrent-c/blob/main/pypi/cc-node/cc_node/benchmarks/vs_alts.py).
|
|
170
188
|
|
|
171
|
-
| | cc-node | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
172
|
-
|
|
173
|
-
| identity RTT | **20µs** |
|
|
174
|
-
| callback | **
|
|
175
|
-
| 8MB typed / list | **
|
|
176
|
-
| `require('fs')` | yes | yes | yes | no | no |
|
|
177
|
-
| process | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
189
|
+
| | cc-node | pythonia | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
190
|
+
|---|---|---|---|---|---|---|
|
|
191
|
+
| identity RTT | **20µs** | 42µs | 19µs | 23ms | **<1µs** | 104µs |
|
|
192
|
+
| callback | **40µs** | 106µs | — | — | 1µs | — |
|
|
193
|
+
| 8MB typed / list | **7.5ms shm** / 266ms | — / 481ms | — / 155ms | — | — / 609ms | — / 78ms |
|
|
194
|
+
| `require('fs')` | yes | yes | yes | yes | no | no |
|
|
195
|
+
| process | child `node` | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
178
196
|
|
|
179
197
|
Tiny scalars: pythonmonkey’s in-process SM beats a child. Real Node
|
|
180
198
|
(`require('fs')`, native addons, callbacks, stdout stays yours): this
|
|
181
|
-
package
|
|
182
|
-
|
|
199
|
+
package — ~2× pythonia on identity, shm for bulk, Python callables are
|
|
200
|
+
sync (pythonia’s JS side sees a Promise). Isolated `node -e` per call is
|
|
201
|
+
~1000× a persistent child. Optional engines SKIP if not importable —
|
|
202
|
+
not package deps.
|
|
183
203
|
|
|
184
204
|
The other direction (Python from Node):
|
|
185
205
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
@@ -233,9 +253,11 @@ total(array.array('d', range(1_000_000)))
|
|
|
233
253
|
## Common issues
|
|
234
254
|
|
|
235
255
|
**`Cannot find module`.** `require` / `import` resolve from the Python
|
|
236
|
-
process cwd (`node_modules` next to your program), not from
|
|
237
|
-
site-packages
|
|
238
|
-
|
|
256
|
+
process cwd (`node_modules` next to your notebook or program), not from
|
|
257
|
+
this wheel’s site-packages, and this package does **not** `npm install`
|
|
258
|
+
on a miss (pythonia does). `npm install lodash` in that directory is
|
|
259
|
+
the fix; or `create(node=…)` / `CC_NODE_BIN` when the wrong Node is on
|
|
260
|
+
`PATH`.
|
|
239
261
|
|
|
240
262
|
### Empty `{}` stays a handle
|
|
241
263
|
|
|
@@ -293,7 +315,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
293
315
|
|
|
294
316
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
295
317
|
`benchmarks.vs_alts`.
|
|
296
|
-
Jupyter: `
|
|
318
|
+
Jupyter: `from cc_node import require` (or `import cc_node` then `%%js`).
|
|
297
319
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
298
320
|
Own hot path in C/CC → native module (40–90ns) —
|
|
299
321
|
[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.23.
|
|
7
|
+
version = "0.23.2"
|
|
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"
|
|
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.23.0 → concurrent_c_node-0.23.2}/concurrent_c_node.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/concurrent_c_node.egg-info/requires.txt
RENAMED
|
File without changes
|
{concurrent_c_node-0.23.0 → concurrent_c_node-0.23.2}/concurrent_c_node.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|