pynect 0.0.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +503 -2
  3. package/SECURITY.md +34 -0
  4. package/binding.gyp +67 -0
  5. package/dist/cli/codegen.d.ts +3 -0
  6. package/dist/cli/codegen.d.ts.map +1 -0
  7. package/dist/cli/codegen.js +543 -0
  8. package/dist/cli/codegen.js.map +1 -0
  9. package/dist/cli/sync.d.ts +3 -0
  10. package/dist/cli/sync.d.ts.map +1 -0
  11. package/dist/cli/sync.js +47 -0
  12. package/dist/cli/sync.js.map +1 -0
  13. package/dist/config/project.d.ts +113 -0
  14. package/dist/config/project.d.ts.map +1 -0
  15. package/dist/config/project.js +344 -0
  16. package/dist/config/project.js.map +1 -0
  17. package/dist/core/isolated-worker.d.ts +39 -0
  18. package/dist/core/isolated-worker.d.ts.map +1 -0
  19. package/dist/core/isolated-worker.js +178 -0
  20. package/dist/core/isolated-worker.js.map +1 -0
  21. package/dist/core/py-callable.d.ts +67 -0
  22. package/dist/core/py-callable.d.ts.map +1 -0
  23. package/dist/core/py-callable.js +104 -0
  24. package/dist/core/py-callable.js.map +1 -0
  25. package/dist/core/py-context.d.ts +76 -0
  26. package/dist/core/py-context.d.ts.map +1 -0
  27. package/dist/core/py-context.js +239 -0
  28. package/dist/core/py-context.js.map +1 -0
  29. package/dist/core/py-iterator.d.ts +84 -0
  30. package/dist/core/py-iterator.d.ts.map +1 -0
  31. package/dist/core/py-iterator.js +243 -0
  32. package/dist/core/py-iterator.js.map +1 -0
  33. package/dist/core/py-module.d.ts +53 -0
  34. package/dist/core/py-module.d.ts.map +1 -0
  35. package/dist/core/py-module.js +162 -0
  36. package/dist/core/py-module.js.map +1 -0
  37. package/dist/core/py-proxy.d.ts +75 -0
  38. package/dist/core/py-proxy.d.ts.map +1 -0
  39. package/dist/core/py-proxy.js +563 -0
  40. package/dist/core/py-proxy.js.map +1 -0
  41. package/dist/core/py-runtime.d.ts +113 -0
  42. package/dist/core/py-runtime.d.ts.map +1 -0
  43. package/dist/core/py-runtime.js +528 -0
  44. package/dist/core/py-runtime.js.map +1 -0
  45. package/dist/env/executables.d.ts +3 -0
  46. package/dist/env/executables.d.ts.map +1 -0
  47. package/dist/env/executables.js +14 -0
  48. package/dist/env/executables.js.map +1 -0
  49. package/dist/env/package-installer.d.ts +232 -0
  50. package/dist/env/package-installer.d.ts.map +1 -0
  51. package/dist/env/package-installer.js +615 -0
  52. package/dist/env/package-installer.js.map +1 -0
  53. package/dist/env/project-environment.d.ts +64 -0
  54. package/dist/env/project-environment.d.ts.map +1 -0
  55. package/dist/env/project-environment.js +388 -0
  56. package/dist/env/project-environment.js.map +1 -0
  57. package/dist/env/pyfig-adapter.d.ts +38 -0
  58. package/dist/env/pyfig-adapter.d.ts.map +1 -0
  59. package/dist/env/pyfig-adapter.js +10 -0
  60. package/dist/env/pyfig-adapter.js.map +1 -0
  61. package/dist/env/python-detector.d.ts +48 -0
  62. package/dist/env/python-detector.d.ts.map +1 -0
  63. package/dist/env/python-detector.js +182 -0
  64. package/dist/env/python-detector.js.map +1 -0
  65. package/dist/env/python-provisioner.d.ts +16 -0
  66. package/dist/env/python-provisioner.d.ts.map +1 -0
  67. package/dist/env/python-provisioner.js +42 -0
  68. package/dist/env/python-provisioner.js.map +1 -0
  69. package/dist/env/venv-manager.d.ts +56 -0
  70. package/dist/env/venv-manager.d.ts.map +1 -0
  71. package/dist/env/venv-manager.js +160 -0
  72. package/dist/env/venv-manager.js.map +1 -0
  73. package/dist/index.d.ts +182 -0
  74. package/dist/index.d.ts.map +1 -0
  75. package/dist/index.js +394 -0
  76. package/dist/index.js.map +1 -0
  77. package/dist/native/native-addon-loader.d.ts +13 -0
  78. package/dist/native/native-addon-loader.d.ts.map +1 -0
  79. package/dist/native/native-addon-loader.js +171 -0
  80. package/dist/native/native-addon-loader.js.map +1 -0
  81. package/dist/plugins/builtin/numpy-plugin.d.ts +17 -0
  82. package/dist/plugins/builtin/numpy-plugin.d.ts.map +1 -0
  83. package/dist/plugins/builtin/numpy-plugin.js +41 -0
  84. package/dist/plugins/builtin/numpy-plugin.js.map +1 -0
  85. package/dist/plugins/builtin/pandas-plugin.d.ts +19 -0
  86. package/dist/plugins/builtin/pandas-plugin.d.ts.map +1 -0
  87. package/dist/plugins/builtin/pandas-plugin.js +57 -0
  88. package/dist/plugins/builtin/pandas-plugin.js.map +1 -0
  89. package/dist/plugins/builtin/torch-plugin.d.ts +23 -0
  90. package/dist/plugins/builtin/torch-plugin.d.ts.map +1 -0
  91. package/dist/plugins/builtin/torch-plugin.js +50 -0
  92. package/dist/plugins/builtin/torch-plugin.js.map +1 -0
  93. package/dist/plugins/index.d.ts +7 -0
  94. package/dist/plugins/index.d.ts.map +1 -0
  95. package/dist/plugins/index.js +12 -0
  96. package/dist/plugins/index.js.map +1 -0
  97. package/dist/plugins/plugin-manager.d.ts +26 -0
  98. package/dist/plugins/plugin-manager.d.ts.map +1 -0
  99. package/dist/plugins/plugin-manager.js +174 -0
  100. package/dist/plugins/plugin-manager.js.map +1 -0
  101. package/dist/plugins/plugin.interface.d.ts +41 -0
  102. package/dist/plugins/plugin.interface.d.ts.map +1 -0
  103. package/dist/plugins/plugin.interface.js +12 -0
  104. package/dist/plugins/plugin.interface.js.map +1 -0
  105. package/dist/serialization/data-frame-bridge.d.ts +141 -0
  106. package/dist/serialization/data-frame-bridge.d.ts.map +1 -0
  107. package/dist/serialization/data-frame-bridge.js +355 -0
  108. package/dist/serialization/data-frame-bridge.js.map +1 -0
  109. package/dist/serialization/msg-pack-serializer.d.ts +45 -0
  110. package/dist/serialization/msg-pack-serializer.d.ts.map +1 -0
  111. package/dist/serialization/msg-pack-serializer.js +242 -0
  112. package/dist/serialization/msg-pack-serializer.js.map +1 -0
  113. package/dist/serialization/numpy-bridge.d.ts +96 -0
  114. package/dist/serialization/numpy-bridge.d.ts.map +1 -0
  115. package/dist/serialization/numpy-bridge.js +323 -0
  116. package/dist/serialization/numpy-bridge.js.map +1 -0
  117. package/dist/serialization/serializer.d.ts +78 -0
  118. package/dist/serialization/serializer.d.ts.map +1 -0
  119. package/dist/serialization/serializer.js +281 -0
  120. package/dist/serialization/serializer.js.map +1 -0
  121. package/dist/types/addon.d.ts +131 -0
  122. package/dist/types/addon.d.ts.map +1 -0
  123. package/dist/types/addon.js +32 -0
  124. package/dist/types/addon.js.map +1 -0
  125. package/dist/types/config.d.ts +236 -0
  126. package/dist/types/config.d.ts.map +1 -0
  127. package/dist/types/config.js +61 -0
  128. package/dist/types/config.js.map +1 -0
  129. package/dist/types/index.d.ts +10 -0
  130. package/dist/types/index.d.ts.map +1 -0
  131. package/dist/types/index.js +12 -0
  132. package/dist/types/index.js.map +1 -0
  133. package/dist/types/python.d.ts +235 -0
  134. package/dist/types/python.d.ts.map +1 -0
  135. package/dist/types/python.js +7 -0
  136. package/dist/types/python.js.map +1 -0
  137. package/dist/utils/error-translator.d.ts +83 -0
  138. package/dist/utils/error-translator.d.ts.map +1 -0
  139. package/dist/utils/error-translator.js +210 -0
  140. package/dist/utils/error-translator.js.map +1 -0
  141. package/dist/utils/locking.d.ts +3 -0
  142. package/dist/utils/locking.d.ts.map +1 -0
  143. package/dist/utils/locking.js +10 -0
  144. package/dist/utils/locking.js.map +1 -0
  145. package/dist/utils/logger.d.ts +27 -0
  146. package/dist/utils/logger.d.ts.map +1 -0
  147. package/dist/utils/logger.js +115 -0
  148. package/dist/utils/logger.js.map +1 -0
  149. package/dist/utils/memory-monitor.d.ts +43 -0
  150. package/dist/utils/memory-monitor.d.ts.map +1 -0
  151. package/dist/utils/memory-monitor.js +143 -0
  152. package/dist/utils/memory-monitor.js.map +1 -0
  153. package/package.json +93 -17
  154. package/prebuilds/darwin-arm64/pynect-prebuild.json +8 -0
  155. package/prebuilds/darwin-arm64/pynect_addon.node +0 -0
  156. package/prebuilds/darwin-x64/pynect-prebuild.json +8 -0
  157. package/prebuilds/darwin-x64/pynect_addon.node +0 -0
  158. package/prebuilds/linux-arm64/pynect-prebuild.json +9 -0
  159. package/prebuilds/linux-arm64/pynect_addon.node +0 -0
  160. package/prebuilds/linux-x64/pynect-prebuild.json +9 -0
  161. package/prebuilds/linux-x64/pynect_addon.node +0 -0
  162. package/prebuilds/win32-arm64/pynect-prebuild.json +8 -0
  163. package/prebuilds/win32-arm64/pynect_addon.node +0 -0
  164. package/prebuilds/win32-x64/pynect-prebuild.json +8 -0
  165. package/prebuilds/win32-x64/pynect_addon.node +0 -0
  166. package/python/ast_inspector.py +248 -0
  167. package/python/error_handler.py +432 -0
  168. package/python/isolated_worker.py +94 -0
  169. package/python/pynect_dispatcher.py +382 -0
  170. package/python/pynect_runtime.py +574 -0
  171. package/python/serializer.py +373 -0
  172. package/python/type_inspector.py +315 -0
  173. package/scripts/build-native.js +50 -0
  174. package/scripts/cli.js +21 -0
  175. package/scripts/ensure-native.js +85 -0
  176. package/scripts/postinstall.js +40 -0
  177. package/scripts/pyfig.js +128 -0
  178. package/scripts/python-provisioner.js +23 -0
  179. package/scripts/smoke-python-boundary.js +103 -0
  180. package/src/native/gil_guard.cpp +25 -0
  181. package/src/native/gil_guard.h +174 -0
  182. package/src/native/native-addon-loader.ts +218 -0
  183. package/src/native/pynect_addon.cpp +942 -0
  184. package/src/native/python_bridge.cpp +904 -0
  185. package/src/native/python_bridge.h +263 -0
  186. package/src/native/thread_pool.cpp +375 -0
  187. package/src/native/thread_pool.h +185 -0
  188. package/src/native/type_converter.cpp +906 -0
  189. package/src/native/type_converter.h +271 -0
  190. package/index.d.ts +0 -3
  191. package/index.js +0 -3
