From 62c577dacec1e6d5a2d6e5bc33b6805e4a1b9e48 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Jun 2026 17:12:07 -0700 Subject: [PATCH 1/8] Add multi-version Python support (3.12 / 3.13 / 3.14) `package_command.dart` gains a `--python-version` option (with `SERIOUS_PYTHON_VERSION` env-var fallback) driving a new `_pythonReleases` table that maps each short version to its CPython-standalone build, Pyodide release, and Pyodide wheel platform tag. The Emscripten `pip install --platform` tag is now resolved from the chosen release rather than the static `platforms` map. Each platform plugin (`android/build.gradle`, `darwin/*.podspec`, `linux/CMakeLists.txt` + plugin `.cc`, `windows/CMakeLists.txt`) reads `SERIOUS_PYTHON_VERSION` (default `3.14`) so `flet-dev/python-build` downloads, library filenames, and runtime module paths track the selected version. The Android Dart runtime now scans `nativeLibraryDir` for `libpython3.*.so` instead of hardcoding `libpython3.12.so`, removing version coupling from the Dart side entirely. CI: each platform job (macOS / iOS / Android / Windows / Linux) now runs across the full `[3.12, 3.13, 3.14]` Python matrix with `fail-fast: false`. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 48 ++++++-- src/serious_python/CHANGELOG.md | 4 + src/serious_python/README.md | 17 ++- src/serious_python/bin/package_command.dart | 107 ++++++++++++++++-- src/serious_python_android/CHANGELOG.md | 5 + .../android/build.gradle | 2 +- .../lib/serious_python_android.dart | 25 +++- src/serious_python_darwin/CHANGELOG.md | 4 + .../darwin/serious_python_darwin.podspec | 2 +- src/serious_python_linux/CHANGELOG.md | 4 + src/serious_python_linux/linux/CMakeLists.txt | 14 ++- .../linux/serious_python_linux_plugin.cc | 11 +- .../CHANGELOG.md | 4 + src/serious_python_windows/CHANGELOG.md | 4 + .../windows/CMakeLists.txt | 12 +- 15 files changed, 224 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2648d55..2f39098e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,14 @@ env: jobs: macos: - name: Test on macOS + name: Test on macOS (Python ${{ matrix.python_version }}) runs-on: macos-latest + strategy: + fail-fast: false + matrix: + python_version: ['3.12', '3.13', '3.14'] + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -32,12 +38,18 @@ jobs: - name: Run tests working-directory: "src/serious_python/example/flet_example" run: | - dart run serious_python:main package app/src --platform Darwin --requirements flet==0.28.3 + dart run serious_python:main package app/src --platform Darwin --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 flutter test integration_test --device-id macos ios: - name: Test on iOS + name: Test on iOS (Python ${{ matrix.python_version }}) runs-on: macos-latest + strategy: + fail-fast: false + matrix: + python_version: ['3.12', '3.13', '3.14'] + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -62,12 +74,18 @@ jobs: - name: Run tests working-directory: "src/serious_python/example/flet_example" run: | - dart run serious_python:main package app/src --platform iOS --requirements flet==0.28.3 + dart run serious_python:main package app/src --platform iOS --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 flutter test integration_test --device-id ${{ steps.simulator.outputs.udid }} android: - name: Test on Android + name: Test on Android (Python ${{ matrix.python_version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python_version: ['3.12', '3.13', '3.14'] + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -115,12 +133,18 @@ jobs: pre-emulator-launch-script: | sdkmanager --list_installed script: | - cd src/serious_python/example/flet_example && dart run serious_python:main package app/src --platform Android --requirements flet==0.28.3 + cd src/serious_python/example/flet_example && dart run serious_python:main package app/src --platform Android --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 cd src/serious_python/example/flet_example && flutter test integration_test --device-id emulator-${{ env.EMULATOR_PORT }} windows: - name: Test on Windows + name: Test on Windows (Python ${{ matrix.python_version }}) runs-on: windows-latest + strategy: + fail-fast: false + matrix: + python_version: ['3.12', '3.13', '3.14'] + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -134,15 +158,17 @@ jobs: - name: Run tests working-directory: "src/serious_python/example/flet_example" run: | - dart run serious_python:main package app/src --platform Windows --requirements flet==0.28.3 + dart run serious_python:main package app/src --platform Windows --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 flutter test integration_test -d windows linux: - name: Test on Linux ${{ matrix.title }} + name: Test on Linux ${{ matrix.title }} (Python ${{ matrix.python_version }}) runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: + python_version: ['3.12', '3.13', '3.14'] + arch: [arm64, amd64] include: - arch: arm64 runner: ubuntu-24.04-arm @@ -150,6 +176,8 @@ jobs: - arch: amd64 runner: ubuntu-24.04 title: AMD64 + env: + SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }} steps: - name: Checkout repository uses: actions/checkout@v4 @@ -205,7 +233,7 @@ jobs: working-directory: src/serious_python/example/flet_example run: | flutter pub get - dart run serious_python:main package app/src --platform Linux --requirements flet==0.28.3 + dart run serious_python:main package app/src --platform Linux --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 xvfb-run flutter test integration_test -d linux publish: diff --git a/src/serious_python/CHANGELOG.md b/src/serious_python/CHANGELOG.md index 931451da..fc8bafac 100644 --- a/src/serious_python/CHANGELOG.md +++ b/src/serious_python/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* Multi-version Python support. The `package` command now accepts `--python-version` (or `SERIOUS_PYTHON_VERSION` env var) to select between Python 3.12 / 3.13 / 3.14. Defaults to the latest supported version (3.14). The matching CPython-standalone build and Pyodide release are looked up from a new `_pythonReleases` table; the Emscripten wheel platform tag is derived from it too, so each Python release picks the right `pyodide-XXXX.X-wasm32` / `pyemscripten-XXXX.X-wasm32` tag automatically. + ## 1.0.0 * **Breaking change:** `--platform` argument value `Pyodide` has been renamed to `Emscripten` to match what `platform.system()` returns in the Pyodide runtime, so PEP 508 markers like `platform_system != 'Emscripten'` work consistently. diff --git a/src/serious_python/README.md b/src/serious_python/README.md index 37767061..17b2f473 100644 --- a/src/serious_python/README.md +++ b/src/serious_python/README.md @@ -16,7 +16,22 @@ Serious Python is part of [Flet](https://flet.dev) project - the fastest way to ### Python versions -* Python 3.12.6 on all platforms. +The plugin can bundle one of several Python releases per build, selected via +the `--python-version X.Y` flag of `serious_python:main package` (or the +`SERIOUS_PYTHON_VERSION` env var picked up by the Android/Darwin/Linux/Windows +plugin build scripts). Defaults to the latest supported version when nothing +is specified. + +| Short | CPython runtime | Pyodide (web) | Pyodide wheel platform tag | +| ----- | --------------- | ------------- | -------------------------- | +| 3.12 | 3.12.13 | 0.27.7 | `pyodide-2024.0-wasm32` | +| 3.13 | 3.13.13 | 0.29.4 | `pyodide-2025.0-wasm32` | +| 3.14 | 3.14.5 | 314.0.0a2 | `pyemscripten-2026.0-wasm32`| + +Source of truth: the `_pythonReleases` map in +[`bin/package_command.dart`](bin/package_command.dart) (Dart side) and +`flet_cli/utils/python_versions.py` (Python side). Adding a new short version +means appending a row to both — there is no separate pre-release gate. ## Usage diff --git a/src/serious_python/bin/package_command.dart b/src/serious_python/bin/package_command.dart index b407a1d6..673c8dca 100644 --- a/src/serious_python/bin/package_command.dart +++ b/src/serious_python/bin/package_command.dart @@ -15,11 +15,8 @@ import 'macos_utils.dart' as macos_utils; import 'sitecustomize.dart'; const mobilePyPiUrl = "https://pypi.flet.dev"; -const pyodideRootUrl = "https://cdn.jsdelivr.net/pyodide/v0.27.7/full"; const pyodideLockFile = "pyodide-lock.json"; -const buildPythonVersion = "3.12.9"; -const buildPythonReleaseDate = "20250205"; const defaultSitePackagesDir = "__pypackages__"; const sitePackagesEnvironmentVariable = "SERIOUS_PYTHON_SITE_PACKAGES"; const flutterPackagesFlutterEnvironmentVariable = @@ -27,6 +24,49 @@ const flutterPackagesFlutterEnvironmentVariable = const allowSourceDistrosEnvironmentVariable = "SERIOUS_PYTHON_ALLOW_SOURCE_DISTRIBUTIONS"; +const pythonVersionEnvironmentVariable = "SERIOUS_PYTHON_VERSION"; +const pythonDistReleaseEnvironmentVariable = "SERIOUS_PYTHON_DIST_RELEASE"; +const pyodideVersionEnvironmentVariable = "SERIOUS_PYTHON_PYODIDE_VERSION"; + +const defaultPythonVersion = "3.14"; + +class _PythonRelease { + const _PythonRelease({ + required this.standaloneVersion, + required this.standaloneReleaseDate, + required this.pyodideVersion, + required this.pyodidePlatformTag, + }); + + final String standaloneVersion; + final String standaloneReleaseDate; + final String pyodideVersion; + final String pyodidePlatformTag; +} + +// Source of truth for the Python <-> CPython standalone <-> Pyodide mapping. +// Mirror any change here in flet-cli's python_versions.py. +const _pythonReleases = { + "3.12": _PythonRelease( + standaloneVersion: "3.12.13", + standaloneReleaseDate: "20260602", + pyodideVersion: "0.27.7", + pyodidePlatformTag: "pyodide-2024.0-wasm32", + ), + "3.13": _PythonRelease( + standaloneVersion: "3.13.13", + standaloneReleaseDate: "20260602", + pyodideVersion: "0.29.4", + pyodidePlatformTag: "pyodide-2025.0-wasm32", + ), + "3.14": _PythonRelease( + standaloneVersion: "3.14.5", + standaloneReleaseDate: "20260602", + pyodideVersion: "314.0.0a2", + pyodidePlatformTag: "pyemscripten-2026.0-wasm32", + ), +}; + const platforms = { "iOS": { "iphoneos.arm64": {"tag": "ios-13.0-arm64-iphoneos", "mac_ver": ""}, @@ -46,7 +86,10 @@ const platforms = { "x86": {"tag": "android-24-x86", "mac_ver": ""} }, "Emscripten": { - "": {"tag": "pyodide-2024.0-wasm32", "mac_ver": ""} + // The actual wheel platform tag is resolved per Python release from + // `_pythonReleases[...].pyodidePlatformTag` (see sitecustomize wiring + // below) since it changes with each Pyodide ABI bump. + "": {"tag": "", "mac_ver": ""} }, "Darwin": { "arm64": {"tag": "", "mac_ver": "arm64"}, @@ -86,6 +129,11 @@ class PackageCommand extends Command { bool _verbose = false; Directory? _buildDir; Directory? _pythonDir; + late String _pythonShortVersion; + late _PythonRelease _release; + + String get _pyodideRootUrl => + "https://cdn.jsdelivr.net/pyodide/v${_release.pyodideVersion}/full"; @override final name = "package"; @@ -99,6 +147,11 @@ class PackageCommand extends Command { allowed: ["iOS", "Android", "Emscripten", "Windows", "Linux", "Darwin"], mandatory: true, help: "Install dependencies for specific platform, e.g. 'Android'."); + argParser.addOption('python-version', + allowed: _pythonReleases.keys.toList(), + help: "Short Python version to bundle (e.g. 3.13). Defaults to " + "\$$pythonVersionEnvironmentVariable env var or " + "'$defaultPythonVersion'."); argParser.addMultiOption('arch', help: "Install dependencies for specific architectures only. Leave empty to install all supported architectures."); @@ -172,6 +225,29 @@ class PackageCommand extends Command { List cleanupPackageFiles = argResults?['cleanup-package-files']; _verbose = argResults?["verbose"]; + _pythonShortVersion = argResults?['python-version'] ?? + Platform.environment[pythonVersionEnvironmentVariable] ?? + defaultPythonVersion; + final baseRelease = _pythonReleases[_pythonShortVersion]; + if (baseRelease == null) { + stderr.writeln( + "Unknown Python version: $_pythonShortVersion. Supported: ${_pythonReleases.keys.join(", ")}"); + exit(2); + } + _release = _PythonRelease( + standaloneVersion: baseRelease.standaloneVersion, + standaloneReleaseDate: + Platform.environment[pythonDistReleaseEnvironmentVariable] ?? + baseRelease.standaloneReleaseDate, + pyodideVersion: + Platform.environment[pyodideVersionEnvironmentVariable] ?? + baseRelease.pyodideVersion, + pyodidePlatformTag: baseRelease.pyodidePlatformTag, + ); + stdout.writeln( + "Python $_pythonShortVersion (CPython ${_release.standaloneVersion}, " + "Pyodide ${_release.pyodideVersion})"); + if (path.isRelative(sourceDirPath)) { sourceDirPath = path.join(currentPath, sourceDirPath); } @@ -307,10 +383,17 @@ class PackageCommand extends Command { "Configured $platform/${arch.key} platform with sitecustomize.py"); } + // Emscripten's wheel platform tag changes between Pyodide ABI + // bumps (e.g. pyodide-2024.0 -> pyodide-2025.0 -> pyemscripten-2026.0), + // so resolve it from the chosen Python release instead of the + // static `platforms` map. + final platformTag = platform == "Emscripten" + ? _release.pyodidePlatformTag + : arch.value["tag"]!; await File(sitecustomizePath).writeAsString(sitecustomizePy .replaceAll( - "{platform}", arch.value["tag"]!.isNotEmpty ? platform : "") - .replaceAll("{tag}", arch.value["tag"]!) + "{platform}", platformTag.isNotEmpty ? platform : "") + .replaceAll("{tag}", platformTag) .replaceAll("{mac_ver}", arch.value["mac_ver"]!)); // print(File(sitecustomizePath).readAsStringSync()); @@ -552,8 +635,8 @@ class PackageCommand extends Command { Future runPython(List args, {Map? environment}) async { if (_pythonDir == null) { - _pythonDir = Directory( - path.join(_buildDir!.path, "build_python_$buildPythonVersion")); + _pythonDir = Directory(path.join( + _buildDir!.path, "build_python_${_release.standaloneVersion}")); if (!await _pythonDir!.exists()) { await _pythonDir!.create(); @@ -574,7 +657,7 @@ class PackageCommand extends Command { } var pythonArchiveFilename = - "cpython-$buildPythonVersion+$buildPythonReleaseDate-$arch-install_only_stripped.tar.gz"; + "cpython-${_release.standaloneVersion}+${_release.standaloneReleaseDate}-$arch-install_only_stripped.tar.gz"; var pythonArchivePath = path.join(_buildDir!.path, pythonArchiveFilename); @@ -582,7 +665,7 @@ class PackageCommand extends Command { if (!await File(pythonArchivePath).exists()) { // download Python distr from GitHub final url = - "https://github.com/astral-sh/python-build-standalone/releases/download/$buildPythonReleaseDate/$pythonArchiveFilename"; + "https://github.com/astral-sh/python-build-standalone/releases/download/${_release.standaloneReleaseDate}/$pythonArchiveFilename"; if (_verbose) { verbose( @@ -649,7 +732,7 @@ class PackageCommand extends Command { const htmlFooter = "\n"; var pyodidePackages = - await fetchJsonFromUrl("$pyodideRootUrl/$pyodideLockFile"); + await fetchJsonFromUrl("$_pyodideRootUrl/$pyodideLockFile"); var wheels = Map.from(pyodidePackages["packages"]) ..removeWhere((k, p) => !p["file_name"].endsWith(".whl")); @@ -671,7 +754,7 @@ class PackageCommand extends Command { wheels.forEach((k, p) { if (k == parts[1].toLowerCase()) { links.add( - "${p['file_name']}
"); + "${p['file_name']}
"); } }); return Response.ok(htmlHeader + links.join("\n") + htmlFooter, diff --git a/src/serious_python_android/CHANGELOG.md b/src/serious_python_android/CHANGELOG.md index 85b86fbf..e2b0401b 100644 --- a/src/serious_python_android/CHANGELOG.md +++ b/src/serious_python_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.1.0 + +* Multi-version Python support. `python_version` in `android/build.gradle` is now read from `SERIOUS_PYTHON_VERSION` (default `3.14`), so the python-build distribution URL points at `v3.12` / `v3.13` / `v3.14` depending on the `flet build` selection. +* The Dart runtime no longer hardcodes `libpython3.12.so` — it scans `nativeLibraryDir` for `libpython3.*.so` so whichever libpython the plugin bundled is loaded automatically. + ## 1.0.0 * **Breaking change:** `--platform` argument value `Pyodide` has been renamed to `Emscripten` to match what `platform.system()` returns in the Pyodide runtime, so PEP 508 markers like `platform_system != 'Emscripten'` work consistently. diff --git a/src/serious_python_android/android/build.gradle b/src/serious_python_android/android/build.gradle index 0d8b4f56..7aeb94ed 100644 --- a/src/serious_python_android/android/build.gradle +++ b/src/serious_python_android/android/build.gradle @@ -1,7 +1,7 @@ group 'com.flet.serious_python_android' version '1.0.0' -def python_version = '3.12' +def python_version = System.getenv('SERIOUS_PYTHON_VERSION') ?: '3.14' buildscript { repositories { diff --git a/src/serious_python_android/lib/serious_python_android.dart b/src/serious_python_android/lib/serious_python_android.dart index 5ce78a06..61b7eaf2 100644 --- a/src/serious_python_android/lib/serious_python_android.dart +++ b/src/serious_python_android/lib/serious_python_android.dart @@ -48,7 +48,25 @@ class SeriousPythonAndroid extends SeriousPythonPlatform { spDebug("Unable to load libpyjni.so library: $e"); } - const pythonSharedLib = "libpython3.12.so"; + // unpack python bundle + final nativeLibraryDir = + await methodChannel.invokeMethod('getNativeLibraryDir'); + spDebug("getNativeLibraryDir: $nativeLibraryDir"); + + // The bundled libpython filename moves with the Python version + // (e.g. libpython3.12.so vs libpython3.13.so), so resolve it by + // scanning nativeLibraryDir rather than hardcoding a constant — the + // plugin only ever bundles one libpython per build. + final libpythonRe = RegExp(r'^libpython3\.\d+\.so$'); + final pythonSharedLib = Directory(nativeLibraryDir!) + .listSync() + .map((e) => p.basename(e.path)) + .firstWhere( + libpythonRe.hasMatch, + orElse: () => throw Exception( + "No libpython3.*.so found in $nativeLibraryDir"), + ); + spDebug("Resolved Python shared library: $pythonSharedLib"); String? getPythonFullVersion() { try { @@ -70,11 +88,6 @@ class SeriousPythonAndroid extends SeriousPythonPlatform { } } - // unpack python bundle - final nativeLibraryDir = - await methodChannel.invokeMethod('getNativeLibraryDir'); - spDebug("getNativeLibraryDir: $nativeLibraryDir"); - var bundlePath = "$nativeLibraryDir/libpythonbundle.so"; var sitePackagesZipPath = "$nativeLibraryDir/libpythonsitepackages.so"; diff --git a/src/serious_python_darwin/CHANGELOG.md b/src/serious_python_darwin/CHANGELOG.md index 424b4a19..3eb717f3 100644 --- a/src/serious_python_darwin/CHANGELOG.md +++ b/src/serious_python_darwin/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* Multi-version Python support. `python_version` in `serious_python_darwin.podspec` is now read from `SERIOUS_PYTHON_VERSION` (default `3.14`), so `prepare_ios.sh` / `prepare_macos.sh` download the matching python-build tarballs (`python-ios-dart-3.13.tar.gz` etc.). + ## 1.0.0 * **Breaking change:** `--platform` argument value `Pyodide` has been renamed to `Emscripten` to match what `platform.system()` returns in the Pyodide runtime, so PEP 508 markers like `platform_system != 'Emscripten'` work consistently. diff --git a/src/serious_python_darwin/darwin/serious_python_darwin.podspec b/src/serious_python_darwin/darwin/serious_python_darwin.podspec index 56aac616..8ab59966 100644 --- a/src/serious_python_darwin/darwin/serious_python_darwin.podspec +++ b/src/serious_python_darwin/darwin/serious_python_darwin.podspec @@ -27,7 +27,7 @@ Pod::Spec.new do |s| } s.swift_version = '5.0' - python_version = "3.12" + python_version = ENV['SERIOUS_PYTHON_VERSION'] || "3.14" dist_ios = "dist_ios" dist_macos = "dist_macos" diff --git a/src/serious_python_linux/CHANGELOG.md b/src/serious_python_linux/CHANGELOG.md index f430b09c..967fdc21 100644 --- a/src/serious_python_linux/CHANGELOG.md +++ b/src/serious_python_linux/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* Multi-version Python support. `PYTHON_VERSION` in `linux/CMakeLists.txt` is now read from `SERIOUS_PYTHON_VERSION` (default `3.14`), and all hardcoded `python3.12` / `libpython3.12.so.1.0` / `lib/python3.12` paths are derived from it. The plugin source receives the version via a `SERIOUS_PYTHON_VERSION` compile-time macro so the runtime module path matches the bundled distro. + ## 1.0.0 * **Breaking change:** `--platform` argument value `Pyodide` has been renamed to `Emscripten` to match what `platform.system()` returns in the Pyodide runtime, so PEP 508 markers like `platform_system != 'Emscripten'` work consistently. diff --git a/src/serious_python_linux/linux/CMakeLists.txt b/src/serious_python_linux/linux/CMakeLists.txt index d371a5d0..89f6eb65 100644 --- a/src/serious_python_linux/linux/CMakeLists.txt +++ b/src/serious_python_linux/linux/CMakeLists.txt @@ -8,7 +8,11 @@ cmake_minimum_required(VERSION 3.10) # Project-level configuration. set(PROJECT_NAME "serious_python_linux") -set(PYTHON_VERSION "3.12") +if(DEFINED ENV{SERIOUS_PYTHON_VERSION}) + set(PYTHON_VERSION "$ENV{SERIOUS_PYTHON_VERSION}") +else() + set(PYTHON_VERSION "3.14") +endif() set(PYTHON_ARCH ${CMAKE_HOST_SYSTEM_PROCESSOR}) project(${PROJECT_NAME} LANGUAGES CXX) @@ -46,6 +50,8 @@ apply_standard_settings(${PLUGIN_NAME}) set_target_properties(${PLUGIN_NAME} PROPERTIES CXX_VISIBILITY_PRESET hidden) target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_compile_definitions(${PLUGIN_NAME} PRIVATE + SERIOUS_PYTHON_VERSION="${PYTHON_VERSION}") # Source include directories and library dependencies. Add any plugin-specific # dependencies here. @@ -53,7 +59,7 @@ target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include" ) include_directories( - "${PYTHON_PACKAGE}/include/python3.12" + "${PYTHON_PACKAGE}/include/python${PYTHON_VERSION}" ) target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) @@ -67,11 +73,11 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE # external build triggered from this build file. set(serious_python_linux_bundled_libraries "${PYTHON_PACKAGE}/lib/libpython3.so" - "${PYTHON_PACKAGE}/lib/libpython3.12.so.1.0" + "${PYTHON_PACKAGE}/lib/libpython${PYTHON_VERSION}.so.1.0" PARENT_SCOPE ) -install(DIRECTORY "${PYTHON_PACKAGE}/lib/python3.12" +install(DIRECTORY "${PYTHON_PACKAGE}/lib/python${PYTHON_VERSION}" DESTINATION "${CMAKE_BINARY_DIR}/bundle") install(CODE " diff --git a/src/serious_python_linux/linux/serious_python_linux_plugin.cc b/src/serious_python_linux/linux/serious_python_linux_plugin.cc index af45cb5d..33d4970d 100644 --- a/src/serious_python_linux/linux/serious_python_linux_plugin.cc +++ b/src/serious_python_linux/linux/serious_python_linux_plugin.cc @@ -11,6 +11,14 @@ #include +// Defined by linux/CMakeLists.txt to match the bundled libpython +// (e.g. "3.13"). The fallback only matters if this file is ever compiled +// outside the plugin's own CMake — defaults to the same version CMakeLists +// falls back to when SERIOUS_PYTHON_VERSION env var is unset. +#ifndef SERIOUS_PYTHON_VERSION +#define SERIOUS_PYTHON_VERSION "3.14" +#endif + #define SERIOUS_PYTHON_LINUX_PLUGIN(obj) \ (G_TYPE_CHECK_INSTANCE_CAST((obj), serious_python_linux_plugin_get_type(), \ SeriousPythonLinuxPlugin)) @@ -102,7 +110,8 @@ static void serious_python_linux_plugin_handle_method_call( module_paths_str_array[i++] = g_strdup_printf("%s", app_dir); module_paths_str_array[i++] = g_strdup_printf("%s/__pypackages__", app_dir); module_paths_str_array[i++] = g_strdup_printf("%s/site-packages", exe_dir); - module_paths_str_array[i++] = g_strdup_printf("%s/python3.12", exe_dir); + module_paths_str_array[i++] = + g_strdup_printf("%s/python" SERIOUS_PYTHON_VERSION, exe_dir); module_paths_str_array[i++] = NULL; gchar *module_paths_str = g_strjoinv(":", module_paths_str_array); // join with comma and space as separators diff --git a/src/serious_python_platform_interface/CHANGELOG.md b/src/serious_python_platform_interface/CHANGELOG.md index 0a5b2566..c5b39f42 100644 --- a/src/serious_python_platform_interface/CHANGELOG.md +++ b/src/serious_python_platform_interface/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* Version bump for the multi-version Python support release across the `serious_python_*` platform plugins. No interface changes. + ## 1.0.0 * **Breaking change:** `--platform` argument value `Pyodide` has been renamed to `Emscripten` to match what `platform.system()` returns in the Pyodide runtime, so PEP 508 markers like `platform_system != 'Emscripten'` work consistently. diff --git a/src/serious_python_windows/CHANGELOG.md b/src/serious_python_windows/CHANGELOG.md index 24eba75d..2c0f2bc7 100644 --- a/src/serious_python_windows/CHANGELOG.md +++ b/src/serious_python_windows/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.1.0 + +* Multi-version Python support. `PYTHON_VERSION` in `windows/CMakeLists.txt` is now read from `SERIOUS_PYTHON_VERSION` (default `3.14`); the `python-build` download URL and the `python312.lib` / `python312.dll` filenames are derived from it (e.g. `python313.lib`, `python314.dll`). + ## 1.0.0 * **Breaking change:** `--platform` argument value `Pyodide` has been renamed to `Emscripten` to match what `platform.system()` returns in the Pyodide runtime, so PEP 508 markers like `platform_system != 'Emscripten'` work consistently. diff --git a/src/serious_python_windows/windows/CMakeLists.txt b/src/serious_python_windows/windows/CMakeLists.txt index 17046ec6..53910244 100644 --- a/src/serious_python_windows/windows/CMakeLists.txt +++ b/src/serious_python_windows/windows/CMakeLists.txt @@ -6,6 +6,12 @@ cmake_minimum_required(VERSION 3.14) # Project-level configuration. set(PROJECT_NAME "serious_python_windows") +if(DEFINED ENV{SERIOUS_PYTHON_VERSION}) + set(PYTHON_VERSION "$ENV{SERIOUS_PYTHON_VERSION}") +else() + set(PYTHON_VERSION "3.14") +endif() +string(REPLACE "." "" PYTHON_VERSION_NODOT "${PYTHON_VERSION}") project(${PROJECT_NAME} LANGUAGES CXX) # Explicitly opt in to modern CMake behaviors to avoid warnings with recent @@ -17,7 +23,7 @@ cmake_policy(VERSION 3.14...3.25) set(PLUGIN_NAME "serious_python_windows_plugin") set(PYTHON_PACKAGE ${CMAKE_BINARY_DIR}/python) -set(PYTHON_URL "https://github.com/flet-dev/python-build/releases/download/v3.12/python-windows-for-dart-3.12.zip") +set(PYTHON_URL "https://github.com/flet-dev/python-build/releases/download/v${PYTHON_VERSION}/python-windows-for-dart-${PYTHON_VERSION}.zip") set(PYTHON_FILE ${CMAKE_BINARY_DIR}/python-windows-for-dart.zip) if (NOT EXISTS ${PYTHON_FILE}) file(DOWNLOAD ${PYTHON_URL} ${PYTHON_FILE}) @@ -63,7 +69,7 @@ include_directories( target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) target_link_libraries(${PLUGIN_NAME} PRIVATE - "${PYTHON_PACKAGE}/libs/python312$<$:_d>.lib" + "${PYTHON_PACKAGE}/libs/python${PYTHON_VERSION_NODOT}$<$:_d>.lib" ) # List of absolute paths to libraries that should be bundled with the plugin. @@ -71,7 +77,7 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE # external build triggered from this build file. string(REPLACE "\\" "/" SERIOUS_PYTHON_WINDIR "$ENV{WINDIR}") set(serious_python_windows_bundled_libraries - "${PYTHON_PACKAGE}/python312$<$:_d>.dll" + "${PYTHON_PACKAGE}/python${PYTHON_VERSION_NODOT}$<$:_d>.dll" "${PYTHON_PACKAGE}/python3$<$:_d>.dll" "${SERIOUS_PYTHON_WINDIR}/System32/msvcp140.dll" "${SERIOUS_PYTHON_WINDIR}/System32/vcruntime140.dll" From c45e62f0f55e25f11c9319b52619f586d20be797 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Jun 2026 17:38:02 -0700 Subject: [PATCH 2/8] Fix Windows/Android CI failures + assert bundled Python in integration test Two regressions surfaced by the wider Python matrix on the newer python-build-standalone (20260602) and the newer pip vendored packaging that ships with it: * Windows: astral-sh dropped the explicit `-shared` MSVC build; only the combined `x86_64-pc-windows-msvc-install_only_stripped` variant remains (which is itself shared). Drop the `-shared` suffix from the constructed archive name. * Android: packaging.tags.android_platforms now calls `platform.android_ver().api_level` (3.13+ API, returns 0 on non-Android hosts even where present), and derives the wheel ABI from `sysconfig.get_platform().split("-")[-1]`. Shim `platform.android_ver` in sitecustomize.py to return a namedtuple with the api_level encoded in the custom tag, and switch the Android platform tags from `android-24-arm64-v8a` to `android-24-arm64_v8a` so the split-by-dash gives the full ABI string. The wheel tag emitted by both old and new packaging remains `android_24_arm64_v8a`, matching the wheels on pypi.flet.dev. The example app now prints/displays the runtime CPython version, and the integration test verifies it matches `--dart-define=EXPECTED_PYTHON_VERSION` (passed per matrix entry from CI). The assertion is skipped when the define is empty, so local runs are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/ci.yml | 10 ++++----- src/serious_python/bin/package_command.dart | 12 ++++++++--- src/serious_python/bin/sitecustomize.dart | 21 +++++++++++++++++++ .../example/flet_example/app/src/main.py | 7 +++++++ .../integration_test/app_test.dart | 21 +++++++++++++++++++ 5 files changed, 63 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f39098e..f1f7b112 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: working-directory: "src/serious_python/example/flet_example" run: | dart run serious_python:main package app/src --platform Darwin --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 - flutter test integration_test --device-id macos + flutter test integration_test --device-id macos --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} ios: name: Test on iOS (Python ${{ matrix.python_version }}) @@ -75,7 +75,7 @@ jobs: working-directory: "src/serious_python/example/flet_example" run: | dart run serious_python:main package app/src --platform iOS --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 - flutter test integration_test --device-id ${{ steps.simulator.outputs.udid }} + flutter test integration_test --device-id ${{ steps.simulator.outputs.udid }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} android: name: Test on Android (Python ${{ matrix.python_version }}) @@ -134,7 +134,7 @@ jobs: sdkmanager --list_installed script: | cd src/serious_python/example/flet_example && dart run serious_python:main package app/src --platform Android --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 - cd src/serious_python/example/flet_example && flutter test integration_test --device-id emulator-${{ env.EMULATOR_PORT }} + cd src/serious_python/example/flet_example && flutter test integration_test --device-id emulator-${{ env.EMULATOR_PORT }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} windows: name: Test on Windows (Python ${{ matrix.python_version }}) @@ -159,7 +159,7 @@ jobs: working-directory: "src/serious_python/example/flet_example" run: | dart run serious_python:main package app/src --platform Windows --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 - flutter test integration_test -d windows + flutter test integration_test -d windows --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} linux: name: Test on Linux ${{ matrix.title }} (Python ${{ matrix.python_version }}) @@ -234,7 +234,7 @@ jobs: run: | flutter pub get dart run serious_python:main package app/src --platform Linux --python-version ${{ matrix.python_version }} --requirements flet==0.28.3 - xvfb-run flutter test integration_test -d linux + xvfb-run flutter test integration_test -d linux --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }} publish: name: Publish to pub.dev diff --git a/src/serious_python/bin/package_command.dart b/src/serious_python/bin/package_command.dart index 673c8dca..491123bd 100644 --- a/src/serious_python/bin/package_command.dart +++ b/src/serious_python/bin/package_command.dart @@ -80,8 +80,12 @@ const platforms = { } }, "Android": { - "arm64-v8a": {"tag": "android-24-arm64-v8a", "mac_ver": ""}, - "armeabi-v7a": {"tag": "android-24-armeabi-v7a", "mac_ver": ""}, + // The ABI segment uses '_' so that packaging.tags.android_platforms (3.13+ + // pip vendored packaging) — which derives the abi from + // `sysconfig.get_platform().split("-")[-1]` — picks up the full ABI + // (e.g. "arm64_v8a") rather than just the trailing token. + "arm64-v8a": {"tag": "android-24-arm64_v8a", "mac_ver": ""}, + "armeabi-v7a": {"tag": "android-24-armeabi_v7a", "mac_ver": ""}, "x86_64": {"tag": "android-24-x86_64", "mac_ver": ""}, "x86": {"tag": "android-24-x86", "mac_ver": ""} }, @@ -653,7 +657,9 @@ class PackageCommand extends Command { } else if (Platform.isLinux && isArm64) { arch = 'aarch64-unknown-linux-gnu'; } else if (Platform.isWindows) { - arch = 'x86_64-pc-windows-msvc-shared'; + // python-build-standalone dropped the explicit `-shared` MSVC + // variant; the remaining install_only_stripped build is shared. + arch = 'x86_64-pc-windows-msvc'; } var pythonArchiveFilename = diff --git a/src/serious_python/bin/sitecustomize.dart b/src/serious_python/bin/sitecustomize.dart index 0dfc0146..8ed6fb1e 100644 --- a/src/serious_python/bin/sitecustomize.dart +++ b/src/serious_python/bin/sitecustomize.dart @@ -41,6 +41,27 @@ if custom_system == "iOS": sys.implementation._multiarch = f"{tag_parts[2]}_{tag_parts[3]}" +if custom_system == "Android": + # Newer pip (vendored packaging >= ~25) computes Android wheel tags from + # `platform.android_ver().api_level`. That API only exists in CPython 3.13+, + # and even there it returns api_level=0 on non-Android hosts. Shim it so + # cross-builds work on any host Python. + AndroidVer = collections.namedtuple( + "AndroidVer", + ["release", "api_level", "manufacturer", "model", "device", "is_emulator"] + ) + + tag_parts = custom_platform.split("-") + try: + _api_level = int(tag_parts[1]) + except (IndexError, ValueError): + _api_level = 24 + + def custom_android_ver(): + return AndroidVer("", _api_level, "", "", "", False) + + platform.android_ver = custom_android_ver + orig_platform_version = platform.version platform.version = lambda: orig_platform_version() + ";embedded" """; diff --git a/src/serious_python/example/flet_example/app/src/main.py b/src/serious_python/example/flet_example/app/src/main.py index 4abac528..2d4eec94 100644 --- a/src/serious_python/example/flet_example/app/src/main.py +++ b/src/serious_python/example/flet_example/app/src/main.py @@ -20,6 +20,12 @@ def main(page: ft.Page): page.title = "Flet counter example" page.vertical_alignment = ft.MainAxisAlignment.CENTER + py_ver = sys.version_info + python_version_text = ( + f"Python version: {py_ver.major}.{py_ver.minor}.{py_ver.micro}" + ) + print(python_version_text) + txt_number = ft.TextField(value="0", text_align=ft.TextAlign.RIGHT, width=100) def minus_click(e): @@ -63,6 +69,7 @@ def check_ssl(e): ), ft.Text(f"App data dir: {app_data}"), ft.Text(f"App temp dir: {app_temp}"), + ft.Text(python_version_text), ) print("This is inside main() method!") diff --git a/src/serious_python/example/flet_example/integration_test/app_test.dart b/src/serious_python/example/flet_example/integration_test/app_test.dart index 880d75ff..254a6c73 100644 --- a/src/serious_python/example/flet_example/integration_test/app_test.dart +++ b/src/serious_python/example/flet_example/integration_test/app_test.dart @@ -37,6 +37,27 @@ void main() { await tester.tap(decrementButton); await tester.pumpAndSettle(); expect(find.text('-1'), findsOneWidget); + + // Verify the bundled Python runtime matches what CI requested. Skipped + // outside CI (no --dart-define). + const expectedPyVersion = + String.fromEnvironment('EXPECTED_PYTHON_VERSION'); + if (expectedPyVersion.isNotEmpty) { + bool versionFound = false; + for (int i = 0; i < 20; i++) { + await tester.pump(const Duration(seconds: 1)); + if (find + .textContaining('Python version: $expectedPyVersion.') + .evaluate() + .isNotEmpty) { + versionFound = true; + break; + } + } + expect(versionFound, isTrue, + reason: + 'Expected `Python version: $expectedPyVersion.x` in the app UI'); + } }); }); } From 521066374a53ef7fb8e1b41cdb8d34de7a06e021 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Jun 2026 17:57:43 -0700 Subject: [PATCH 3/8] Android: drop armeabi-v7a from abiFilters on Python 3.13+ `flet-dev/python-build` stopped publishing the 32-bit `python-android-dart--armeabi-v7a.tar.gz` tarball for Python 3.13+ (64-bit only), so the gradle download task that's driven by abiFilters fails for those versions. Keep all three ABIs on 3.12, restrict to arm64-v8a + x86_64 from 3.13 onward. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/serious_python_android/android/build.gradle | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/serious_python_android/android/build.gradle b/src/serious_python_android/android/build.gradle index 7aeb94ed..c520dfef 100644 --- a/src/serious_python_android/android/build.gradle +++ b/src/serious_python_android/android/build.gradle @@ -57,7 +57,13 @@ android { minSdkVersion 21 ndk { - abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' + // python-build dropped 32-bit Android in 3.13 (PEP 738), so the + // python-android-dart--armeabi-v7a tarball only exists for 3.12. + if (python_version == '3.12') { + abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64' + } else { + abiFilters 'arm64-v8a', 'x86_64' + } } } From d39e178e334521be2d8db942f0f38f7f23d3d21b Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Jun 2026 18:22:15 -0700 Subject: [PATCH 4/8] package_command: skip 32-bit Android ABIs on Python 3.13+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit python-build dropped 32-bit Android (armeabi-v7a, x86) in 3.13 (PEP 738), and the serious_python_android plugin already restricts abiFilters accordingly. Without a matching guard in the package command, the pip install loop still iterates the 32-bit ABIs and produces site-packages trees that the Flutter build then ignores — pure wasted work. Skip those arches when --python-version is anything other than 3.12. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/serious_python/bin/package_command.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/serious_python/bin/package_command.dart b/src/serious_python/bin/package_command.dart index 491123bd..429906c4 100644 --- a/src/serious_python/bin/package_command.dart +++ b/src/serious_python/bin/package_command.dart @@ -368,6 +368,14 @@ class PackageCommand extends Command { if (archArg.isNotEmpty && !archArg.contains(arch.key)) { continue; } + // python-build dropped 32-bit Android in 3.13 (PEP 738); the + // platform plugin only bundles arm64-v8a + x86_64 for those + // versions, so installing 32-bit wheels would be wasted work. + if (platform == "Android" && + _pythonShortVersion != "3.12" && + (arch.key == "armeabi-v7a" || arch.key == "x86")) { + continue; + } String? sitePackagesDir; Map? pipEnv; Directory? sitecustomizeDir; From 983592348a62b1391ba732ebcaef59c6813e77b4 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Jun 2026 18:35:11 -0700 Subject: [PATCH 5/8] Unpin numpy and update app zip hash Replace the strict numpy==1.26.4 pin with an unpinned 'numpy' in run_example requirements to allow flexible numpy versions. Also update the flet_example app.zip.hash to the new checksum matching the updated packaged app. --- src/serious_python/example/flet_example/app/app.zip.hash | 2 +- src/serious_python/example/run_example/app/src/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serious_python/example/flet_example/app/app.zip.hash b/src/serious_python/example/flet_example/app/app.zip.hash index 4a9ba080..4ef49a27 100644 --- a/src/serious_python/example/flet_example/app/app.zip.hash +++ b/src/serious_python/example/flet_example/app/app.zip.hash @@ -1 +1 @@ -2ce2f5ff38bcf342e42fb77b2bc8e4262d90d73d5bc3284055242c6313cbe5c0 \ No newline at end of file +c70dd39b2f894beb08c977300587fe6938c3a02649598f13b80d68d998d9cc07 \ No newline at end of file diff --git a/src/serious_python/example/run_example/app/src/requirements.txt b/src/serious_python/example/run_example/app/src/requirements.txt index 7576bb83..50630e5d 100644 --- a/src/serious_python/example/run_example/app/src/requirements.txt +++ b/src/serious_python/example/run_example/app/src/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.26.4 +numpy #lru-dict #pyjnius #flet-libsodium From 3a0449d57d204fc18132a58990ab3a4f51c42a14 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Jun 2026 18:38:14 -0700 Subject: [PATCH 6/8] Bump to 2.0.0; add prerelease field to version registry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The default Python version bumped from 3.12 to 3.14 (latest stable) — a breaking change for any caller of `dart run serious_python:main package` that omits `--python-version`. Bump all six pub packages to 2.0.0 and call this out in the CHANGELOGs alongside the other breaking surfaces (Android `sysconfig.get_platform()` separator, Windows arch identifier). `_pythonReleases` entries gain a required `prerelease` field. The Dart side keeps the version-print line honest by appending " — pre-release" when applicable; the Flet CLI mirrors the field in its `PythonRelease` dataclass and gates `requires-python` resolution: stable rows win, prereleases are a fallback so explicit specifiers like `==3.15.*` (and `>=3.15` when no stable matches) still opt in without exposing a CLI flag or making the default jump to a beta line. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/serious_python/CHANGELOG.md | 12 ++++++--- src/serious_python/bin/package_command.dart | 25 ++++++++++++++++++- src/serious_python/pubspec.yaml | 2 +- src/serious_python_android/CHANGELOG.md | 6 +++-- src/serious_python_android/pubspec.yaml | 2 +- src/serious_python_darwin/CHANGELOG.md | 5 ++-- src/serious_python_darwin/pubspec.yaml | 2 +- src/serious_python_linux/CHANGELOG.md | 5 ++-- src/serious_python_linux/pubspec.yaml | 2 +- .../CHANGELOG.md | 4 +-- .../pubspec.yaml | 2 +- src/serious_python_windows/CHANGELOG.md | 5 ++-- src/serious_python_windows/pubspec.yaml | 2 +- 13 files changed, 54 insertions(+), 20 deletions(-) diff --git a/src/serious_python/CHANGELOG.md b/src/serious_python/CHANGELOG.md index fc8bafac..7160c547 100644 --- a/src/serious_python/CHANGELOG.md +++ b/src/serious_python/CHANGELOG.md @@ -1,6 +1,12 @@ -## 1.1.0 - -* Multi-version Python support. The `package` command now accepts `--python-version` (or `SERIOUS_PYTHON_VERSION` env var) to select between Python 3.12 / 3.13 / 3.14. Defaults to the latest supported version (3.14). The matching CPython-standalone build and Pyodide release are looked up from a new `_pythonReleases` table; the Emscripten wheel platform tag is derived from it too, so each Python release picks the right `pyodide-XXXX.X-wasm32` / `pyemscripten-XXXX.X-wasm32` tag automatically. +## 2.0.0 + +* **Breaking change:** the `package` command's default Python is now the latest supported stable (3.14), up from the previously implicit 3.12. Scripts that ran `dart run serious_python:main package …` without `--python-version` will now download CPython 3.14, install 3.14 wheels, and use the matching Pyodide / Android platform tags. Pin explicitly with `--python-version 3.12` (or `SERIOUS_PYTHON_VERSION=3.12`) to preserve the old behavior. +* **Breaking change:** Android `sysconfig.get_platform()` tag format changed from `android-24-arm64-v8a` to `android-24-arm64_v8a` (and similarly for `armeabi-v7a`). The emitted wheel tag (`android_24_arm64_v8a`) is unchanged, but anything reading the raw `sysconfig.get_platform()` string from `sitecustomize.py` should switch separators. +* **Breaking change:** Windows host arch identifier dropped the `-shared` suffix (`x86_64-pc-windows-msvc-shared` → `x86_64-pc-windows-msvc`); follows astral-sh/python-build-standalone, which only publishes the combined (already shared) `install_only_stripped` build. +* Multi-version Python support. The `package` command accepts `--python-version` (or `SERIOUS_PYTHON_VERSION` env var) to select between Python 3.12 / 3.13 / 3.14. The matching CPython-standalone build, Pyodide release, and Emscripten wheel platform tag are looked up from a new `_pythonReleases` table. Adding a future pre-release line (e.g. 3.15 beta) is a one-row append with `prerelease: true`; the Flet CLI uses that flag to keep open-ended `requires-python` specifiers (`>=3.14`) on stable, while still letting `--python-version 3.15` or `==3.15.*` opt in. +* The Emscripten pip platform tag is now derived per Python release (e.g. `pyodide-2024.0-wasm32` for 0.27.7, `pyemscripten-2026.0-wasm32` for 314.0.0a2), via a `pyodide_platform_tag` field in the version registry. The previous static `pyodide-2024.0-wasm32` entry in `platforms["Emscripten"]` has been removed. +* `sitecustomize.py` now shims `platform.android_ver` so the new pip / packaging that ships with python-build-standalone 20260602+ can compute Android wheel tags on Python 3.12 hosts (where `android_ver` didn't exist) and on Python 3.13+ hosts (where it returns `api_level=0` off-device). +* Skip 32-bit Android ABIs (`armeabi-v7a`, `x86`) when Python ≥ 3.13 — PEP 738 dropped 32-bit Android support, and `flet-dev/python-build` no longer publishes those runtimes for those versions. ## 1.0.0 diff --git a/src/serious_python/bin/package_command.dart b/src/serious_python/bin/package_command.dart index 429906c4..b21541b8 100644 --- a/src/serious_python/bin/package_command.dart +++ b/src/serious_python/bin/package_command.dart @@ -36,12 +36,18 @@ class _PythonRelease { required this.standaloneReleaseDate, required this.pyodideVersion, required this.pyodidePlatformTag, + required this.prerelease, }); final String standaloneVersion; final String standaloneReleaseDate; final String pyodideVersion; final String pyodidePlatformTag; + + // When true, this release is supported by `--python-version` but is not + // picked automatically by the default or by `[project].requires-python` + // resolution on the Flet CLI side. Use for beta CPython lines. + final bool prerelease; } // Source of truth for the Python <-> CPython standalone <-> Pyodide mapping. @@ -52,19 +58,34 @@ const _pythonReleases = { standaloneReleaseDate: "20260602", pyodideVersion: "0.27.7", pyodidePlatformTag: "pyodide-2024.0-wasm32", + prerelease: false, ), "3.13": _PythonRelease( standaloneVersion: "3.13.13", standaloneReleaseDate: "20260602", pyodideVersion: "0.29.4", pyodidePlatformTag: "pyodide-2025.0-wasm32", + prerelease: false, ), "3.14": _PythonRelease( standaloneVersion: "3.14.5", standaloneReleaseDate: "20260602", pyodideVersion: "314.0.0a2", pyodidePlatformTag: "pyemscripten-2026.0-wasm32", + prerelease: false, ), + // Add future pre-release CPython lines by setting `prerelease: true`. They + // become opt-in via `--python-version 3.15` (or an explicit + // `requires-python = "==3.15.*"` on the Flet CLI side) without becoming + // the default or matching open-ended `requires-python` specifiers. + // + // "3.15": _PythonRelease( + // standaloneVersion: "3.15.0", + // standaloneReleaseDate: "...", + // pyodideVersion: "...", + // pyodidePlatformTag: "...", + // prerelease: true, + // ), }; const platforms = { @@ -247,9 +268,11 @@ class PackageCommand extends Command { Platform.environment[pyodideVersionEnvironmentVariable] ?? baseRelease.pyodideVersion, pyodidePlatformTag: baseRelease.pyodidePlatformTag, + prerelease: baseRelease.prerelease, ); + final preNote = _release.prerelease ? " — pre-release" : ""; stdout.writeln( - "Python $_pythonShortVersion (CPython ${_release.standaloneVersion}, " + "Python $_pythonShortVersion$preNote (CPython ${_release.standaloneVersion}, " "Pyodide ${_release.pyodideVersion})"); if (path.isRelative(sourceDirPath)) { diff --git a/src/serious_python/pubspec.yaml b/src/serious_python/pubspec.yaml index 14956eb8..84671715 100644 --- a/src/serious_python/pubspec.yaml +++ b/src/serious_python/pubspec.yaml @@ -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: 1.0.0 +version: 2.0.0 platforms: ios: diff --git a/src/serious_python_android/CHANGELOG.md b/src/serious_python_android/CHANGELOG.md index e2b0401b..fb3c9e12 100644 --- a/src/serious_python_android/CHANGELOG.md +++ b/src/serious_python_android/CHANGELOG.md @@ -1,7 +1,9 @@ -## 1.1.0 +## 2.0.0 -* Multi-version Python support. `python_version` in `android/build.gradle` is now read from `SERIOUS_PYTHON_VERSION` (default `3.14`), so the python-build distribution URL points at `v3.12` / `v3.13` / `v3.14` depending on the `flet build` selection. +* **Breaking change:** default bundled Python version is now 3.14 (was 3.12). Apps built without an explicit `SERIOUS_PYTHON_VERSION` env var pull the 3.14 python-build distribution and ship `libpython3.14.so`. Set `SERIOUS_PYTHON_VERSION=3.12` (typically threaded through `flet build`) to preserve the previous default. +* Multi-version Python support. `python_version` in `android/build.gradle` reads from `SERIOUS_PYTHON_VERSION` and drives the `flet-dev/python-build` download URL. * The Dart runtime no longer hardcodes `libpython3.12.so` — it scans `nativeLibraryDir` for `libpython3.*.so` so whichever libpython the plugin bundled is loaded automatically. +* `abiFilters` now branches on `python_version`: keep `armeabi-v7a` for 3.12, restrict to `arm64-v8a` + `x86_64` for 3.13+ (python-build dropped 32-bit Android per PEP 738). ## 1.0.0 diff --git a/src/serious_python_android/pubspec.yaml b/src/serious_python_android/pubspec.yaml index abee8e16..81be5f4d 100644 --- a/src/serious_python_android/pubspec.yaml +++ b/src/serious_python_android/pubspec.yaml @@ -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: 1.0.0 +version: 2.0.0 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/src/serious_python_darwin/CHANGELOG.md b/src/serious_python_darwin/CHANGELOG.md index 3eb717f3..a31158ff 100644 --- a/src/serious_python_darwin/CHANGELOG.md +++ b/src/serious_python_darwin/CHANGELOG.md @@ -1,6 +1,7 @@ -## 1.1.0 +## 2.0.0 -* Multi-version Python support. `python_version` in `serious_python_darwin.podspec` is now read from `SERIOUS_PYTHON_VERSION` (default `3.14`), so `prepare_ios.sh` / `prepare_macos.sh` download the matching python-build tarballs (`python-ios-dart-3.13.tar.gz` etc.). +* **Breaking change:** default bundled Python version is now 3.14 (was 3.12). Apps built without an explicit `SERIOUS_PYTHON_VERSION` env var pull `python-ios-dart-3.14.tar.gz` / `python-macos-dart-3.14.tar.gz` from `flet-dev/python-build`. Set `SERIOUS_PYTHON_VERSION=3.12` to preserve the previous default. +* Multi-version Python support. `python_version` in `serious_python_darwin.podspec` reads from `SERIOUS_PYTHON_VERSION`; `prepare_ios.sh` / `prepare_macos.sh` already took the version as `$1` and download the matching tarballs. ## 1.0.0 diff --git a/src/serious_python_darwin/pubspec.yaml b/src/serious_python_darwin/pubspec.yaml index 35d1407c..e2b781e2 100644 --- a/src/serious_python_darwin/pubspec.yaml +++ b/src/serious_python_darwin/pubspec.yaml @@ -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: 1.0.0 +version: 2.0.0 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/src/serious_python_linux/CHANGELOG.md b/src/serious_python_linux/CHANGELOG.md index 967fdc21..bb72ee80 100644 --- a/src/serious_python_linux/CHANGELOG.md +++ b/src/serious_python_linux/CHANGELOG.md @@ -1,6 +1,7 @@ -## 1.1.0 +## 2.0.0 -* Multi-version Python support. `PYTHON_VERSION` in `linux/CMakeLists.txt` is now read from `SERIOUS_PYTHON_VERSION` (default `3.14`), and all hardcoded `python3.12` / `libpython3.12.so.1.0` / `lib/python3.12` paths are derived from it. The plugin source receives the version via a `SERIOUS_PYTHON_VERSION` compile-time macro so the runtime module path matches the bundled distro. +* **Breaking change:** default bundled Python version is now 3.14 (was 3.12). The plugin downloads `python-linux-dart-3.14-.tar.gz` from `flet-dev/python-build` and bundles `libpython3.14.so.1.0` unless `SERIOUS_PYTHON_VERSION=3.12` is set in the build environment. +* Multi-version Python support. `PYTHON_VERSION` in `linux/CMakeLists.txt` reads from `SERIOUS_PYTHON_VERSION`, and all `python3.12` / `libpython3.12.so.1.0` / `lib/python3.12` paths are derived from it. The plugin source receives the version via a `SERIOUS_PYTHON_VERSION` compile-time macro so the runtime module path matches the bundled distro. ## 1.0.0 diff --git a/src/serious_python_linux/pubspec.yaml b/src/serious_python_linux/pubspec.yaml index f21bba1f..9a834cb7 100644 --- a/src/serious_python_linux/pubspec.yaml +++ b/src/serious_python_linux/pubspec.yaml @@ -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: 1.0.0 +version: 2.0.0 environment: sdk: '>=3.1.3 <4.0.0' diff --git a/src/serious_python_platform_interface/CHANGELOG.md b/src/serious_python_platform_interface/CHANGELOG.md index c5b39f42..ad57d39b 100644 --- a/src/serious_python_platform_interface/CHANGELOG.md +++ b/src/serious_python_platform_interface/CHANGELOG.md @@ -1,6 +1,6 @@ -## 1.1.0 +## 2.0.0 -* Version bump for the multi-version Python support release across the `serious_python_*` platform plugins. No interface changes. +* Version bump aligning with the major release of the `serious_python_*` platform plugins (multi-version Python support, default Python now 3.14). No interface changes. ## 1.0.0 diff --git a/src/serious_python_platform_interface/pubspec.yaml b/src/serious_python_platform_interface/pubspec.yaml index 85560443..22dbe9ee 100644 --- a/src/serious_python_platform_interface/pubspec.yaml +++ b/src/serious_python_platform_interface/pubspec.yaml @@ -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: 1.0.0 +version: 2.0.0 environment: sdk: ">=3.0.0 <4.0.0" diff --git a/src/serious_python_windows/CHANGELOG.md b/src/serious_python_windows/CHANGELOG.md index 2c0f2bc7..36f5d44a 100644 --- a/src/serious_python_windows/CHANGELOG.md +++ b/src/serious_python_windows/CHANGELOG.md @@ -1,6 +1,7 @@ -## 1.1.0 +## 2.0.0 -* Multi-version Python support. `PYTHON_VERSION` in `windows/CMakeLists.txt` is now read from `SERIOUS_PYTHON_VERSION` (default `3.14`); the `python-build` download URL and the `python312.lib` / `python312.dll` filenames are derived from it (e.g. `python313.lib`, `python314.dll`). +* **Breaking change:** default bundled Python version is now 3.14 (was 3.12). The plugin downloads `python-windows-for-dart-3.14.zip` and bundles `python314.dll` / `python314.lib` unless `SERIOUS_PYTHON_VERSION=3.12` is set in the build environment. +* Multi-version Python support. `PYTHON_VERSION` in `windows/CMakeLists.txt` reads from `SERIOUS_PYTHON_VERSION`; the `python-build` download URL and the `python312.lib` / `python312.dll` filenames are derived from it (e.g. `python313.lib`, `python314.dll`). ## 1.0.0 diff --git a/src/serious_python_windows/pubspec.yaml b/src/serious_python_windows/pubspec.yaml index 4e432db1..f41422dd 100644 --- a/src/serious_python_windows/pubspec.yaml +++ b/src/serious_python_windows/pubspec.yaml @@ -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: 1.0.0 +version: 2.0.0 environment: sdk: '>=3.1.3 <4.0.0' From 5f723daad630e083f90c33bf9cbdafb73fe1a651 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Fri, 5 Jun 2026 19:23:24 -0700 Subject: [PATCH 7/8] Fold 1.0.1 changelog bullet into the 2.0.0 entry 1.0.1 was prepared on `release-1.0.1` but never published, so the PIP_REQUIRE_VIRTUALENV=false fix should ship as part of 2.0.0 rather than as its own release header. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/serious_python/CHANGELOG.md | 1 + src/serious_python_android/CHANGELOG.md | 1 + src/serious_python_darwin/CHANGELOG.md | 1 + src/serious_python_linux/CHANGELOG.md | 1 + src/serious_python_platform_interface/CHANGELOG.md | 1 + src/serious_python_windows/CHANGELOG.md | 1 + 6 files changed, 6 insertions(+) diff --git a/src/serious_python/CHANGELOG.md b/src/serious_python/CHANGELOG.md index 7160c547..f297348e 100644 --- a/src/serious_python/CHANGELOG.md +++ b/src/serious_python/CHANGELOG.md @@ -7,6 +7,7 @@ * The Emscripten pip platform tag is now derived per Python release (e.g. `pyodide-2024.0-wasm32` for 0.27.7, `pyemscripten-2026.0-wasm32` for 314.0.0a2), via a `pyodide_platform_tag` field in the version registry. The previous static `pyodide-2024.0-wasm32` entry in `platforms["Emscripten"]` has been removed. * `sitecustomize.py` now shims `platform.android_ver` so the new pip / packaging that ships with python-build-standalone 20260602+ can compute Android wheel tags on Python 3.12 hosts (where `android_ver` didn't exist) and on Python 3.13+ hosts (where it returns `api_level=0` off-device). * Skip 32-bit Android ABIs (`armeabi-v7a`, `x86`) when Python ≥ 3.13 — PEP 738 dropped 32-bit Android support, and `flet-dev/python-build` no longer publishes those runtimes for those versions. +* Set `PIP_REQUIRE_VIRTUALENV=false` for `pip install` so the package command works when `require-virtualenv = true` is set in pip config ([#202](https://github.com/flet-dev/serious-python/issues/202)). ## 1.0.0 diff --git a/src/serious_python_android/CHANGELOG.md b/src/serious_python_android/CHANGELOG.md index fb3c9e12..bd57a53b 100644 --- a/src/serious_python_android/CHANGELOG.md +++ b/src/serious_python_android/CHANGELOG.md @@ -4,6 +4,7 @@ * Multi-version Python support. `python_version` in `android/build.gradle` reads from `SERIOUS_PYTHON_VERSION` and drives the `flet-dev/python-build` download URL. * The Dart runtime no longer hardcodes `libpython3.12.so` — it scans `nativeLibraryDir` for `libpython3.*.so` so whichever libpython the plugin bundled is loaded automatically. * `abiFilters` now branches on `python_version`: keep `armeabi-v7a` for 3.12, restrict to `arm64-v8a` + `x86_64` for 3.13+ (python-build dropped 32-bit Android per PEP 738). +* Set `PIP_REQUIRE_VIRTUALENV=false` for `pip install` so the package command works when `require-virtualenv = true` is set in pip config ([#202](https://github.com/flet-dev/serious-python/issues/202)). ## 1.0.0 diff --git a/src/serious_python_darwin/CHANGELOG.md b/src/serious_python_darwin/CHANGELOG.md index a31158ff..bff4e848 100644 --- a/src/serious_python_darwin/CHANGELOG.md +++ b/src/serious_python_darwin/CHANGELOG.md @@ -2,6 +2,7 @@ * **Breaking change:** default bundled Python version is now 3.14 (was 3.12). Apps built without an explicit `SERIOUS_PYTHON_VERSION` env var pull `python-ios-dart-3.14.tar.gz` / `python-macos-dart-3.14.tar.gz` from `flet-dev/python-build`. Set `SERIOUS_PYTHON_VERSION=3.12` to preserve the previous default. * Multi-version Python support. `python_version` in `serious_python_darwin.podspec` reads from `SERIOUS_PYTHON_VERSION`; `prepare_ios.sh` / `prepare_macos.sh` already took the version as `$1` and download the matching tarballs. +* Set `PIP_REQUIRE_VIRTUALENV=false` for `pip install` so the package command works when `require-virtualenv = true` is set in pip config ([#202](https://github.com/flet-dev/serious-python/issues/202)). ## 1.0.0 diff --git a/src/serious_python_linux/CHANGELOG.md b/src/serious_python_linux/CHANGELOG.md index bb72ee80..df4dc70d 100644 --- a/src/serious_python_linux/CHANGELOG.md +++ b/src/serious_python_linux/CHANGELOG.md @@ -2,6 +2,7 @@ * **Breaking change:** default bundled Python version is now 3.14 (was 3.12). The plugin downloads `python-linux-dart-3.14-.tar.gz` from `flet-dev/python-build` and bundles `libpython3.14.so.1.0` unless `SERIOUS_PYTHON_VERSION=3.12` is set in the build environment. * Multi-version Python support. `PYTHON_VERSION` in `linux/CMakeLists.txt` reads from `SERIOUS_PYTHON_VERSION`, and all `python3.12` / `libpython3.12.so.1.0` / `lib/python3.12` paths are derived from it. The plugin source receives the version via a `SERIOUS_PYTHON_VERSION` compile-time macro so the runtime module path matches the bundled distro. +* Set `PIP_REQUIRE_VIRTUALENV=false` for `pip install` so the package command works when `require-virtualenv = true` is set in pip config ([#202](https://github.com/flet-dev/serious-python/issues/202)). ## 1.0.0 diff --git a/src/serious_python_platform_interface/CHANGELOG.md b/src/serious_python_platform_interface/CHANGELOG.md index ad57d39b..619d8de8 100644 --- a/src/serious_python_platform_interface/CHANGELOG.md +++ b/src/serious_python_platform_interface/CHANGELOG.md @@ -1,6 +1,7 @@ ## 2.0.0 * Version bump aligning with the major release of the `serious_python_*` platform plugins (multi-version Python support, default Python now 3.14). No interface changes. +* Set `PIP_REQUIRE_VIRTUALENV=false` for `pip install` so the package command works when `require-virtualenv = true` is set in pip config ([#202](https://github.com/flet-dev/serious-python/issues/202)). ## 1.0.0 diff --git a/src/serious_python_windows/CHANGELOG.md b/src/serious_python_windows/CHANGELOG.md index 36f5d44a..5195c057 100644 --- a/src/serious_python_windows/CHANGELOG.md +++ b/src/serious_python_windows/CHANGELOG.md @@ -2,6 +2,7 @@ * **Breaking change:** default bundled Python version is now 3.14 (was 3.12). The plugin downloads `python-windows-for-dart-3.14.zip` and bundles `python314.dll` / `python314.lib` unless `SERIOUS_PYTHON_VERSION=3.12` is set in the build environment. * Multi-version Python support. `PYTHON_VERSION` in `windows/CMakeLists.txt` reads from `SERIOUS_PYTHON_VERSION`; the `python-build` download URL and the `python312.lib` / `python312.dll` filenames are derived from it (e.g. `python313.lib`, `python314.dll`). +* Set `PIP_REQUIRE_VIRTUALENV=false` for `pip install` so the package command works when `require-virtualenv = true` is set in pip config ([#202](https://github.com/flet-dev/serious-python/issues/202)). ## 1.0.0 From 70136ce138149e501c44cb01c6a00998c043ab9b Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Sat, 6 Jun 2026 14:52:29 -0700 Subject: [PATCH 8/8] docs: refresh serious_python README for 2.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Targeted documentation pass after the 1.0.0 (Pyodide -> Emscripten) and 2.0.0 (multi-version Python) releases: * Fix `environmnent` typo and iOS Podfile target (`12.0` -> `13.0` to match the podspec). * Platform list under `-p {platform}`: `macOS` -> `Darwin` to match the `allowed:` constant in `package_command.dart`. * Rewrite both `--requirements` examples to the modern `-r DEP_1 -r DEP_2` / `-r -r -r requirements.txt` syntax with a callout that the comma-separated form was removed in 0.9.2 (so specifiers like `pandas>=2.2,<3` work). * New `Selecting a Python version` subsection covering `--python-version`, `SERIOUS_PYTHON_VERSION`, and the fact that a single env-var export covers both the package phase and the later `flutter build` phase. * Expand the Python versions section with the default-3.14 rule and document how a future pre-release CPython line (e.g. 3.15) is expressed via `prerelease: true` without becoming the auto-resolved default. * Document the previously-undocumented `sync: true` parameter on `SeriousPython.run`. * Brief `flet build` pointer at the top of the Packaging section (https://flet.dev/docs/publish/) — direct CLI usage remains the canonical documentation below it. * Fix all three example links at the bottom (drop the stale `src/serious_python/` prefix so they resolve on pub.dev and GitHub). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/serious_python/README.md | 70 ++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/src/serious_python/README.md b/src/serious_python/README.md index 17b2f473..6c921c59 100644 --- a/src/serious_python/README.md +++ b/src/serious_python/README.md @@ -4,7 +4,7 @@ A cross-platform plugin for adding embedded Python runtime to your Flutter apps. Serious Python embeds Python runtime into a mobile or desktop Flutter app to run a Python program on a background, without blocking UI. Processing files, working with SQLite databases, calling REST APIs, image processing, ML, AI and other heavy lifting tasks can be conveniently done in Python and run directly on a mobile device. -Build app backend service in Python and host it inside a Flutter app. Flutter app is not directly calling Python functions or modules, but instead communicating with Python environmnent via some API provided by a Python program, such as: REST API, sockets, SQLite database or files. +Build app backend service in Python and host it inside a Flutter app. Flutter app is not directly calling Python functions or modules, but instead communicating with Python environment via some API provided by a Python program, such as: REST API, sockets, SQLite database or files. Serious Python is part of [Flet](https://flet.dev) project - the fastest way to build multi-platform apps in Python. The motivation for building Serious Python was having a re-usable easy-to-use plugin, maintained and supported, to run real-world Python apps, not just "1+2" or "hello world" examples, on iOS or Android devices and hence the name "Serious Python". @@ -28,10 +28,19 @@ is specified. | 3.13 | 3.13.13 | 0.29.4 | `pyodide-2025.0-wasm32` | | 3.14 | 3.14.5 | 314.0.0a2 | `pyemscripten-2026.0-wasm32`| +The default is the latest stable row (currently **3.14**) when neither +`--python-version` nor `SERIOUS_PYTHON_VERSION` is set. When running through +[`flet build`](https://flet.dev/docs/publish/), the same resolution is +applied to `[project].requires-python` in your `pyproject.toml`, so most +users never need to touch this flag directly. + Source of truth: the `_pythonReleases` map in [`bin/package_command.dart`](bin/package_command.dart) (Dart side) and `flet_cli/utils/python_versions.py` (Python side). Adding a new short version -means appending a row to both — there is no separate pre-release gate. +means appending a row to both. Pre-release CPython lines (e.g. 3.15) can be +listed with `prerelease: true` so they're opt-in via explicit +`--python-version 3.15` (or `requires-python = "==3.15.*"`) without becoming +the auto-resolved default. ## Usage @@ -47,11 +56,11 @@ Import Serious Python package into your app: `import 'package:serious_python/serious_python.dart';` -The plugin is built against iOS 12.0, so you might need to update iOS version in `ios/Podfile`: +The plugin is built against iOS 13.0, so you might need to update iOS version in `ios/Podfile`: ```bash # Uncomment this line to define a global platform for your project -platform :ios, '12.0' +platform :ios, '13.0' ``` Create an instance of `SeriousPython` class and call its `run()` method: @@ -84,8 +93,16 @@ SeriousPython.run("app/app.zip", modulePaths: ["/absolute/path/to/my/site-packages"]); ``` +By default the Python program runs in a background thread so the Flutter UI stays responsive. Pass `sync: true` to run it on the calling thread instead — useful for short utility programs or when calling from a Dart isolate; long-running synchronous programs will freeze the UI on the main isolate: + +```dart +SeriousPython.run("app/app.zip", sync: true); +``` + ### Packaging Python app +> **Tip:** `serious_python` is also driven automatically by [`flet build`](https://flet.dev/docs/publish/), which threads `--python-version` and friends through for you. The instructions below cover direct standalone usage for non-Flet Flutter apps. + To simplify the packaging of your Python app Serious Python provides a CLI which can be run with the following command: ``` @@ -100,7 +117,7 @@ To package Python files for the specific platform: dart run serious_python:main package app/src -p {platform} ``` -where `{platform}` can be one of the following: `Android`, `iOS`, `macOS`, `Windows`, `Linux` or `Emscripten`. +where `{platform}` can be one of the following: `Android`, `iOS`, `Darwin`, `Windows`, `Linux` or `Emscripten`. (`Darwin` covers both macOS apps and is the value used internally by `platform.system()` in the bundled Python; it is **not** spelled `macOS`.) By default, the command creates `app/app.zip` asset, but you can change its path/name with `--asset` argument: @@ -108,7 +125,40 @@ By default, the command creates `app/app.zip` asset, but you can change its path dart run serious_python:main package --asset assets/myapp.zip app/src -p {platform} ``` -Python app dependencies can be installed with `--requirements` option. The value of `--requirements` option is passed "as is" to `pip` command. For example, `--requirements flet,numpy==2.1.1` will install two requirements directly, or `--requirements -r,requirements.txt` installs deps from `requirements.txt` file. +#### Selecting a Python version + +Pick which CPython line to bundle with `--python-version` (or set +`SERIOUS_PYTHON_VERSION` in the build environment): + +``` +dart run serious_python:main package app/src -p Android --python-version 3.13 -r flet +``` + +Supported short versions today are **3.12**, **3.13**, **3.14**; the default +is the latest stable. The same env var (`SERIOUS_PYTHON_VERSION`) is also +read by each platform plugin's build script (`build.gradle`, the +`serious_python_darwin` podspec, the Linux/Windows `CMakeLists.txt`) when +`flutter build` runs later, so a single `export` covers both the packaging +phase and the Flutter build phase. See the [Python versions](#python-versions) +table above for the matching CPython and Pyodide releases. + +#### Installing requirements + +Python app dependencies are installed with the `--requirements` option (alias `-r`). The value is passed verbatim to `pip`, so any flag pip accepts works. Pass each dependency as its own option to support specifiers that contain commas: + +``` +dart run serious_python:main package app/src -p Darwin \ + -r flet -r 'pandas>=2.2,<3' -r numpy==2.1.1 +``` + +To install from a `requirements.txt` file, pass `-r` three times (twice for pip's own `-r` flag, once more for the file path) so the Dart CLI hands the literal `-r requirements.txt` invocation to pip: + +``` +dart run serious_python:main package app/src -p iOS \ + -r -r -r app/src/requirements.txt +``` + +> The comma-separated form (`--requirements flet,numpy==2.1.1`) was removed in **0.9.2** as a breaking change so dependency specifiers containing `,` (like `pandas>=2.2,<3`) can be expressed; use the per-option form shown above instead. To package for `iOS` and `Android` platforms developer should set `SERIOUS_PYTHON_SITE_PACKAGES` environment variable with a path to a temp directory for installed app packages. The contents of that directory is embedded into app bundle during app compilation. @@ -116,7 +166,7 @@ For example: ``` export SERIOUS_PYTHON_SITE_PACKAGES=$(pwd)/build/site-packages -dart run serious_python:main package app/src -p iOS --requirements -r,app/src/requirements.txt +dart run serious_python:main package app/src -p iOS -r -r -r app/src/requirements.txt ``` For macOS, Linux and Windows app packages are installed into `__pypackages__` inside app package asset zip. @@ -198,8 +248,8 @@ dart run serious_python:main package app/src -p Darwin --verbose ## Examples -[Python REPL with Flask backend](src/serious_python/example/flask_example). +[Python REPL with Flask backend](example/flask_example). -[Flet app](src/serious_python/example/flet_example). +[Flet app](example/flet_example). -[Run Python app](src/serious_python/example/run_example). +[Run Python app](example/run_example).