pynect 0.2.5 → 0.2.7
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/README.md +3 -3
- package/binding.gyp +2 -1
- package/dist/config/project.d.ts +1 -0
- package/dist/config/project.d.ts.map +1 -1
- package/dist/config/project.js +3 -2
- package/dist/config/project.js.map +1 -1
- package/dist/core/py-runtime.d.ts +3 -0
- package/dist/core/py-runtime.d.ts.map +1 -1
- package/dist/core/py-runtime.js +20 -3
- package/dist/core/py-runtime.js.map +1 -1
- package/dist/env/project-environment.d.ts.map +1 -1
- package/dist/env/project-environment.js +1 -0
- package/dist/env/project-environment.js.map +1 -1
- package/dist/native/native-addon-loader.js +19 -5
- package/dist/native/native-addon-loader.js.map +1 -1
- package/dist/types/addon.d.ts +4 -0
- package/dist/types/addon.d.ts.map +1 -1
- package/dist/types/addon.js.map +1 -1
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/pynect-prebuild.json +5 -4
- package/prebuilds/darwin-arm64/pynect_addon.node +0 -0
- package/prebuilds/darwin-x64/pynect-prebuild.json +5 -4
- package/prebuilds/darwin-x64/pynect_addon.node +0 -0
- package/prebuilds/linux-arm64/pynect-prebuild.json +5 -4
- package/prebuilds/linux-arm64/pynect_addon.node +0 -0
- package/prebuilds/linux-x64/pynect-prebuild.json +5 -4
- package/prebuilds/linux-x64/pynect_addon.node +0 -0
- package/prebuilds/win32-arm64/pynect-prebuild.json +5 -4
- package/prebuilds/win32-arm64/pynect_addon.node +0 -0
- package/prebuilds/win32-x64/pynect-prebuild.json +5 -4
- package/prebuilds/win32-x64/pynect_addon.node +0 -0
- package/scripts/build-native.js +9 -3
- package/scripts/ensure-native.js +8 -2
- package/scripts/smoke-python-boundary.js +1 -1
- package/src/native/native-addon-loader.ts +26 -7
- package/src/native/pynect_addon.cpp +8 -0
- package/src/native/python_bridge.cpp +105 -40
- package/src/native/python_bridge.h +6 -0
- package/src/native/type_converter.cpp +32 -22
|
Binary file
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 2,
|
|
3
3
|
"platform": "linux",
|
|
4
4
|
"architecture": "arm64",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"pythonMinimumVersion": "3.10",
|
|
6
|
+
"pythonAbi": "cp310-abi3",
|
|
7
|
+
"pythonLibraryName": "libpython3.so",
|
|
7
8
|
"napi": 8,
|
|
8
|
-
"nativeFingerprint": "sha256:
|
|
9
|
+
"nativeFingerprint": "sha256:e375cc859a1ef9a6e8e72f182886c847c2489e9ed5a6017f08d43dd01425bd52",
|
|
9
10
|
"glibc": "2.39"
|
|
10
11
|
}
|
|
Binary file
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 2,
|
|
3
3
|
"platform": "linux",
|
|
4
4
|
"architecture": "x64",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"pythonMinimumVersion": "3.10",
|
|
6
|
+
"pythonAbi": "cp310-abi3",
|
|
7
|
+
"pythonLibraryName": "libpython3.so",
|
|
7
8
|
"napi": 8,
|
|
8
|
-
"nativeFingerprint": "sha256:
|
|
9
|
+
"nativeFingerprint": "sha256:e375cc859a1ef9a6e8e72f182886c847c2489e9ed5a6017f08d43dd01425bd52",
|
|
9
10
|
"glibc": "2.35"
|
|
10
11
|
}
|
|
Binary file
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 2,
|
|
3
3
|
"platform": "win32",
|
|
4
4
|
"architecture": "arm64",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"pythonMinimumVersion": "3.10",
|
|
6
|
+
"pythonAbi": "cp310-abi3",
|
|
7
|
+
"pythonLibraryName": "python3.dll",
|
|
7
8
|
"napi": 8,
|
|
8
|
-
"nativeFingerprint": "sha256:
|
|
9
|
+
"nativeFingerprint": "sha256:e375cc859a1ef9a6e8e72f182886c847c2489e9ed5a6017f08d43dd01425bd52"
|
|
9
10
|
}
|
|
Binary file
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schemaVersion":
|
|
2
|
+
"schemaVersion": 2,
|
|
3
3
|
"platform": "win32",
|
|
4
4
|
"architecture": "x64",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
5
|
+
"pythonMinimumVersion": "3.10",
|
|
6
|
+
"pythonAbi": "cp310-abi3",
|
|
7
|
+
"pythonLibraryName": "python3.dll",
|
|
7
8
|
"napi": 8,
|
|
8
|
-
"nativeFingerprint": "sha256:
|
|
9
|
+
"nativeFingerprint": "sha256:e375cc859a1ef9a6e8e72f182886c847c2489e9ed5a6017f08d43dd01425bd52"
|
|
9
10
|
}
|
|
Binary file
|
package/scripts/build-native.js
CHANGED
|
@@ -14,6 +14,12 @@ async function main() {
|
|
|
14
14
|
pythonPath: process.env.PYNECT_PYTHON,
|
|
15
15
|
});
|
|
16
16
|
const embedding = python.embedding;
|
|
17
|
+
const stableAbiLibrary = process.platform === 'win32'
|
|
18
|
+
? embedding.stableAbiLibraryPath || path.join(embedding.libraryDirectory, 'python3.lib')
|
|
19
|
+
: '';
|
|
20
|
+
const linkLibrary = stableAbiLibrary && fs.existsSync(stableAbiLibrary)
|
|
21
|
+
? stableAbiLibrary
|
|
22
|
+
: embedding.libraryPath;
|
|
17
23
|
const nodeGyp = require.resolve('node-gyp/bin/node-gyp.js');
|
|
18
24
|
const mode = process.argv.includes('--debug') ? '--debug' : '--release';
|
|
19
25
|
const args = [nodeGyp, 'rebuild', mode];
|
|
@@ -24,8 +30,8 @@ async function main() {
|
|
|
24
30
|
npm_config_python: python.executable,
|
|
25
31
|
PYTHON: python.executable,
|
|
26
32
|
PYNECT_PYTHON_INCLUDE_DIR: embedding.includeDirectory,
|
|
27
|
-
PYNECT_PYTHON_LIBRARY:
|
|
28
|
-
PYNECT_PYTHON_LIBRARY_DIR:
|
|
33
|
+
PYNECT_PYTHON_LIBRARY: linkLibrary,
|
|
34
|
+
PYNECT_PYTHON_LIBRARY_DIR: path.dirname(linkLibrary),
|
|
29
35
|
PATH: [embedding.libraryDirectory, path.dirname(python.executable), process.env.PATH || '']
|
|
30
36
|
.filter(Boolean)
|
|
31
37
|
.join(path.delimiter),
|
|
@@ -40,7 +46,7 @@ async function main() {
|
|
|
40
46
|
schemaVersion: 1,
|
|
41
47
|
pythonExecutable: path.resolve(python.executable),
|
|
42
48
|
pythonVersion: python.version,
|
|
43
|
-
pythonLibrary: path.resolve(
|
|
49
|
+
pythonLibrary: path.resolve(linkLibrary),
|
|
44
50
|
buildType: mode === '--debug' ? 'Debug' : 'Release',
|
|
45
51
|
}, null, 2)}\n`,
|
|
46
52
|
);
|
package/scripts/ensure-native.js
CHANGED
|
@@ -7,6 +7,7 @@ const { spawnSync } = require('child_process');
|
|
|
7
7
|
const ROOT = path.resolve(__dirname, '..');
|
|
8
8
|
const BUILD_DIRECTORY = path.join(ROOT, 'build');
|
|
9
9
|
const METADATA = path.join(BUILD_DIRECTORY, 'pynect-python.json');
|
|
10
|
+
let lastLoadError;
|
|
10
11
|
|
|
11
12
|
function normalize(value) {
|
|
12
13
|
const resolved = path.resolve(value);
|
|
@@ -33,12 +34,14 @@ function matchingAddonPath() {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
function loads() {
|
|
37
|
+
lastLoadError = undefined;
|
|
36
38
|
const addon = matchingAddonPath();
|
|
37
39
|
if (!addon || !fs.existsSync(addon)) return false;
|
|
38
40
|
try {
|
|
39
41
|
require(addon);
|
|
40
42
|
return true;
|
|
41
|
-
} catch {
|
|
43
|
+
} catch (error) {
|
|
44
|
+
lastLoadError = error;
|
|
42
45
|
return false;
|
|
43
46
|
}
|
|
44
47
|
}
|
|
@@ -55,7 +58,10 @@ function build() {
|
|
|
55
58
|
if (result.status !== 0) {
|
|
56
59
|
throw new Error(`Native addon build failed with exit code ${result.status}.`);
|
|
57
60
|
}
|
|
58
|
-
if (!loads())
|
|
61
|
+
if (!loads()) {
|
|
62
|
+
const detail = lastLoadError instanceof Error ? ` ${lastLoadError.message}` : '';
|
|
63
|
+
throw new Error(`Native addon did not load after it was built.${detail}`);
|
|
64
|
+
}
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
async function main() {
|
|
@@ -77,7 +77,7 @@ async function main() {
|
|
|
77
77
|
await pynect.shutdown();
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
console.log(`Python boundary verified: ${python.executable}`);
|
|
80
|
+
console.log(`Python ${python.version} boundary verified: ${python.executable}`);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
function assertSuccess(result, label) {
|
|
@@ -72,10 +72,11 @@ interface NativeBuildMetadata {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
interface NativePrebuildMetadata {
|
|
75
|
-
schemaVersion:
|
|
75
|
+
schemaVersion: 2;
|
|
76
76
|
platform: NodeJS.Platform;
|
|
77
77
|
architecture: string;
|
|
78
|
-
|
|
78
|
+
pythonMinimumVersion: string;
|
|
79
|
+
pythonAbi: string;
|
|
79
80
|
napi: number;
|
|
80
81
|
glibc?: string;
|
|
81
82
|
pythonLibraryName: string;
|
|
@@ -91,17 +92,22 @@ function loadMatchingPrebuild(options: NativeAddonBuildOptions): NativeAddon {
|
|
|
91
92
|
} catch (error) {
|
|
92
93
|
throw new Error(`Native prebuild metadata is unavailable at ${metadataPath}.`, { cause: error });
|
|
93
94
|
}
|
|
94
|
-
const
|
|
95
|
+
const pythonVersion = majorMinor(options.pythonVersion);
|
|
96
|
+
const minimumVersion = typeof metadata.pythonMinimumVersion === 'string'
|
|
97
|
+
? majorMinor(metadata.pythonMinimumVersion)
|
|
98
|
+
: [Number.POSITIVE_INFINITY, 0] as const;
|
|
95
99
|
if (
|
|
96
|
-
metadata.schemaVersion !==
|
|
100
|
+
metadata.schemaVersion !== 2 ||
|
|
97
101
|
metadata.platform !== process.platform ||
|
|
98
102
|
metadata.architecture !== process.arch ||
|
|
99
|
-
metadata.
|
|
103
|
+
metadata.pythonAbi !== 'cp310-abi3' ||
|
|
104
|
+
pythonVersion[0] !== 3 ||
|
|
105
|
+
compareMajorMinor(pythonVersion, minimumVersion) < 0 ||
|
|
100
106
|
metadata.napi > Number(process.versions.napi ?? 0)
|
|
101
107
|
) {
|
|
102
108
|
throw new Error(
|
|
103
109
|
`Native prebuild does not match ${process.platform}-${process.arch}, ` +
|
|
104
|
-
`Python ${
|
|
110
|
+
`Python ${options.pythonVersion}, N-API ${process.versions.napi ?? 'unknown'}.`,
|
|
105
111
|
);
|
|
106
112
|
}
|
|
107
113
|
if (process.platform === 'linux' && metadata.glibc) {
|
|
@@ -137,7 +143,7 @@ function materializePortablePrebuild(
|
|
|
137
143
|
'node_modules',
|
|
138
144
|
'.pynect',
|
|
139
145
|
'native',
|
|
140
|
-
`${process.platform}-${process.arch}
|
|
146
|
+
`${process.platform}-${process.arch}-${metadata.pythonAbi}-${identity}`,
|
|
141
147
|
);
|
|
142
148
|
const addon = path.join(directory, 'pynect_addon.node');
|
|
143
149
|
const library = path.join(directory, metadata.pythonLibraryName);
|
|
@@ -216,3 +222,16 @@ function compareVersions(left: string, right: string): number {
|
|
|
216
222
|
}
|
|
217
223
|
return 0;
|
|
218
224
|
}
|
|
225
|
+
|
|
226
|
+
function majorMinor(version: string): readonly [number, number] {
|
|
227
|
+
const match = version.match(/^(\d+)\.(\d+)/u);
|
|
228
|
+
if (!match) throw new Error(`Invalid Python version: ${version}`);
|
|
229
|
+
return [Number(match[1]), Number(match[2])];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function compareMajorMinor(
|
|
233
|
+
left: readonly [number, number],
|
|
234
|
+
right: readonly [number, number],
|
|
235
|
+
): number {
|
|
236
|
+
return left[0] - right[0] || left[1] - right[1];
|
|
237
|
+
}
|
|
@@ -141,6 +141,12 @@ static Napi::Value InitializePython(const Napi::CallbackInfo& info) {
|
|
|
141
141
|
std::string pythonExecutable = opts.Has("pythonExecutable") ?
|
|
142
142
|
opts.Get("pythonExecutable").As<Napi::String>().Utf8Value() : "python3";
|
|
143
143
|
|
|
144
|
+
std::string pythonBaseExecutable = opts.Has("pythonBaseExecutable") ?
|
|
145
|
+
opts.Get("pythonBaseExecutable").As<Napi::String>().Utf8Value() : pythonExecutable;
|
|
146
|
+
|
|
147
|
+
std::string pythonPrefix = opts.Has("pythonPrefix") ?
|
|
148
|
+
opts.Get("pythonPrefix").As<Napi::String>().Utf8Value() : "";
|
|
149
|
+
|
|
144
150
|
std::string pythonLibrary = opts.Has("pythonLibrary") ?
|
|
145
151
|
opts.Get("pythonLibrary").As<Napi::String>().Utf8Value() : "";
|
|
146
152
|
|
|
@@ -180,6 +186,8 @@ static Napi::Value InitializePython(const Napi::CallbackInfo& info) {
|
|
|
180
186
|
PythonBridge::getInstance().initializePython(
|
|
181
187
|
pythonHome,
|
|
182
188
|
pythonExecutable,
|
|
189
|
+
pythonBaseExecutable,
|
|
190
|
+
pythonPrefix,
|
|
183
191
|
pythonLibrary,
|
|
184
192
|
pythonPathExtra,
|
|
185
193
|
envVars
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
#include <stdexcept>
|
|
13
13
|
#include <iostream>
|
|
14
14
|
#include <algorithm>
|
|
15
|
+
#include <fstream>
|
|
15
16
|
|
|
16
17
|
#if defined(__linux__) || defined(__APPLE__)
|
|
17
18
|
#include <dlfcn.h>
|
|
@@ -21,6 +22,40 @@ namespace pynect {
|
|
|
21
22
|
|
|
22
23
|
namespace {
|
|
23
24
|
|
|
25
|
+
void setEnvironmentVariable(const std::string& key, const std::string& value) {
|
|
26
|
+
#ifdef _WIN32
|
|
27
|
+
_putenv_s(key.c_str(), value.c_str());
|
|
28
|
+
#else
|
|
29
|
+
setenv(key.c_str(), value.c_str(), 1);
|
|
30
|
+
#endif
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
PyObject* evaluateSource(
|
|
34
|
+
const std::string& source,
|
|
35
|
+
const std::string& filename,
|
|
36
|
+
const char* mode,
|
|
37
|
+
PyObject* globals
|
|
38
|
+
) {
|
|
39
|
+
PyObject* builtins = PyImport_ImportModule("builtins");
|
|
40
|
+
if (!builtins) return nullptr;
|
|
41
|
+
PyRef builtinsRef = PyRef::steal(builtins);
|
|
42
|
+
|
|
43
|
+
PyObject* compile = PyObject_GetAttrString(builtins, "compile");
|
|
44
|
+
if (!compile) return nullptr;
|
|
45
|
+
PyRef compileRef = PyRef::steal(compile);
|
|
46
|
+
|
|
47
|
+
PyObject* code = PyObject_CallFunction(
|
|
48
|
+
compile,
|
|
49
|
+
"sss",
|
|
50
|
+
source.c_str(),
|
|
51
|
+
filename.c_str(),
|
|
52
|
+
mode
|
|
53
|
+
);
|
|
54
|
+
if (!code) return nullptr;
|
|
55
|
+
PyRef codeRef = PyRef::steal(code);
|
|
56
|
+
return PyEval_EvalCode(code, globals, globals);
|
|
57
|
+
}
|
|
58
|
+
|
|
24
59
|
#if defined(__linux__) || defined(__APPLE__)
|
|
25
60
|
void promotePythonLibrary(const std::string& libraryPath) {
|
|
26
61
|
if (libraryPath.empty()) {
|
|
@@ -60,6 +95,8 @@ PythonBridge& PythonBridge::getInstance() {
|
|
|
60
95
|
void PythonBridge::initializePython(
|
|
61
96
|
const std::string& pythonHome,
|
|
62
97
|
const std::string& pythonExecutable,
|
|
98
|
+
const std::string& pythonBaseExecutable,
|
|
99
|
+
const std::string& pythonPrefix,
|
|
63
100
|
const std::string& pythonLibrary,
|
|
64
101
|
const std::vector<std::string>& extraSysPaths,
|
|
65
102
|
const std::vector<std::pair<std::string, std::string>>& envVars
|
|
@@ -74,52 +111,57 @@ void PythonBridge::initializePython(
|
|
|
74
111
|
|
|
75
112
|
// Set environment variables before initialization
|
|
76
113
|
for (const auto& [key, val] : envVars) {
|
|
77
|
-
|
|
78
|
-
_putenv_s(key.c_str(), val.c_str());
|
|
79
|
-
#else
|
|
80
|
-
setenv(key.c_str(), val.c_str(), 1);
|
|
81
|
-
#endif
|
|
114
|
+
setEnvironmentVariable(key, val);
|
|
82
115
|
}
|
|
83
116
|
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
&config,
|
|
90
|
-
&config.program_name,
|
|
91
|
-
pythonExecutable.c_str()
|
|
92
|
-
);
|
|
93
|
-
if (!PyStatus_Exception(status) && !pythonHome.empty()) {
|
|
94
|
-
status = PyConfig_SetBytesString(&config, &config.home, pythonHome.c_str());
|
|
95
|
-
}
|
|
96
|
-
if (!PyStatus_Exception(status)) {
|
|
97
|
-
status = Py_InitializeFromConfig(&config);
|
|
117
|
+
// These process-wide setters are in Python's Stable ABI. Keep their decoded
|
|
118
|
+
// storage alive until finalization, as required by CPython.
|
|
119
|
+
_programName = Py_DecodeLocale(pythonExecutable.c_str(), nullptr);
|
|
120
|
+
if (!_programName) {
|
|
121
|
+
throw std::runtime_error("Failed to decode the Python executable path");
|
|
98
122
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
123
|
+
Py_SetProgramName(_programName);
|
|
124
|
+
if (!pythonHome.empty()) {
|
|
125
|
+
_pythonHome = Py_DecodeLocale(pythonHome.c_str(), nullptr);
|
|
126
|
+
if (!_pythonHome) {
|
|
127
|
+
throw std::runtime_error("Failed to decode the Python home path");
|
|
128
|
+
}
|
|
129
|
+
Py_SetPythonHome(_pythonHome);
|
|
105
130
|
}
|
|
131
|
+
Py_Initialize();
|
|
106
132
|
|
|
107
133
|
if (!Py_IsInitialized()) {
|
|
108
134
|
throw std::runtime_error("Failed to initialize Python interpreter");
|
|
109
135
|
}
|
|
110
136
|
|
|
111
|
-
// Enable thread support
|
|
112
|
-
PyEval_InitThreads();
|
|
113
|
-
|
|
114
137
|
// Save main thread state and release GIL so threads can run
|
|
115
138
|
_mainThreadState = PyEval_SaveThread();
|
|
116
139
|
|
|
117
|
-
//
|
|
118
|
-
|
|
140
|
+
// Restore the selected environment identity and add its import paths after
|
|
141
|
+
// Stable ABI initialization used the base installation for the stdlib.
|
|
142
|
+
{
|
|
119
143
|
GILAcquire gil;
|
|
120
144
|
PyObject* sysModule = PyImport_ImportModule("sys");
|
|
121
145
|
if (sysModule) {
|
|
122
146
|
PyRef sysRef = PyRef::steal(sysModule);
|
|
147
|
+
PyObject* executable = PyUnicode_FromString(pythonExecutable.c_str());
|
|
148
|
+
if (executable) {
|
|
149
|
+
PyRef executableRef = PyRef::steal(executable);
|
|
150
|
+
PyObject_SetAttrString(sysModule, "executable", executable);
|
|
151
|
+
}
|
|
152
|
+
PyObject* baseExecutable = PyUnicode_FromString(pythonBaseExecutable.c_str());
|
|
153
|
+
if (baseExecutable) {
|
|
154
|
+
PyRef baseExecutableRef = PyRef::steal(baseExecutable);
|
|
155
|
+
PyObject_SetAttrString(sysModule, "_base_executable", baseExecutable);
|
|
156
|
+
}
|
|
157
|
+
if (!pythonPrefix.empty()) {
|
|
158
|
+
PyObject* prefix = PyUnicode_FromString(pythonPrefix.c_str());
|
|
159
|
+
if (prefix) {
|
|
160
|
+
PyRef prefixRef = PyRef::steal(prefix);
|
|
161
|
+
PyObject_SetAttrString(sysModule, "prefix", prefix);
|
|
162
|
+
PyObject_SetAttrString(sysModule, "exec_prefix", prefix);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
123
165
|
PyObject* sysPath = PyObject_GetAttrString(sysModule, "path");
|
|
124
166
|
if (sysPath) {
|
|
125
167
|
PyRef pathRef = PyRef::steal(sysPath);
|
|
@@ -131,6 +173,23 @@ void PythonBridge::initializePython(
|
|
|
131
173
|
}
|
|
132
174
|
}
|
|
133
175
|
}
|
|
176
|
+
PyObject* siteModule = PyImport_ImportModule("site");
|
|
177
|
+
if (siteModule) {
|
|
178
|
+
PyRef siteRef = PyRef::steal(siteModule);
|
|
179
|
+
PyObject* addSiteDirectory = PyObject_GetAttrString(siteModule, "addsitedir");
|
|
180
|
+
if (addSiteDirectory) {
|
|
181
|
+
PyRef addSiteDirectoryRef = PyRef::steal(addSiteDirectory);
|
|
182
|
+
for (const auto& p : extraSysPaths) {
|
|
183
|
+
PyObject* added = PyObject_CallFunction(addSiteDirectory, "s", p.c_str());
|
|
184
|
+
if (added) Py_DECREF(added);
|
|
185
|
+
else PyErr_Clear();
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
PyErr_Clear();
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
PyErr_Clear();
|
|
192
|
+
}
|
|
134
193
|
}
|
|
135
194
|
}
|
|
136
195
|
|
|
@@ -203,8 +262,7 @@ TaskResult PythonBridge::_makeSuccessResult(PyObject* obj) {
|
|
|
203
262
|
}
|
|
204
263
|
|
|
205
264
|
// For callable objects
|
|
206
|
-
if (PyCallable_Check(obj) && !
|
|
207
|
-
!PyModule_Check(obj) && !PyType_Check(obj)) {
|
|
265
|
+
if (PyCallable_Check(obj) && !PyModule_Check(obj) && !PyType_Check(obj)) {
|
|
208
266
|
// Instance of a class with __call__ - usually want as reference
|
|
209
267
|
result.objectId = TypeConverter::registerObject(obj);
|
|
210
268
|
result.isObject = true;
|
|
@@ -563,7 +621,11 @@ bool PythonBridge::_buildCallArgs(
|
|
|
563
621
|
argsOut = nullptr;
|
|
564
622
|
return false;
|
|
565
623
|
}
|
|
566
|
-
|
|
624
|
+
if (PyTuple_SetItem(argsOut, static_cast<Py_ssize_t>(i), arg) != 0) {
|
|
625
|
+
Py_DECREF(argsOut);
|
|
626
|
+
argsOut = nullptr;
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
567
629
|
}
|
|
568
630
|
|
|
569
631
|
// Build kwargs dict
|
|
@@ -643,7 +705,7 @@ TaskResult PythonBridge::runCodeSync(const std::string& code) {
|
|
|
643
705
|
|
|
644
706
|
PyObject* globalDict = PyModule_GetDict(mainModule); // borrowed
|
|
645
707
|
|
|
646
|
-
PyObject* result =
|
|
708
|
+
PyObject* result = evaluateSource(code, "<pynect>", "exec", globalDict);
|
|
647
709
|
if (!result) {
|
|
648
710
|
return _makeErrorResult();
|
|
649
711
|
}
|
|
@@ -661,7 +723,7 @@ TaskResult PythonBridge::evalExpressionSync(const std::string& expression) {
|
|
|
661
723
|
|
|
662
724
|
PyObject* globalDict = PyModule_GetDict(mainModule); // borrowed
|
|
663
725
|
|
|
664
|
-
PyObject* result =
|
|
726
|
+
PyObject* result = evaluateSource(expression, "<pynect>", "eval", globalDict);
|
|
665
727
|
if (!result) {
|
|
666
728
|
return _makeErrorResult();
|
|
667
729
|
}
|
|
@@ -671,8 +733,8 @@ TaskResult PythonBridge::evalExpressionSync(const std::string& expression) {
|
|
|
671
733
|
}
|
|
672
734
|
|
|
673
735
|
TaskResult PythonBridge::runFileSync(const std::string& filePath) {
|
|
674
|
-
|
|
675
|
-
if (!
|
|
736
|
+
std::ifstream input(filePath, std::ios::binary);
|
|
737
|
+
if (!input) {
|
|
676
738
|
TaskResult r;
|
|
677
739
|
r.success = false;
|
|
678
740
|
r.errorType = "FileNotFoundError";
|
|
@@ -680,11 +742,14 @@ TaskResult PythonBridge::runFileSync(const std::string& filePath) {
|
|
|
680
742
|
return r;
|
|
681
743
|
}
|
|
682
744
|
|
|
745
|
+
std::ostringstream source;
|
|
746
|
+
source << input.rdbuf();
|
|
747
|
+
|
|
683
748
|
PyObject* mainModule = PyImport_AddModule("__main__");
|
|
749
|
+
if (!mainModule) return _makeErrorResult();
|
|
684
750
|
PyObject* globalDict = PyModule_GetDict(mainModule);
|
|
685
751
|
|
|
686
|
-
PyObject* result =
|
|
687
|
-
fclose(fp);
|
|
752
|
+
PyObject* result = evaluateSource(source.str(), filePath, "exec", globalDict);
|
|
688
753
|
|
|
689
754
|
if (!result) {
|
|
690
755
|
return _makeErrorResult();
|
|
@@ -763,7 +828,7 @@ TaskResult PythonBridge::getObjectKeysSync(uint32_t objectId) {
|
|
|
763
828
|
PyObject* name = PyList_GetItem(dirList, i);
|
|
764
829
|
if (!name || !PyUnicode_Check(name)) continue;
|
|
765
830
|
|
|
766
|
-
const char* nameStr =
|
|
831
|
+
const char* nameStr = PyUnicode_AsUTF8AndSize(name, nullptr);
|
|
767
832
|
if (!nameStr) continue;
|
|
768
833
|
|
|
769
834
|
// Filter out private names
|
|
@@ -67,6 +67,8 @@ public:
|
|
|
67
67
|
void initializePython(
|
|
68
68
|
const std::string& pythonHome,
|
|
69
69
|
const std::string& pythonExecutable,
|
|
70
|
+
const std::string& pythonBaseExecutable,
|
|
71
|
+
const std::string& pythonPrefix,
|
|
70
72
|
const std::string& pythonLibrary,
|
|
71
73
|
const std::vector<std::string>& extraSysPaths,
|
|
72
74
|
const std::vector<std::pair<std::string, std::string>>& envVars
|
|
@@ -253,6 +255,10 @@ private:
|
|
|
253
255
|
|
|
254
256
|
bool _initialized = false;
|
|
255
257
|
PyThreadState* _mainThreadState = nullptr;
|
|
258
|
+
// CPython requires these buffers to remain unchanged for the process
|
|
259
|
+
// lifetime, so the singleton intentionally retains them after finalization.
|
|
260
|
+
wchar_t* _programName = nullptr;
|
|
261
|
+
wchar_t* _pythonHome = nullptr;
|
|
256
262
|
|
|
257
263
|
// Iterator registry: iteratorId -> Python iterator object
|
|
258
264
|
std::map<uint32_t, PyObject*> _iterators;
|