How to use Python for backend in Tauri
Introduction to integrating Python backend in Tauri applications using PyTauri, leveraging Python's AI ecosystem advantages, and detailed explanation of project setup, IPC communication, and packaging process.
PyTauri allows using Python to implement backend in Tauri.
Some people will definitely question: why use Python when I have Rust?
The biggest use case for Python in Tauri is integrating Python’s AI ecosystem. Although we can also use tch-rs, onnx, etc. in Tauri, the difficulty level is simply incomparable. Using Python for AI inference is just too simple.
Due to ecosystem issues, many AI models are actually difficult to integrate into Tauri through Rust.
(Then why not use pywebview? :> )
PyTauri uses PyO3 to rebind Tauri’s Rust API, enabling interaction between Python and JavaScript. Specifically, at runtime:
- Tauri frontend sends requests to Rust backend through IPC calls
- Rust backend forwards calls to Python functions through invoke_handler
- Python processes business logic and calls Tauri API through extension modules
- Results are returned to frontend through async Future mechanism
uvx copier copy https://github.com/pytauri/create-pytauri-app .
uv venv --python-preference only-system
source .venv/bin/activate
uv pip install -e src-tauri
Simply using uv sync will result in errors like the following:
error: linking with `cc` failed: exit status: 1
|
= note: "cc" "-Wl,-exported_symbols_list" "-Wl,/var/folders/m4/nln9zc4j1ln3ctqj_q_gd3s80000gn/T/rustcIvgbkZ/list" "/var/folders/m4/nln9zc4j1ln3ctqj_q_gd3s80000gn/T/rustcIvgbkZ/symbols.o" "<130 object files omitted>" "/Users/xxx/.cache/target/debug/deps/"CoreGraphics"
...
...
"-framework" "CloudKit" "-framework" "QuartzCore" "-framework" "Foundation" "-framework" "CoreFoundation" "-lobjc" "-framework" "Foundation" "-lpython3.12" "-liconv" "-lSystem" "-lc" "-lm" "-arch" "arm64" "-mmacosx-version-min=11.0.0" "-L" "/Users/xxx/.cache/target/debug/build/objc2-exception-helper-c628b52639d8b792/out" "-L" "/install/lib" "-o" "/Users/xxx/.cache/target/debug/deps/libmatting_lib.dylib" "-Wl,-dead_strip" "-dynamiclib" "-nodefaultlibs"
= note: some arguments are omitted. use `--verbose` to show all linker arguments
= note: ld: warning: ignoring duplicate libraries: '-lSystem'
ld: warning: search path '/install/lib' not found
ld: library 'python3.12' not found
clang: error: linker command failed with exit code 1 (use -v to see invocation)
error: could not compile `matting` (lib) due to 1 previous error
Therefore, --python-preference only-system is necessary. Using uv to manage Python may cause dynamic libraries to not be found.
IPC
Since PyTauri exports Rust API, the frontend can call Python-defined Commands.
from pytauri import AppHandle, Commands
commands = Commands()
@commands.command()
async def command(in_body: bytes) -> bytes: ...
In Tauri, Rust-defined parameter names default to underscore-connected in_body, which is Rust’s convention. When calling from frontend, parameter names will use camelCase inBody to satisfy JavaScript conventions.
The current version of PyTauri is a bit strange. Since Python mostly also uses underscore naming, but in frontend calls, parameter names need to remain consistent, which is in_body.
import { pyInvoke } from "tauri-plugin-pytauri-api";
const output = await pyInvoke<string>("command", { in_body: ...});
Calling frontend from Python
PyTauri can use channels to quickly transmit ordered data. Mainly used for streaming operations such as download progress, subprocess output, and WebSocket messages.
from pytauri.ipc import Channel, JavaScriptChannelId
Msg = RootModel[str]
@commands.command()
async def command(
in_body: JavaScriptChannelId[Msg], webview_window: WebviewWindow
) -> None:
channel: Channel[Msg] = body.channel_on(webview_window.as_ref_webview())
channel.send_model(Msg("Sending data"))
Frontend usage:
const channel = new Channel<string>((msg) => console.log(msg));
await pyInvoke("command", channel);
Of course, PyTauri also exports the event system.
Other places are not much different. Of course, when using PyTauri, we can still use Rust for development.
Packaging
Packaging requires embedding a Python instance. PyTauri uses uv’s official python-build-standalone as the distribution instance.
Download and extract to src-tauri/pyembed:
├── src-tauri/pyembed/python
├── bin/
├── include/
├── lib/
└── share/
Install project dependencies to this distributed instance environment:
export PYTAURI_STANDALONE="1"
# tauri-app is your project package name
uv pip install \
--exact \
--python="./src-tauri/pyembed/python/bin/python3" \
--reinstall-package=tauri-app \
./src-tauri
Specify the Python runtime used by pyo3:
export PYO3_PYTHON=$(realpath ./src-tauri/pyembed/python/bin/python3)
Packaging:
pnpm -- tauri build --config="src-tauri/tauri.bundle.json" -- --profile bundle-release
Additionally, pywebview is actually the Python version of Tauri. If you just want to use Python’s ecosystem and don’t have much code that must use Rust, then pywebview is sufficient.