@@ -0,0 +1,218 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { createHash } from 'crypto';
4
+ import { spawnSync } from 'child_process';
5
+ import type { NativeAddon } from '../types/addon';
6
+
7
+ export interface NativeAddonBuildOptions {
8
+ projectRoot: string;
9
+ pythonExecutable: string;
10
+ pythonLibrary: string;
11
+ pythonVersion: string;
12
+ }
13
+
14
+ /**
15
+ * Load a compatible packaged prebuild or exact local addon, compiling through
16
+ * the active JavaScript runtime only when neither can be used.
17
+ */
18
+ export function loadOrBuildNativeAddon(options: NativeAddonBuildOptions): NativeAddon {
19
+ let initialError: unknown = new Error('Native rebuild explicitly requested.');
20
+ if (process.env['PYNECT_FORCE_BUILD'] !== '1') {
21
+ try {
22
+ return loadMatchingPrebuild(options);
23
+ } catch (error) {
24
+ initialError = error;
25
+ }
26
+ try {
27
+ return loadMatchingLocalAddon(options);
28
+ } catch (error) {
29
+ initialError = new AggregateError([initialError, error], 'No compatible native addon loaded.');
30
+ }
31
+ }
32
+
33
+ const packageRoot = path.resolve(__dirname, '..', '..');
34
+ const script = path.join(packageRoot, 'scripts', 'ensure-native.js');
35
+ const result = spawnSync(process.execPath, [script], {
36
+ cwd: packageRoot,
37
+ env: {
38
+ ...process.env,
39
+ PYNECT_PROJECT_ROOT: options.projectRoot,
40
+ PYNECT_PYTHON: options.pythonExecutable,
41
+ },
42
+ stdio: 'inherit',
43
+ windowsHide: true,
44
+ });
45
+ if (result.error) {
46
+ throw new Error(
47
+ `Could not launch the native builder with ${process.execPath}: ${result.error.message}`,
48
+ { cause: initialError },
49
+ );
50
+ }
51
+ if (result.status !== 0) {
52
+ throw new Error(
53
+ `Pynect could not load or build its native addon with ${process.execPath}. ` +
54
+ 'Install a C++ toolchain or provide a compatible prebuilt addon.',
55
+ { cause: initialError },
56
+ );
57
+ }
58
+
59
+ try {
60
+ return loadMatchingLocalAddon(options);
61
+ } catch (error) {
62
+ throw new Error('Pynect built the native addon, but the active runtime could not load it.', {
63
+ cause: error,
64
+ });
65
+ }
66
+ }
67
+
68
+ interface NativeBuildMetadata {
69
+ schemaVersion: number;
70
+ pythonExecutable: string;
71
+ buildType: 'Debug' | 'Release';
72
+ }
73
+
74
+ interface NativePrebuildMetadata {
75
+ schemaVersion: 1;
76
+ platform: NodeJS.Platform;
77
+ architecture: string;
78
+ pythonMajorMinor: string;
79
+ napi: number;
80
+ glibc?: string;
81
+ pythonLibraryName: string;
82
+ }
83
+
84
+ function loadMatchingPrebuild(options: NativeAddonBuildOptions): NativeAddon {
85
+ const packageRoot = path.resolve(__dirname, '..', '..');
86
+ const directory = path.join(packageRoot, 'prebuilds', `${process.platform}-${process.arch}`);
87
+ const metadataPath = path.join(directory, 'pynect-prebuild.json');
88
+ let metadata: NativePrebuildMetadata;
89
+ try {
90
+ metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8')) as NativePrebuildMetadata;
91
+ } catch (error) {
92
+ throw new Error(`Native prebuild metadata is unavailable at ${metadataPath}.`, { cause: error });
93
+ }
94
+ const pythonMajorMinor = options.pythonVersion.split('.').slice(0, 2).join('.');
95
+ if (
96
+ metadata.schemaVersion !== 1 ||
97
+ metadata.platform !== process.platform ||
98
+ metadata.architecture !== process.arch ||
99
+ metadata.pythonMajorMinor !== pythonMajorMinor ||
100
+ metadata.napi > Number(process.versions.napi ?? 0)
101
+ ) {
102
+ throw new Error(
103
+ `Native prebuild does not match ${process.platform}-${process.arch}, ` +
104
+ `Python ${pythonMajorMinor}, N-API ${process.versions.napi ?? 'unknown'}.`,
105
+ );
106
+ }
107
+ if (process.platform === 'linux' && metadata.glibc) {
108
+ const report = process.report?.getReport() as {
109
+ header?: { glibcVersionRuntime?: string };
110
+ } | undefined;
111
+ const runtimeGlibc = report?.header?.glibcVersionRuntime;
112
+ if (!runtimeGlibc || compareVersions(runtimeGlibc, metadata.glibc) < 0) {
113
+ throw new Error(
114
+ `Native prebuild requires glibc ${metadata.glibc}; runtime has ${runtimeGlibc ?? 'unknown'}.`,
115
+ );
116
+ }
117
+ }
118
+ const packagedAddon = path.join(directory, 'pynect_addon.node');
119
+ const addon = process.platform === 'win32'
120
+ ? packagedAddon
121
+ : materializePortablePrebuild(packagedAddon, metadata, options);
122
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
123
+ return require(addon) as NativeAddon;
124
+ }
125
+
126
+ function materializePortablePrebuild(
127
+ packagedAddon: string,
128
+ metadata: NativePrebuildMetadata,
129
+ options: NativeAddonBuildOptions,
130
+ ): string {
131
+ const identity = createHash('sha256')
132
+ .update(path.resolve(options.pythonLibrary))
133
+ .digest('hex')
134
+ .slice(0, 12);
135
+ const directory = path.join(
136
+ options.projectRoot,
137
+ 'node_modules',
138
+ '.pynect',
139
+ 'native',
140
+ `${process.platform}-${process.arch}-python${metadata.pythonMajorMinor}-${identity}`,
141
+ );
142
+ const addon = path.join(directory, 'pynect_addon.node');
143
+ const library = path.join(directory, metadata.pythonLibraryName);
144
+ fs.mkdirSync(directory, { recursive: true });
145
+ publishFile(packagedAddon, addon);
146
+ publishSymlink(options.pythonLibrary, library);
147
+ return addon;
148
+ }
149
+
150
+ function publishFile(source: string, destination: string): void {
151
+ if (fs.existsSync(destination)) return;
152
+ const temporary = `${destination}.${process.pid}.tmp`;
153
+ try {
154
+ fs.copyFileSync(source, temporary, fs.constants.COPYFILE_EXCL);
155
+ fs.renameSync(temporary, destination);
156
+ } catch (error) {
157
+ if (!fs.existsSync(destination)) throw error;
158
+ } finally {
159
+ fs.rmSync(temporary, { force: true });
160
+ }
161
+ }
162
+
163
+ function publishSymlink(source: string, destination: string): void {
164
+ if (fs.existsSync(destination)) return;
165
+ const temporary = `${destination}.${process.pid}.tmp`;
166
+ try {
167
+ fs.symlinkSync(path.resolve(source), temporary, 'file');
168
+ fs.renameSync(temporary, destination);
169
+ } catch (error) {
170
+ if (!fs.existsSync(destination)) throw error;
171
+ } finally {
172
+ fs.rmSync(temporary, { force: true });
173
+ }
174
+ }
175
+
176
+ function loadMatchingLocalAddon(options: NativeAddonBuildOptions): NativeAddon {
177
+ const packageRoot = path.resolve(__dirname, '..', '..');
178
+ const metadataPath = path.join(packageRoot, 'build', 'pynect-python.json');
179
+ let metadata: NativeBuildMetadata;
180
+ try {
181
+ metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8')) as NativeBuildMetadata;
182
+ } catch (error) {
183
+ throw new Error(`Native build metadata is unavailable at ${metadataPath}.`, { cause: error });
184
+ }
185
+ if (
186
+ metadata.schemaVersion !== 1 ||
187
+ normalizePath(metadata.pythonExecutable) !== normalizePath(options.pythonExecutable)
188
+ ) {
189
+ throw new Error(
190
+ `Native addon Python ${metadata.pythonExecutable} does not match selected Python ` +
191
+ `${options.pythonExecutable}.`,
192
+ );
193
+ }
194
+
195
+ const addonPath = path.join(
196
+ packageRoot,
197
+ 'build',
198
+ metadata.buildType === 'Debug' ? 'Debug' : 'Release',
199
+ 'pynect_addon.node',
200
+ );
201
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
202
+ return require(addonPath) as NativeAddon;
203
+ }
204
+
205
+ function normalizePath(value: string): string {
206
+ const resolved = path.resolve(value);
207
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
208
+ }
209
+
210
+ function compareVersions(left: string, right: string): number {
211
+ const leftParts = left.split('.').map(Number);
212
+ const rightParts = right.split('.').map(Number);
213
+ for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
214
+ const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
215
+ if (difference !== 0) return difference;
216
+ }
217
+ return 0;
218
+ }