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,901 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* nodepyx — Type Converter Implementation
|
|
3
|
+
* Python ↔ JavaScript/JSON type conversion with object registry.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
#include "type_converter.h"
|
|
7
|
+
#include "gil_guard.h"
|
|
8
|
+
|
|
9
|
+
#include <Python.h>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
#include <map>
|
|
12
|
+
#include <mutex>
|
|
13
|
+
#include <atomic>
|
|
14
|
+
#include <cassert>
|
|
15
|
+
#include <cstring>
|
|
16
|
+
#include <algorithm>
|
|
17
|
+
#include <stdexcept>
|
|
18
|
+
|
|
19
|
+
namespace nodepyx {
|
|
20
|
+
|
|
21
|
+
// ─── Object Registry (Thread-safe) ──────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
struct RegistryEntry {
|
|
24
|
+
PyObject* obj;
|
|
25
|
+
std::atomic<int32_t> refCount{1};
|
|
26
|
+
|
|
27
|
+
explicit RegistryEntry(PyObject* o) : obj(o) {}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
static std::map<uint32_t, RegistryEntry*> g_registry;
|
|
31
|
+
static std::mutex g_registryMutex;
|
|
32
|
+
static std::atomic<uint32_t> g_nextId{1};
|
|
33
|
+
|
|
34
|
+
uint32_t TypeConverter::registerObject(PyObject* obj) {
|
|
35
|
+
if (!obj) return 0;
|
|
36
|
+
|
|
37
|
+
// GIL must be held by caller
|
|
38
|
+
Py_INCREF(obj);
|
|
39
|
+
|
|
40
|
+
uint32_t id = g_nextId.fetch_add(1, std::memory_order_relaxed);
|
|
41
|
+
|
|
42
|
+
std::lock_guard<std::mutex> lock(g_registryMutex);
|
|
43
|
+
g_registry.emplace(id, new RegistryEntry(obj));
|
|
44
|
+
|
|
45
|
+
return id;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
PyObject* TypeConverter::getObject(uint32_t id) {
|
|
49
|
+
std::lock_guard<std::mutex> lock(g_registryMutex);
|
|
50
|
+
auto it = g_registry.find(id);
|
|
51
|
+
if (it == g_registry.end()) return nullptr;
|
|
52
|
+
return it->second->obj;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void TypeConverter::incRef(uint32_t id) {
|
|
56
|
+
std::lock_guard<std::mutex> lock(g_registryMutex);
|
|
57
|
+
auto it = g_registry.find(id);
|
|
58
|
+
if (it != g_registry.end()) {
|
|
59
|
+
it->second->refCount.fetch_add(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
void TypeConverter::decRef(uint32_t id) {
|
|
64
|
+
RegistryEntry* toDelete = nullptr;
|
|
65
|
+
{
|
|
66
|
+
std::lock_guard<std::mutex> lock(g_registryMutex);
|
|
67
|
+
auto it = g_registry.find(id);
|
|
68
|
+
if (it == g_registry.end()) return;
|
|
69
|
+
|
|
70
|
+
int32_t newCount = it->second->refCount.fetch_sub(1) - 1;
|
|
71
|
+
if (newCount <= 0) {
|
|
72
|
+
toDelete = it->second;
|
|
73
|
+
g_registry.erase(it);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (toDelete) {
|
|
78
|
+
// Must hold GIL to decref Python object
|
|
79
|
+
GILAcquire gil;
|
|
80
|
+
Py_XDECREF(toDelete->obj);
|
|
81
|
+
delete toDelete;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
size_t TypeConverter::getRegisteredCount() {
|
|
86
|
+
std::lock_guard<std::mutex> lock(g_registryMutex);
|
|
87
|
+
return g_registry.size();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
void TypeConverter::clearRegistry() {
|
|
91
|
+
std::map<uint32_t, RegistryEntry*> toDelete;
|
|
92
|
+
{
|
|
93
|
+
std::lock_guard<std::mutex> lock(g_registryMutex);
|
|
94
|
+
toDelete.swap(g_registry);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Decref all objects with GIL held
|
|
98
|
+
GILAcquire gil;
|
|
99
|
+
for (auto& [id, entry] : toDelete) {
|
|
100
|
+
Py_XDECREF(entry->obj);
|
|
101
|
+
delete entry;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ─── Type Name ─────────────────────────────────────────────────────────────
|
|
106
|
+
|
|
107
|
+
std::string TypeConverter::getTypeName(PyObject* obj) {
|
|
108
|
+
if (!obj) return "NoneType";
|
|
109
|
+
PyObject* type = reinterpret_cast<PyObject*>(Py_TYPE(obj));
|
|
110
|
+
PyObject* name = PyObject_GetAttrString(type, "__name__");
|
|
111
|
+
if (!name) {
|
|
112
|
+
PyErr_Clear();
|
|
113
|
+
return "unknown";
|
|
114
|
+
}
|
|
115
|
+
PyRef nameRef = PyRef::steal(name);
|
|
116
|
+
const char* str = PyUnicode_AsUTF8(name);
|
|
117
|
+
if (!str) {
|
|
118
|
+
PyErr_Clear();
|
|
119
|
+
return "unknown";
|
|
120
|
+
}
|
|
121
|
+
return str;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
std::string TypeConverter::getQualifiedTypeName(PyObject* obj) {
|
|
125
|
+
if (!obj) return "NoneType";
|
|
126
|
+
PyObject* type = reinterpret_cast<PyObject*>(Py_TYPE(obj));
|
|
127
|
+
|
|
128
|
+
// Try __module__.__qualname__ first
|
|
129
|
+
PyObject* module = PyObject_GetAttrString(type, "__module__");
|
|
130
|
+
PyObject* qualname = PyObject_GetAttrString(type, "__qualname__");
|
|
131
|
+
PyErr_Clear();
|
|
132
|
+
|
|
133
|
+
std::string result;
|
|
134
|
+
if (module && PyUnicode_Check(module)) {
|
|
135
|
+
const char* mod = PyUnicode_AsUTF8(module);
|
|
136
|
+
if (mod) {
|
|
137
|
+
result = std::string(mod) + ".";
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (qualname && PyUnicode_Check(qualname)) {
|
|
141
|
+
const char* qn = PyUnicode_AsUTF8(qualname);
|
|
142
|
+
if (qn) result += qn;
|
|
143
|
+
} else {
|
|
144
|
+
result += getTypeName(obj);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
Py_XDECREF(module);
|
|
148
|
+
Py_XDECREF(qualname);
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
bool TypeConverter::isCallable(PyObject* obj) {
|
|
153
|
+
return obj && PyCallable_Check(obj);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
bool TypeConverter::isIterable(PyObject* obj) {
|
|
157
|
+
if (!obj) return false;
|
|
158
|
+
PyObject* iter = PyObject_GetIter(obj);
|
|
159
|
+
if (!iter) {
|
|
160
|
+
PyErr_Clear();
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
Py_DECREF(iter);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
bool TypeConverter::isAsyncIterable(PyObject* obj) {
|
|
168
|
+
if (!obj) return false;
|
|
169
|
+
// Check for __aiter__ method
|
|
170
|
+
return PyObject_HasAttrString(obj, "__aiter__") == 1;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
bool TypeConverter::isModule(PyObject* obj) {
|
|
174
|
+
return obj && PyModule_Check(obj);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
bool TypeConverter::isClass(PyObject* obj) {
|
|
178
|
+
return obj && PyType_Check(obj);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ─── Type Checks (Library-specific) ─────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
bool TypeConverter::isNumpyArray(PyObject* obj) {
|
|
184
|
+
if (!obj) return false;
|
|
185
|
+
// Check by type name to avoid hard dependency on numpy C API
|
|
186
|
+
std::string typeName = getQualifiedTypeName(obj);
|
|
187
|
+
return typeName == "numpy.ndarray" || typeName == "numpy.core.multiarray.ndarray";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
bool TypeConverter::isDataFrame(PyObject* obj) {
|
|
191
|
+
if (!obj) return false;
|
|
192
|
+
std::string typeName = getQualifiedTypeName(obj);
|
|
193
|
+
return typeName == "pandas.core.frame.DataFrame" ||
|
|
194
|
+
typeName.find("DataFrame") != std::string::npos;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
bool TypeConverter::isSeries(PyObject* obj) {
|
|
198
|
+
if (!obj) return false;
|
|
199
|
+
std::string typeName = getQualifiedTypeName(obj);
|
|
200
|
+
return typeName == "pandas.core.series.Series" ||
|
|
201
|
+
(typeName.find("Series") != std::string::npos &&
|
|
202
|
+
typeName.find("pandas") != std::string::npos);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
bool TypeConverter::isTorchTensor(PyObject* obj) {
|
|
206
|
+
if (!obj) return false;
|
|
207
|
+
std::string typeName = getQualifiedTypeName(obj);
|
|
208
|
+
return typeName == "torch.Tensor" ||
|
|
209
|
+
typeName == "torch._C._TensorBase";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ─── Python → SerializedValue ───────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
SerializedValue TypeConverter::pythonToSerialized(PyObject* obj, int depth) {
|
|
215
|
+
SerializedValue result;
|
|
216
|
+
|
|
217
|
+
if (depth > MAX_DEPTH) {
|
|
218
|
+
// Too deep — register as opaque ref
|
|
219
|
+
result.format = SerializedFormat::PYTHON_REF;
|
|
220
|
+
result.metadata.objectId = registerObject(obj);
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// None
|
|
225
|
+
if (obj == Py_None) {
|
|
226
|
+
result.format = SerializedFormat::JSON;
|
|
227
|
+
result.jsonData = "null";
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Bool (must check before int!)
|
|
232
|
+
if (PyBool_Check(obj)) {
|
|
233
|
+
result.format = SerializedFormat::JSON;
|
|
234
|
+
result.jsonData = (obj == Py_True) ? "true" : "false";
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Integer
|
|
239
|
+
if (PyLong_Check(obj)) {
|
|
240
|
+
long long val = PyLong_AsLongLong(obj);
|
|
241
|
+
if (PyErr_Occurred()) {
|
|
242
|
+
PyErr_Clear();
|
|
243
|
+
// Try as unsigned long long
|
|
244
|
+
unsigned long long uval = PyLong_AsUnsignedLongLong(obj);
|
|
245
|
+
if (PyErr_Occurred()) {
|
|
246
|
+
PyErr_Clear();
|
|
247
|
+
// Very large integer — convert to string
|
|
248
|
+
PyObject* strObj = PyObject_Str(obj);
|
|
249
|
+
if (strObj) {
|
|
250
|
+
PyRef strRef = PyRef::steal(strObj);
|
|
251
|
+
result.format = SerializedFormat::JSON;
|
|
252
|
+
result.jsonData = "\"" + std::string(PyUnicode_AsUTF8(strObj)) + "\"";
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
result.format = SerializedFormat::JSON;
|
|
257
|
+
result.jsonData = std::to_string(uval);
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
result.format = SerializedFormat::JSON;
|
|
261
|
+
result.jsonData = std::to_string(val);
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Float
|
|
266
|
+
if (PyFloat_Check(obj)) {
|
|
267
|
+
double val = PyFloat_AsDouble(obj);
|
|
268
|
+
if (val != val) { // NaN
|
|
269
|
+
result.format = SerializedFormat::JSON;
|
|
270
|
+
result.jsonData = "null"; // JSON doesn't have NaN
|
|
271
|
+
} else if (val == std::numeric_limits<double>::infinity()) {
|
|
272
|
+
result.format = SerializedFormat::JSON;
|
|
273
|
+
result.jsonData = "1e308"; // Large number approximation
|
|
274
|
+
} else if (val == -std::numeric_limits<double>::infinity()) {
|
|
275
|
+
result.format = SerializedFormat::JSON;
|
|
276
|
+
result.jsonData = "-1e308";
|
|
277
|
+
} else {
|
|
278
|
+
result.format = SerializedFormat::JSON;
|
|
279
|
+
std::ostringstream oss;
|
|
280
|
+
oss.precision(17);
|
|
281
|
+
oss << val;
|
|
282
|
+
result.jsonData = oss.str();
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// String
|
|
288
|
+
if (PyUnicode_Check(obj)) {
|
|
289
|
+
const char* str = PyUnicode_AsUTF8(obj);
|
|
290
|
+
if (!str) {
|
|
291
|
+
PyErr_Clear();
|
|
292
|
+
result.format = SerializedFormat::JSON;
|
|
293
|
+
result.jsonData = "\"\"";
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
result.format = SerializedFormat::JSON;
|
|
297
|
+
result.jsonData = "\"" + jsonEscape(str) + "\"";
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Bytes
|
|
302
|
+
if (PyBytes_Check(obj)) {
|
|
303
|
+
Py_ssize_t size = PyBytes_Size(obj);
|
|
304
|
+
const char* data = PyBytes_AsString(obj);
|
|
305
|
+
result.format = SerializedFormat::BYTES;
|
|
306
|
+
result.binaryData.assign(data, data + size);
|
|
307
|
+
result.metadata.size_bytes = size;
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Numpy array — check before list/tuple
|
|
312
|
+
if (isNumpyArray(obj)) {
|
|
313
|
+
return numpyArrayToBuffer(obj);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Pandas DataFrame
|
|
317
|
+
if (isDataFrame(obj)) {
|
|
318
|
+
return dataFrameToBuffer(obj);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Pandas Series
|
|
322
|
+
if (isSeries(obj)) {
|
|
323
|
+
return seriesToBuffer(obj);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// List — serialize as JSON if small enough
|
|
327
|
+
if (PyList_Check(obj)) {
|
|
328
|
+
Py_ssize_t size = PyList_Size(obj);
|
|
329
|
+
if (size <= static_cast<Py_ssize_t>(MAX_INLINE_SIZE)) {
|
|
330
|
+
result.format = SerializedFormat::JSON;
|
|
331
|
+
result.jsonData = listToJson(obj, depth + 1);
|
|
332
|
+
} else {
|
|
333
|
+
// Large list — serialize as msgpack (fallback to JSON for now)
|
|
334
|
+
result.format = SerializedFormat::JSON;
|
|
335
|
+
result.jsonData = listToJson(obj, depth + 1);
|
|
336
|
+
}
|
|
337
|
+
result.metadata.length = size;
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Tuple
|
|
342
|
+
if (PyTuple_Check(obj)) {
|
|
343
|
+
result.format = SerializedFormat::JSON;
|
|
344
|
+
// Convert tuple to JSON array
|
|
345
|
+
PyObject* list = PySequence_List(obj);
|
|
346
|
+
if (list) {
|
|
347
|
+
PyRef listRef = PyRef::steal(list);
|
|
348
|
+
result.jsonData = listToJson(list, depth + 1);
|
|
349
|
+
result.metadata.length = PyList_Size(list);
|
|
350
|
+
} else {
|
|
351
|
+
PyErr_Clear();
|
|
352
|
+
result.jsonData = "[]";
|
|
353
|
+
}
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Dict
|
|
358
|
+
if (PyDict_Check(obj)) {
|
|
359
|
+
result.format = SerializedFormat::JSON;
|
|
360
|
+
result.jsonData = dictToJson(obj, depth + 1);
|
|
361
|
+
result.metadata.length = PyDict_Size(obj);
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Everything else → opaque Python reference
|
|
366
|
+
result.format = SerializedFormat::PYTHON_REF;
|
|
367
|
+
result.metadata.objectId = registerObject(obj);
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ─── Collection serialization ───────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
std::string TypeConverter::listToJson(PyObject* seq, int depth) {
|
|
374
|
+
std::ostringstream oss;
|
|
375
|
+
oss << "[";
|
|
376
|
+
|
|
377
|
+
Py_ssize_t size = PySequence_Length(seq);
|
|
378
|
+
for (Py_ssize_t i = 0; i < size; ++i) {
|
|
379
|
+
if (i > 0) oss << ",";
|
|
380
|
+
|
|
381
|
+
PyObject* item = PySequence_GetItem(seq, i);
|
|
382
|
+
if (!item) {
|
|
383
|
+
PyErr_Clear();
|
|
384
|
+
oss << "null";
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
PyRef itemRef = PyRef::steal(item);
|
|
388
|
+
|
|
389
|
+
SerializedValue sv = pythonToSerialized(item, depth);
|
|
390
|
+
if (sv.format == SerializedFormat::JSON) {
|
|
391
|
+
oss << sv.jsonData;
|
|
392
|
+
} else {
|
|
393
|
+
// Non-JSON item in list — represent as object ref
|
|
394
|
+
oss << "{\"__pyref__\":" << sv.metadata.objectId << "}";
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
oss << "]";
|
|
399
|
+
return oss.str();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
std::string TypeConverter::dictToJson(PyObject* mapping, int depth) {
|
|
403
|
+
std::ostringstream oss;
|
|
404
|
+
oss << "{";
|
|
405
|
+
|
|
406
|
+
PyObject* keys = PyMapping_Keys(mapping);
|
|
407
|
+
if (!keys) {
|
|
408
|
+
PyErr_Clear();
|
|
409
|
+
return "{}";
|
|
410
|
+
}
|
|
411
|
+
PyRef keysRef = PyRef::steal(keys);
|
|
412
|
+
|
|
413
|
+
Py_ssize_t size = PyList_Size(keys);
|
|
414
|
+
bool first = true;
|
|
415
|
+
|
|
416
|
+
for (Py_ssize_t i = 0; i < size; ++i) {
|
|
417
|
+
PyObject* key = PyList_GetItem(keys, i); // borrowed
|
|
418
|
+
if (!key) continue;
|
|
419
|
+
|
|
420
|
+
// Convert key to string
|
|
421
|
+
std::string keyStr;
|
|
422
|
+
if (PyUnicode_Check(key)) {
|
|
423
|
+
const char* k = PyUnicode_AsUTF8(key);
|
|
424
|
+
if (k) keyStr = k;
|
|
425
|
+
} else {
|
|
426
|
+
PyObject* strKey = PyObject_Str(key);
|
|
427
|
+
if (strKey) {
|
|
428
|
+
PyRef strRef = PyRef::steal(strKey);
|
|
429
|
+
const char* k = PyUnicode_AsUTF8(strKey);
|
|
430
|
+
if (k) keyStr = k;
|
|
431
|
+
} else {
|
|
432
|
+
PyErr_Clear();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
PyObject* value = PyObject_GetItem(mapping, key);
|
|
437
|
+
if (!value) {
|
|
438
|
+
PyErr_Clear();
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
PyRef valueRef = PyRef::steal(value);
|
|
442
|
+
|
|
443
|
+
if (!first) oss << ",";
|
|
444
|
+
first = false;
|
|
445
|
+
|
|
446
|
+
oss << "\"" << jsonEscape(keyStr) << "\":";
|
|
447
|
+
|
|
448
|
+
SerializedValue sv = pythonToSerialized(value, depth);
|
|
449
|
+
if (sv.format == SerializedFormat::JSON) {
|
|
450
|
+
oss << sv.jsonData;
|
|
451
|
+
} else {
|
|
452
|
+
oss << "{\"__pyref__\":" << sv.metadata.objectId << "}";
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
oss << "}";
|
|
457
|
+
return oss.str();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ─── Numpy Array Serialization ───────────────────────────────────────────────
|
|
461
|
+
|
|
462
|
+
SerializedValue TypeConverter::numpyArrayToBuffer(PyObject* array) {
|
|
463
|
+
SerializedValue result;
|
|
464
|
+
result.format = SerializedFormat::NUMPY_ARRAY;
|
|
465
|
+
|
|
466
|
+
// Get dtype name
|
|
467
|
+
PyObject* dtypeObj = PyObject_GetAttrString(array, "dtype");
|
|
468
|
+
if (dtypeObj) {
|
|
469
|
+
PyRef dtypeRef = PyRef::steal(dtypeObj);
|
|
470
|
+
PyObject* dtypeName = PyObject_GetAttrString(dtypeObj, "name");
|
|
471
|
+
if (dtypeName) {
|
|
472
|
+
PyRef nameRef = PyRef::steal(dtypeName);
|
|
473
|
+
const char* name = PyUnicode_AsUTF8(dtypeName);
|
|
474
|
+
if (name) result.metadata.dtype = name;
|
|
475
|
+
} else { PyErr_Clear(); }
|
|
476
|
+
} else { PyErr_Clear(); }
|
|
477
|
+
|
|
478
|
+
// Get shape
|
|
479
|
+
PyObject* shapeObj = PyObject_GetAttrString(array, "shape");
|
|
480
|
+
if (shapeObj) {
|
|
481
|
+
PyRef shapeRef = PyRef::steal(shapeObj);
|
|
482
|
+
Py_ssize_t ndim = PyTuple_Size(shapeObj);
|
|
483
|
+
for (Py_ssize_t i = 0; i < ndim; ++i) {
|
|
484
|
+
PyObject* dim = PyTuple_GetItem(shapeObj, i);
|
|
485
|
+
result.metadata.shape.push_back(PyLong_AsLongLong(dim));
|
|
486
|
+
}
|
|
487
|
+
} else { PyErr_Clear(); }
|
|
488
|
+
|
|
489
|
+
// Get the raw data buffer via numpy's tobytes() method
|
|
490
|
+
PyObject* tobytes = PyObject_GetAttrString(array, "tobytes");
|
|
491
|
+
if (tobytes) {
|
|
492
|
+
PyRef tobytesRef = PyRef::steal(tobytes);
|
|
493
|
+
PyObject* bytesObj = PyObject_CallNoArgs(tobytes);
|
|
494
|
+
if (bytesObj) {
|
|
495
|
+
PyRef bytesRef = PyRef::steal(bytesObj);
|
|
496
|
+
char* buffer;
|
|
497
|
+
Py_ssize_t bufLen;
|
|
498
|
+
if (PyBytes_AsStringAndSize(bytesObj, &buffer, &bufLen) == 0) {
|
|
499
|
+
result.binaryData.assign(buffer, buffer + bufLen);
|
|
500
|
+
result.metadata.size_bytes = bufLen;
|
|
501
|
+
} else {
|
|
502
|
+
PyErr_Clear();
|
|
503
|
+
}
|
|
504
|
+
} else { PyErr_Clear(); }
|
|
505
|
+
} else { PyErr_Clear(); }
|
|
506
|
+
|
|
507
|
+
// Get contiguity info
|
|
508
|
+
PyObject* flagsObj = PyObject_GetAttrString(array, "flags");
|
|
509
|
+
if (flagsObj) {
|
|
510
|
+
PyRef flagsRef = PyRef::steal(flagsObj);
|
|
511
|
+
PyObject* cContig = PyObject_GetAttrString(flagsObj, "c_contiguous");
|
|
512
|
+
if (cContig) {
|
|
513
|
+
PyRef ccRef = PyRef::steal(cContig);
|
|
514
|
+
result.metadata.isC_contiguous = PyObject_IsTrue(cContig) == 1;
|
|
515
|
+
} else { PyErr_Clear(); }
|
|
516
|
+
} else { PyErr_Clear(); }
|
|
517
|
+
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ─── Pandas DataFrame Serialization ─────────────────────────────────────────
|
|
522
|
+
|
|
523
|
+
SerializedValue TypeConverter::dataFrameToBuffer(PyObject* df) {
|
|
524
|
+
SerializedValue result;
|
|
525
|
+
result.format = SerializedFormat::PANDAS_DATAFRAME;
|
|
526
|
+
|
|
527
|
+
// Get column names
|
|
528
|
+
PyObject* columns = PyObject_GetAttrString(df, "columns");
|
|
529
|
+
if (columns) {
|
|
530
|
+
PyRef colRef = PyRef::steal(columns);
|
|
531
|
+
PyObject* tolist = PyObject_GetAttrString(columns, "tolist");
|
|
532
|
+
if (tolist) {
|
|
533
|
+
PyRef tolistRef = PyRef::steal(tolist);
|
|
534
|
+
PyObject* colList = PyObject_CallNoArgs(tolist);
|
|
535
|
+
if (colList) {
|
|
536
|
+
PyRef colListRef = PyRef::steal(colList);
|
|
537
|
+
Py_ssize_t ncols = PyList_Size(colList);
|
|
538
|
+
for (Py_ssize_t i = 0; i < ncols; ++i) {
|
|
539
|
+
PyObject* col = PyList_GetItem(colList, i);
|
|
540
|
+
if (col && PyUnicode_Check(col)) {
|
|
541
|
+
const char* colName = PyUnicode_AsUTF8(col);
|
|
542
|
+
if (colName) result.metadata.columns.push_back(colName);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
} else { PyErr_Clear(); }
|
|
546
|
+
} else { PyErr_Clear(); }
|
|
547
|
+
} else { PyErr_Clear(); }
|
|
548
|
+
|
|
549
|
+
// Serialize to JSON via to_json() for broad compatibility
|
|
550
|
+
// In production, use Arrow format for better performance
|
|
551
|
+
PyObject* toJson = PyObject_GetAttrString(df, "to_json");
|
|
552
|
+
if (toJson) {
|
|
553
|
+
PyRef toJsonRef = PyRef::steal(toJson);
|
|
554
|
+
|
|
555
|
+
// Call df.to_json(orient='records')
|
|
556
|
+
PyObject* kwargs = PyDict_New();
|
|
557
|
+
PyObject* orientStr = PyUnicode_FromString("records");
|
|
558
|
+
PyDict_SetItemString(kwargs, "orient", orientStr);
|
|
559
|
+
Py_DECREF(orientStr);
|
|
560
|
+
|
|
561
|
+
PyObject* jsonStr = PyObject_Call(toJson, PyTuple_New(0), kwargs);
|
|
562
|
+
Py_DECREF(kwargs);
|
|
563
|
+
|
|
564
|
+
if (jsonStr) {
|
|
565
|
+
PyRef jsonRef = PyRef::steal(jsonStr);
|
|
566
|
+
const char* json = PyUnicode_AsUTF8(jsonStr);
|
|
567
|
+
if (json) {
|
|
568
|
+
result.jsonData = json;
|
|
569
|
+
result.format = SerializedFormat::PANDAS_DATAFRAME;
|
|
570
|
+
}
|
|
571
|
+
} else { PyErr_Clear(); }
|
|
572
|
+
} else { PyErr_Clear(); }
|
|
573
|
+
|
|
574
|
+
return result;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// ─── Pandas Series Serialization ─────────────────────────────────────────────
|
|
578
|
+
|
|
579
|
+
SerializedValue TypeConverter::seriesToBuffer(PyObject* series) {
|
|
580
|
+
SerializedValue result;
|
|
581
|
+
result.format = SerializedFormat::PANDAS_SERIES;
|
|
582
|
+
|
|
583
|
+
PyObject* toJson = PyObject_GetAttrString(series, "to_json");
|
|
584
|
+
if (toJson) {
|
|
585
|
+
PyRef toJsonRef = PyRef::steal(toJson);
|
|
586
|
+
PyObject* jsonStr = PyObject_CallNoArgs(toJson);
|
|
587
|
+
if (jsonStr) {
|
|
588
|
+
PyRef jsonRef = PyRef::steal(jsonStr);
|
|
589
|
+
const char* json = PyUnicode_AsUTF8(jsonStr);
|
|
590
|
+
if (json) result.jsonData = json;
|
|
591
|
+
} else { PyErr_Clear(); }
|
|
592
|
+
} else { PyErr_Clear(); }
|
|
593
|
+
|
|
594
|
+
// Get series name
|
|
595
|
+
PyObject* nameObj = PyObject_GetAttrString(series, "name");
|
|
596
|
+
if (nameObj && nameObj != Py_None) {
|
|
597
|
+
PyRef nameRef = PyRef::steal(nameObj);
|
|
598
|
+
if (PyUnicode_Check(nameObj)) {
|
|
599
|
+
const char* name = PyUnicode_AsUTF8(nameObj);
|
|
600
|
+
if (name) result.metadata.columns.push_back(name);
|
|
601
|
+
}
|
|
602
|
+
} else if (nameObj) {
|
|
603
|
+
Py_DECREF(nameObj);
|
|
604
|
+
}
|
|
605
|
+
PyErr_Clear();
|
|
606
|
+
|
|
607
|
+
return result;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// ─── JSON Deserialization ───────────────────────────────────────────────────
|
|
611
|
+
|
|
612
|
+
PyObject* TypeConverter::jsonToPython(const std::string& json) {
|
|
613
|
+
// Use Python's built-in json module
|
|
614
|
+
PyObject* jsonModule = PyImport_ImportModule("json");
|
|
615
|
+
if (!jsonModule) {
|
|
616
|
+
PyErr_Clear();
|
|
617
|
+
Py_RETURN_NONE;
|
|
618
|
+
}
|
|
619
|
+
PyRef jsonRef = PyRef::steal(jsonModule);
|
|
620
|
+
|
|
621
|
+
PyObject* loads = PyObject_GetAttrString(jsonModule, "loads");
|
|
622
|
+
if (!loads) {
|
|
623
|
+
PyErr_Clear();
|
|
624
|
+
Py_RETURN_NONE;
|
|
625
|
+
}
|
|
626
|
+
PyRef loadsRef = PyRef::steal(loads);
|
|
627
|
+
|
|
628
|
+
PyObject* strObj = PyUnicode_FromStringAndSize(json.c_str(), json.size());
|
|
629
|
+
if (!strObj) {
|
|
630
|
+
PyErr_Clear();
|
|
631
|
+
Py_RETURN_NONE;
|
|
632
|
+
}
|
|
633
|
+
PyRef strRef = PyRef::steal(strObj);
|
|
634
|
+
|
|
635
|
+
PyObject* result = PyObject_CallOneArg(loads, strObj);
|
|
636
|
+
if (!result) {
|
|
637
|
+
PyErr_Clear();
|
|
638
|
+
Py_RETURN_NONE;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return result; // new reference
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
PyObject* TypeConverter::msgpackToPython(const std::vector<uint8_t>& data) {
|
|
645
|
+
// Use msgpack Python library
|
|
646
|
+
PyObject* msgpackModule = PyImport_ImportModule("msgpack");
|
|
647
|
+
if (!msgpackModule) {
|
|
648
|
+
PyErr_Clear();
|
|
649
|
+
// Fallback: treat as bytes
|
|
650
|
+
return PyBytes_FromStringAndSize(
|
|
651
|
+
reinterpret_cast<const char*>(data.data()),
|
|
652
|
+
data.size()
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
PyRef mpRef = PyRef::steal(msgpackModule);
|
|
656
|
+
|
|
657
|
+
PyObject* unpackb = PyObject_GetAttrString(msgpackModule, "unpackb");
|
|
658
|
+
if (!unpackb) {
|
|
659
|
+
PyErr_Clear();
|
|
660
|
+
return PyBytes_FromStringAndSize(
|
|
661
|
+
reinterpret_cast<const char*>(data.data()),
|
|
662
|
+
data.size()
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
PyRef unpackbRef = PyRef::steal(unpackb);
|
|
666
|
+
|
|
667
|
+
PyObject* bytesObj = PyBytes_FromStringAndSize(
|
|
668
|
+
reinterpret_cast<const char*>(data.data()),
|
|
669
|
+
data.size()
|
|
670
|
+
);
|
|
671
|
+
if (!bytesObj) return nullptr;
|
|
672
|
+
PyRef bytesRef = PyRef::steal(bytesObj);
|
|
673
|
+
|
|
674
|
+
PyObject* kwargs = PyDict_New();
|
|
675
|
+
PyObject* rawFalse = Py_False;
|
|
676
|
+
Py_INCREF(rawFalse);
|
|
677
|
+
PyDict_SetItemString(kwargs, "raw", rawFalse);
|
|
678
|
+
Py_DECREF(rawFalse);
|
|
679
|
+
|
|
680
|
+
PyObject* result = PyObject_Call(unpackb, PyTuple_Pack(1, bytesObj), kwargs);
|
|
681
|
+
Py_DECREF(kwargs);
|
|
682
|
+
|
|
683
|
+
if (!result) {
|
|
684
|
+
PyErr_Clear();
|
|
685
|
+
Py_RETURN_NONE;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return result;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
PyObject* TypeConverter::bufferToNumpyArray(
|
|
692
|
+
const std::vector<uint8_t>& data,
|
|
693
|
+
const std::string& dtype,
|
|
694
|
+
const std::vector<int64_t>& shape
|
|
695
|
+
) {
|
|
696
|
+
PyObject* np = PyImport_ImportModule("numpy");
|
|
697
|
+
if (!np) {
|
|
698
|
+
PyErr_Clear();
|
|
699
|
+
return nullptr;
|
|
700
|
+
}
|
|
701
|
+
PyRef npRef = PyRef::steal(np);
|
|
702
|
+
|
|
703
|
+
PyObject* frombuffer = PyObject_GetAttrString(np, "frombuffer");
|
|
704
|
+
if (!frombuffer) {
|
|
705
|
+
PyErr_Clear();
|
|
706
|
+
return nullptr;
|
|
707
|
+
}
|
|
708
|
+
PyRef frombufferRef = PyRef::steal(frombuffer);
|
|
709
|
+
|
|
710
|
+
PyObject* bytesObj = PyBytes_FromStringAndSize(
|
|
711
|
+
reinterpret_cast<const char*>(data.data()),
|
|
712
|
+
data.size()
|
|
713
|
+
);
|
|
714
|
+
if (!bytesObj) return nullptr;
|
|
715
|
+
PyRef bytesRef = PyRef::steal(bytesObj);
|
|
716
|
+
|
|
717
|
+
PyObject* dtypeStr = PyUnicode_FromString(dtype.c_str());
|
|
718
|
+
PyRef dtypeRef = PyRef::steal(dtypeStr);
|
|
719
|
+
|
|
720
|
+
PyObject* kwargs = PyDict_New();
|
|
721
|
+
PyDict_SetItemString(kwargs, "dtype", dtypeStr);
|
|
722
|
+
|
|
723
|
+
PyObject* arr = PyObject_Call(frombuffer, PyTuple_Pack(1, bytesObj), kwargs);
|
|
724
|
+
Py_DECREF(kwargs);
|
|
725
|
+
|
|
726
|
+
if (!arr) {
|
|
727
|
+
PyErr_Clear();
|
|
728
|
+
return nullptr;
|
|
729
|
+
}
|
|
730
|
+
PyRef arrRef = PyRef::steal(arr);
|
|
731
|
+
|
|
732
|
+
// Reshape if needed
|
|
733
|
+
if (shape.size() > 1) {
|
|
734
|
+
PyObject* reshape = PyObject_GetAttrString(arr, "reshape");
|
|
735
|
+
if (reshape) {
|
|
736
|
+
PyRef reshapeRef = PyRef::steal(reshape);
|
|
737
|
+
|
|
738
|
+
PyObject* shapeList = PyList_New(shape.size());
|
|
739
|
+
for (size_t i = 0; i < shape.size(); ++i) {
|
|
740
|
+
PyList_SetItem(shapeList, i, PyLong_FromLongLong(shape[i]));
|
|
741
|
+
}
|
|
742
|
+
PyRef shapeListRef = PyRef::steal(shapeList);
|
|
743
|
+
|
|
744
|
+
PyObject* reshaped = PyObject_CallOneArg(reshape, shapeList);
|
|
745
|
+
if (reshaped) {
|
|
746
|
+
return reshaped;
|
|
747
|
+
}
|
|
748
|
+
PyErr_Clear();
|
|
749
|
+
} else {
|
|
750
|
+
PyErr_Clear();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return arrRef.release();
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// ─── String / Repr ──────────────────────────────────────────────────────────
|
|
758
|
+
|
|
759
|
+
std::string TypeConverter::getRepr(PyObject* obj) {
|
|
760
|
+
if (!obj) return "<null>";
|
|
761
|
+
PyObject* repr = PyObject_Repr(obj);
|
|
762
|
+
if (!repr) {
|
|
763
|
+
PyErr_Clear();
|
|
764
|
+
return "<error getting repr>";
|
|
765
|
+
}
|
|
766
|
+
PyRef reprRef = PyRef::steal(repr);
|
|
767
|
+
const char* str = PyUnicode_AsUTF8(repr);
|
|
768
|
+
if (!str) {
|
|
769
|
+
PyErr_Clear();
|
|
770
|
+
return "<encoding error>";
|
|
771
|
+
}
|
|
772
|
+
return str;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
std::string TypeConverter::getStr(PyObject* obj) {
|
|
776
|
+
if (!obj) return "None";
|
|
777
|
+
PyObject* str = PyObject_Str(obj);
|
|
778
|
+
if (!str) {
|
|
779
|
+
PyErr_Clear();
|
|
780
|
+
return "<error>";
|
|
781
|
+
}
|
|
782
|
+
PyRef strRef = PyRef::steal(str);
|
|
783
|
+
const char* cstr = PyUnicode_AsUTF8(str);
|
|
784
|
+
if (!cstr) {
|
|
785
|
+
PyErr_Clear();
|
|
786
|
+
return "";
|
|
787
|
+
}
|
|
788
|
+
return cstr;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// ─── Exception Capture ──────────────────────────────────────────────────────
|
|
792
|
+
|
|
793
|
+
std::tuple<std::string, std::string, std::string> TypeConverter::captureException() {
|
|
794
|
+
std::string typeName, message, traceback;
|
|
795
|
+
|
|
796
|
+
PyObject* excType = nullptr;
|
|
797
|
+
PyObject* excValue = nullptr;
|
|
798
|
+
PyObject* excTb = nullptr;
|
|
799
|
+
|
|
800
|
+
PyErr_Fetch(&excType, &excValue, &excTb);
|
|
801
|
+
PyErr_NormalizeException(&excType, &excValue, &excTb);
|
|
802
|
+
|
|
803
|
+
PyRef typeRef = PyRef::steal(excType);
|
|
804
|
+
PyRef valueRef = PyRef::steal(excValue);
|
|
805
|
+
PyRef tbRef = PyRef::steal(excTb);
|
|
806
|
+
|
|
807
|
+
// Get exception type name
|
|
808
|
+
if (excType) {
|
|
809
|
+
PyObject* name = PyObject_GetAttrString(excType, "__name__");
|
|
810
|
+
if (name) {
|
|
811
|
+
PyRef nameRef = PyRef::steal(name);
|
|
812
|
+
const char* n = PyUnicode_AsUTF8(name);
|
|
813
|
+
if (n) typeName = n;
|
|
814
|
+
} else { PyErr_Clear(); }
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Get exception message
|
|
818
|
+
if (excValue) {
|
|
819
|
+
PyObject* str = PyObject_Str(excValue);
|
|
820
|
+
if (str) {
|
|
821
|
+
PyRef strRef = PyRef::steal(str);
|
|
822
|
+
const char* s = PyUnicode_AsUTF8(str);
|
|
823
|
+
if (s) message = s;
|
|
824
|
+
} else { PyErr_Clear(); }
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Get traceback
|
|
828
|
+
if (excTb) {
|
|
829
|
+
PyObject* tbModule = PyImport_ImportModule("traceback");
|
|
830
|
+
if (tbModule) {
|
|
831
|
+
PyRef tbModRef = PyRef::steal(tbModule);
|
|
832
|
+
PyObject* formatTb = PyObject_GetAttrString(tbModule, "format_tb");
|
|
833
|
+
if (formatTb) {
|
|
834
|
+
PyRef formatTbRef = PyRef::steal(formatTb);
|
|
835
|
+
PyObject* tbLines = PyObject_CallOneArg(formatTb, excTb);
|
|
836
|
+
if (tbLines) {
|
|
837
|
+
PyRef tbLinesRef = PyRef::steal(tbLines);
|
|
838
|
+
std::ostringstream oss;
|
|
839
|
+
oss << "Traceback (most recent call last):\n";
|
|
840
|
+
Py_ssize_t nlines = PyList_Size(tbLines);
|
|
841
|
+
for (Py_ssize_t i = 0; i < nlines; ++i) {
|
|
842
|
+
PyObject* line = PyList_GetItem(tbLines, i);
|
|
843
|
+
if (line && PyUnicode_Check(line)) {
|
|
844
|
+
const char* l = PyUnicode_AsUTF8(line);
|
|
845
|
+
if (l) oss << l;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
oss << typeName << ": " << message;
|
|
849
|
+
traceback = oss.str();
|
|
850
|
+
} else { PyErr_Clear(); }
|
|
851
|
+
} else { PyErr_Clear(); }
|
|
852
|
+
} else { PyErr_Clear(); }
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if (traceback.empty()) {
|
|
856
|
+
traceback = typeName + ": " + message;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
return {typeName, message, traceback};
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
std::string TypeConverter::exceptionToJson() {
|
|
863
|
+
auto [type, msg, tb] = captureException();
|
|
864
|
+
std::ostringstream oss;
|
|
865
|
+
oss << "{\"type\":\"" << jsonEscape(type) << "\","
|
|
866
|
+
<< "\"message\":\"" << jsonEscape(msg) << "\","
|
|
867
|
+
<< "\"traceback\":\"" << jsonEscape(tb) << "\"}";
|
|
868
|
+
return oss.str();
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// ─── JSON Escape ────────────────────────────────────────────────────────────
|
|
872
|
+
|
|
873
|
+
std::string TypeConverter::jsonEscape(const std::string& s) {
|
|
874
|
+
std::string result;
|
|
875
|
+
result.reserve(s.size() + 16);
|
|
876
|
+
|
|
877
|
+
for (unsigned char c : s) {
|
|
878
|
+
switch (c) {
|
|
879
|
+
case '"': result += "\\\""; break;
|
|
880
|
+
case '\\': result += "\\\\"; break;
|
|
881
|
+
case '\b': result += "\\b"; break;
|
|
882
|
+
case '\f': result += "\\f"; break;
|
|
883
|
+
case '\n': result += "\\n"; break;
|
|
884
|
+
case '\r': result += "\\r"; break;
|
|
885
|
+
case '\t': result += "\\t"; break;
|
|
886
|
+
default:
|
|
887
|
+
if (c < 0x20) {
|
|
888
|
+
char buf[8];
|
|
889
|
+
snprintf(buf, sizeof(buf), "\\u%04x", c);
|
|
890
|
+
result += buf;
|
|
891
|
+
} else {
|
|
892
|
+
result += static_cast<char>(c);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return result;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
} // namespace nodepyx
|
|
901
|
+
|