nodepyx 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +399 -0
  3. package/binding.gyp +73 -0
  4. package/dist/core/PyCallable.d.ts +65 -0
  5. package/dist/core/PyCallable.d.ts.map +1 -0
  6. package/dist/core/PyCallable.js +109 -0
  7. package/dist/core/PyCallable.js.map +1 -0
  8. package/dist/core/PyContext.d.ts +76 -0
  9. package/dist/core/PyContext.d.ts.map +1 -0
  10. package/dist/core/PyContext.js +228 -0
  11. package/dist/core/PyContext.js.map +1 -0
  12. package/dist/core/PyIterator.d.ts +84 -0
  13. package/dist/core/PyIterator.d.ts.map +1 -0
  14. package/dist/core/PyIterator.js +243 -0
  15. package/dist/core/PyIterator.js.map +1 -0
  16. package/dist/core/PyModule.d.ts +55 -0
  17. package/dist/core/PyModule.d.ts.map +1 -0
  18. package/dist/core/PyModule.js +172 -0
  19. package/dist/core/PyModule.js.map +1 -0
  20. package/dist/core/PyProxy.d.ts +65 -0
  21. package/dist/core/PyProxy.d.ts.map +1 -0
  22. package/dist/core/PyProxy.js +483 -0
  23. package/dist/core/PyProxy.js.map +1 -0
  24. package/dist/core/PyRuntime.d.ts +105 -0
  25. package/dist/core/PyRuntime.d.ts.map +1 -0
  26. package/dist/core/PyRuntime.js +438 -0
  27. package/dist/core/PyRuntime.js.map +1 -0
  28. package/dist/env/CondaManager.d.ts +118 -0
  29. package/dist/env/CondaManager.d.ts.map +1 -0
  30. package/dist/env/CondaManager.js +401 -0
  31. package/dist/env/CondaManager.js.map +1 -0
  32. package/dist/env/PackageInstaller.d.ts +233 -0
  33. package/dist/env/PackageInstaller.d.ts.map +1 -0
  34. package/dist/env/PackageInstaller.js +609 -0
  35. package/dist/env/PackageInstaller.js.map +1 -0
  36. package/dist/env/PythonDetector.d.ts +103 -0
  37. package/dist/env/PythonDetector.d.ts.map +1 -0
  38. package/dist/env/PythonDetector.js +381 -0
  39. package/dist/env/PythonDetector.js.map +1 -0
  40. package/dist/env/VenvManager.d.ts +117 -0
  41. package/dist/env/VenvManager.d.ts.map +1 -0
  42. package/dist/env/VenvManager.js +331 -0
  43. package/dist/env/VenvManager.js.map +1 -0
  44. package/dist/index.d.ts +169 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +393 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/plugins/Plugin.interface.d.ts +41 -0
  49. package/dist/plugins/Plugin.interface.d.ts.map +1 -0
  50. package/dist/plugins/Plugin.interface.js +12 -0
  51. package/dist/plugins/Plugin.interface.js.map +1 -0
  52. package/dist/plugins/PluginManager.d.ts +26 -0
  53. package/dist/plugins/PluginManager.d.ts.map +1 -0
  54. package/dist/plugins/PluginManager.js +174 -0
  55. package/dist/plugins/PluginManager.js.map +1 -0
  56. package/dist/plugins/builtin/NumpyPlugin.d.ts +17 -0
  57. package/dist/plugins/builtin/NumpyPlugin.d.ts.map +1 -0
  58. package/dist/plugins/builtin/NumpyPlugin.js +41 -0
  59. package/dist/plugins/builtin/NumpyPlugin.js.map +1 -0
  60. package/dist/plugins/builtin/PandasPlugin.d.ts +19 -0
  61. package/dist/plugins/builtin/PandasPlugin.d.ts.map +1 -0
  62. package/dist/plugins/builtin/PandasPlugin.js +57 -0
  63. package/dist/plugins/builtin/PandasPlugin.js.map +1 -0
  64. package/dist/plugins/builtin/TorchPlugin.d.ts +23 -0
  65. package/dist/plugins/builtin/TorchPlugin.d.ts.map +1 -0
  66. package/dist/plugins/builtin/TorchPlugin.js +50 -0
  67. package/dist/plugins/builtin/TorchPlugin.js.map +1 -0
  68. package/dist/plugins/index.d.ts +7 -0
  69. package/dist/plugins/index.d.ts.map +1 -0
  70. package/dist/plugins/index.js +12 -0
  71. package/dist/plugins/index.js.map +1 -0
  72. package/dist/serialization/DataFrameBridge.d.ts +141 -0
  73. package/dist/serialization/DataFrameBridge.d.ts.map +1 -0
  74. package/dist/serialization/DataFrameBridge.js +355 -0
  75. package/dist/serialization/DataFrameBridge.js.map +1 -0
  76. package/dist/serialization/MsgPackSerializer.d.ts +45 -0
  77. package/dist/serialization/MsgPackSerializer.d.ts.map +1 -0
  78. package/dist/serialization/MsgPackSerializer.js +242 -0
  79. package/dist/serialization/MsgPackSerializer.js.map +1 -0
  80. package/dist/serialization/NumpyBridge.d.ts +96 -0
  81. package/dist/serialization/NumpyBridge.d.ts.map +1 -0
  82. package/dist/serialization/NumpyBridge.js +323 -0
  83. package/dist/serialization/NumpyBridge.js.map +1 -0
  84. package/dist/serialization/Serializer.d.ts +78 -0
  85. package/dist/serialization/Serializer.d.ts.map +1 -0
  86. package/dist/serialization/Serializer.js +281 -0
  87. package/dist/serialization/Serializer.js.map +1 -0
  88. package/dist/types/PythonTypeMapper.d.ts +87 -0
  89. package/dist/types/PythonTypeMapper.d.ts.map +1 -0
  90. package/dist/types/PythonTypeMapper.js +449 -0
  91. package/dist/types/PythonTypeMapper.js.map +1 -0
  92. package/dist/types/StubCache.d.ts +109 -0
  93. package/dist/types/StubCache.d.ts.map +1 -0
  94. package/dist/types/StubCache.js +333 -0
  95. package/dist/types/StubCache.js.map +1 -0
  96. package/dist/types/TypeGenerator.d.ts +139 -0
  97. package/dist/types/TypeGenerator.d.ts.map +1 -0
  98. package/dist/types/TypeGenerator.js +372 -0
  99. package/dist/types/TypeGenerator.js.map +1 -0
  100. package/dist/types/addon.d.ts +114 -0
  101. package/dist/types/addon.d.ts.map +1 -0
  102. package/dist/types/addon.js +32 -0
  103. package/dist/types/addon.js.map +1 -0
  104. package/dist/types/config.d.ts +175 -0
  105. package/dist/types/config.d.ts.map +1 -0
  106. package/dist/types/config.js +35 -0
  107. package/dist/types/config.js.map +1 -0
  108. package/dist/types/index.d.ts +10 -0
  109. package/dist/types/index.d.ts.map +1 -0
  110. package/dist/types/index.js +12 -0
  111. package/dist/types/index.js.map +1 -0
  112. package/dist/types/python.d.ts +235 -0
  113. package/dist/types/python.d.ts.map +1 -0
  114. package/dist/types/python.js +7 -0
  115. package/dist/types/python.js.map +1 -0
  116. package/dist/utils/ErrorTranslator.d.ts +83 -0
  117. package/dist/utils/ErrorTranslator.d.ts.map +1 -0
  118. package/dist/utils/ErrorTranslator.js +210 -0
  119. package/dist/utils/ErrorTranslator.js.map +1 -0
  120. package/dist/utils/Logger.d.ts +27 -0
  121. package/dist/utils/Logger.d.ts.map +1 -0
  122. package/dist/utils/Logger.js +115 -0
  123. package/dist/utils/Logger.js.map +1 -0
  124. package/dist/utils/MemoryMonitor.d.ts +44 -0
  125. package/dist/utils/MemoryMonitor.d.ts.map +1 -0
  126. package/dist/utils/MemoryMonitor.js +143 -0
  127. package/dist/utils/MemoryMonitor.js.map +1 -0
  128. package/package.json +177 -0
  129. package/python/error_handler.py +433 -0
  130. package/python/nodepyx_runtime.py +575 -0
  131. package/python/serializer.py +379 -0
  132. package/python/type_inspector.py +288 -0
  133. package/scripts/build-native.js +68 -0
  134. package/scripts/download-prebuilds.js +99 -0
  135. package/scripts/generate-stubs.js +405 -0
  136. package/scripts/install.js +260 -0
  137. package/src/core/PyCallable.ts +137 -0
  138. package/src/core/PyContext.ts +296 -0
  139. package/src/core/PyIterator.ts +294 -0
  140. package/src/core/PyModule.ts +194 -0
  141. package/src/core/PyProxy.ts +605 -0
  142. package/src/core/PyRuntime.ts +504 -0
  143. package/src/env/CondaManager.ts +451 -0
  144. package/src/env/PackageInstaller.ts +738 -0
  145. package/src/env/PythonDetector.ts +414 -0
  146. package/src/env/VenvManager.ts +396 -0
  147. package/src/index.ts +425 -0
  148. package/src/native/gil_guard.cpp +26 -0
  149. package/src/native/gil_guard.h +175 -0
  150. package/src/native/nodepyx_addon.cpp +886 -0
  151. package/src/native/python_bridge.cpp +790 -0
  152. package/src/native/python_bridge.h +257 -0
  153. package/src/native/thread_pool.cpp +336 -0
  154. package/src/native/thread_pool.h +175 -0
  155. package/src/native/type_converter.cpp +901 -0
  156. package/src/native/type_converter.h +272 -0
  157. package/src/nextjs/PyProvider.tsx +123 -0
  158. package/src/nextjs/index.ts +21 -0
  159. package/src/nextjs/usePython.ts +106 -0
  160. package/src/nextjs/withnodepyx.ts +88 -0
  161. package/src/plugins/Plugin.interface.ts +51 -0
  162. package/src/plugins/PluginManager.ts +155 -0
  163. package/src/plugins/builtin/NumpyPlugin.ts +36 -0
  164. package/src/plugins/builtin/PandasPlugin.ts +49 -0
  165. package/src/plugins/builtin/TorchPlugin.ts +56 -0
  166. package/src/plugins/index.ts +7 -0
  167. package/src/serialization/DataFrameBridge.ts +398 -0
  168. package/src/serialization/MsgPackSerializer.ts +220 -0
  169. package/src/serialization/NumpyBridge.ts +332 -0
  170. package/src/serialization/Serializer.ts +320 -0
  171. package/src/types/PythonTypeMapper.ts +495 -0
  172. package/src/types/StubCache.ts +340 -0
  173. package/src/types/TypeGenerator.ts +491 -0
  174. package/src/types/addon.ts +170 -0
  175. package/src/types/config.ts +226 -0
  176. package/src/types/index.ts +55 -0
  177. package/src/types/python.ts +309 -0
  178. package/src/types/stubs/numpy.d.ts +441 -0
  179. package/src/types/stubs/pandas.d.ts +575 -0
  180. package/src/types/stubs/sklearn.d.ts +728 -0
  181. package/src/types/stubs/torch.d.ts +694 -0
  182. package/src/utils/ErrorTranslator.ts +220 -0
  183. package/src/utils/Logger.ts +119 -0
  184. package/src/utils/MemoryMonitor.ts +175 -0
