Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/serious_python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.8

* Fix logging on Android.

## 0.9.7

* Fix app restart on Android 10.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python
description: A cross-platform plugin for adding embedded Python runtime to your Flutter apps.
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 0.9.7
version: 0.9.8

platforms:
ios:
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.8

* Fix logging on Android.

## 0.9.7

* Fix app restart on Android 10.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_android/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'com.flet.serious_python_android'
version '0.9.7'
version '0.9.8'

def python_version = '3.12'

Expand Down
182 changes: 116 additions & 66 deletions src/serious_python_android/lib/src/cpython.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,22 @@ export 'gen.dart';

CPython? _cpython;
String? _logcatForwardingError;
Future<void> _pythonRunQueue = Future<void>.value();

Future<T> _enqueuePythonRun<T>(Future<T> Function() action) {
final completer = Completer<T>();
_pythonRunQueue = _pythonRunQueue.then((_) async {
try {
completer.complete(await action());
} catch (e, st) {
completer.completeError(e, st);
}
});
return completer.future;
}

const _logcatInitScript = r'''
import sys, logging
import logging,sys

# Make this init idempotent across Dart isolate restarts.
if not getattr(sys, "__serious_python_logcat_configured__", False):
Expand Down Expand Up @@ -41,7 +55,7 @@ if not getattr(sys, "__serious_python_logcat_configured__", False):
handler.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
root = logging.getLogger()
root.handlers[:] = [handler]
root.setLevel(logging.DEBUG)
root.setLevel(logging.ERROR)
''';

