Skip to content

Commit 4d27e14

Browse files
committed
Refactor Python error formatting and cleanup
Simplifies and improves Python exception formatting by extracting logic into helper functions and using more robust error handling. Removes unused run sequence tracking and an obsolete isolate runner. Enhances debug logging and clarifies error messages for better maintainability.
1 parent 1634d26 commit 4d27e14

File tree

1 file changed

+74
-127
lines changed

1 file changed

+74
-127
lines changed

src/serious_python_android/lib/src/cpython.dart

Lines changed: 74 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export 'gen.dart';
1313
CPython? _cpython;
1414
String? _logcatForwardingError;
1515
Future<void> _pythonRunQueue = Future<void>.value();
16-
var _pythonRunSeq = 0;
1716

1817
Future<T> _enqueuePythonRun<T>(Future<T> Function() action) {
1918
final completer = Completer<T>();
@@ -66,50 +65,32 @@ CPython getCPython(String dynamicLibPath) {
6665
Future<String> runPythonProgramFFI(bool sync, String dynamicLibPath,
6766
String pythonProgramPath, String script) async {
6867
return _enqueuePythonRun(() async {
69-
final runId = ++_pythonRunSeq;
7068
spDebug(
71-
"Python run#$runId start (sync=$sync, script=${script.isNotEmpty}, program=$pythonProgramPath)");
69+
"Python run start (sync=$sync, script=${script.isNotEmpty}, program=$pythonProgramPath)");
7270
if (sync) {
7371
// Sync run: do not involve ports (avoids GC/close races).
7472
final result =
7573
_runPythonProgram(dynamicLibPath, pythonProgramPath, script);
76-
spDebug("Python run#$runId done (resultLength=${result.length})");
74+
spDebug("Python run done (resultLength=${result.length})");
7775
return result;
7876
} else {
7977
// Async run: use Isolate.run() to avoid manual port lifecycle issues.
8078
try {
79+
spDebug(
80+
"Python async run start (sync=$sync, script=${script.isNotEmpty}, program=$pythonProgramPath)");
8181
final result = await Isolate.run(
8282
() => _runPythonProgram(dynamicLibPath, pythonProgramPath, script));
83-
spDebug("Python run#$runId done (resultLength=${result.length})");
83+
spDebug("Python run done (resultLength=${result.length})");
8484
return result;
8585
} catch (e, st) {
8686
final message = "Dart error running Python: $e\n$st";
8787
spDebug(message);
88-
spDebug("Python run#$runId failed");
8988
return message;
9089
}
9190
}
9291
});
9392
}
9493

95-
Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
96-
final sendPort = arguments[0] as SendPort;
97-
final dynamicLibPath = arguments[1] as String;
98-
final pythonProgramPath = arguments[2] as String;
99-
final script = arguments[3] as String;
100-
101-
try {
102-
final result = _runPythonProgram(dynamicLibPath, pythonProgramPath, script);
103-
sendPort.send(result);
104-
return result;
105-
} catch (e, st) {
106-
final message = "Dart error running Python: $e\n$st";
107-
spDebug(message);
108-
sendPort.send(message);
109-
return message;
110-
}
111-
}
112-
11394
String _runPythonProgram(
11495
String dynamicLibPath, String pythonProgramPath, String script) {
11596
var programDirPath = p.dirname(pythonProgramPath);
@@ -122,7 +103,8 @@ String _runPythonProgram(
122103
final cpython = getCPython(dynamicLibPath);
123104
spDebug("CPython loaded");
124105
if (cpython.Py_IsInitialized() != 0) {
125-
spDebug("Python already initialized, skipping execution.");
106+
spDebug(
107+
"Python already initialized and another program is running, skipping execution.");
126108
return "";
127109
}
128110

@@ -163,119 +145,84 @@ String _runPythonProgram(
163145
}
164146

165147
String getPythonError(CPython cpython) {
166-
// get error object
167148
final exPtr = cpython.PyErr_GetRaisedException();
168-
if (exPtr == nullptr) {
169-
return "Unknown Python error (no exception set).";
149+
if (exPtr == nullptr) return "Unknown Python error (no exception set).";
150+
151+
try {
152+
final formatted = _formatPythonException(cpython, exPtr);
153+
if (formatted != null && formatted.isNotEmpty) return formatted;
154+
155+
final fallback = _pyObjectToDartString(cpython, exPtr);
156+
return fallback ?? "Unknown Python error (failed to stringify exception).";
157+
} finally {
158+
cpython.Py_DecRef(exPtr);
159+
// Defensive: formatting can set a new Python error.
160+
cpython.PyErr_Clear();
170161
}
162+
}
171163

172-
// use 'traceback' module to format exception
164+
String? _formatPythonException(
165+
CPython cpython, Pointer<PyObject> exceptionPtr) {
166+
// Uses `traceback.format_exception(exc)` (Python 3.10+ signature).
173167
final tracebackModuleNamePtr = "traceback".toNativeUtf8();
174-
var tracebackModulePtr =
168+
final tracebackModulePtr =
175169
cpython.PyImport_ImportModule(tracebackModuleNamePtr.cast<Char>());
176170
malloc.free(tracebackModuleNamePtr);
171+
if (tracebackModulePtr == nullptr) return null;
177172

178-
if (tracebackModulePtr != nullptr) {
179-
//spDebug("Traceback module loaded");
180-
181-
final formatFuncName = "format_exception".toNativeUtf8();
182-
final pFormatFunc = cpython.PyObject_GetAttrString(
183-
tracebackModulePtr, formatFuncName.cast());
184-
malloc.free(formatFuncName);
185-
186-
if (pFormatFunc != nullptr && cpython.PyCallable_Check(pFormatFunc) != 0) {
187-
// call `traceback.format_exception()` method
188-
final pArgs = cpython.PyTuple_New(1);
189-
if (pArgs == nullptr) {
190-
final fallback = cpython.PyObject_Str(exPtr);
191-
if (fallback == nullptr) {
192-
cpython.Py_DecRef(pFormatFunc);
193-
cpython.Py_DecRef(tracebackModulePtr);
194-
cpython.Py_DecRef(exPtr);
195-
return "Failed to allocate args to format Python exception.";
196-
}
197-
final s = cpython
198-
.PyUnicode_AsUTF8(fallback)
199-
.cast<Utf8>()
200-
.toDartString();
201-
cpython.Py_DecRef(fallback);
202-
cpython.Py_DecRef(pFormatFunc);
203-
cpython.Py_DecRef(tracebackModulePtr);
204-
cpython.Py_DecRef(exPtr);
205-
return s;
206-
}
207-
// Keep a reference for fallback error formatting.
208-
cpython.Py_IncRef(exPtr);
209-
cpython.PyTuple_SetItem(pArgs, 0, exPtr);
210-
211-
// result is a list
212-
var listPtr = cpython.PyObject_CallObject(pFormatFunc, pArgs);
213-
cpython.Py_DecRef(pArgs);
214-
cpython.Py_DecRef(pFormatFunc);
215-
cpython.Py_DecRef(tracebackModulePtr);
216-
217-
// get and combine list items
218-
var exLines = [];
219-
if (listPtr == nullptr) {
220-
final fallback = cpython.PyObject_Str(exPtr);
221-
if (fallback == nullptr) {
222-
cpython.Py_DecRef(exPtr);
223-
return "Failed to format Python exception.";
224-
}
225-
final s = cpython
226-
.PyUnicode_AsUTF8(fallback)
227-
.cast<Utf8>()
228-
.toDartString();
229-
cpython.Py_DecRef(fallback);
230-
cpython.Py_DecRef(exPtr);
231-
return s;
232-
}
173+
try {
174+
final formatFuncNamePtr = "format_exception".toNativeUtf8();
175+
final formatFuncPtr = cpython.PyObject_GetAttrString(
176+
tracebackModulePtr, formatFuncNamePtr.cast());
177+
malloc.free(formatFuncNamePtr);
178+
if (formatFuncPtr == nullptr) return null;
233179

234-
var listSize = cpython.PyList_Size(listPtr);
235-
if (listSize < 0) {
236-
cpython.Py_DecRef(listPtr);
237-
final fallback = cpython.PyObject_Str(exPtr);
238-
if (fallback == nullptr) {
239-
cpython.Py_DecRef(exPtr);
240-
return "Failed to format Python exception.";
241-
}
242-
final s = cpython
243-
.PyUnicode_AsUTF8(fallback)
244-
.cast<Utf8>()
245-
.toDartString();
246-
cpython.Py_DecRef(fallback);
247-
cpython.Py_DecRef(exPtr);
248-
return s;
249-
}
250-
for (var i = 0; i < listSize; i++) {
251-
var itemObj = cpython.PyList_GetItem(listPtr, i);
252-
var itemObjStr = cpython.PyObject_Str(itemObj);
253-
if (itemObjStr == nullptr) {
254-
continue;
255-
}
256-
final cStr = cpython.PyUnicode_AsUTF8(itemObjStr);
257-
if (cStr == nullptr) {
258-
cpython.Py_DecRef(itemObjStr);
259-
continue;
180+
try {
181+
if (cpython.PyCallable_Check(formatFuncPtr) == 0) return null;
182+
183+
final listPtr = cpython.PyObject_CallOneArg(formatFuncPtr, exceptionPtr);
184+
if (listPtr == nullptr) return null;
185+
186+
try {
187+
final listSize = cpython.PyList_Size(listPtr);
188+
if (listSize < 0) return null;
189+
190+
final buffer = StringBuffer();
191+
for (var i = 0; i < listSize; i++) {
192+
final itemObj = cpython.PyList_GetItem(listPtr, i); // borrowed ref
193+
if (itemObj == nullptr) continue;
194+
195+
final line = _pyUnicodeToDartString(cpython, itemObj) ??
196+
_pyObjectToDartString(cpython, itemObj);
197+
if (line == null) continue;
198+
buffer.write(line);
260199
}
261-
var s = cStr.cast<Utf8>().toDartString();
262-
cpython.Py_DecRef(itemObjStr);
263-
exLines.add(s);
264-
}
265-
cpython.Py_DecRef(listPtr);
266-
cpython.Py_DecRef(exPtr);
267-
return exLines.join("");
268-
} else {
269-
if (pFormatFunc != nullptr) {
270-
cpython.Py_DecRef(pFormatFunc);
200+
return buffer.toString();
201+
} finally {
202+
cpython.Py_DecRef(listPtr);
271203
}
272-
cpython.Py_DecRef(tracebackModulePtr);
273-
cpython.Py_DecRef(exPtr);
274-
return "traceback.format_exception() method not found.";
204+
} finally {
205+
cpython.Py_DecRef(formatFuncPtr);
275206
}
276-
} else {
277-
cpython.Py_DecRef(exPtr);
278-
return "Error loading traceback module.";
207+
} finally {
208+
cpython.Py_DecRef(tracebackModulePtr);
209+
}
210+
}
211+
212+
String? _pyUnicodeToDartString(
213+
CPython cpython, Pointer<PyObject> unicodeObjPtr) {
214+
final cStr = cpython.PyUnicode_AsUTF8(unicodeObjPtr);
215+
if (cStr == nullptr) return null;
216+
return cStr.cast<Utf8>().toDartString();
217+
}
218+
219+
String? _pyObjectToDartString(CPython cpython, Pointer<PyObject> objPtr) {
220+
final strObj = cpython.PyObject_Str(objPtr);
221+
if (strObj == nullptr) return null;
222+
try {
223+
return _pyUnicodeToDartString(cpython, strObj);
224+
} finally {
225+
cpython.Py_DecRef(strObj);
279226
}
280227
}
281228

0 commit comments

Comments
 (0)