package/src/index.ts ADDED
@@ -0,0 +1,425 @@
1
+ /**
2
+ * nodepyx — Main Entry Point
3
+ *
4
+ * Run Python libraries from Node.js as if they were Native.
5
+ * Embeds CPython in-process via N-API addon with full TypeScript types,
6
+ * Proxy-based API, async/await support, and Next.js integration.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { init, py } from 'nodepyx';
11
+ *
12
+ * await init({
13
+ * virtualenv: {
14
+ * path: './.venv',
15
+ * packages: ['pandas', 'numpy'],
16
+ * autoInstall: true,
17
+ * },
18
+ * });
19
+ *
20
+ * const pd = await py.import('pandas');
21
+ * const df = await pd.read_csv('data.csv');
22
+ * const result = await df.describe().to_dict('records');
23
+ * ```
24
+ */
25
+
26
+ import { PyRuntime } from './core/PyRuntime';
27
+ import { Logger } from './utils/Logger';
28
+ import { nodepyxNotInitializedError } from './utils/ErrorTranslator';
29
+ import type { nodepyxConfig } from './types/config';
30
+ import type { PyProxyInterface } from './types/python';
31
+
32
+ // ─── Singleton Runtime Management ──────────────────────────────────────────
33
+
34
+ let _runtime: PyRuntime | null = null;
35
+ let _initializing = false;
36
+ let _initPromise: Promise<void> | null = null;
37
+
38
+ const _logger = new Logger('nodepyx');
39
+
40
+ // ─── Main py Object (Proxy over PyRuntime) ─────────────────────────────────
41
+
42
+ /**
43
+ * The main `py` object — a proxy over the PyRuntime singleton.
44
+ * Provides access to all PyRuntime methods after initialization.
45
+ *
46
+ * @throws {nodepyxNotInitializedError} if accessed before calling `init()`
47
+ */
48
+ export const py: PyRuntime = new Proxy({} as PyRuntime, {
49
+ get(_target, prop: string | symbol): unknown {
50
+ if (!_runtime) {
51
+ // Provide helpful error for common misuse
52
+ if (prop === 'then') {
53
+ // Prevent silent "awaiting py" mistakes
54
+ return undefined;
55
+ }
56
+ throw new nodepyxNotInitializedError(
57
+ `Cannot access py.${String(prop)} — nodepyx is not initialized. ` +
58
+ `Call 'await nodepyx.init()' before using 'py'.`
59
+ );
60
+ }
61
+ const value = (_runtime as unknown as Record<string | symbol, unknown>)[prop];
62
+ if (typeof value === 'function') {
63
+ return value.bind(_runtime);
64
+ }
65
+ return value;
66
+ },
67
+
68
+ set(_target, prop: string | symbol, value: unknown): boolean {
69
+ if (!_runtime) {
70
+ throw new nodepyxNotInitializedError('nodepyx is not initialized.');
71
+ }
72
+ (_runtime as unknown as Record<string | symbol, unknown>)[prop] = value;
73
+ return true;
74
+ },
75
+
76
+ has(_target, prop: string | symbol): boolean {
77
+ if (!_runtime) {return false;}
78
+ return prop in _runtime;
79
+ },
80
+ });
81
+
82
+ // ─── Lifecycle Functions ────────────────────────────────────────────────────
83
+
84
+ /**
85
+ * Initialize the nodepyx runtime.
86
+ * Must be called before using `py` or any other nodepyx API.
87
+ *
88
+ * Calling `init()` multiple times is safe — subsequent calls are no-ops
89
+ * unless `shutdown()` was called first, or `force: true` is passed.
90
+ *
91
+ * @param config - nodepyx configuration options
92
+ * @param options - Additional initialization options
93
+ * @returns Promise that resolves when Python is ready
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * await init({
98
+ * virtualenv: { path: '.venv', packages: ['pandas'], autoInstall: true },
99
+ * logLevel: 'info',
100
+ * });
101
+ * ```
102
+ */
103
+ export async function init(
104
+ config?: nodepyxConfig,
105
+ options?: { force?: boolean }
106
+ ): Promise<void> {
107
+ // Already initialized
108
+ if (_runtime && !options?.force) {
109
+ _logger.debug('nodepyx already initialized, skipping');
110
+ return;
111
+ }
112
+
113
+ // Initialization in progress — wait for it
114
+ if (_initializing && _initPromise) {
115
+ return _initPromise;
116
+ }
117
+
118
+ // Force re-init: shut down first
119
+ if (_runtime && options?.force) {
120
+ await shutdown();
121
+ }
122
+
123
+ _initializing = true;
124
+ _initPromise = _doInit(config);
125
+
126
+ try {
127
+ await _initPromise;
128
+ } finally {
129
+ _initializing = false;
130
+ _initPromise = null;
131
+ }
132
+ }
133
+
134
+ async function _doInit(config?: nodepyxConfig): Promise<void> {
135
+ _logger.info('Initializing nodepyx runtime...');
136
+ const startTime = performance.now();
137
+
138
+ try {
139
+ _runtime = await PyRuntime.create(config);
140
+ const elapsed = (performance.now() - startTime).toFixed(0);
141
+ _logger.success(`nodepyx runtime ready in ${elapsed}ms`);
142
+ } catch (err) {
143
+ _runtime = null;
144
+ _logger.error('Failed to initialize nodepyx runtime', err);
145
+ throw err;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Shut down the nodepyx runtime and release all Python resources.
151
+ * After calling this, `py` is no longer accessible until `init()` is called again.
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * await shutdown();
156
+ * ```
157
+ */
158
+ export async function shutdown(): Promise<void> {
159
+ if (!_runtime) {
160
+ _logger.debug('nodepyx not initialized, nothing to shut down');
161
+ return;
162
+ }
163
+
164
+ _logger.info('Shutting down nodepyx runtime...');
165
+ const runtime = _runtime;
166
+ _runtime = null;
167
+
168
+ try {
169
+ await runtime.shutdown();
170
+ _logger.success('nodepyx runtime shut down');
171
+ } catch (err) {
172
+ _logger.error('Error during nodepyx shutdown', err);
173
+ throw err;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Check if nodepyx is initialized and ready to use.
179
+ */
180
+ export function isInitialized(): boolean {
181
+ return _runtime !== null && _runtime.isRunning();
182
+ }
183
+
184
+ /**
185
+ * Get the current PyRuntime instance.
186
+ * @throws {nodepyxNotInitializedError} if not initialized
187
+ */
188
+ export function getRuntime(): PyRuntime {
189
+ if (!_runtime) {
190
+ throw new nodepyxNotInitializedError();
191
+ }
192
+ return _runtime;
193
+ }
194
+
195
+ // ─── Convenience API ────────────────────────────────────────────────────────
196
+
197
+ /**
198
+ * Import a Python module and return a Proxy.
199
+ * Shorthand for `py.import(moduleName)`.
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * const np = await importModule('numpy');
204
+ * const arr = await np.array([1, 2, 3]);
205
+ * ```
206
+ */
207
+ export async function importModule(moduleName: string): Promise<PyProxyInterface> {
208
+ if (!_runtime) {throw new nodepyxNotInitializedError();}
209
+ return _runtime.import(moduleName);
210
+ }
211
+
212
+ /**
213
+ * Evaluate a Python expression and return the result.
214
+ * Shorthand for `py.eval(expression)`.
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const result = await evalPython('1 + 2 + 3');
219
+ * // result === 6
220
+ * ```
221
+ */
222
+ export async function evalPython(expression: string): Promise<unknown> {
223
+ if (!_runtime) {throw new nodepyxNotInitializedError();}
224
+ return _runtime.eval(expression);
225
+ }
226
+
227
+ /**
228
+ * Execute Python code (statements, not expressions).
229
+ * Shorthand for `py.exec(code)`.
230
+ *
231
+ * @example
232
+ * ```typescript
233
+ * await execPython(`
234
+ * import sys
235
+ * print(f"Python {sys.version}")
236
+ * `);
237
+ * ```
238
+ */
239
+ export async function execPython(code: string): Promise<void> {
240
+ if (!_runtime) {throw new nodepyxNotInitializedError();}
241
+ return _runtime.exec(code);
242
+ }
243
+
244
+ /**
245
+ * Run a Python script file.
246
+ */
247
+ export async function runFile(filePath: string): Promise<unknown> {
248
+ if (!_runtime) {throw new nodepyxNotInitializedError();}
249
+ return _runtime.runFile(filePath);
250
+ }
251
+
252
+ /**
253
+ * Install Python packages into the configured virtualenv.
254
+ *
255
+ * @example
256
+ * ```typescript
257
+ * await installPackages(['pandas', 'numpy>=1.24.0', 'scikit-learn']);
258
+ * ```
259
+ */
260
+ export async function installPackages(packages: string[]): Promise<void> {
261
+ if (!_runtime) {throw new nodepyxNotInitializedError();}
262
+ return _runtime.installPackages(packages);
263
+ }
264
+
265
+ /**
266
+ * Get memory statistics for the Python heap.
267
+ */
268
+ export async function getMemoryStats(): Promise<{
269
+ pythonHeapBytes: number;
270
+ pythonHeapPeakBytes: number;
271
+ trackedObjects: number;
272
+ pendingCallbacks: number;
273
+ }> {
274
+ if (!_runtime) {throw new nodepyxNotInitializedError();}
275
+ return _runtime.getMemoryStats();
276
+ }
277
+
278
+ /**
279
+ * Trigger Python garbage collection.
280
+ */
281
+ export async function collectGarbage(): Promise<void> {
282
+ if (!_runtime) {throw new nodepyxNotInitializedError();}
283
+ return _runtime.collectGarbage();
284
+ }
285
+
286
+ // ─── Auto-cleanup on process exit ──────────────────────────────────────────
287
+
288
+ let _cleanupRegistered = false;
289
+
290
+ function _registerCleanup(): void {
291
+ if (_cleanupRegistered) {return;}
292
+ _cleanupRegistered = true;
293
+
294
+ const cleanup = async () => {
295
+ if (_runtime) {
296
+ try {
297
+ await shutdown();
298
+ } catch {
299
+ // Ignore errors during cleanup
300
+ }
301
+ }
302
+ };
303
+
304
+ process.on('exit', () => {
305
+ // Synchronous cleanup on exit (best-effort)
306
+ if (_runtime) {
307
+ try {
308
+ _runtime.shutdownSync();
309
+ } catch {
310
+ // ignore
311
+ }
312
+ }
313
+ });
314
+
315
+ process.on('SIGINT', async () => {
316
+ await cleanup();
317
+ process.exit(0);
318
+ });
319
+
320
+ process.on('SIGTERM', async () => {
321
+ await cleanup();
322
+ process.exit(0);
323
+ });
324
+
325
+ process.on('uncaughtException', async (err) => {
326
+ _logger.error('Uncaught exception, shutting down nodepyx', err);
327
+ await cleanup();
328
+ throw err;
329
+ });
330
+ }
331
+
332
+ // Register cleanup automatically
333
+ _registerCleanup();
334
+
335
+ // ─── Exports ───────────────────────────────────────────────────────────────
336
+
337
+ // Core
338
+ export { PyRuntime } from './core/PyRuntime';
339
+ export { PyProxy } from './core/PyProxy';
340
+ export { PyModule } from './core/PyModule';
341
+ export { PyCallable } from './core/PyCallable';
342
+ export { PyIterator } from './core/PyIterator';
343
+ export { PyContext } from './core/PyContext';
344
+
345
+ // Serialization
346
+ export { Serializer } from './serialization/Serializer';
347
+ export { NumpyBridge } from './serialization/NumpyBridge';
348
+ export { DataFrameBridge } from './serialization/DataFrameBridge';
349
+
350
+ // Error types
351
+ export {
352
+ PythonError,
353
+ PythonValueError,
354
+ PythonTypeError,
355
+ PythonKeyError,
356
+ PythonIndexError,
357
+ PythonAttributeError,
358
+ PythonImportError,
359
+ PythonModuleNotFoundError,
360
+ PythonFileNotFoundError,
361
+ PythonPermissionError,
362
+ PythonRuntimeError,
363
+ PythonMemoryError,
364
+ PythonStopIteration,
365
+ PythonTimeoutError,
366
+ nodepyxNotInitializedError,
367
+ nodepyxShutdownError,
368
+ translatePythonError,
369
+ isPythonError,
370
+ isPythonErrorOfType,
371
+ } from './utils/ErrorTranslator';
372
+
373
+ // Type generation
374
+ export { TypeGenerator } from './types/TypeGenerator';
375
+
376
+ // Environment management
377
+ export { PythonDetector } from './env/PythonDetector';
378
+ export { VenvManager } from './env/VenvManager';
379
+ export { CondaManager } from './env/CondaManager';
380
+ export { PackageInstaller } from './env/PackageInstaller';
381
+
382
+ // Plugins
383
+ export { PluginManager } from './plugins/PluginManager';
384
+
385
+ // Types
386
+ export type {
387
+ nodepyxConfig,
388
+ ResolvednodepyxConfig,
389
+ VirtualenvConfig,
390
+ CondaConfig,
391
+ LogLevel,
392
+ PyObjectId,
393
+ PyTypeInfo,
394
+ PyParamInfo,
395
+ PyCallableInfo,
396
+ PyClassInfo,
397
+ PyModuleInspection,
398
+ SerializedFormat,
399
+ SerializedValue,
400
+ PyCallResult,
401
+ PyErrorInfo,
402
+ DataFrameResult,
403
+ SeriesResult,
404
+ NumPyArrayResult,
405
+ PyProxyInterface,
406
+ AttributePath,
407
+ NativeAddon,
408
+ } from './types/index';
409
+
410
+ // Default export
411
+ export default {
412
+ init,
413
+ shutdown,
414
+ isInitialized,
415
+ getRuntime,
416
+ py,
417
+ import: importModule,
418
+ eval: evalPython,
419
+ exec: execPython,
420
+ runFile,
421
+ installPackages,
422
+ getMemoryStats,
423
+ collectGarbage,
424
+ };
425
+
@@ -0,0 +1,26 @@
1
+ /**
2
+ * nodepyx — GIL Guard Implementation
3
+ * The GIL guard is header-only; this file provides explicit template
4
+ * instantiations and any platform-specific workarounds.
5
+ */
6
+
7
+ #include "gil_guard.h"
8
+
9
+ // All implementation is in the header file (RAII classes are trivial inline).
10
+ // This .cpp exists to satisfy the build system and future non-inline additions.
11
+
12
+ namespace nodepyx {
13
+
14
+ // Verify at compile time that our RAII types are not accidentally copied
15
+ static_assert(!std::is_copy_constructible<GILAcquire>::value,
16
+ "GILAcquire must not be copy-constructible");
17
+ static_assert(!std::is_copy_constructible<GILRelease>::value,
18
+ "GILRelease must not be copy-constructible");
19
+ static_assert(!std::is_copy_constructible<PyRef>::value,
20
+ "PyRef must not be copy-constructible");
21
+
22
+ static_assert(std::is_move_constructible<PyRef>::value,
23
+ "PyRef must be move-constructible");
24
+
25
+ } // namespace nodepyx
26
+
@@ -0,0 +1,175 @@
1
+ /**
2
+ * nodepyx — GIL Guard Header
3
+ * RAII wrappers for the Python Global Interpreter Lock (GIL).
4
+ *
5
+ * The GIL ensures only one thread executes Python bytecode at a time.
6
+ * These guards make GIL management safe via RAII idiom.
7
+ */
8
+
9
+ #pragma once
10
+
11
+ #include <Python.h>
12
+ #include <stdexcept>
13
+
14
+ namespace nodepyx {
15
+
16
+ /**
17
+ * GILAcquire — RAII guard that acquires the GIL on construction
18
+ * and releases it on destruction.
19
+ *
20
+ * Use when Python code needs to run in a non-Python thread (e.g. a worker thread).
21
+ * The calling thread must NOT already hold the GIL.
22
+ *
23
+ * @example
24
+ * ```cpp
25
+ * void worker_thread_func() {
26
+ * GILAcquire gil; // GIL acquired
27
+ * PyObject* result = PyEval_EvalCode(...);
28
+ * // GIL released when gil goes out of scope
29
+ * }
30
+ * ```
31
+ */
32
+ class GILAcquire {
33
+ public:
34
+ GILAcquire() noexcept : _state(PyGILState_Ensure()) {}
35
+ ~GILAcquire() noexcept { PyGILState_Release(_state); }
36
+
37
+ // Non-copyable, non-movable
38
+ GILAcquire(const GILAcquire&) = delete;
39
+ GILAcquire& operator=(const GILAcquire&) = delete;
40
+ GILAcquire(GILAcquire&&) = delete;
41
+ GILAcquire& operator=(GILAcquire&&) = delete;
42
+
43
+ private:
44
+ PyGILState_STATE _state;
45
+ };
46
+
47
+ /**
48
+ * GILRelease — RAII guard that releases the GIL on construction
49
+ * and re-acquires it on destruction.
50
+ *
51
+ * Use to temporarily release the GIL while performing blocking I/O or
52
+ * other operations that don't need Python, allowing other Python threads to run.
53
+ * The calling thread MUST already hold the GIL.
54
+ *
55
+ * @example
56
+ * ```cpp
57
+ * // Inside Python-holding code:
58
+ * {
59
+ * GILRelease release; // GIL released — other Python threads can run
60
+ * perform_blocking_io();
61
+ * } // GIL re-acquired here
62
+ * ```
63
+ */
64
+ class GILRelease {
65
+ public:
66
+ GILRelease() noexcept : _state(PyEval_SaveThread()) {}
67
+ ~GILRelease() noexcept { PyEval_RestoreThread(_state); }
68
+
69
+ // Non-copyable, non-movable
70
+ GILRelease(const GILRelease&) = delete;
71
+ GILRelease& operator=(const GILRelease&) = delete;
72
+ GILRelease(GILRelease&&) = delete;
73
+ GILRelease& operator=(GILRelease&&) = delete;
74
+
75
+ private:
76
+ PyThreadState* _state;
77
+ };
78
+
79
+ /**
80
+ * PyRef — RAII wrapper for PyObject* reference counting.
81
+ *
82
+ * Automatically calls Py_XDECREF on destruction.
83
+ * Use steal() for objects returned from Python C API with new references.
84
+ * Use borrow() for borrowed references (won't decref).
85
+ */
86
+ class PyRef {
87
+ public:
88
+ PyRef() noexcept : _obj(nullptr), _owned(false) {}
89
+
90
+ ~PyRef() noexcept {
91
+ reset();
92
+ }
93
+
94
+ /** Take ownership of an object (new reference from Python API) */
95
+ static PyRef steal(PyObject* obj) noexcept {
96
+ PyRef ref;
97
+ ref._obj = obj;
98
+ ref._owned = true;
99
+ return ref;
100
+ }
101
+
102
+ /** Borrow a reference (will Py_INCREF and Py_DECREF) */
103
+ static PyRef borrow(PyObject* obj) noexcept {
104
+ PyRef ref;
105
+ ref._obj = obj;
106
+ ref._owned = false;
107
+ if (obj) {
108
+ Py_INCREF(obj);
109
+ ref._owned = true;
110
+ }
111
+ return ref;
112
+ }
113
+
114
+ /** Increment reference count and get raw pointer */
115
+ PyObject* incref() const noexcept {
116
+ Py_XINCREF(_obj);
117
+ return _obj;
118
+ }
119
+
120
+ /** Get raw pointer without changing reference count */
121
+ PyObject* get() const noexcept { return _obj; }
122
+
123
+ /** Release ownership without decrementing ref count */
124
+ PyObject* release() noexcept {
125
+ PyObject* obj = _obj;
126
+ _obj = nullptr;
127
+ _owned = false;
128
+ return obj;
129
+ }
130
+
131
+ /** Reset — decrements ref count if owned */
132
+ void reset() noexcept {
133
+ if (_owned && _obj) {
134
+ Py_DECREF(_obj);
135
+ }
136
+ _obj = nullptr;
137
+ _owned = false;
138
+ }
139
+
140
+ /** Check if the object is valid (non-null) */
141
+ explicit operator bool() const noexcept { return _obj != nullptr; }
142
+
143
+ /** Check for Python exception state */
144
+ bool hasException() const noexcept {
145
+ return _obj == nullptr && PyErr_Occurred() != nullptr;
146
+ }
147
+
148
+ // Non-copyable
149
+ PyRef(const PyRef&) = delete;
150
+ PyRef& operator=(const PyRef&) = delete;
151
+
152
+ // Movable
153
+ PyRef(PyRef&& other) noexcept : _obj(other._obj), _owned(other._owned) {
154
+ other._obj = nullptr;
155
+ other._owned = false;
156
+ }
157
+
158
+ PyRef& operator=(PyRef&& other) noexcept {
159
+ if (this != &other) {
160
+ reset();
161
+ _obj = other._obj;
162
+ _owned = other._owned;
163
+ other._obj = nullptr;
164
+ other._owned = false;
165
+ }
166
+ return *this;
167
+ }
168
+
169
+ private:
170
+ PyObject* _obj;
171
+ bool _owned;
172
+ };
173
+
174
+ } // namespace nodepyx
175
+