Skip to content
Open
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
58 changes: 43 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
flutter test integration_test --device-id macos
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 --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}

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
Expand All @@ -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
flutter test integration_test --device-id ${{ steps.simulator.outputs.udid }}
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 }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}

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
Expand Down Expand Up @@ -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 && flutter test integration_test --device-id emulator-${{ env.EMULATOR_PORT }}
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 }} --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}

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
Expand All @@ -134,22 +158,26 @@ 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
flutter test integration_test -d windows
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 --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}

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
title: ARM64
- arch: amd64
runner: ubuntu-24.04
title: AMD64
env:
SERIOUS_PYTHON_VERSION: ${{ matrix.python_version }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand Down Expand Up @@ -205,8 +233,8 @@ 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
xvfb-run flutter test integration_test -d linux
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 --dart-define=EXPECTED_PYTHON_VERSION=${{ matrix.python_version }}

publish:
name: Publish to pub.dev
Expand Down
10 changes: 10 additions & 0 deletions src/serious_python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 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.1

### Improvements
Expand Down
85 changes: 75 additions & 10 deletions src/serious_python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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".

Expand All @@ -16,7 +16,31 @@ 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`|

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. 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

Expand All @@ -32,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:
Expand Down Expand Up @@ -69,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:

```
Expand All @@ -85,23 +117,56 @@ 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:

```
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.

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.
Expand Down Expand Up @@ -183,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).
Loading
Loading