CPython getCPython(String dynamicLibPath) {
Expand All @@ -50,31 +64,33 @@ CPython getCPython(String dynamicLibPath) {

Future<String> runPythonProgramFFI(bool sync, String dynamicLibPath,
String pythonProgramPath, String script) async {
final receivePort = ReceivePort();
if (sync) {
// sync run
return await runPythonProgramInIsolate(
[receivePort.sendPort, dynamicLibPath, pythonProgramPath, script]);
} else {
var completer = Completer<String>();
// async run
final isolate = await Isolate.spawn(runPythonProgramInIsolate,
[receivePort.sendPort, dynamicLibPath, pythonProgramPath, script]);
receivePort.listen((message) {
receivePort.close();
isolate.kill();
completer.complete(message);
});
return completer.future;
}
return _enqueuePythonRun(() async {
spDebug(
"Python run start (sync=$sync, script=${script.isNotEmpty}, program=$pythonProgramPath)");
if (sync) {
// Sync run: do not involve ports (avoids GC/close races).
final result =
_runPythonProgram(dynamicLibPath, pythonProgramPath, script);
spDebug("Python run done (resultLength=${result.length})");
return result;
} else {
// Async run: use Isolate.run() to avoid manual port lifecycle issues.
try {
final result = await Isolate.run(
() => _runPythonProgram(dynamicLibPath, pythonProgramPath, script));
spDebug("Python run done (resultLength=${result.length})");
return result;
} catch (e, st) {
final message = "Dart error running Python: $e\n$st";
spDebug(message);
return message;
}
}
});
}

Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
final sendPort = arguments[0] as SendPort;
final dynamicLibPath = arguments[1] as String;
final pythonProgramPath = arguments[2] as String;
final script = arguments[3] as String;

String _runPythonProgram(
String dynamicLibPath, String pythonProgramPath, String script) {
var programDirPath = p.dirname(pythonProgramPath);
var programModuleName = p.basenameWithoutExtension(pythonProgramPath);

Expand All @@ -85,8 +101,8 @@ Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
final cpython = getCPython(dynamicLibPath);
spDebug("CPython loaded");
if (cpython.Py_IsInitialized() != 0) {
spDebug("Python already initialized, skipping execution.");
sendPort.send("");
spDebug(
"Python already initialized and another program is running, skipping execution.");
return "";
}

Expand All @@ -98,7 +114,6 @@ Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
final logcatSetupError = _setupLogcatForwarding(cpython);
if (logcatSetupError != null) {
cpython.Py_Finalize();
sendPort.send(logcatSetupError);
return logcatSetupError;
}

Expand All @@ -124,53 +139,88 @@ Future<String> runPythonProgramInIsolate(List<Object> arguments) async {
cpython.Py_Finalize();
spDebug("after Py_Finalize()");

sendPort.send(result);

return result;
}

String getPythonError(CPython cpython) {
// get error object
var exPtr = cpython.PyErr_GetRaisedException();
final exPtr = cpython.PyErr_GetRaisedException();
if (exPtr == nullptr) return "Unknown Python error (no exception set).";

try {
final formatted = _formatPythonException(cpython, exPtr);
if (formatted != null && formatted.isNotEmpty) return formatted;

final fallback = _pyObjectToDartString(cpython, exPtr);
return fallback ?? "Unknown Python error (failed to stringify exception).";
} finally {
cpython.Py_DecRef(exPtr);
// Defensive: formatting can set a new Python error.
cpython.PyErr_Clear();
}
}

// use 'traceback' module to format exception
String? _formatPythonException(
CPython cpython, Pointer<PyObject> exceptionPtr) {
// Uses `traceback.format_exception(exc)` (Python 3.10+ signature).
final tracebackModuleNamePtr = "traceback".toNativeUtf8();
var tracebackModulePtr =
final tracebackModulePtr =
cpython.PyImport_ImportModule(tracebackModuleNamePtr.cast<Char>());
cpython.Py_DecRef(tracebackModuleNamePtr.cast());

if (tracebackModulePtr != nullptr) {
//spDebug("Traceback module loaded");

final formatFuncName = "format_exception".toNativeUtf8();
final pFormatFunc = cpython.PyObject_GetAttrString(
tracebackModulePtr, formatFuncName.cast());
cpython.Py_DecRef(tracebackModuleNamePtr.cast());

if (pFormatFunc != nullptr && cpython.PyCallable_Check(pFormatFunc) != 0) {
// call `traceback.format_exception()` method
final pArgs = cpython.PyTuple_New(1);
cpython.PyTuple_SetItem(pArgs, 0, exPtr);

// result is a list
var listPtr = cpython.PyObject_CallObject(pFormatFunc, pArgs);

// get and combine list items
var exLines = [];
var listSize = cpython.PyList_Size(listPtr);
for (var i = 0; i < listSize; i++) {
var itemObj = cpython.PyList_GetItem(listPtr, i);
var itemObjStr = cpython.PyObject_Str(itemObj);
var s =
cpython.PyUnicode_AsUTF8(itemObjStr).cast<Utf8>().toDartString();
exLines.add(s);
malloc.free(tracebackModuleNamePtr);
if (tracebackModulePtr == nullptr) return null;

try {
final formatFuncNamePtr = "format_exception".toNativeUtf8();
final formatFuncPtr = cpython.PyObject_GetAttrString(
tracebackModulePtr, formatFuncNamePtr.cast());
malloc.free(formatFuncNamePtr);
if (formatFuncPtr == nullptr) return null;

try {
if (cpython.PyCallable_Check(formatFuncPtr) == 0) return null;

final listPtr = cpython.PyObject_CallOneArg(formatFuncPtr, exceptionPtr);
if (listPtr == nullptr) return null;

try {
final listSize = cpython.PyList_Size(listPtr);
if (listSize < 0) return null;

final buffer = StringBuffer();
for (var i = 0; i < listSize; i++) {
final itemObj = cpython.PyList_GetItem(listPtr, i); // borrowed ref
if (itemObj == nullptr) continue;

final line = _pyUnicodeToDartString(cpython, itemObj) ??
_pyObjectToDartString(cpython, itemObj);
if (line == null) continue;
buffer.write(line);
}
return buffer.toString();
} finally {
cpython.Py_DecRef(listPtr);
}
return exLines.join("");
} else {
return "traceback.format_exception() method not found.";
} finally {
cpython.Py_DecRef(formatFuncPtr);
}
} else {
return "Error loading traceback module.";
} finally {
cpython.Py_DecRef(tracebackModulePtr);
}
}

String? _pyUnicodeToDartString(
CPython cpython, Pointer<PyObject> unicodeObjPtr) {
final cStr = cpython.PyUnicode_AsUTF8(unicodeObjPtr);
if (cStr == nullptr) return null;
return cStr.cast<Utf8>().toDartString();
}

String? _pyObjectToDartString(CPython cpython, Pointer<PyObject> objPtr) {
final strObj = cpython.PyObject_Str(objPtr);
if (strObj == nullptr) return null;
try {
return _pyUnicodeToDartString(cpython, strObj);
} finally {
cpython.Py_DecRef(strObj);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_android/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_android
description: Android implementation of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 0.9.7
version: 0.9.8

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_darwin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.8

* Fix logging on Android.

## 0.9.7

* Fix app restart on Android 10.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
Pod::Spec.new do |s|
s.name = 'serious_python_darwin'
s.version = '0.9.7'
s.version = '0.9.8'
s.summary = 'A cross-platform plugin for adding embedded Python runtime to your Flutter apps.'
s.description = <<-DESC
A cross-platform plugin for adding embedded Python runtime to your Flutter apps.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_darwin/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_darwin
description: iOS and macOS implementations of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 0.9.7
version: 0.9.8

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_linux/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.8

* Fix logging on Android.

## 0.9.7

* Fix app restart on Android 10.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_linux/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_linux
description: Linux implementations of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 0.9.7
version: 0.9.8

environment:
sdk: '>=3.1.3 <4.0.0'
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_platform_interface/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.8

* Fix logging on Android.

## 0.9.7

* Fix app restart on Android 10.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_platform_interface/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_platform_interface
description: A common platform interface for the serious_python plugin.
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 0.9.7
version: 0.9.8

environment:
sdk: ">=3.0.0 <4.0.0"
Expand Down
4 changes: 4 additions & 0 deletions src/serious_python_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.8

* Fix logging on Android.

## 0.9.7

* Fix app restart on Android 10.
Expand Down
2 changes: 1 addition & 1 deletion src/serious_python_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: serious_python_windows
description: Windows implementations of the serious_python plugin
homepage: https://flet.dev
repository: https://github.com/flet-dev/serious-python
version: 0.9.7
version: 0.9.8

environment:
sdk: '>=3.1.3 <4.0.0'
Expand Down
Loading