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.
- package/LICENSE +22 -0
- package/README.md +399 -0
- package/binding.gyp +73 -0
- package/dist/core/PyCallable.d.ts +65 -0
- package/dist/core/PyCallable.d.ts.map +1 -0
- package/dist/core/PyCallable.js +109 -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 +228 -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 +55 -0
- package/dist/core/PyModule.d.ts.map +1 -0
- package/dist/core/PyModule.js +172 -0
- package/dist/core/PyModule.js.map +1 -0
- package/dist/core/PyProxy.d.ts +65 -0
- package/dist/core/PyProxy.d.ts.map +1 -0
- package/dist/core/PyProxy.js +483 -0
- package/dist/core/PyProxy.js.map +1 -0
- package/dist/core/PyRuntime.d.ts +105 -0
- package/dist/core/PyRuntime.d.ts.map +1 -0
- package/dist/core/PyRuntime.js +438 -0
- package/dist/core/PyRuntime.js.map +1 -0
- package/dist/env/CondaManager.d.ts +118 -0
- package/dist/env/CondaManager.d.ts.map +1 -0
- package/dist/env/CondaManager.js +401 -0
- package/dist/env/CondaManager.js.map +1 -0
- package/dist/env/PackageInstaller.d.ts +233 -0
- package/dist/env/PackageInstaller.d.ts.map +1 -0
- package/dist/env/PackageInstaller.js +609 -0
- package/dist/env/PackageInstaller.js.map +1 -0
- package/dist/env/PythonDetector.d.ts +103 -0
- package/dist/env/PythonDetector.d.ts.map +1 -0
- package/dist/env/PythonDetector.js +381 -0
- package/dist/env/PythonDetector.js.map +1 -0
- package/dist/env/VenvManager.d.ts +117 -0
- package/dist/env/VenvManager.d.ts.map +1 -0
- package/dist/env/VenvManager.js +331 -0
- package/dist/env/VenvManager.js.map +1 -0
- package/dist/index.d.ts +169 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +393 -0
- package/dist/index.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/PythonTypeMapper.d.ts +87 -0
- package/dist/types/PythonTypeMapper.d.ts.map +1 -0
- package/dist/types/PythonTypeMapper.js +449 -0
- package/dist/types/PythonTypeMapper.js.map +1 -0
- package/dist/types/StubCache.d.ts +109 -0
- package/dist/types/StubCache.d.ts.map +1 -0
- package/dist/types/StubCache.js +333 -0
- package/dist/types/StubCache.js.map +1 -0
- package/dist/types/TypeGenerator.d.ts +139 -0
- package/dist/types/TypeGenerator.d.ts.map +1 -0
- package/dist/types/TypeGenerator.js +372 -0
- package/dist/types/TypeGenerator.js.map +1 -0
- package/dist/types/addon.d.ts +114 -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 +175 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +35 -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/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 +44 -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 +177 -0
- package/python/error_handler.py +433 -0
- package/python/nodepyx_runtime.py +575 -0
- package/python/serializer.py +379 -0
- package/python/type_inspector.py +288 -0
- package/scripts/build-native.js +68 -0
- package/scripts/download-prebuilds.js +99 -0
- package/scripts/generate-stubs.js +405 -0
- package/scripts/install.js +260 -0
- package/src/core/PyCallable.ts +137 -0
- package/src/core/PyContext.ts +296 -0
- package/src/core/PyIterator.ts +294 -0
- package/src/core/PyModule.ts +194 -0
- package/src/core/PyProxy.ts +605 -0
- package/src/core/PyRuntime.ts +504 -0
- package/src/env/CondaManager.ts +451 -0
- package/src/env/PackageInstaller.ts +738 -0
- package/src/env/PythonDetector.ts +414 -0
- package/src/env/VenvManager.ts +396 -0
- package/src/index.ts +425 -0
- package/src/native/gil_guard.cpp +26 -0
- package/src/native/gil_guard.h +175 -0
- package/src/native/nodepyx_addon.cpp +886 -0
- package/src/native/python_bridge.cpp +790 -0
- package/src/native/python_bridge.h +257 -0
- package/src/native/thread_pool.cpp +336 -0
- package/src/native/thread_pool.h +175 -0
- package/src/native/type_converter.cpp +901 -0
- package/src/native/type_converter.h +272 -0
- package/src/nextjs/PyProvider.tsx +123 -0
- package/src/nextjs/index.ts +21 -0
- package/src/nextjs/usePython.ts +106 -0
- package/src/nextjs/withnodepyx.ts +88 -0
- package/src/plugins/Plugin.interface.ts +51 -0
- package/src/plugins/PluginManager.ts +155 -0
- package/src/plugins/builtin/NumpyPlugin.ts +36 -0
- package/src/plugins/builtin/PandasPlugin.ts +49 -0
- package/src/plugins/builtin/TorchPlugin.ts +56 -0
- package/src/plugins/index.ts +7 -0
- package/src/serialization/DataFrameBridge.ts +398 -0
- package/src/serialization/MsgPackSerializer.ts +220 -0
- package/src/serialization/NumpyBridge.ts +332 -0
- package/src/serialization/Serializer.ts +320 -0
- package/src/types/PythonTypeMapper.ts +495 -0
- package/src/types/StubCache.ts +340 -0
- package/src/types/TypeGenerator.ts +491 -0
- package/src/types/addon.ts +170 -0
- package/src/types/config.ts +226 -0
- package/src/types/index.ts +55 -0
- package/src/types/python.ts +309 -0
- package/src/types/stubs/numpy.d.ts +441 -0
- package/src/types/stubs/pandas.d.ts +575 -0
- package/src/types/stubs/sklearn.d.ts +728 -0
- package/src/types/stubs/torch.d.ts +694 -0
- package/src/utils/ErrorTranslator.ts +220 -0
- package/src/utils/Logger.ts +119 -0
- package/src/utils/MemoryMonitor.ts +175 -0
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nodepyx — Python Bridge Implementation
|
|
3
|
+
* Core Python operations: import, eval, attribute access, function calls.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#include "python_bridge.h"
|
|
7
|
+
#include "gil_guard.h"
|
|
8
|
+
#include "type_converter.h"
|
|
9
|
+
|
|
10
|
+
#include <Python.h>
|
|
11
|
+
#include <sstream>
|
|
12
|
+
#include <stdexcept>
|
|
13
|
+
#include <iostream>
|
|
14
|
+
#include <algorithm>
|
|
15
|
+
|
|
16
|
+
namespace nodepyx {
|
|
17
|
+
|
|
18
|
+
// ─── Singleton ──────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
PythonBridge& PythonBridge::getInstance() {
|
|
21
|
+
static PythonBridge instance;
|
|
22
|
+
return instance;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Initialize Python ──────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
void PythonBridge::initializePython(
|
|
28
|
+
const std::string& pythonHome,
|
|
29
|
+
const std::string& pythonExecutable,
|
|
30
|
+
const std::vector<std::string>& extraSysPaths,
|
|
31
|
+
const std::vector<std::pair<std::string, std::string>>& envVars
|
|
32
|
+
) {
|
|
33
|
+
if (_initialized) {
|
|
34
|
+
throw std::runtime_error("Python already initialized");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Set environment variables before initialization
|
|
38
|
+
for (const auto& [key, val] : envVars) {
|
|
39
|
+
#ifdef _WIN32
|
|
40
|
+
_putenv_s(key.c_str(), val.c_str());
|
|
41
|
+
#else
|
|
42
|
+
setenv(key.c_str(), val.c_str(), 1);
|
|
43
|
+
#endif
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Configure Python home if specified
|
|
47
|
+
if (!pythonHome.empty()) {
|
|
48
|
+
PyConfig config;
|
|
49
|
+
PyConfig_InitPythonConfig(&config);
|
|
50
|
+
PyConfig_SetBytesString(&config, &config.home, pythonHome.c_str());
|
|
51
|
+
Py_InitializeFromConfig(&config);
|
|
52
|
+
PyConfig_Clear(&config);
|
|
53
|
+
} else {
|
|
54
|
+
Py_Initialize();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!Py_IsInitialized()) {
|
|
58
|
+
throw std::runtime_error("Failed to initialize Python interpreter");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Enable thread support
|
|
62
|
+
PyEval_InitThreads();
|
|
63
|
+
|
|
64
|
+
// Save main thread state and release GIL so threads can run
|
|
65
|
+
_mainThreadState = PyEval_SaveThread();
|
|
66
|
+
|
|
67
|
+
// Add extra sys.path entries (done with GIL held)
|
|
68
|
+
if (!extraSysPaths.empty()) {
|
|
69
|
+
GILAcquire gil;
|
|
70
|
+
PyObject* sysModule = PyImport_ImportModule("sys");
|
|
71
|
+
if (sysModule) {
|
|
72
|
+
PyRef sysRef = PyRef::steal(sysModule);
|
|
73
|
+
PyObject* sysPath = PyObject_GetAttrString(sysModule, "path");
|
|
74
|
+
if (sysPath) {
|
|
75
|
+
PyRef pathRef = PyRef::steal(sysPath);
|
|
76
|
+
for (const auto& p : extraSysPaths) {
|
|
77
|
+
PyObject* pathStr = PyUnicode_FromString(p.c_str());
|
|
78
|
+
if (pathStr) {
|
|
79
|
+
PyList_Insert(sysPath, 0, pathStr);
|
|
80
|
+
Py_DECREF(pathStr);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
_initialized = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Finalize Python ────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
void PythonBridge::finalizePython() {
|
|
93
|
+
if (!_initialized) return;
|
|
94
|
+
|
|
95
|
+
// Re-acquire main thread's GIL before calling Py_Finalize
|
|
96
|
+
if (_mainThreadState) {
|
|
97
|
+
PyEval_RestoreThread(_mainThreadState);
|
|
98
|
+
_mainThreadState = nullptr;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Clear iterators
|
|
102
|
+
{
|
|
103
|
+
std::lock_guard<std::mutex> lock(_iteratorsMutex);
|
|
104
|
+
for (auto& [id, iter] : _iterators) {
|
|
105
|
+
Py_XDECREF(iter);
|
|
106
|
+
}
|
|
107
|
+
_iterators.clear();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Clear object registry
|
|
111
|
+
TypeConverter::clearRegistry();
|
|
112
|
+
|
|
113
|
+
// Finalize Python
|
|
114
|
+
Py_Finalize();
|
|
115
|
+
_initialized = false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ─── Helper: Make Error Result ───────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
TaskResult PythonBridge::_makeErrorResult() {
|
|
121
|
+
TaskResult result;
|
|
122
|
+
result.success = false;
|
|
123
|
+
auto [type, msg, tb] = TypeConverter::captureException();
|
|
124
|
+
result.errorType = type;
|
|
125
|
+
result.errorMessage = msg;
|
|
126
|
+
result.errorTraceback = tb;
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─── Helper: Make Success Result ────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
TaskResult PythonBridge::_makeSuccessResult(PyObject* obj) {
|
|
133
|
+
TaskResult result;
|
|
134
|
+
result.success = true;
|
|
135
|
+
|
|
136
|
+
if (!obj || obj == Py_None) {
|
|
137
|
+
result.resultJson = "null";
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// For objects that can't be fully serialized, register as opaque ref
|
|
142
|
+
if (TypeConverter::isNumpyArray(obj) ||
|
|
143
|
+
TypeConverter::isDataFrame(obj) ||
|
|
144
|
+
TypeConverter::isSeries(obj) ||
|
|
145
|
+
TypeConverter::isTorchTensor(obj) ||
|
|
146
|
+
TypeConverter::isModule(obj) ||
|
|
147
|
+
TypeConverter::isClass(obj)) {
|
|
148
|
+
// Always return as object reference for these types
|
|
149
|
+
result.objectId = TypeConverter::registerObject(obj);
|
|
150
|
+
result.isObject = true;
|
|
151
|
+
result.resultJson = "{\"__pyref__\":" + std::to_string(result.objectId) + "}";
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// For callable objects
|
|
156
|
+
if (PyCallable_Check(obj) && !PyFunction_Check(obj) &&
|
|
157
|
+
!PyModule_Check(obj) && !PyType_Check(obj)) {
|
|
158
|
+
// Instance of a class with __call__ — usually want as reference
|
|
159
|
+
result.objectId = TypeConverter::registerObject(obj);
|
|
160
|
+
result.isObject = true;
|
|
161
|
+
result.resultJson = "{\"__pyref__\":" + std::to_string(result.objectId) + "}";
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
SerializedValue sv = TypeConverter::pythonToSerialized(obj);
|
|
166
|
+
|
|
167
|
+
if (sv.format == SerializedFormat::PYTHON_REF) {
|
|
168
|
+
result.objectId = sv.metadata.objectId;
|
|
169
|
+
result.isObject = true;
|
|
170
|
+
result.resultJson = "{\"__pyref__\":" + std::to_string(result.objectId) + "}";
|
|
171
|
+
} else if (sv.format == SerializedFormat::JSON) {
|
|
172
|
+
result.resultJson = sv.jsonData;
|
|
173
|
+
result.isObject = false;
|
|
174
|
+
} else {
|
|
175
|
+
// Binary format — register as object for now
|
|
176
|
+
// In production, pass binary data through
|
|
177
|
+
result.objectId = TypeConverter::registerObject(obj);
|
|
178
|
+
result.isObject = true;
|
|
179
|
+
result.resultJson = "{\"__pyref__\":" + std::to_string(result.objectId) + "}";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ─── Module Import ──────────────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
TaskResult PythonBridge::importModuleSync(const std::string& moduleName) {
|
|
188
|
+
// GIL must be held by caller
|
|
189
|
+
PyObject* module = PyImport_ImportModule(moduleName.c_str());
|
|
190
|
+
if (!module) {
|
|
191
|
+
return _makeErrorResult();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
PyRef modRef = PyRef::steal(module);
|
|
195
|
+
TaskResult result;
|
|
196
|
+
result.success = true;
|
|
197
|
+
result.objectId = TypeConverter::registerObject(module);
|
|
198
|
+
result.isObject = true;
|
|
199
|
+
result.resultJson = "{\"__pyref__\":" + std::to_string(result.objectId) + "}";
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
TaskResult PythonBridge::reloadModuleSync(uint32_t objectId) {
|
|
204
|
+
PyObject* module = TypeConverter::getObject(objectId);
|
|
205
|
+
if (!module) {
|
|
206
|
+
TaskResult r;
|
|
207
|
+
r.success = false;
|
|
208
|
+
r.errorType = "ValueError";
|
|
209
|
+
r.errorMessage = "Object ID " + std::to_string(objectId) + " not found";
|
|
210
|
+
return r;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
PyObject* reloaded = PyImport_ReloadModule(module);
|
|
214
|
+
if (!reloaded) {
|
|
215
|
+
return _makeErrorResult();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
PyRef reloadedRef = PyRef::steal(reloaded);
|
|
219
|
+
|
|
220
|
+
// Update registry
|
|
221
|
+
TypeConverter::decRef(objectId);
|
|
222
|
+
uint32_t newId = TypeConverter::registerObject(reloaded);
|
|
223
|
+
|
|
224
|
+
TaskResult result;
|
|
225
|
+
result.success = true;
|
|
226
|
+
result.objectId = newId;
|
|
227
|
+
result.isObject = true;
|
|
228
|
+
result.resultJson = "{\"__pyref__\":" + std::to_string(newId) + "}";
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ─── Attribute Access ────────────────────────────────────────────────────────
|
|
233
|
+
|
|
234
|
+
TaskResult PythonBridge::getAttributeSync(uint32_t objectId, const std::string& attrName) {
|
|
235
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
236
|
+
if (!obj) {
|
|
237
|
+
TaskResult r;
|
|
238
|
+
r.success = false;
|
|
239
|
+
r.errorType = "ValueError";
|
|
240
|
+
r.errorMessage = "Object not found: id=" + std::to_string(objectId);
|
|
241
|
+
return r;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
PyObject* attr = PyObject_GetAttrString(obj, attrName.c_str());
|
|
245
|
+
if (!attr) {
|
|
246
|
+
return _makeErrorResult();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
PyRef attrRef = PyRef::steal(attr);
|
|
250
|
+
return _makeSuccessResult(attr);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
TaskResult PythonBridge::getAttributePathSync(
|
|
254
|
+
uint32_t objectId,
|
|
255
|
+
const std::vector<std::string>& path
|
|
256
|
+
) {
|
|
257
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
258
|
+
if (!obj) {
|
|
259
|
+
TaskResult r;
|
|
260
|
+
r.success = false;
|
|
261
|
+
r.errorType = "ValueError";
|
|
262
|
+
r.errorMessage = "Object not found: id=" + std::to_string(objectId);
|
|
263
|
+
return r;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (path.empty()) {
|
|
267
|
+
return _makeSuccessResult(obj);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
PyObject* resolved = _resolveAttributePath(obj, path);
|
|
271
|
+
if (!resolved) {
|
|
272
|
+
return _makeErrorResult();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
PyRef resolvedRef = PyRef::steal(resolved);
|
|
276
|
+
return _makeSuccessResult(resolved);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
PyObject* PythonBridge::_resolveAttributePath(
|
|
280
|
+
PyObject* obj,
|
|
281
|
+
const std::vector<std::string>& path
|
|
282
|
+
) {
|
|
283
|
+
if (!obj || path.empty()) {
|
|
284
|
+
if (obj) Py_INCREF(obj);
|
|
285
|
+
return obj;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Py_INCREF(obj);
|
|
289
|
+
PyObject* current = obj;
|
|
290
|
+
|
|
291
|
+
for (const auto& attr : path) {
|
|
292
|
+
PyObject* next = PyObject_GetAttrString(current, attr.c_str());
|
|
293
|
+
Py_DECREF(current);
|
|
294
|
+
|
|
295
|
+
if (!next) {
|
|
296
|
+
return nullptr; // Exception set
|
|
297
|
+
}
|
|
298
|
+
current = next;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return current; // new reference
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
TaskResult PythonBridge::setAttributeSync(
|
|
305
|
+
uint32_t objectId,
|
|
306
|
+
const std::string& attrName,
|
|
307
|
+
const SerializedValue& value
|
|
308
|
+
) {
|
|
309
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
310
|
+
if (!obj) {
|
|
311
|
+
TaskResult r;
|
|
312
|
+
r.success = false;
|
|
313
|
+
r.errorType = "ValueError";
|
|
314
|
+
r.errorMessage = "Object not found";
|
|
315
|
+
return r;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
PyObject* pyValue = _deserialize(value);
|
|
319
|
+
if (!pyValue) {
|
|
320
|
+
TaskResult r;
|
|
321
|
+
r.success = false;
|
|
322
|
+
r.errorType = "ValueError";
|
|
323
|
+
r.errorMessage = "Failed to deserialize value";
|
|
324
|
+
return r;
|
|
325
|
+
}
|
|
326
|
+
PyRef valueRef = PyRef::steal(pyValue);
|
|
327
|
+
|
|
328
|
+
int ret = PyObject_SetAttrString(obj, attrName.c_str(), pyValue);
|
|
329
|
+
if (ret != 0) {
|
|
330
|
+
return _makeErrorResult();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
TaskResult result;
|
|
334
|
+
result.success = true;
|
|
335
|
+
result.resultJson = "true";
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
bool PythonBridge::hasAttributeSync(uint32_t objectId, const std::string& attrName) {
|
|
340
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
341
|
+
if (!obj) return false;
|
|
342
|
+
return PyObject_HasAttrString(obj, attrName.c_str()) == 1;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
TaskResult PythonBridge::deleteAttributeSync(uint32_t objectId, const std::string& attrName) {
|
|
346
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
347
|
+
if (!obj) {
|
|
348
|
+
TaskResult r;
|
|
349
|
+
r.success = false;
|
|
350
|
+
r.errorType = "ValueError";
|
|
351
|
+
r.errorMessage = "Object not found";
|
|
352
|
+
return r;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
int ret = PyObject_DelAttrString(obj, attrName.c_str());
|
|
356
|
+
if (ret != 0) {
|
|
357
|
+
return _makeErrorResult();
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
TaskResult result;
|
|
361
|
+
result.success = true;
|
|
362
|
+
result.resultJson = "true";
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ─── Function Calls ──────────────────────────────────────────────────────────
|
|
367
|
+
|
|
368
|
+
TaskResult PythonBridge::callFunctionSync(
|
|
369
|
+
uint32_t objectId,
|
|
370
|
+
const std::vector<std::string>& path,
|
|
371
|
+
const CallOptions& options
|
|
372
|
+
) {
|
|
373
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
374
|
+
if (!obj) {
|
|
375
|
+
TaskResult r;
|
|
376
|
+
r.success = false;
|
|
377
|
+
r.errorType = "ValueError";
|
|
378
|
+
r.errorMessage = "Object not found: id=" + std::to_string(objectId);
|
|
379
|
+
return r;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Resolve path to callable
|
|
383
|
+
PyObject* callable;
|
|
384
|
+
if (path.empty()) {
|
|
385
|
+
Py_INCREF(obj);
|
|
386
|
+
callable = obj;
|
|
387
|
+
} else {
|
|
388
|
+
callable = _resolveAttributePath(obj, path);
|
|
389
|
+
if (!callable) {
|
|
390
|
+
return _makeErrorResult();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
PyRef callableRef = PyRef::steal(callable);
|
|
394
|
+
|
|
395
|
+
if (!PyCallable_Check(callable)) {
|
|
396
|
+
// The result is the object at the path, not a callable
|
|
397
|
+
// Return it as a value
|
|
398
|
+
return _makeSuccessResult(callable);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Build args and kwargs
|
|
402
|
+
PyObject* argsObj = nullptr;
|
|
403
|
+
PyObject* kwargsObj = nullptr;
|
|
404
|
+
|
|
405
|
+
if (!_buildCallArgs(options, argsObj, kwargsObj)) {
|
|
406
|
+
Py_XDECREF(argsObj);
|
|
407
|
+
Py_XDECREF(kwargsObj);
|
|
408
|
+
return _makeErrorResult();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
PyRef argsRef = PyRef::steal(argsObj);
|
|
412
|
+
PyRef kwargsRef = PyRef::steal(kwargsObj);
|
|
413
|
+
|
|
414
|
+
// Make the call
|
|
415
|
+
PyObject* result = PyObject_Call(callable, argsObj, kwargsObj && PyDict_Size(kwargsObj) > 0 ? kwargsObj : nullptr);
|
|
416
|
+
|
|
417
|
+
if (!result) {
|
|
418
|
+
return _makeErrorResult();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
PyRef resultRef = PyRef::steal(result);
|
|
422
|
+
return _makeSuccessResult(result);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
TaskResult PythonBridge::callMethodSync(
|
|
426
|
+
uint32_t objectId,
|
|
427
|
+
const std::string& methodName,
|
|
428
|
+
const CallOptions& options
|
|
429
|
+
) {
|
|
430
|
+
return callFunctionSync(objectId, {methodName}, options);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ─── Build Call Args ─────────────────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
bool PythonBridge::_buildCallArgs(
|
|
436
|
+
const CallOptions& options,
|
|
437
|
+
PyObject*& argsOut,
|
|
438
|
+
PyObject*& kwargsOut
|
|
439
|
+
) {
|
|
440
|
+
// Build positional args tuple
|
|
441
|
+
argsOut = PyTuple_New(static_cast<Py_ssize_t>(options.args.size()));
|
|
442
|
+
if (!argsOut) return false;
|
|
443
|
+
|
|
444
|
+
for (size_t i = 0; i < options.args.size(); ++i) {
|
|
445
|
+
PyObject* arg = _deserialize(options.args[i]);
|
|
446
|
+
if (!arg) {
|
|
447
|
+
Py_DECREF(argsOut);
|
|
448
|
+
argsOut = nullptr;
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
PyTuple_SET_ITEM(argsOut, static_cast<Py_ssize_t>(i), arg); // steals reference
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Build kwargs dict
|
|
455
|
+
kwargsOut = PyDict_New();
|
|
456
|
+
if (!kwargsOut) {
|
|
457
|
+
Py_DECREF(argsOut);
|
|
458
|
+
argsOut = nullptr;
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
for (const auto& [key, val] : options.kwargs) {
|
|
463
|
+
PyObject* pyVal = _deserialize(val);
|
|
464
|
+
if (!pyVal) {
|
|
465
|
+
Py_DECREF(argsOut);
|
|
466
|
+
Py_DECREF(kwargsOut);
|
|
467
|
+
argsOut = nullptr;
|
|
468
|
+
kwargsOut = nullptr;
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
PyDict_SetItemString(kwargsOut, key.c_str(), pyVal);
|
|
472
|
+
Py_DECREF(pyVal);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ─── Deserialize ─────────────────────────────────────────────────────────────
|
|
479
|
+
|
|
480
|
+
PyObject* PythonBridge::_deserialize(const SerializedValue& value) {
|
|
481
|
+
switch (value.format) {
|
|
482
|
+
case SerializedFormat::JSON:
|
|
483
|
+
return TypeConverter::jsonToPython(value.jsonData);
|
|
484
|
+
|
|
485
|
+
case SerializedFormat::MSGPACK:
|
|
486
|
+
return TypeConverter::msgpackToPython(value.binaryData);
|
|
487
|
+
|
|
488
|
+
case SerializedFormat::NUMPY_ARRAY:
|
|
489
|
+
return TypeConverter::bufferToNumpyArray(
|
|
490
|
+
value.binaryData,
|
|
491
|
+
value.metadata.dtype,
|
|
492
|
+
value.metadata.shape
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
case SerializedFormat::PYTHON_REF: {
|
|
496
|
+
PyObject* obj = TypeConverter::getObject(value.metadata.objectId);
|
|
497
|
+
if (!obj) {
|
|
498
|
+
PyErr_SetString(PyExc_ValueError, "Python object reference not found");
|
|
499
|
+
return nullptr;
|
|
500
|
+
}
|
|
501
|
+
Py_INCREF(obj);
|
|
502
|
+
return obj;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
case SerializedFormat::BYTES:
|
|
506
|
+
return PyBytes_FromStringAndSize(
|
|
507
|
+
reinterpret_cast<const char*>(value.binaryData.data()),
|
|
508
|
+
static_cast<Py_ssize_t>(value.binaryData.size())
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
case SerializedFormat::NONE:
|
|
512
|
+
Py_RETURN_NONE;
|
|
513
|
+
|
|
514
|
+
default:
|
|
515
|
+
// Try JSON fallback
|
|
516
|
+
if (!value.jsonData.empty()) {
|
|
517
|
+
return TypeConverter::jsonToPython(value.jsonData);
|
|
518
|
+
}
|
|
519
|
+
Py_RETURN_NONE;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ─── Code Execution ──────────────────────────────────────────────────────────
|
|
524
|
+
|
|
525
|
+
TaskResult PythonBridge::runCodeSync(const std::string& code) {
|
|
526
|
+
PyObject* mainModule = PyImport_AddModule("__main__");
|
|
527
|
+
if (!mainModule) return _makeErrorResult();
|
|
528
|
+
|
|
529
|
+
PyObject* globalDict = PyModule_GetDict(mainModule); // borrowed
|
|
530
|
+
|
|
531
|
+
PyObject* result = PyRun_String(code.c_str(), Py_file_input, globalDict, globalDict);
|
|
532
|
+
if (!result) {
|
|
533
|
+
return _makeErrorResult();
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
PyRef resultRef = PyRef::steal(result);
|
|
537
|
+
TaskResult r;
|
|
538
|
+
r.success = true;
|
|
539
|
+
r.resultJson = "null";
|
|
540
|
+
return r;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
TaskResult PythonBridge::evalExpressionSync(const std::string& expression) {
|
|
544
|
+
PyObject* mainModule = PyImport_AddModule("__main__");
|
|
545
|
+
if (!mainModule) return _makeErrorResult();
|
|
546
|
+
|
|
547
|
+
PyObject* globalDict = PyModule_GetDict(mainModule); // borrowed
|
|
548
|
+
|
|
549
|
+
PyObject* result = PyRun_String(expression.c_str(), Py_eval_input, globalDict, globalDict);
|
|
550
|
+
if (!result) {
|
|
551
|
+
return _makeErrorResult();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
PyRef resultRef = PyRef::steal(result);
|
|
555
|
+
return _makeSuccessResult(result);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
TaskResult PythonBridge::runFileSync(const std::string& filePath) {
|
|
559
|
+
FILE* fp = fopen(filePath.c_str(), "r");
|
|
560
|
+
if (!fp) {
|
|
561
|
+
TaskResult r;
|
|
562
|
+
r.success = false;
|
|
563
|
+
r.errorType = "FileNotFoundError";
|
|
564
|
+
r.errorMessage = "Cannot open file: " + filePath;
|
|
565
|
+
return r;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
PyObject* mainModule = PyImport_AddModule("__main__");
|
|
569
|
+
PyObject* globalDict = PyModule_GetDict(mainModule);
|
|
570
|
+
|
|
571
|
+
PyObject* result = PyRun_File(fp, filePath.c_str(), Py_file_input, globalDict, globalDict);
|
|
572
|
+
fclose(fp);
|
|
573
|
+
|
|
574
|
+
if (!result) {
|
|
575
|
+
return _makeErrorResult();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
PyRef resultRef = PyRef::steal(result);
|
|
579
|
+
TaskResult r;
|
|
580
|
+
r.success = true;
|
|
581
|
+
r.resultJson = "null";
|
|
582
|
+
return r;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ─── Object Operations ───────────────────────────────────────────────────────
|
|
586
|
+
|
|
587
|
+
TaskResult PythonBridge::getObjectValueSync(uint32_t objectId) {
|
|
588
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
589
|
+
if (!obj) {
|
|
590
|
+
TaskResult r;
|
|
591
|
+
r.success = false;
|
|
592
|
+
r.errorType = "ValueError";
|
|
593
|
+
r.errorMessage = "Object not found: id=" + std::to_string(objectId);
|
|
594
|
+
return r;
|
|
595
|
+
}
|
|
596
|
+
return _makeSuccessResult(obj);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
std::string PythonBridge::getObjectReprSync(uint32_t objectId) {
|
|
600
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
601
|
+
if (!obj) return "<deleted object>";
|
|
602
|
+
return TypeConverter::getRepr(obj);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
TaskResult PythonBridge::getObjectLengthSync(uint32_t objectId) {
|
|
606
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
607
|
+
if (!obj) {
|
|
608
|
+
TaskResult r;
|
|
609
|
+
r.success = false;
|
|
610
|
+
r.errorType = "ValueError";
|
|
611
|
+
r.errorMessage = "Object not found";
|
|
612
|
+
return r;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
Py_ssize_t len = PyObject_Length(obj);
|
|
616
|
+
if (len < 0) {
|
|
617
|
+
return _makeErrorResult();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
TaskResult result;
|
|
621
|
+
result.success = true;
|
|
622
|
+
result.resultJson = std::to_string(len);
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
TaskResult PythonBridge::getObjectKeysSync(uint32_t objectId) {
|
|
627
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
628
|
+
if (!obj) {
|
|
629
|
+
TaskResult r;
|
|
630
|
+
r.success = false;
|
|
631
|
+
r.errorType = "ValueError";
|
|
632
|
+
r.errorMessage = "Object not found";
|
|
633
|
+
return r;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
PyObject* dirList = PyObject_Dir(obj);
|
|
637
|
+
if (!dirList) {
|
|
638
|
+
return _makeErrorResult();
|
|
639
|
+
}
|
|
640
|
+
PyRef dirRef = PyRef::steal(dirList);
|
|
641
|
+
|
|
642
|
+
std::ostringstream oss;
|
|
643
|
+
oss << "[";
|
|
644
|
+
Py_ssize_t size = PyList_Size(dirList);
|
|
645
|
+
bool first = true;
|
|
646
|
+
|
|
647
|
+
for (Py_ssize_t i = 0; i < size; ++i) {
|
|
648
|
+
PyObject* name = PyList_GetItem(dirList, i);
|
|
649
|
+
if (!name || !PyUnicode_Check(name)) continue;
|
|
650
|
+
|
|
651
|
+
const char* nameStr = PyUnicode_AsUTF8(name);
|
|
652
|
+
if (!nameStr) continue;
|
|
653
|
+
|
|
654
|
+
// Filter out private names
|
|
655
|
+
if (nameStr[0] == '_') continue;
|
|
656
|
+
|
|
657
|
+
if (!first) oss << ",";
|
|
658
|
+
first = false;
|
|
659
|
+
oss << "\"" << nameStr << "\"";
|
|
660
|
+
}
|
|
661
|
+
oss << "]";
|
|
662
|
+
|
|
663
|
+
TaskResult result;
|
|
664
|
+
result.success = true;
|
|
665
|
+
result.resultJson = oss.str();
|
|
666
|
+
return result;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// ─── Iterator Support ─────────────────────────────────────────────────────────
|
|
670
|
+
|
|
671
|
+
TaskResult PythonBridge::createIteratorSync(
|
|
672
|
+
uint32_t objectId,
|
|
673
|
+
const std::vector<std::string>& path
|
|
674
|
+
) {
|
|
675
|
+
PyObject* obj = TypeConverter::getObject(objectId);
|
|
676
|
+
if (!obj) {
|
|
677
|
+
TaskResult r;
|
|
678
|
+
r.success = false;
|
|
679
|
+
r.errorType = "ValueError";
|
|
680
|
+
r.errorMessage = "Object not found";
|
|
681
|
+
return r;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Resolve path
|
|
685
|
+
PyObject* target;
|
|
686
|
+
if (path.empty()) {
|
|
687
|
+
Py_INCREF(obj);
|
|
688
|
+
target = obj;
|
|
689
|
+
} else {
|
|
690
|
+
target = _resolveAttributePath(obj, path);
|
|
691
|
+
if (!target) return _makeErrorResult();
|
|
692
|
+
}
|
|
693
|
+
PyRef targetRef = PyRef::steal(target);
|
|
694
|
+
|
|
695
|
+
// Create iterator
|
|
696
|
+
PyObject* iter = PyObject_GetIter(target);
|
|
697
|
+
if (!iter) {
|
|
698
|
+
return _makeErrorResult();
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
uint32_t iteratorId = _nextIteratorId.fetch_add(1, std::memory_order_relaxed);
|
|
702
|
+
{
|
|
703
|
+
std::lock_guard<std::mutex> lock(_iteratorsMutex);
|
|
704
|
+
_iterators[iteratorId] = iter; // takes ownership
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
TaskResult result;
|
|
708
|
+
result.success = true;
|
|
709
|
+
result.objectId = iteratorId;
|
|
710
|
+
result.resultJson = "{\"iteratorId\":" + std::to_string(iteratorId) + "}";
|
|
711
|
+
return result;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
TaskResult PythonBridge::iteratorNextSync(uint32_t iteratorId) {
|
|
715
|
+
PyObject* iter = nullptr;
|
|
716
|
+
{
|
|
717
|
+
std::lock_guard<std::mutex> lock(_iteratorsMutex);
|
|
718
|
+
auto it = _iterators.find(iteratorId);
|
|
719
|
+
if (it == _iterators.end()) {
|
|
720
|
+
TaskResult r;
|
|
721
|
+
r.success = false;
|
|
722
|
+
r.errorType = "ValueError";
|
|
723
|
+
r.errorMessage = "Iterator not found: id=" + std::to_string(iteratorId);
|
|
724
|
+
return r;
|
|
725
|
+
}
|
|
726
|
+
iter = it->second;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
PyObject* next = PyIter_Next(iter);
|
|
730
|
+
|
|
731
|
+
if (!next) {
|
|
732
|
+
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
|
|
733
|
+
PyErr_Clear();
|
|
734
|
+
// Normal end of iteration
|
|
735
|
+
TaskResult result;
|
|
736
|
+
result.success = true;
|
|
737
|
+
result.resultJson = "{\"done\":true}";
|
|
738
|
+
return result;
|
|
739
|
+
}
|
|
740
|
+
return _makeErrorResult();
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
PyRef nextRef = PyRef::steal(next);
|
|
744
|
+
TaskResult itemResult = _makeSuccessResult(next);
|
|
745
|
+
|
|
746
|
+
if (itemResult.isObject) {
|
|
747
|
+
itemResult.resultJson = "{\"done\":false,\"value\":{\"__pyref__\":" +
|
|
748
|
+
std::to_string(itemResult.objectId) + "}}";
|
|
749
|
+
} else {
|
|
750
|
+
itemResult.resultJson = "{\"done\":false,\"value\":" + itemResult.resultJson + "}";
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return itemResult;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
void PythonBridge::destroyIteratorSync(uint32_t iteratorId) {
|
|
757
|
+
PyObject* iter = nullptr;
|
|
758
|
+
{
|
|
759
|
+
std::lock_guard<std::mutex> lock(_iteratorsMutex);
|
|
760
|
+
auto it = _iterators.find(iteratorId);
|
|
761
|
+
if (it != _iterators.end()) {
|
|
762
|
+
iter = it->second;
|
|
763
|
+
_iterators.erase(it);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
Py_XDECREF(iter);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// ─── Memory ─────────────────────────────────────────────────────────────────
|
|
770
|
+
|
|
771
|
+
void PythonBridge::collectGarbage() {
|
|
772
|
+
GILAcquire gil;
|
|
773
|
+
PyObject* gcModule = PyImport_ImportModule("gc");
|
|
774
|
+
if (gcModule) {
|
|
775
|
+
PyRef gcRef = PyRef::steal(gcModule);
|
|
776
|
+
PyObject* collect = PyObject_GetAttrString(gcModule, "collect");
|
|
777
|
+
if (collect) {
|
|
778
|
+
PyRef collectRef = PyRef::steal(collect);
|
|
779
|
+
PyObject* result = PyObject_CallNoArgs(collect);
|
|
780
|
+
Py_XDECREF(result);
|
|
781
|
+
} else { PyErr_Clear(); }
|
|
782
|
+
} else { PyErr_Clear(); }
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
size_t PythonBridge::getTrackedObjectCount() const {
|
|
786
|
+
return TypeConverter::getRegisteredCount();
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
} // namespace nodepyx
|
|
790
|
+
|