Using pytauri¶
Note
The dependency versions specified in the following tutorial are the versions at the time of writing. There may be newer versions available when you use it.
Create venv¶
Create a virtual environment using uv
:
Warning
--python-preference only-system
is necessary. Using uv
's managed Python may result in not finding dynamic libraries.
activate the virtual environment:
Init pyproject¶
Create the src-tauri/python/tauri_app
folder to store Python code, and add the following file:
ref: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
[project]
name = "tauri-app" # (1)!
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.9"
dependencies = ["pytauri == 0.4.*"] # (2)!
[project.entry-points.pytauri]
ext_mod = "tauri_app.ext_mod"
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
[tool.setuptools.packages]
find = { where = ["python"] } # (3)!
- your python package name.
- This is the version at the time of writing this tutorial. There may be a newer version of pytauri available when you use it.
- the folder where your python code is stored, i.e.,
src-tauri/python
.
Tip
Note the highlighted project.entry-points
. We will explain its specific meaning when building the Wheel. For now, let's continue with the tutorial.
Install your project¶
Use uv
to install your Python package in editable mode:
Add following code:
"""The tauri-app."""
from pytauri import (
BuilderArgs,
builder_factory,
context_factory,
)
def main() -> None:
"""Run the tauri-app."""
app = builder_factory().build(
BuilderArgs(
context=context_factory(),
)
)
app.run()
"""The main entry point for the Tauri app."""
from multiprocessing import freeze_support
from tauri_app import main
# - If you don't use `multiprocessing`, you can remove this line.
# - If you do use `multiprocessing` but without this line,
# you will get endless spawn loop of your application process.
# See: <https://pyinstaller.org/en/v6.11.1/common-issues-and-pitfalls.html#multi-processing>.
freeze_support()
main()
Run pytauri from rust¶
Add following dependencies to Cargo.toml
:
ref: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries
# ...
[[bin]]
# the same as the package name
name = "tauri-app"
path = "src/main.rs"
required-features = ["pytauri/standalone"]
[dependencies]
# ...
pyo3 = { version = "0.23" }
pytauri = { version = "0.4" } # (1)!
- This is the version at the time of writing this tutorial. There may be a newer version of pytauri available when you use it.
Also, enable the pytauri/standalone
feature:
Warning
If you do not enable required-features
in tauri-cli
, cargo will silently skip building your main.rs
executable file.
Change following rust code:
use pyo3::prelude::*;
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
pub fn tauri_generate_context() -> tauri::Context {
tauri::generate_context!()
}
#[pymodule(gil_used = false)]
#[pyo3(name = "ext_mod")]
pub mod ext_mod {
use super::*;
#[pymodule_init]
fn init(module: &Bound<'_, PyModule>) -> PyResult<()> {
pytauri::pymodule_export(
module,
// i.e., `context_factory` function of python binding
|_args, _kwargs| Ok(tauri_generate_context()),
// i.e., `builder_factory` function of python binding
|_args, _kwargs| {
let builder = tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet]);
Ok(builder)
},
)
}
}
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::{convert::Infallible, env::var, error::Error, path::PathBuf};
use pyo3::wrap_pymodule;
use pytauri::standalone::{
dunce::simplified, PythonInterpreterBuilder, PythonInterpreterEnv, PythonScript,
};
use tauri::{Builder, Manager as _};
use tauri_app_lib::{ext_mod, tauri_generate_context};
fn main() -> Result<Infallible, Box<dyn Error>> {
let py_env = if cfg!(dev) {
// `cfg(dev)` is set by `tauri-build` in `build.rs`, which means running with `tauri dev`,
// see: <https://github.com/tauri-apps/tauri/pull/8937>.
let venv_dir = var("VIRTUAL_ENV").map_err(|err| {
format!(
"The app is running in tauri dev mode, \
please activate the python virtual environment first \
or set the `VIRTUAL_ENV` environment variable: {err}",
)
})?;
PythonInterpreterEnv::Venv(PathBuf::from(venv_dir).into())
} else {
// embedded Python, i.e., bundle mode with `tauri build`.
// Actually, we don't use this app, we just use it to get the resource directory
let sample_app = Builder::default()
.build(tauri_generate_context())
.map_err(|err| format!("failed to build sample app: {err}"))?;
let resource_dir = sample_app
.path()
.resource_dir()
.map_err(|err| format!("failed to get resource dir: {err}"))?;
// 👉 Remove the UNC prefix `\\?\`, Python ecosystems don't like it.
let resource_dir = simplified(&resource_dir).to_owned();
// 👉 When bundled as a standalone App, we will put python in the resource directory
PythonInterpreterEnv::Standalone(resource_dir.into())
};
// 👉 Equivalent to `python -m tauri_app`,
// i.e, run the `src-tauri/python/tauri_app/__main__.py`
let py_script = PythonScript::Module("tauri_app".into());
// 👉 `ext_mod` is your extension module, we export it from memory,
// so you don't need to compile it into a binary file (.pyd/.so).
let builder =
PythonInterpreterBuilder::new(py_env, py_script, |py| wrap_pymodule!(ext_mod)(py));
let interpreter = builder.build()?;
let exit_code = interpreter.run();
std::process::exit(exit_code);
}
Launch the app in dev mode¶
The tauri-cli
has the ability to watch code changes and hot reload. Before starting, we need to add the following file to tell tauri-cli
to ignore the python bytecode:
ref: https://tauri.app/develop/#reacting-to-source-code-changes
Also, we need tell vite
to ignore .venv
:
// https://vitejs.dev/config/
export default defineConfig(async () => ({
server: {
watch: {
// 3. tell vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**", "**/.venv/**"],
},
},
}));
Run pnpm tauri dev
, and after recompiling, you will see a window similar to the previous step.
Try modifying the Python code, and you will notice that the Python code is quickly reloaded without needing to recompile the Rust code.
Next Steps¶
Next, we will demonstrate how to package your application.