pynect 0.0.0-stage → 0.2.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.
- package/LICENSE +21 -0
- package/README.md +478 -2
- package/SECURITY.md +34 -0
- package/binding.gyp +60 -0
- package/dist/cli/codegen.d.ts +3 -0
- package/dist/cli/codegen.d.ts.map +1 -0
- package/dist/cli/codegen.js +543 -0
- package/dist/cli/codegen.js.map +1 -0
- package/dist/cli/sync.d.ts +3 -0
- package/dist/cli/sync.d.ts.map +1 -0
- package/dist/cli/sync.js +48 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/config/project.d.ts +57 -0
- package/dist/config/project.d.ts.map +1 -0
- package/dist/config/project.js +176 -0
- package/dist/config/project.js.map +1 -0
- package/dist/core/IsolatedWorker.d.ts +39 -0
- package/dist/core/IsolatedWorker.d.ts.map +1 -0
- package/dist/core/IsolatedWorker.js +178 -0
- package/dist/core/IsolatedWorker.js.map +1 -0
- package/dist/core/PyCallable.d.ts +67 -0
- package/dist/core/PyCallable.d.ts.map +1 -0
- package/dist/core/PyCallable.js +104 -0
- package/dist/core/PyCallable.js.map +1 -0
- package/dist/core/PyContext.d.ts +76 -0
- package/dist/core/PyContext.d.ts.map +1 -0
- package/dist/core/PyContext.js +239 -0
- package/dist/core/PyContext.js.map +1 -0
- package/dist/core/PyIterator.d.ts +84 -0
- package/dist/core/PyIterator.d.ts.map +1 -0
- package/dist/core/PyIterator.js +243 -0
- package/dist/core/PyIterator.js.map +1 -0
- package/dist/core/PyModule.d.ts +53 -0
- package/dist/core/PyModule.d.ts.map +1 -0
- package/dist/core/PyModule.js +162 -0
- package/dist/core/PyModule.js.map +1 -0
- package/dist/core/PyProxy.d.ts +75 -0
- package/dist/core/PyProxy.d.ts.map +1 -0
- package/dist/core/PyProxy.js +563 -0
- package/dist/core/PyProxy.js.map +1 -0
- package/dist/core/PyRuntime.d.ts +112 -0
- package/dist/core/PyRuntime.d.ts.map +1 -0
- package/dist/core/PyRuntime.js +546 -0
- package/dist/core/PyRuntime.js.map +1 -0
- package/dist/env/PackageInstaller.d.ts +232 -0
- package/dist/env/PackageInstaller.d.ts.map +1 -0
- package/dist/env/PackageInstaller.js +628 -0
- package/dist/env/PackageInstaller.js.map +1 -0
- package/dist/env/ProjectEnvironment.d.ts +63 -0
- package/dist/env/ProjectEnvironment.d.ts.map +1 -0
- package/dist/env/ProjectEnvironment.js +379 -0
- package/dist/env/ProjectEnvironment.js.map +1 -0
- package/dist/env/PyfigAdapter.d.ts +32 -0
- package/dist/env/PyfigAdapter.d.ts.map +1 -0
- package/dist/env/PyfigAdapter.js +10 -0
- package/dist/env/PyfigAdapter.js.map +1 -0
- package/dist/env/PythonDetector.d.ts +48 -0
- package/dist/env/PythonDetector.d.ts.map +1 -0
- package/dist/env/PythonDetector.js +182 -0
- package/dist/env/PythonDetector.js.map +1 -0
- package/dist/env/PythonProvisioner.d.ts +16 -0
- package/dist/env/PythonProvisioner.d.ts.map +1 -0
- package/dist/env/PythonProvisioner.js +42 -0
- package/dist/env/PythonProvisioner.js.map +1 -0
- package/dist/env/VenvManager.d.ts +55 -0
- package/dist/env/VenvManager.d.ts.map +1 -0
- package/dist/env/VenvManager.js +153 -0
- package/dist/env/VenvManager.js.map +1 -0
- package/dist/env/executables.d.ts +3 -0
- package/dist/env/executables.d.ts.map +1 -0
- package/dist/env/executables.js +14 -0
- package/dist/env/executables.js.map +1 -0
- package/dist/index.d.ts +182 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +393 -0
- package/dist/index.js.map +1 -0
- package/dist/native/NativeAddonLoader.d.ts +11 -0
- package/dist/native/NativeAddonLoader.d.ts.map +1 -0
- package/dist/native/NativeAddonLoader.js +75 -0
- package/dist/native/NativeAddonLoader.js.map +1 -0
- package/dist/plugins/Plugin.interface.d.ts +41 -0
- package/dist/plugins/Plugin.interface.d.ts.map +1 -0
- package/dist/plugins/Plugin.interface.js +12 -0
- package/dist/plugins/Plugin.interface.js.map +1 -0
- package/dist/plugins/PluginManager.d.ts +26 -0
- package/dist/plugins/PluginManager.d.ts.map +1 -0
- package/dist/plugins/PluginManager.js +174 -0
- package/dist/plugins/PluginManager.js.map +1 -0
- package/dist/plugins/builtin/NumpyPlugin.d.ts +17 -0
- package/dist/plugins/builtin/NumpyPlugin.d.ts.map +1 -0
- package/dist/plugins/builtin/NumpyPlugin.js +41 -0
- package/dist/plugins/builtin/NumpyPlugin.js.map +1 -0
- package/dist/plugins/builtin/PandasPlugin.d.ts +19 -0
- package/dist/plugins/builtin/PandasPlugin.d.ts.map +1 -0
- package/dist/plugins/builtin/PandasPlugin.js +57 -0
- package/dist/plugins/builtin/PandasPlugin.js.map +1 -0
- package/dist/plugins/builtin/TorchPlugin.d.ts +23 -0
- package/dist/plugins/builtin/TorchPlugin.d.ts.map +1 -0
- package/dist/plugins/builtin/TorchPlugin.js +50 -0
- package/dist/plugins/builtin/TorchPlugin.js.map +1 -0
- package/dist/plugins/index.d.ts +7 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +12 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/serialization/DataFrameBridge.d.ts +141 -0
- package/dist/serialization/DataFrameBridge.d.ts.map +1 -0
- package/dist/serialization/DataFrameBridge.js +355 -0
- package/dist/serialization/DataFrameBridge.js.map +1 -0
- package/dist/serialization/MsgPackSerializer.d.ts +45 -0
- package/dist/serialization/MsgPackSerializer.d.ts.map +1 -0
- package/dist/serialization/MsgPackSerializer.js +242 -0
- package/dist/serialization/MsgPackSerializer.js.map +1 -0
- package/dist/serialization/NumpyBridge.d.ts +96 -0
- package/dist/serialization/NumpyBridge.d.ts.map +1 -0
- package/dist/serialization/NumpyBridge.js +323 -0
- package/dist/serialization/NumpyBridge.js.map +1 -0
- package/dist/serialization/Serializer.d.ts +78 -0
- package/dist/serialization/Serializer.d.ts.map +1 -0
- package/dist/serialization/Serializer.js +281 -0
- package/dist/serialization/Serializer.js.map +1 -0
- package/dist/types/addon.d.ts +131 -0
- package/dist/types/addon.d.ts.map +1 -0
- package/dist/types/addon.js +32 -0
- package/dist/types/addon.js.map +1 -0
- package/dist/types/config.d.ts +233 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +60 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +12 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/python.d.ts +235 -0
- package/dist/types/python.d.ts.map +1 -0
- package/dist/types/python.js +7 -0
- package/dist/types/python.js.map +1 -0
- package/dist/utils/ErrorTranslator.d.ts +83 -0
- package/dist/utils/ErrorTranslator.d.ts.map +1 -0
- package/dist/utils/ErrorTranslator.js +210 -0
- package/dist/utils/ErrorTranslator.js.map +1 -0
- package/dist/utils/Locking.d.ts +3 -0
- package/dist/utils/Locking.d.ts.map +1 -0
- package/dist/utils/Locking.js +10 -0
- package/dist/utils/Locking.js.map +1 -0
- package/dist/utils/Logger.d.ts +27 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +115 -0
- package/dist/utils/Logger.js.map +1 -0
- package/dist/utils/MemoryMonitor.d.ts +43 -0
- package/dist/utils/MemoryMonitor.d.ts.map +1 -0
- package/dist/utils/MemoryMonitor.js +143 -0
- package/dist/utils/MemoryMonitor.js.map +1 -0
- package/package.json +114 -4
- package/python/ast_inspector.py +248 -0
- package/python/error_handler.py +432 -0
- package/python/isolated_worker.py +94 -0
- package/python/pynect_dispatcher.py +382 -0
- package/python/pynect_runtime.py +574 -0
- package/python/serializer.py +373 -0
- package/python/type_inspector.py +315 -0
- package/scripts/build-native.js +50 -0
- package/scripts/cli.js +21 -0
- package/scripts/ensure-native.js +85 -0
- package/scripts/postinstall.js +40 -0
- package/scripts/pyfig.js +113 -0
- package/scripts/python-provisioner.js +23 -0
- package/scripts/smoke-python-boundary.js +103 -0
- package/src/native/NativeAddonLoader.ts +98 -0
- package/src/native/gil_guard.cpp +25 -0
- package/src/native/gil_guard.h +174 -0
- package/src/native/pynect_addon.cpp +942 -0
- package/src/native/python_bridge.cpp +904 -0
- package/src/native/python_bridge.h +263 -0
- package/src/native/thread_pool.cpp +375 -0
- package/src/native/thread_pool.h +185 -0
- package/src/native/type_converter.cpp +906 -0
- package/src/native/type_converter.h +271 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pynect - PyRuntime
|
|
3
|
+
* The central manager for the Python interpreter lifecycle.
|
|
4
|
+
* Creates, configures, and shuts down the embedded CPython instance.
|
|
5
|
+
*/
|
|
6
|
+
import type { NativeAddon } from '../types/addon';
|
|
7
|
+
import type { PynectConfig, ResolvedPynectConfig } from '../types/config';
|
|
8
|
+
import { PyModule } from './PyModule';
|
|
9
|
+
import { PyContext } from './PyContext';
|
|
10
|
+
import { type IsolatedCallOptions } from './IsolatedWorker';
|
|
11
|
+
/**
|
|
12
|
+
* PyRuntime - manages the lifecycle of the embedded Python interpreter.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* const runtime = await PyRuntime.create({
|
|
17
|
+
* virtualenv: { path: '.venv', packages: ['pandas'], autoInstall: true },
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* const pd = await runtime.import('pandas');
|
|
21
|
+
* const df = await pd.read_csv('data.csv');
|
|
22
|
+
* const result = await df.to_dict('records');
|
|
23
|
+
*
|
|
24
|
+
* await runtime.shutdown();
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class PyRuntime {
|
|
28
|
+
private readonly _config;
|
|
29
|
+
private _pythonExecutable;
|
|
30
|
+
private readonly _pythonLibrary;
|
|
31
|
+
private readonly _addon;
|
|
32
|
+
private readonly _pluginManager;
|
|
33
|
+
private readonly _memoryMonitor;
|
|
34
|
+
private _isolatedWorker?;
|
|
35
|
+
private _running;
|
|
36
|
+
private constructor();
|
|
37
|
+
/**
|
|
38
|
+
* Create and initialize a PyRuntime instance.
|
|
39
|
+
*/
|
|
40
|
+
static create(config?: PynectConfig): Promise<PyRuntime>;
|
|
41
|
+
private _initialize;
|
|
42
|
+
private _setupPythonEnvironment;
|
|
43
|
+
private _bootstrapPythonRuntime;
|
|
44
|
+
/**
|
|
45
|
+
* Import a Python module and return a PyProxy.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const pd = await runtime.import('pandas');
|
|
50
|
+
* const df = await pd.read_csv('data.csv');
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
import(moduleName: string): Promise<PyModule>;
|
|
54
|
+
/**
|
|
55
|
+
* Import multiple modules at once (concurrent).
|
|
56
|
+
*/
|
|
57
|
+
importAll(moduleNames: string[]): Promise<Record<string, PyModule>>;
|
|
58
|
+
/**
|
|
59
|
+
* Call a JSON-compatible Python function in a killable child process.
|
|
60
|
+
* This is a process boundary, not an operating-system sandbox.
|
|
61
|
+
*/
|
|
62
|
+
callIsolated<T = unknown>(moduleName: string, functionName: string, args?: unknown[], options?: IsolatedCallOptions): Promise<T>;
|
|
63
|
+
/**
|
|
64
|
+
* Evaluate a Python expression.
|
|
65
|
+
*/
|
|
66
|
+
eval(expression: string): Promise<unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* Execute Python statements.
|
|
69
|
+
*/
|
|
70
|
+
exec(code: string): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Run a Python script file.
|
|
73
|
+
*/
|
|
74
|
+
runFile(filePath: string): Promise<unknown>;
|
|
75
|
+
/**
|
|
76
|
+
* Create an isolated Python execution context.
|
|
77
|
+
*/
|
|
78
|
+
createContext(): Promise<PyContext>;
|
|
79
|
+
/**
|
|
80
|
+
* Install Python packages into the active environment.
|
|
81
|
+
*/
|
|
82
|
+
installPackages(packages: string[]): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Get current memory statistics.
|
|
85
|
+
*/
|
|
86
|
+
getMemoryStats(): ReturnType<NativeAddon['getMemoryStats']>;
|
|
87
|
+
/**
|
|
88
|
+
* Trigger Python garbage collection.
|
|
89
|
+
*/
|
|
90
|
+
collectGarbage(): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Get the underlying native addon.
|
|
93
|
+
* Advanced use only.
|
|
94
|
+
*/
|
|
95
|
+
getAddon(): NativeAddon;
|
|
96
|
+
isRunning(): boolean;
|
|
97
|
+
getConfig(): ResolvedPynectConfig;
|
|
98
|
+
/**
|
|
99
|
+
* Gracefully shut down the Python runtime.
|
|
100
|
+
* Releases all Python objects and finalizes the interpreter.
|
|
101
|
+
*/
|
|
102
|
+
shutdown(): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Synchronous shutdown (for process.on('exit') handler).
|
|
105
|
+
* Best-effort only - may not complete all cleanup.
|
|
106
|
+
*/
|
|
107
|
+
shutdownSync(): void;
|
|
108
|
+
private _ensureRunning;
|
|
109
|
+
private _ensureUnsafeCodeAllowed;
|
|
110
|
+
private _parseBytes;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=PyRuntime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PyRuntime.d.ts","sourceRoot":"","sources":["../../src/core/PyRuntime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAG1E,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAkB,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAsB5E;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAS;IAEzB,OAAO;IAgBP;;OAEG;WACU,MAAM,CAAC,MAAM,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;YAuKpD,WAAW;YA2DX,uBAAuB;YAMvB,uBAAuB;IAkCrC;;;;;;;;OAQG;IACG,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA2CnD;;OAEG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAOzE;;;OAGG;IACG,YAAY,CAAC,CAAC,GAAG,OAAO,EAC5B,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,IAAI,GAAE,OAAO,EAAO,EACpB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,CAAC,CAAC;IAUb;;OAEG;IACG,IAAI,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4ChD;;OAEG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBvC;;OAEG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BjD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,SAAS,CAAC;IAQzC;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxD;;OAEG;IACH,cAAc,IAAI,UAAU,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAK3D;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAOrC;;;OAGG;IACH,QAAQ,IAAI,WAAW;IAOvB,SAAS,IAAI,OAAO;IAIpB,SAAS,IAAI,oBAAoB;IAMjC;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAwB/B;;;OAGG;IACH,YAAY,IAAI,IAAI;IAYpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,wBAAwB;IAQhC,OAAO,CAAC,WAAW;CAUpB"}
|
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* pynect - PyRuntime
|
|
4
|
+
* The central manager for the Python interpreter lifecycle.
|
|
5
|
+
* Creates, configures, and shuts down the embedded CPython instance.
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.PyRuntime = void 0;
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const project_1 = require("../config/project");
|
|
14
|
+
const config_1 = require("../types/config");
|
|
15
|
+
const PyProxy_1 = require("./PyProxy");
|
|
16
|
+
const PyModule_1 = require("./PyModule");
|
|
17
|
+
const PyContext_1 = require("./PyContext");
|
|
18
|
+
const IsolatedWorker_1 = require("./IsolatedWorker");
|
|
19
|
+
const PluginManager_1 = require("../plugins/PluginManager");
|
|
20
|
+
const PackageInstaller_1 = require("../env/PackageInstaller");
|
|
21
|
+
const PyfigAdapter_1 = require("../env/PyfigAdapter");
|
|
22
|
+
const ProjectEnvironment_1 = require("../env/ProjectEnvironment");
|
|
23
|
+
const NativeAddonLoader_1 = require("../native/NativeAddonLoader");
|
|
24
|
+
const MemoryMonitor_1 = require("../utils/MemoryMonitor");
|
|
25
|
+
const Logger_1 = require("../utils/Logger");
|
|
26
|
+
const ErrorTranslator_1 = require("../utils/ErrorTranslator");
|
|
27
|
+
const logger = new Logger_1.Logger('PyRuntime');
|
|
28
|
+
/**
|
|
29
|
+
* PyRuntime - manages the lifecycle of the embedded Python interpreter.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const runtime = await PyRuntime.create({
|
|
34
|
+
* virtualenv: { path: '.venv', packages: ['pandas'], autoInstall: true },
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* const pd = await runtime.import('pandas');
|
|
38
|
+
* const df = await pd.read_csv('data.csv');
|
|
39
|
+
* const result = await df.to_dict('records');
|
|
40
|
+
*
|
|
41
|
+
* await runtime.shutdown();
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
class PyRuntime {
|
|
45
|
+
_config;
|
|
46
|
+
_pythonExecutable;
|
|
47
|
+
_pythonLibrary;
|
|
48
|
+
_addon;
|
|
49
|
+
_pluginManager;
|
|
50
|
+
_memoryMonitor;
|
|
51
|
+
_isolatedWorker;
|
|
52
|
+
_running = false;
|
|
53
|
+
constructor(config, addon, pythonLibrary) {
|
|
54
|
+
this._config = config;
|
|
55
|
+
this._pythonExecutable = config.pythonExecutable;
|
|
56
|
+
this._pythonLibrary = pythonLibrary;
|
|
57
|
+
this._addon = addon;
|
|
58
|
+
this._pluginManager = new PluginManager_1.PluginManager();
|
|
59
|
+
this._memoryMonitor = new MemoryMonitor_1.MemoryMonitor({
|
|
60
|
+
gcThreshold: this._parseBytes(config.gcThreshold),
|
|
61
|
+
});
|
|
62
|
+
this._memoryMonitor.attach(addon);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create and initialize a PyRuntime instance.
|
|
66
|
+
*/
|
|
67
|
+
static async create(config = {}) {
|
|
68
|
+
const project = (0, project_1.resolveProjectConfig)({
|
|
69
|
+
projectRoot: config.projectRoot,
|
|
70
|
+
python: config.python ?? config.pythonPath,
|
|
71
|
+
requiresPython: config.requiresPython,
|
|
72
|
+
managedPython: config.managedPython,
|
|
73
|
+
uvVersion: config.uvVersion,
|
|
74
|
+
installPython: config.installPython ?? config.autoInstallPython,
|
|
75
|
+
installUv: config.installUv ?? config.autoInstallUv,
|
|
76
|
+
createVirtualEnvironment: config.createVirtualEnvironment ?? config.autoCreateVirtualEnvironment,
|
|
77
|
+
autoSync: config.autoSync,
|
|
78
|
+
lockfile: config.lockfile,
|
|
79
|
+
env: config.env,
|
|
80
|
+
});
|
|
81
|
+
const plan = (0, ProjectEnvironment_1.createProjectPlan)({
|
|
82
|
+
projectRoot: project.projectRoot,
|
|
83
|
+
python: project.python,
|
|
84
|
+
requiresPython: project.requiresPython,
|
|
85
|
+
managedPython: project.managedPython,
|
|
86
|
+
uvVersion: project.uvVersion,
|
|
87
|
+
installPython: project.installPython,
|
|
88
|
+
installUv: project.installUv,
|
|
89
|
+
createVirtualEnvironment: project.createVirtualEnvironment,
|
|
90
|
+
autoSync: project.autoSync,
|
|
91
|
+
lockfile: project.lockfile,
|
|
92
|
+
env: project.env,
|
|
93
|
+
});
|
|
94
|
+
let environmentState;
|
|
95
|
+
if ((0, ProjectEnvironment_1.requiresProjectSync)(plan)) {
|
|
96
|
+
if (project.autoSync === 'if-missing') {
|
|
97
|
+
try {
|
|
98
|
+
environmentState = (0, ProjectEnvironment_1.validateProjectEnvironment)(plan);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
environmentState = (await (0, ProjectEnvironment_1.syncProject)({
|
|
102
|
+
projectRoot: project.projectRoot,
|
|
103
|
+
python: project.python,
|
|
104
|
+
requiresPython: project.requiresPython,
|
|
105
|
+
managedPython: project.managedPython,
|
|
106
|
+
uvVersion: project.uvVersion,
|
|
107
|
+
installPython: project.installPython,
|
|
108
|
+
installUv: project.installUv,
|
|
109
|
+
createVirtualEnvironment: project.createVirtualEnvironment,
|
|
110
|
+
lockfile: project.lockfile,
|
|
111
|
+
env: project.env,
|
|
112
|
+
})).state;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
environmentState = (0, ProjectEnvironment_1.validateProjectEnvironment)(plan);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (config.virtualenv?.autoInstall && (config.virtualenv.packages?.length ?? 0) > 0) {
|
|
120
|
+
throw new Error('virtualenv.autoInstall is no longer supported. Declare Python dependencies in the ' +
|
|
121
|
+
'root package.json pynect.requirements field and run "pynect sync".');
|
|
122
|
+
}
|
|
123
|
+
// Resolve Python before loading the native addon. On Windows the addon
|
|
124
|
+
// depends on the selected Python DLL, so its directory must already be on
|
|
125
|
+
// PATH when Node loads the .node file.
|
|
126
|
+
const { python: selected } = await (0, PyfigAdapter_1.ensurePynectPython)({
|
|
127
|
+
autoInstallPython: project.installPython,
|
|
128
|
+
autoInstallUv: project.installUv,
|
|
129
|
+
autoCreateVirtualEnvironment: project.createVirtualEnvironment,
|
|
130
|
+
managedPython: project.managedPython,
|
|
131
|
+
uvVersion: project.uvVersion,
|
|
132
|
+
pythonRange: plan.pythonRange,
|
|
133
|
+
pythonPath: project.python,
|
|
134
|
+
projectRoot: project.projectRoot,
|
|
135
|
+
env: project.env,
|
|
136
|
+
});
|
|
137
|
+
if (environmentState) {
|
|
138
|
+
const selectedAbi = `${selected.implementation}-${selected.version}-` +
|
|
139
|
+
`${selected.architecture}-${selected.pointerBits}`;
|
|
140
|
+
if (!pathsEqual(environmentState.pythonExecutable, selected.executable) ||
|
|
141
|
+
environmentState.pythonAbi !== selectedAbi) {
|
|
142
|
+
throw new Error('Pynect environment Python changed since the last sync. Run "pynect sync" before ' +
|
|
143
|
+
'starting the application.');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const selectedProjectRoot = selected.virtualEnvironment
|
|
147
|
+
? path_1.default.dirname(selected.virtualEnvironment)
|
|
148
|
+
: config.projectRoot ?? process.cwd();
|
|
149
|
+
if (config.virtualenv?.path) {
|
|
150
|
+
const configuredVenv = path_1.default.resolve(config.virtualenv.path);
|
|
151
|
+
if (!selected.virtualEnvironment || !pathsEqual(configuredVenv, selected.virtualEnvironment)) {
|
|
152
|
+
throw new Error(`virtualenv.path must resolve to the repository .venv at ${selected.virtualEnvironment}.`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const preparedConfig = {
|
|
156
|
+
...config,
|
|
157
|
+
python: selected.executable,
|
|
158
|
+
requiresPython: plan.pythonRange,
|
|
159
|
+
projectRoot: selectedProjectRoot,
|
|
160
|
+
installPython: project.installPython,
|
|
161
|
+
installUv: project.installUv,
|
|
162
|
+
createVirtualEnvironment: project.createVirtualEnvironment,
|
|
163
|
+
managedPython: project.managedPython,
|
|
164
|
+
uvVersion: project.uvVersion,
|
|
165
|
+
autoSync: project.autoSync,
|
|
166
|
+
lockfile: project.lockfile,
|
|
167
|
+
env: project.env,
|
|
168
|
+
dispatcher: {
|
|
169
|
+
...config.dispatcher,
|
|
170
|
+
include: [
|
|
171
|
+
...(config.dispatcher?.include ?? []),
|
|
172
|
+
...(0, ProjectEnvironment_1.moduleDispatcherIncludes)(plan),
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
if (process.platform === 'win32') {
|
|
177
|
+
const requiredDirectories = [
|
|
178
|
+
selected.basePrefix,
|
|
179
|
+
selected.embedding.libraryDirectory,
|
|
180
|
+
path_1.default.dirname(selected.executable),
|
|
181
|
+
];
|
|
182
|
+
const entries = (process.env['PATH'] ?? '').split(path_1.default.delimiter);
|
|
183
|
+
for (const directory of requiredDirectories.reverse()) {
|
|
184
|
+
if (!entries.some((entry) => entry.toLowerCase() === directory.toLowerCase())) {
|
|
185
|
+
process.env['PATH'] = `${directory}${path_1.default.delimiter}${process.env['PATH'] ?? ''}`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const resolved = (0, config_1.resolveConfig)(preparedConfig);
|
|
190
|
+
// Set log level
|
|
191
|
+
Logger_1.Logger.setGlobalLevel(resolved.logLevel);
|
|
192
|
+
logger.info('Creating PyRuntime...');
|
|
193
|
+
// Load the native addon
|
|
194
|
+
let addon;
|
|
195
|
+
try {
|
|
196
|
+
addon = (0, NativeAddonLoader_1.loadOrBuildNativeAddon)({
|
|
197
|
+
projectRoot: selectedProjectRoot,
|
|
198
|
+
pythonExecutable: selected.executable,
|
|
199
|
+
});
|
|
200
|
+
logger.debug('Native addon loaded');
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
logger.error('Failed to load the Pynect native addon.', err);
|
|
204
|
+
throw new Error('Pynect native addon could not be loaded or built by the active JavaScript runtime.\n' +
|
|
205
|
+
`Original error: ${err.message}`);
|
|
206
|
+
}
|
|
207
|
+
const runtime = new PyRuntime(resolved, addon, selected.embedding.libraryPath);
|
|
208
|
+
try {
|
|
209
|
+
await runtime._initialize();
|
|
210
|
+
return runtime;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (addon.isInitialized()) {
|
|
214
|
+
await addon.finalizePython().catch(() => undefined);
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Initialization
|
|
220
|
+
async _initialize() {
|
|
221
|
+
const startTime = performance.now();
|
|
222
|
+
// 1. Detect/setup Python environment
|
|
223
|
+
const pythonExecutable = await this._setupPythonEnvironment();
|
|
224
|
+
this._pythonExecutable = pythonExecutable;
|
|
225
|
+
if (this._config.isolation.enabled) {
|
|
226
|
+
const packagedPython = path_1.default.join(__dirname, '..', '..', 'python');
|
|
227
|
+
this._isolatedWorker = new IsolatedWorker_1.IsolatedWorker({
|
|
228
|
+
pythonExecutable,
|
|
229
|
+
projectRoot: this._config.projectRoot,
|
|
230
|
+
scriptPath: path_1.default.join(packagedPython, 'isolated_worker.py'),
|
|
231
|
+
cwd: this._config.isolation.cwd,
|
|
232
|
+
timeout: this._config.isolation.timeout,
|
|
233
|
+
env: this._config.env,
|
|
234
|
+
pythonPath: [packagedPython, ...this._config.pythonPathExtra],
|
|
235
|
+
dispatcher: this._config.dispatcher,
|
|
236
|
+
includeTracebacks: this._config.includeTracebacks,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// 2. Initialize Python interpreter via C++ addon
|
|
240
|
+
logger.debug(`Initializing Python interpreter: ${pythonExecutable}`);
|
|
241
|
+
await this._addon.initializePython({
|
|
242
|
+
pythonHome: this._config.pythonHome || '',
|
|
243
|
+
pythonExecutable,
|
|
244
|
+
pythonLibrary: this._pythonLibrary,
|
|
245
|
+
threadPoolSize: this._config.threadPoolSize,
|
|
246
|
+
maxQueueSize: this._config.maxQueueSize,
|
|
247
|
+
gilReleaseInterval: this._config.gil.releaseInterval,
|
|
248
|
+
callTimeout: this._config.callTimeout,
|
|
249
|
+
pythonPathExtra: [
|
|
250
|
+
path_1.default.join(__dirname, '..', '..', 'python'),
|
|
251
|
+
...this._config.pythonPathExtra,
|
|
252
|
+
],
|
|
253
|
+
env: this._config.env,
|
|
254
|
+
enableProfiling: this._config.profilingEnabled,
|
|
255
|
+
includeTracebacks: this._config.includeTracebacks,
|
|
256
|
+
});
|
|
257
|
+
logger.debug('Python interpreter initialized');
|
|
258
|
+
// 3. Bootstrap Python runtime helpers
|
|
259
|
+
await this._bootstrapPythonRuntime();
|
|
260
|
+
// 4. Register built-in plugins
|
|
261
|
+
await this._pluginManager.registerBuiltins();
|
|
262
|
+
// 5. Start memory monitoring
|
|
263
|
+
this._memoryMonitor.startMonitoring(10000);
|
|
264
|
+
this._running = true;
|
|
265
|
+
const elapsed = (performance.now() - startTime).toFixed(0);
|
|
266
|
+
logger.success(`PyRuntime ready in ${elapsed}ms (Python: ${pythonExecutable})`);
|
|
267
|
+
}
|
|
268
|
+
async _setupPythonEnvironment() {
|
|
269
|
+
const pythonExecutable = this._config.pythonExecutable;
|
|
270
|
+
logger.info(`Using repository virtualenv: ${path_1.default.dirname(path_1.default.dirname(pythonExecutable))}`);
|
|
271
|
+
return pythonExecutable;
|
|
272
|
+
}
|
|
273
|
+
async _bootstrapPythonRuntime() {
|
|
274
|
+
try {
|
|
275
|
+
const dispatcherConfig = Buffer.from(JSON.stringify(this._config.dispatcher), 'utf8').toString('base64');
|
|
276
|
+
// Initialize pynect Python runtime helpers
|
|
277
|
+
const bootstrapCode = `
|
|
278
|
+
# Initialize optional upstream helpers. The packaged Python directory is
|
|
279
|
+
# already inserted into sys.path by the native initialization options.
|
|
280
|
+
try:
|
|
281
|
+
import pynect_runtime
|
|
282
|
+
pynect_runtime.initialize()
|
|
283
|
+
except ImportError:
|
|
284
|
+
pass # Runtime helpers not available, basic operation only
|
|
285
|
+
|
|
286
|
+
import base64 as _pynect_base64
|
|
287
|
+
import json as _pynect_json
|
|
288
|
+
import pynect_dispatcher
|
|
289
|
+
pynect_dispatcher.configure(
|
|
290
|
+
_pynect_json.loads(_pynect_base64.b64decode('${dispatcherConfig}'))
|
|
291
|
+
)
|
|
292
|
+
`;
|
|
293
|
+
await this._addon.runPythonCode(bootstrapCode);
|
|
294
|
+
logger.debug('Python runtime helpers bootstrapped');
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
logger.error('Failed to bootstrap the Python dispatcher', err);
|
|
298
|
+
throw err;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Module Import
|
|
302
|
+
/**
|
|
303
|
+
* Import a Python module and return a PyProxy.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```typescript
|
|
307
|
+
* const pd = await runtime.import('pandas');
|
|
308
|
+
* const df = await pd.read_csv('data.csv');
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
async import(moduleName) {
|
|
312
|
+
this._ensureRunning();
|
|
313
|
+
logger.debug(`Importing Python module: ${moduleName}`);
|
|
314
|
+
const result = await this._addon.importModule(moduleName);
|
|
315
|
+
if (!result.success) {
|
|
316
|
+
if (result.error) {
|
|
317
|
+
throw (0, ErrorTranslator_1.translatePythonError)({
|
|
318
|
+
type: result.error.type,
|
|
319
|
+
message: result.error.message,
|
|
320
|
+
traceback: result.error.traceback || '',
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
throw new Error(`Failed to import Python module '${moduleName}'`);
|
|
324
|
+
}
|
|
325
|
+
if (!result.objectId) {
|
|
326
|
+
throw new Error(`Module '${moduleName}' imported but no object ID returned`);
|
|
327
|
+
}
|
|
328
|
+
// Run plugin hooks
|
|
329
|
+
await this._pluginManager.onModuleImported(moduleName, result.objectId);
|
|
330
|
+
// Create PyModule
|
|
331
|
+
const module = PyModule_1.PyModule.fromRef(result.objectId, moduleName, this._addon, this._config.callTimeout, this._config.executionTimeout);
|
|
332
|
+
logger.debug(`Module '${moduleName}' imported (id=${result.objectId})`);
|
|
333
|
+
return module;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Import multiple modules at once (concurrent).
|
|
337
|
+
*/
|
|
338
|
+
async importAll(moduleNames) {
|
|
339
|
+
const modules = await Promise.all(moduleNames.map(async (name) => [name, await this.import(name)]));
|
|
340
|
+
return Object.fromEntries(modules);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Call a JSON-compatible Python function in a killable child process.
|
|
344
|
+
* This is a process boundary, not an operating-system sandbox.
|
|
345
|
+
*/
|
|
346
|
+
async callIsolated(moduleName, functionName, args = [], options = {}) {
|
|
347
|
+
this._ensureRunning();
|
|
348
|
+
if (!this._isolatedWorker) {
|
|
349
|
+
throw new Error('Isolated calls are disabled; initialize with isolation.enabled: true.');
|
|
350
|
+
}
|
|
351
|
+
return this._isolatedWorker.call(moduleName, functionName, args, options);
|
|
352
|
+
}
|
|
353
|
+
// Code Execution
|
|
354
|
+
/**
|
|
355
|
+
* Evaluate a Python expression.
|
|
356
|
+
*/
|
|
357
|
+
async eval(expression) {
|
|
358
|
+
this._ensureRunning();
|
|
359
|
+
this._ensureUnsafeCodeAllowed('eval');
|
|
360
|
+
const result = await this._addon.evalPython(expression);
|
|
361
|
+
if (!result.success) {
|
|
362
|
+
if (result.error) {
|
|
363
|
+
throw (0, ErrorTranslator_1.translatePythonError)({
|
|
364
|
+
type: result.error.type,
|
|
365
|
+
message: result.error.message,
|
|
366
|
+
traceback: result.error.traceback || '',
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
throw new Error('Python eval failed');
|
|
370
|
+
}
|
|
371
|
+
if (result.isObject && result.objectId) {
|
|
372
|
+
return PyProxy_1.PyProxy._createFromObjectId(result.objectId, [], this._addon, this._config.callTimeout, this._config.executionTimeout);
|
|
373
|
+
}
|
|
374
|
+
if (result.resultJson !== undefined) {
|
|
375
|
+
try {
|
|
376
|
+
return JSON.parse(result.resultJson);
|
|
377
|
+
}
|
|
378
|
+
catch {
|
|
379
|
+
return result.resultJson;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Execute Python statements.
|
|
386
|
+
*/
|
|
387
|
+
async exec(code) {
|
|
388
|
+
this._ensureRunning();
|
|
389
|
+
this._ensureUnsafeCodeAllowed('exec');
|
|
390
|
+
const result = await this._addon.runPythonCode(code);
|
|
391
|
+
if (!result.success && result.error) {
|
|
392
|
+
throw (0, ErrorTranslator_1.translatePythonError)({
|
|
393
|
+
type: result.error.type,
|
|
394
|
+
message: result.error.message,
|
|
395
|
+
traceback: result.error.traceback || '',
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Run a Python script file.
|
|
401
|
+
*/
|
|
402
|
+
async runFile(filePath) {
|
|
403
|
+
this._ensureRunning();
|
|
404
|
+
this._ensureUnsafeCodeAllowed('runFile');
|
|
405
|
+
const result = await this._addon.runPythonFile(filePath);
|
|
406
|
+
if (!result.success) {
|
|
407
|
+
if (result.error) {
|
|
408
|
+
throw (0, ErrorTranslator_1.translatePythonError)({
|
|
409
|
+
type: result.error.type,
|
|
410
|
+
message: result.error.message,
|
|
411
|
+
traceback: result.error.traceback || '',
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
throw new Error('Failed to run Python file');
|
|
415
|
+
}
|
|
416
|
+
return null;
|
|
417
|
+
}
|
|
418
|
+
// Context Management
|
|
419
|
+
/**
|
|
420
|
+
* Create an isolated Python execution context.
|
|
421
|
+
*/
|
|
422
|
+
async createContext() {
|
|
423
|
+
this._ensureRunning();
|
|
424
|
+
this._ensureUnsafeCodeAllowed('createContext');
|
|
425
|
+
return PyContext_1.PyContext.create(this._addon);
|
|
426
|
+
}
|
|
427
|
+
// Package Management
|
|
428
|
+
/**
|
|
429
|
+
* Install Python packages into the active environment.
|
|
430
|
+
*/
|
|
431
|
+
async installPackages(packages) {
|
|
432
|
+
this._ensureRunning();
|
|
433
|
+
if (!this._config.allowPackageInstall) {
|
|
434
|
+
throw new Error('Runtime package installation is disabled; set allowPackageInstall: true.');
|
|
435
|
+
}
|
|
436
|
+
const installer = new PackageInstaller_1.PackageInstaller({
|
|
437
|
+
pythonExecutable: this._pythonExecutable,
|
|
438
|
+
cwd: this._config.projectRoot,
|
|
439
|
+
});
|
|
440
|
+
await installer.install(packages);
|
|
441
|
+
}
|
|
442
|
+
// Memory Management
|
|
443
|
+
/**
|
|
444
|
+
* Get current memory statistics.
|
|
445
|
+
*/
|
|
446
|
+
getMemoryStats() {
|
|
447
|
+
this._ensureRunning();
|
|
448
|
+
return this._addon.getMemoryStats();
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Trigger Python garbage collection.
|
|
452
|
+
*/
|
|
453
|
+
async collectGarbage() {
|
|
454
|
+
this._ensureRunning();
|
|
455
|
+
await this._addon.collectGarbage();
|
|
456
|
+
}
|
|
457
|
+
// Direct Addon Access
|
|
458
|
+
/**
|
|
459
|
+
* Get the underlying native addon.
|
|
460
|
+
* Advanced use only.
|
|
461
|
+
*/
|
|
462
|
+
getAddon() {
|
|
463
|
+
this._ensureUnsafeCodeAllowed('getAddon');
|
|
464
|
+
return this._addon;
|
|
465
|
+
}
|
|
466
|
+
// State
|
|
467
|
+
isRunning() {
|
|
468
|
+
return this._running;
|
|
469
|
+
}
|
|
470
|
+
getConfig() {
|
|
471
|
+
return { ...this._config };
|
|
472
|
+
}
|
|
473
|
+
// Shutdown
|
|
474
|
+
/**
|
|
475
|
+
* Gracefully shut down the Python runtime.
|
|
476
|
+
* Releases all Python objects and finalizes the interpreter.
|
|
477
|
+
*/
|
|
478
|
+
async shutdown() {
|
|
479
|
+
if (!this._running) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
logger.info('Shutting down PyRuntime...');
|
|
483
|
+
// Stop memory monitoring
|
|
484
|
+
this._memoryMonitor.stopMonitoring();
|
|
485
|
+
await this._isolatedWorker?.close();
|
|
486
|
+
this._isolatedWorker = undefined;
|
|
487
|
+
// Finalize Python interpreter
|
|
488
|
+
try {
|
|
489
|
+
await this._addon.runPythonCode('import pynect_dispatcher as _d; _d.shutdown()');
|
|
490
|
+
await this._addon.finalizePython();
|
|
491
|
+
this._running = false;
|
|
492
|
+
logger.success('PyRuntime shut down');
|
|
493
|
+
}
|
|
494
|
+
catch (err) {
|
|
495
|
+
logger.error('Error during Python finalization', err);
|
|
496
|
+
throw err;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Synchronous shutdown (for process.on('exit') handler).
|
|
501
|
+
* Best-effort only - may not complete all cleanup.
|
|
502
|
+
*/
|
|
503
|
+
shutdownSync() {
|
|
504
|
+
if (!this._running) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
this._running = false;
|
|
508
|
+
this._memoryMonitor.stopMonitoring();
|
|
509
|
+
this._isolatedWorker?.closeSync();
|
|
510
|
+
this._isolatedWorker = undefined;
|
|
511
|
+
// Cannot await here - best effort
|
|
512
|
+
logger.debug('Synchronous shutdown initiated');
|
|
513
|
+
}
|
|
514
|
+
// Private Helpers
|
|
515
|
+
_ensureRunning() {
|
|
516
|
+
if (!this._running) {
|
|
517
|
+
throw new ErrorTranslator_1.PynectShutdownError('PyRuntime has been shut down. Create a new runtime with PyRuntime.create().');
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
_ensureUnsafeCodeAllowed(operation) {
|
|
521
|
+
if (!this._config.allowUnsafeCode) {
|
|
522
|
+
throw new Error(`${operation} is disabled by default; set allowUnsafeCode: true for trusted local code.`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
_parseBytes(str) {
|
|
526
|
+
const match = str.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB)?$/i);
|
|
527
|
+
if (!match) {
|
|
528
|
+
return 1_073_741_824;
|
|
529
|
+
}
|
|
530
|
+
const value = parseFloat(match[1]);
|
|
531
|
+
const unit = (match[2] ?? 'B').toUpperCase();
|
|
532
|
+
const units = {
|
|
533
|
+
B: 1, KB: 1024, MB: 1024 ** 2, GB: 1024 ** 3, TB: 1024 ** 4,
|
|
534
|
+
};
|
|
535
|
+
return Math.floor(value * (units[unit] ?? 1));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
exports.PyRuntime = PyRuntime;
|
|
539
|
+
function pathsEqual(left, right) {
|
|
540
|
+
const normalize = (value) => {
|
|
541
|
+
const resolved = path_1.default.resolve(value);
|
|
542
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
543
|
+
};
|
|
544
|
+
return normalize(left) === normalize(right);
|
|
545
|
+
}
|
|
546
|
+
//# sourceMappingURL=PyRuntime.js.map
|