concurrent-c-node 0.23.0__tar.gz → 0.23.1__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.1}/PKG-INFO +57 -42
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/README.md +56 -41
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/__init__.py +8 -6
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/benchmarks/vs_alts.py +75 -2
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/concurrent_c_node.egg-info/PKG-INFO +57 -42
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/pyproject.toml +1 -1
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/benchmarks/__init__.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/benchmarks/multi_domain.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/broker.cjs +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/examples/__init__.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/examples/bench_wire.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/examples/use_node.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/magics.py +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/cc_node/stdio_line.cjs +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/concurrent_c_node.egg-info/SOURCES.txt +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/concurrent_c_node.egg-info/dependency_links.txt +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/concurrent_c_node.egg-info/requires.txt +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/concurrent_c_node.egg-info/top_level.txt +0 -0
- {concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/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.1
|
|
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
|
|
@@ -28,15 +28,19 @@ bridge):
|
|
|
28
28
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
29
29
|
|
|
30
30
|
```python
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
js = cc_node.create() # always a child `node` process
|
|
34
|
-
_ = js.require('lodash') # cwd node_modules
|
|
31
|
+
from cc_node import require # one session child (lazy)
|
|
32
|
+
_ = require('lodash') # cwd node_modules
|
|
35
33
|
_.chunk([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
A private child (`create()`) is still there when you want N Nodes or
|
|
37
|
+
an explicit lifetime — not required for the first call.
|
|
36
38
|
|
|
39
|
+
```python
|
|
40
|
+
import cc_node
|
|
41
|
+
js = cc_node.create() # a private Node, not the session
|
|
37
42
|
semver = js.require('semver')
|
|
38
43
|
semver.satisfies('1.2.3', '^1.0.0') # True
|
|
39
|
-
|
|
40
44
|
js.destroy() # or: with cc_node.create() as js:
|
|
41
45
|
```
|
|
42
46
|
|
|
@@ -44,12 +48,13 @@ The other direction (Python from Node):
|
|
|
44
48
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
45
49
|
(in-process by default; vs pymport / ncp / pythonia in that README).
|
|
46
50
|
|
|
47
|
-
Every `create()`
|
|
48
|
-
measurable wire. N domains = N processes.
|
|
51
|
+
Every `create()` is a separate Node — real addons, crash isolation,
|
|
52
|
+
measurable wire. N domains = N processes. `require()` / `get()` share
|
|
53
|
+
one session for the process (the Jupyter kernel).
|
|
49
54
|
|
|
50
55
|
| | this package | CC hosted (`cc_js_new(false, …)`) |
|
|
51
56
|
|---|---|---|
|
|
52
|
-
| API | `
|
|
57
|
+
| API | `require()` / `create()` | `.ccs` program |
|
|
53
58
|
| Where | child `node` | libnode in-process |
|
|
54
59
|
| Hot call | ~105µs RTT | sub-µs (needs libnode) |
|
|
55
60
|
| Bulk | shm (~9.5ms / 8MB) | in-process |
|
|
@@ -60,11 +65,12 @@ measurable wire. N domains = N processes.
|
|
|
60
65
|
|
|
61
66
|
- Always a child `node`. A call blocks until JS answers; thenables wait
|
|
62
67
|
in the child. No `{ async: true }`.
|
|
68
|
+
- `from cc_node import require` is the session. `create()` is a private
|
|
69
|
+
child. `reset()` / `%js_reset` / `%reset` / atexit tear the session down.
|
|
63
70
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
64
71
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
65
|
-
- `import cc_node`
|
|
66
|
-
|
|
67
|
-
- `cc_node.require('path')` is `get().require('path')`.
|
|
72
|
+
- Notebook: `import cc_node` registers `%%js` (no `%load_ext`). Same
|
|
73
|
+
session as `require()`.
|
|
68
74
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
69
75
|
cwd `require` once.
|
|
70
76
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -74,35 +80,36 @@ measurable wire. N domains = N processes.
|
|
|
74
80
|
|
|
75
81
|
```
|
|
76
82
|
pip install concurrent-c-node # needs node on PATH
|
|
77
|
-
pip install 'concurrent-c-node[jupyter]' #
|
|
83
|
+
pip install 'concurrent-c-node[jupyter]' # IPython (%%js); require() does not need this
|
|
78
84
|
python -m cc_node.examples.use_node
|
|
79
85
|
python -m cc_node.examples.bench_wire
|
|
80
86
|
python -m cc_node.benchmarks.multi_domain
|
|
81
|
-
python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / mini-racer
|
|
87
|
+
python -m cc_node.benchmarks.vs_alts # vs pythonia / DIY node / pythonmonkey / mini-racer
|
|
82
88
|
```
|
|
83
89
|
|
|
84
90
|
## Jupyter / Colab
|
|
85
91
|
|
|
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).
|
|
92
|
+
Same verb as pythonia: `require`. No `%load_ext`, no `create()`, no
|
|
93
|
+
`destroy()` for the happy path. One session child for the kernel;
|
|
94
|
+
`console.log` lands in the cell.
|
|
91
95
|
|
|
92
96
|
```python
|
|
93
97
|
%pip install concurrent-c-node
|
|
94
98
|
# if `node` is missing (typical Colab):
|
|
95
99
|
!apt-get install -y nodejs
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
path.join('a', 'b') # 'a/b'
|
|
101
|
+
from cc_node import require
|
|
102
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
100
103
|
```
|
|
101
104
|
|
|
105
|
+
`import cc_node` also registers `%%js` (IPython already running; the
|
|
106
|
+
`[jupyter]` extra is only if you want magics without IPython already
|
|
107
|
+
installed).
|
|
108
|
+
|
|
102
109
|
```python
|
|
103
110
|
%%js
|
|
104
111
|
console.log('hi') # shows in the cell
|
|
105
|
-
require('
|
|
112
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
106
113
|
```
|
|
107
114
|
|
|
108
115
|
```python
|
|
@@ -114,9 +121,10 @@ xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
|
114
121
|
|
|
115
122
|
| | |
|
|
116
123
|
|---|---|
|
|
117
|
-
| `import
|
|
118
|
-
|
|
|
119
|
-
| `%
|
|
124
|
+
| `from cc_node import require` | session `require`; spawns on first call |
|
|
125
|
+
| `import cc_node` | registers magics; does **not** spawn until first `require()` / `%%js` |
|
|
126
|
+
| `%load_ext cc_node` | same, idempotent; not required |
|
|
127
|
+
| `%js 1+1` / `%%js` | eval on the same session; last expression is the result |
|
|
120
128
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
121
129
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
122
130
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
@@ -161,25 +169,30 @@ Colab is not that. There, default `create()` blocks the kernel thread —
|
|
|
161
169
|
Wire: line-JSON on dedicated fds (stdio stays yours). Bulk spill: private
|
|
162
170
|
0700 dir, 0600 files, removed with the bridge.
|
|
163
171
|
|
|
164
|
-
### Vs pythonmonkey / mini-racer / DIY node
|
|
172
|
+
### Vs pythonia / pythonmonkey / mini-racer / DIY node
|
|
165
173
|
|
|
166
|
-
Most “JS from Python” libraries are **not Node**.
|
|
167
|
-
|
|
174
|
+
Most “JS from Python” libraries are **not Node**. pythonia (PyPI
|
|
175
|
+
[`javascript`](https://pypi.org/project/javascript/), JSPyBridge) is the
|
|
176
|
+
packaged peer that is: `require()` on import, one child. Bulk is a
|
|
177
|
+
**sum** over 1M floats (`.length` on an in-process wrapper is free and
|
|
178
|
+
lies). Snapshot:
|
|
168
179
|
[`cc_node_vs_alts_20260813.txt`](https://github.com/sreekotay/concurrent-c/blob/main/perf/baselines/cc_node_vs_alts_20260813.txt)
|
|
169
180
|
· harness: [`benchmarks/vs_alts.py`](https://github.com/sreekotay/concurrent-c/blob/main/pypi/cc-node/cc_node/benchmarks/vs_alts.py).
|
|
170
181
|
|
|
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 |
|
|
182
|
+
| | cc-node | pythonia | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
183
|
+
|---|---|---|---|---|---|---|
|
|
184
|
+
| identity RTT | **20µs** | 42µs | 19µs | 23ms | **<1µs** | 104µs |
|
|
185
|
+
| callback | **40µs** | 106µs | — | — | 1µs | — |
|
|
186
|
+
| 8MB typed / list | **7.5ms shm** / 266ms | — / 481ms | — / 155ms | — | — / 609ms | — / 78ms |
|
|
187
|
+
| `require('fs')` | yes | yes | yes | yes | no | no |
|
|
188
|
+
| process | child `node` | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
178
189
|
|
|
179
190
|
Tiny scalars: pythonmonkey’s in-process SM beats a child. Real Node
|
|
180
191
|
(`require('fs')`, native addons, callbacks, stdout stays yours): this
|
|
181
|
-
package
|
|
182
|
-
|
|
192
|
+
package — ~2× pythonia on identity, shm for bulk, Python callables are
|
|
193
|
+
sync (pythonia’s JS side sees a Promise). Isolated `node -e` per call is
|
|
194
|
+
~1000× a persistent child. Optional engines SKIP if not importable —
|
|
195
|
+
not package deps.
|
|
183
196
|
|
|
184
197
|
The other direction (Python from Node):
|
|
185
198
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
@@ -233,9 +246,11 @@ total(array.array('d', range(1_000_000)))
|
|
|
233
246
|
## Common issues
|
|
234
247
|
|
|
235
248
|
**`Cannot find module`.** `require` / `import` resolve from the Python
|
|
236
|
-
process cwd (`node_modules` next to your program), not from
|
|
237
|
-
site-packages
|
|
238
|
-
|
|
249
|
+
process cwd (`node_modules` next to your notebook or program), not from
|
|
250
|
+
this wheel’s site-packages, and this package does **not** `npm install`
|
|
251
|
+
on a miss (pythonia does). `npm install lodash` in that directory is
|
|
252
|
+
the fix; or `create(node=…)` / `CC_NODE_BIN` when the wrong Node is on
|
|
253
|
+
`PATH`.
|
|
239
254
|
|
|
240
255
|
### Empty `{}` stays a handle
|
|
241
256
|
|
|
@@ -293,7 +308,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
293
308
|
|
|
294
309
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
295
310
|
`benchmarks.vs_alts`.
|
|
296
|
-
Jupyter: `
|
|
311
|
+
Jupyter: `from cc_node import require` (or `import cc_node` then `%%js`).
|
|
297
312
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
298
313
|
Own hot path in C/CC → native module (40–90ns) —
|
|
299
314
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
@@ -13,15 +13,19 @@ bridge):
|
|
|
13
13
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
14
14
|
|
|
15
15
|
```python
|
|
16
|
-
import
|
|
17
|
-
|
|
18
|
-
js = cc_node.create() # always a child `node` process
|
|
19
|
-
_ = js.require('lodash') # cwd node_modules
|
|
16
|
+
from cc_node import require # one session child (lazy)
|
|
17
|
+
_ = require('lodash') # cwd node_modules
|
|
20
18
|
_.chunk([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
A private child (`create()`) is still there when you want N Nodes or
|
|
22
|
+
an explicit lifetime — not required for the first call.
|
|
21
23
|
|
|
24
|
+
```python
|
|
25
|
+
import cc_node
|
|
26
|
+
js = cc_node.create() # a private Node, not the session
|
|
22
27
|
semver = js.require('semver')
|
|
23
28
|
semver.satisfies('1.2.3', '^1.0.0') # True
|
|
24
|
-
|
|
25
29
|
js.destroy() # or: with cc_node.create() as js:
|
|
26
30
|
```
|
|
27
31
|
|
|
@@ -29,12 +33,13 @@ The other direction (Python from Node):
|
|
|
29
33
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
30
34
|
(in-process by default; vs pymport / ncp / pythonia in that README).
|
|
31
35
|
|
|
32
|
-
Every `create()`
|
|
33
|
-
measurable wire. N domains = N processes.
|
|
36
|
+
Every `create()` is a separate Node — real addons, crash isolation,
|
|
37
|
+
measurable wire. N domains = N processes. `require()` / `get()` share
|
|
38
|
+
one session for the process (the Jupyter kernel).
|
|
34
39
|
|
|
35
40
|
| | this package | CC hosted (`cc_js_new(false, …)`) |
|
|
36
41
|
|---|---|---|
|
|
37
|
-
| API | `
|
|
42
|
+
| API | `require()` / `create()` | `.ccs` program |
|
|
38
43
|
| Where | child `node` | libnode in-process |
|
|
39
44
|
| Hot call | ~105µs RTT | sub-µs (needs libnode) |
|
|
40
45
|
| Bulk | shm (~9.5ms / 8MB) | in-process |
|
|
@@ -45,11 +50,12 @@ measurable wire. N domains = N processes.
|
|
|
45
50
|
|
|
46
51
|
- Always a child `node`. A call blocks until JS answers; thenables wait
|
|
47
52
|
in the child. No `{ async: true }`.
|
|
53
|
+
- `from cc_node import require` is the session. `create()` is a private
|
|
54
|
+
child. `reset()` / `%js_reset` / `%reset` / atexit tear the session down.
|
|
48
55
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
49
56
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
50
|
-
- `import cc_node`
|
|
51
|
-
|
|
52
|
-
- `cc_node.require('path')` is `get().require('path')`.
|
|
57
|
+
- Notebook: `import cc_node` registers `%%js` (no `%load_ext`). Same
|
|
58
|
+
session as `require()`.
|
|
53
59
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
54
60
|
cwd `require` once.
|
|
55
61
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -59,35 +65,36 @@ measurable wire. N domains = N processes.
|
|
|
59
65
|
|
|
60
66
|
```
|
|
61
67
|
pip install concurrent-c-node # needs node on PATH
|
|
62
|
-
pip install 'concurrent-c-node[jupyter]' #
|
|
68
|
+
pip install 'concurrent-c-node[jupyter]' # IPython (%%js); require() does not need this
|
|
63
69
|
python -m cc_node.examples.use_node
|
|
64
70
|
python -m cc_node.examples.bench_wire
|
|
65
71
|
python -m cc_node.benchmarks.multi_domain
|
|
66
|
-
python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / mini-racer
|
|
72
|
+
python -m cc_node.benchmarks.vs_alts # vs pythonia / DIY node / pythonmonkey / mini-racer
|
|
67
73
|
```
|
|
68
74
|
|
|
69
75
|
## Jupyter / Colab
|
|
70
76
|
|
|
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).
|
|
77
|
+
Same verb as pythonia: `require`. No `%load_ext`, no `create()`, no
|
|
78
|
+
`destroy()` for the happy path. One session child for the kernel;
|
|
79
|
+
`console.log` lands in the cell.
|
|
76
80
|
|
|
77
81
|
```python
|
|
78
82
|
%pip install concurrent-c-node
|
|
79
83
|
# if `node` is missing (typical Colab):
|
|
80
84
|
!apt-get install -y nodejs
|
|
81
85
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
path.join('a', 'b') # 'a/b'
|
|
86
|
+
from cc_node import require
|
|
87
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
85
88
|
```
|
|
86
89
|
|
|
90
|
+
`import cc_node` also registers `%%js` (IPython already running; the
|
|
91
|
+
`[jupyter]` extra is only if you want magics without IPython already
|
|
92
|
+
installed).
|
|
93
|
+
|
|
87
94
|
```python
|
|
88
95
|
%%js
|
|
89
96
|
console.log('hi') # shows in the cell
|
|
90
|
-
require('
|
|
97
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
91
98
|
```
|
|
92
99
|
|
|
93
100
|
```python
|
|
@@ -99,9 +106,10 @@ xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
|
99
106
|
|
|
100
107
|
| | |
|
|
101
108
|
|---|---|
|
|
102
|
-
| `import
|
|
103
|
-
|
|
|
104
|
-
| `%
|
|
109
|
+
| `from cc_node import require` | session `require`; spawns on first call |
|
|
110
|
+
| `import cc_node` | registers magics; does **not** spawn until first `require()` / `%%js` |
|
|
111
|
+
| `%load_ext cc_node` | same, idempotent; not required |
|
|
112
|
+
| `%js 1+1` / `%%js` | eval on the same session; last expression is the result |
|
|
105
113
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
106
114
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
107
115
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
@@ -146,25 +154,30 @@ Colab is not that. There, default `create()` blocks the kernel thread —
|
|
|
146
154
|
Wire: line-JSON on dedicated fds (stdio stays yours). Bulk spill: private
|
|
147
155
|
0700 dir, 0600 files, removed with the bridge.
|
|
148
156
|
|
|
149
|
-
### Vs pythonmonkey / mini-racer / DIY node
|
|
157
|
+
### Vs pythonia / pythonmonkey / mini-racer / DIY node
|
|
150
158
|
|
|
151
|
-
Most “JS from Python” libraries are **not Node**.
|
|
152
|
-
|
|
159
|
+
Most “JS from Python” libraries are **not Node**. pythonia (PyPI
|
|
160
|
+
[`javascript`](https://pypi.org/project/javascript/), JSPyBridge) is the
|
|
161
|
+
packaged peer that is: `require()` on import, one child. Bulk is a
|
|
162
|
+
**sum** over 1M floats (`.length` on an in-process wrapper is free and
|
|
163
|
+
lies). Snapshot:
|
|
153
164
|
[`cc_node_vs_alts_20260813.txt`](https://github.com/sreekotay/concurrent-c/blob/main/perf/baselines/cc_node_vs_alts_20260813.txt)
|
|
154
165
|
· harness: [`benchmarks/vs_alts.py`](https://github.com/sreekotay/concurrent-c/blob/main/pypi/cc-node/cc_node/benchmarks/vs_alts.py).
|
|
155
166
|
|
|
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 |
|
|
167
|
+
| | cc-node | pythonia | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
168
|
+
|---|---|---|---|---|---|---|
|
|
169
|
+
| identity RTT | **20µs** | 42µs | 19µs | 23ms | **<1µs** | 104µs |
|
|
170
|
+
| callback | **40µs** | 106µs | — | — | 1µs | — |
|
|
171
|
+
| 8MB typed / list | **7.5ms shm** / 266ms | — / 481ms | — / 155ms | — | — / 609ms | — / 78ms |
|
|
172
|
+
| `require('fs')` | yes | yes | yes | yes | no | no |
|
|
173
|
+
| process | child `node` | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
163
174
|
|
|
164
175
|
Tiny scalars: pythonmonkey’s in-process SM beats a child. Real Node
|
|
165
176
|
(`require('fs')`, native addons, callbacks, stdout stays yours): this
|
|
166
|
-
package
|
|
167
|
-
|
|
177
|
+
package — ~2× pythonia on identity, shm for bulk, Python callables are
|
|
178
|
+
sync (pythonia’s JS side sees a Promise). Isolated `node -e` per call is
|
|
179
|
+
~1000× a persistent child. Optional engines SKIP if not importable —
|
|
180
|
+
not package deps.
|
|
168
181
|
|
|
169
182
|
The other direction (Python from Node):
|
|
170
183
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
@@ -218,9 +231,11 @@ total(array.array('d', range(1_000_000)))
|
|
|
218
231
|
## Common issues
|
|
219
232
|
|
|
220
233
|
**`Cannot find module`.** `require` / `import` resolve from the Python
|
|
221
|
-
process cwd (`node_modules` next to your program), not from
|
|
222
|
-
site-packages
|
|
223
|
-
|
|
234
|
+
process cwd (`node_modules` next to your notebook or program), not from
|
|
235
|
+
this wheel’s site-packages, and this package does **not** `npm install`
|
|
236
|
+
on a miss (pythonia does). `npm install lodash` in that directory is
|
|
237
|
+
the fix; or `create(node=…)` / `CC_NODE_BIN` when the wrong Node is on
|
|
238
|
+
`PATH`.
|
|
224
239
|
|
|
225
240
|
### Empty `{}` stays a handle
|
|
226
241
|
|
|
@@ -278,7 +293,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
278
293
|
|
|
279
294
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
280
295
|
`benchmarks.vs_alts`.
|
|
281
|
-
Jupyter: `
|
|
296
|
+
Jupyter: `from cc_node import require` (or `import cc_node` then `%%js`).
|
|
282
297
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
283
298
|
Own hot path in C/CC → native module (40–90ns) —
|
|
284
299
|
[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.1"
|
|
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.1
|
|
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
|
|
@@ -28,15 +28,19 @@ bridge):
|
|
|
28
28
|
[JS / Python interop](https://github.com/sreekotay/concurrent-c/blob/main/docs/js-py-modules.md).
|
|
29
29
|
|
|
30
30
|
```python
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
js = cc_node.create() # always a child `node` process
|
|
34
|
-
_ = js.require('lodash') # cwd node_modules
|
|
31
|
+
from cc_node import require # one session child (lazy)
|
|
32
|
+
_ = require('lodash') # cwd node_modules
|
|
35
33
|
_.chunk([1, 2, 3, 4, 5], 2) # [[1, 2], [3, 4], [5]]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
A private child (`create()`) is still there when you want N Nodes or
|
|
37
|
+
an explicit lifetime — not required for the first call.
|
|
36
38
|
|
|
39
|
+
```python
|
|
40
|
+
import cc_node
|
|
41
|
+
js = cc_node.create() # a private Node, not the session
|
|
37
42
|
semver = js.require('semver')
|
|
38
43
|
semver.satisfies('1.2.3', '^1.0.0') # True
|
|
39
|
-
|
|
40
44
|
js.destroy() # or: with cc_node.create() as js:
|
|
41
45
|
```
|
|
42
46
|
|
|
@@ -44,12 +48,13 @@ The other direction (Python from Node):
|
|
|
44
48
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
45
49
|
(in-process by default; vs pymport / ncp / pythonia in that README).
|
|
46
50
|
|
|
47
|
-
Every `create()`
|
|
48
|
-
measurable wire. N domains = N processes.
|
|
51
|
+
Every `create()` is a separate Node — real addons, crash isolation,
|
|
52
|
+
measurable wire. N domains = N processes. `require()` / `get()` share
|
|
53
|
+
one session for the process (the Jupyter kernel).
|
|
49
54
|
|
|
50
55
|
| | this package | CC hosted (`cc_js_new(false, …)`) |
|
|
51
56
|
|---|---|---|
|
|
52
|
-
| API | `
|
|
57
|
+
| API | `require()` / `create()` | `.ccs` program |
|
|
53
58
|
| Where | child `node` | libnode in-process |
|
|
54
59
|
| Hot call | ~105µs RTT | sub-µs (needs libnode) |
|
|
55
60
|
| Bulk | shm (~9.5ms / 8MB) | in-process |
|
|
@@ -60,11 +65,12 @@ measurable wire. N domains = N processes.
|
|
|
60
65
|
|
|
61
66
|
- Always a child `node`. A call blocks until JS answers; thenables wait
|
|
62
67
|
in the child. No `{ async: true }`.
|
|
68
|
+
- `from cc_node import require` is the session. `create()` is a private
|
|
69
|
+
child. `reset()` / `%js_reset` / `%reset` / atexit tear the session down.
|
|
63
70
|
- Scalars / `None` materialize; empty `{}` stays a handle; everything
|
|
64
71
|
else is a `JsHandle` until `str()` / attrs / a call.
|
|
65
|
-
- `import cc_node`
|
|
66
|
-
|
|
67
|
-
- `cc_node.require('path')` is `get().require('path')`.
|
|
72
|
+
- Notebook: `import cc_node` registers `%%js` (no `%load_ext`). Same
|
|
73
|
+
session as `require()`.
|
|
68
74
|
- `eval()` is one RTT, no extra globals. `%%js` / `eval_cell` install
|
|
69
75
|
cwd `require` once.
|
|
70
76
|
- `--bind` is `Object.assign(globalThis, …)` of names you name (wire
|
|
@@ -74,35 +80,36 @@ measurable wire. N domains = N processes.
|
|
|
74
80
|
|
|
75
81
|
```
|
|
76
82
|
pip install concurrent-c-node # needs node on PATH
|
|
77
|
-
pip install 'concurrent-c-node[jupyter]' #
|
|
83
|
+
pip install 'concurrent-c-node[jupyter]' # IPython (%%js); require() does not need this
|
|
78
84
|
python -m cc_node.examples.use_node
|
|
79
85
|
python -m cc_node.examples.bench_wire
|
|
80
86
|
python -m cc_node.benchmarks.multi_domain
|
|
81
|
-
python -m cc_node.benchmarks.vs_alts # vs DIY node / pythonmonkey / mini-racer
|
|
87
|
+
python -m cc_node.benchmarks.vs_alts # vs pythonia / DIY node / pythonmonkey / mini-racer
|
|
82
88
|
```
|
|
83
89
|
|
|
84
90
|
## Jupyter / Colab
|
|
85
91
|
|
|
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).
|
|
92
|
+
Same verb as pythonia: `require`. No `%load_ext`, no `create()`, no
|
|
93
|
+
`destroy()` for the happy path. One session child for the kernel;
|
|
94
|
+
`console.log` lands in the cell.
|
|
91
95
|
|
|
92
96
|
```python
|
|
93
97
|
%pip install concurrent-c-node
|
|
94
98
|
# if `node` is missing (typical Colab):
|
|
95
99
|
!apt-get install -y nodejs
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
path.join('a', 'b') # 'a/b'
|
|
101
|
+
from cc_node import require
|
|
102
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
100
103
|
```
|
|
101
104
|
|
|
105
|
+
`import cc_node` also registers `%%js` (IPython already running; the
|
|
106
|
+
`[jupyter]` extra is only if you want magics without IPython already
|
|
107
|
+
installed).
|
|
108
|
+
|
|
102
109
|
```python
|
|
103
110
|
%%js
|
|
104
111
|
console.log('hi') # shows in the cell
|
|
105
|
-
require('
|
|
112
|
+
require('lodash').chunk([1, 2, 3, 4, 5], 2)
|
|
106
113
|
```
|
|
107
114
|
|
|
108
115
|
```python
|
|
@@ -114,9 +121,10 @@ xs.map(x => x * 2) # wire types only; no pickle fallback
|
|
|
114
121
|
|
|
115
122
|
| | |
|
|
116
123
|
|---|---|
|
|
117
|
-
| `import
|
|
118
|
-
|
|
|
119
|
-
| `%
|
|
124
|
+
| `from cc_node import require` | session `require`; spawns on first call |
|
|
125
|
+
| `import cc_node` | registers magics; does **not** spawn until first `require()` / `%%js` |
|
|
126
|
+
| `%load_ext cc_node` | same, idempotent; not required |
|
|
127
|
+
| `%js 1+1` / `%%js` | eval on the same session; last expression is the result |
|
|
120
128
|
| `-b xs` / `--bind xs,n` | publish those Python names on `globalThis` for the cell |
|
|
121
129
|
| `-t chunks` / `--to` | store the result in the notebook namespace |
|
|
122
130
|
| `%js_stats` | handle-table size (spawns if needed) |
|
|
@@ -161,25 +169,30 @@ Colab is not that. There, default `create()` blocks the kernel thread —
|
|
|
161
169
|
Wire: line-JSON on dedicated fds (stdio stays yours). Bulk spill: private
|
|
162
170
|
0700 dir, 0600 files, removed with the bridge.
|
|
163
171
|
|
|
164
|
-
### Vs pythonmonkey / mini-racer / DIY node
|
|
172
|
+
### Vs pythonia / pythonmonkey / mini-racer / DIY node
|
|
165
173
|
|
|
166
|
-
Most “JS from Python” libraries are **not Node**.
|
|
167
|
-
|
|
174
|
+
Most “JS from Python” libraries are **not Node**. pythonia (PyPI
|
|
175
|
+
[`javascript`](https://pypi.org/project/javascript/), JSPyBridge) is the
|
|
176
|
+
packaged peer that is: `require()` on import, one child. Bulk is a
|
|
177
|
+
**sum** over 1M floats (`.length` on an in-process wrapper is free and
|
|
178
|
+
lies). Snapshot:
|
|
168
179
|
[`cc_node_vs_alts_20260813.txt`](https://github.com/sreekotay/concurrent-c/blob/main/perf/baselines/cc_node_vs_alts_20260813.txt)
|
|
169
180
|
· harness: [`benchmarks/vs_alts.py`](https://github.com/sreekotay/concurrent-c/blob/main/pypi/cc-node/cc_node/benchmarks/vs_alts.py).
|
|
170
181
|
|
|
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 |
|
|
182
|
+
| | cc-node | pythonia | DIY JSON stdio | `node -e` each | pythonmonkey | mini-racer |
|
|
183
|
+
|---|---|---|---|---|---|---|
|
|
184
|
+
| identity RTT | **20µs** | 42µs | 19µs | 23ms | **<1µs** | 104µs |
|
|
185
|
+
| callback | **40µs** | 106µs | — | — | 1µs | — |
|
|
186
|
+
| 8MB typed / list | **7.5ms shm** / 266ms | — / 481ms | — / 155ms | — | — / 609ms | — / 78ms |
|
|
187
|
+
| `require('fs')` | yes | yes | yes | yes | no | no |
|
|
188
|
+
| process | child `node` | child `node` | child `node` | new process/call | SpiderMonkey in-process | V8 isolate |
|
|
178
189
|
|
|
179
190
|
Tiny scalars: pythonmonkey’s in-process SM beats a child. Real Node
|
|
180
191
|
(`require('fs')`, native addons, callbacks, stdout stays yours): this
|
|
181
|
-
package
|
|
182
|
-
|
|
192
|
+
package — ~2× pythonia on identity, shm for bulk, Python callables are
|
|
193
|
+
sync (pythonia’s JS side sees a Promise). Isolated `node -e` per call is
|
|
194
|
+
~1000× a persistent child. Optional engines SKIP if not importable —
|
|
195
|
+
not package deps.
|
|
183
196
|
|
|
184
197
|
The other direction (Python from Node):
|
|
185
198
|
[`concurrent-c-python`](https://www.npmjs.com/package/concurrent-c-python)
|
|
@@ -233,9 +246,11 @@ total(array.array('d', range(1_000_000)))
|
|
|
233
246
|
## Common issues
|
|
234
247
|
|
|
235
248
|
**`Cannot find module`.** `require` / `import` resolve from the Python
|
|
236
|
-
process cwd (`node_modules` next to your program), not from
|
|
237
|
-
site-packages
|
|
238
|
-
|
|
249
|
+
process cwd (`node_modules` next to your notebook or program), not from
|
|
250
|
+
this wheel’s site-packages, and this package does **not** `npm install`
|
|
251
|
+
on a miss (pythonia does). `npm install lodash` in that directory is
|
|
252
|
+
the fix; or `create(node=…)` / `CC_NODE_BIN` when the wrong Node is on
|
|
253
|
+
`PATH`.
|
|
239
254
|
|
|
240
255
|
### Empty `{}` stays a handle
|
|
241
256
|
|
|
@@ -293,7 +308,7 @@ Both bridges (npm OIDC + PyPI OIDC):
|
|
|
293
308
|
|
|
294
309
|
Examples: `use_node`, `bench_wire`, `benchmarks.multi_domain`,
|
|
295
310
|
`benchmarks.vs_alts`.
|
|
296
|
-
Jupyter: `
|
|
311
|
+
Jupyter: `from cc_node import require` (or `import cc_node` then `%%js`).
|
|
297
312
|
Stress: [`stress/bridge/`](https://github.com/sreekotay/concurrent-c/tree/main/stress/bridge).
|
|
298
313
|
Own hot path in C/CC → native module (40–90ns) —
|
|
299
314
|
[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.1"
|
|
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.1}/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.1}/concurrent_c_node.egg-info/requires.txt
RENAMED
|
File without changes
|
{concurrent_c_node-0.23.0 → concurrent_c_node-0.23.1}/concurrent_c_node.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|