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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
[package]
name = "tauri-plugin-python"
version = "0.3.6"
version = "0.3.7"
authors = [ "Marco Mengelkoch" ]
description = "A tauri 2 plugin to use python code in the backend."
keywords = ["rust", "python", "tauri", "gui"]
edition = "2021"
rust-version = "1.77.2"
exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"]
links = "tauri-plugin-python"
license = "MIT"
Expand All @@ -16,23 +15,23 @@ repository = "https://github.com/marcomq/tauri-plugin-python"
tauri = { version = "2" }
serde = { version = "1", features = ["derive"] }
thiserror = "2"
async-trait = "0.1"

lazy_static = "1.5.0"
pyo3 = { version = "0.23.3", features=["auto-initialize", "generate-import-lib"], optional = true }
rustpython-pylib = { version = "0.4.0" }
rustpython-stdlib = { version = "0.4.0", features = ["threading"] }
rustpython-vm = { version = "0.4.0", features = [
"importlib",
"serde",
"threading",
] }
async_py = { version = "0.2.1", default-features = false }
tokio = { version = "1", features = ["full"] }
serde_json = "1.0.136"
dunce = "1.0.5"

[build-dependencies]
tauri-plugin = { version = "2", features = ["build"] }

[dev-dependencies]
tauri = { version = "2", features = ["test"] }

[features]
venv = []
default = ["venv"] # auto load src-python/.venv
# default = ["venv", "pyo3"] # enable to use pyo3 instead of rustpython
pyo3 = ["dep:pyo3"]
default = ["venv", "pyo3"] # auto load src-python/.venv
# default = ["venv", "pyo3"] # enable to use pyo3 instead of rustpython.
rustpython = ["async_py/rustpython"]
pyo3 = ["async_py/pyo3"]
3 changes: 0 additions & 3 deletions examples/plain-javascript/src-tauri/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@

[env]
PYO3_CONFIG_FILE = { value = "target/pyembed/pyo3-build-config-file.txt", relative = true }
8 changes: 4 additions & 4 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,26 @@ pub(crate) async fn run_python<R: Runtime>(
app: AppHandle<R>,
payload: StringRequest,
) -> Result<StringResponse> {
app.run_python(payload)
app.run_python(payload).await
}
#[command]
pub(crate) async fn register_function<R: Runtime>(
app: AppHandle<R>,
payload: RegisterRequest,
) -> Result<StringResponse> {
app.register_function(payload)
app.register_function(payload).await
}
#[command]
pub(crate) async fn call_function<R: Runtime>(
app: AppHandle<R>,
payload: RunRequest,
) -> Result<StringResponse> {
app.call_function(payload)
app.call_function(payload).await
}
#[command]
pub(crate) async fn read_variable<R: Runtime>(
app: AppHandle<R>,
payload: StringRequest,
) -> Result<StringResponse> {
app.read_variable(payload)
app.read_variable(payload).await
}
125 changes: 3 additions & 122 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
// © Copyright 2024, by Marco Mengelkoch
// Licensed under MIT License, see License file for more details
// git clone https://github.com/marcomq/tauri-plugin-python
use async_py::PyRunnerError;

#[cfg(feature = "pyo3")]
use pyo3::{prelude::*, PyErr};
use serde::{ser::Serializer, Serialize};

pub type Result<T> = std::result::Result<T, Error>;
Expand All @@ -18,6 +17,8 @@ pub enum Error {
#[cfg(mobile)]
#[error(transparent)]
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
#[error(transparent)]
PyRunner(#[from] PyRunnerError),
}

impl Serialize for Error {
Expand All @@ -35,126 +36,6 @@ impl From<&str> for Error {
}
}

#[cfg(not(feature = "pyo3"))]
impl From<rustpython_vm::PyRef<rustpython_vm::builtins::PyBaseException>> for Error {
fn from(error: rustpython_vm::PyRef<rustpython_vm::builtins::PyBaseException>) -> Self {
let msg = format!("{:?}", &error);
println!("error: {}", &msg);
if let Some(tb) = error.traceback() {
println!("Traceback (most recent call last):");
for trace in tb.iter() {
let file = trace.frame.code.source_path.as_str();
let original_line = trace.lineno.to_usize();
let line = if file == "main.py" {
original_line - 2 // sys.path import has 2 additional lines
} else {
original_line
};
println!(
" File \"{file}\", line {line}, in {}",
trace.frame.code.obj_name
);
}
}
Error::String(msg)
}
}

#[cfg(feature = "pyo3")]
impl From<PyErr> for Error {
fn from(error: PyErr) -> Self {
let error_msg = match pyo3::Python::with_gil(|py| -> Result<Vec<String>> {
let traceback_module = py.import("traceback")?;
let traceback_object = error
.traceback(py)
.ok_or(pyo3::exceptions::PyWarning::new_err("No traceback found."))?;
let extract_traceback = traceback_module.getattr("extract_tb")?;

// Get the formatted traceback lines
let result = extract_traceback.call1((traceback_object,)).and_then(|r| {
match r.extract::<Vec<PyObject>>() {
Ok(v) => {
let mut formatted_lines = Vec::new();
for arg in v.iter() {
let frame = arg.bind(py);

// Extract filename
let filename = match frame.getattr("filename") {
Ok(f) => match f.extract::<String>() {
Ok(s) if s == "<string>".to_string() => {
// Special handling for <string>
frame.setattr("filename", "main.py")?;
let lineno = frame.getattr("lineno")?.extract::<usize>()?;
frame.setattr("lineno", lineno - 2)?;
"main.py".to_string()
}
Ok(s) => s,
Err(_) => "<unknown>".to_string(),
},
Err(_) => "<unknown>".to_string(),
};

// Extract line number
let lineno = match frame.getattr("lineno") {
Ok(l) => match l.extract::<usize>() {
Ok(n) => n,
Err(_) => 0,
},
Err(_) => 0,
};

// Extract function name
let name = match frame.getattr("name") {
Ok(n) => match n.extract::<String>() {
Ok(s) => s,
Err(_) => "<unknown>".to_string(),
},
Err(_) => "<unknown>".to_string(),
};

// Extract line content (if available)
let line = match frame.getattr("line") {
Ok(l) => match l.extract::<Option<String>>() {
Ok(Some(s)) => format!("\t{}", s),
_ => "".to_string(),
},
Err(_) => "".to_string(),
};

// Format the line like requested
let formatted_line = format!(
"File \"{}\", line {}, in {}\n{}",
filename, lineno, name, line
);

formatted_lines.push(formatted_line);
}

Ok(formatted_lines)
}
Err(_) => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Failed to extract traceback",
)),
}
})?;

// Add traceback header
let mut full_traceback = vec!["Traceback (most recent call last):".to_string()];
full_traceback.extend(result);

// Add error type and message
full_traceback.push(error.to_string());

Ok(full_traceback)
}) {
Ok(formatted) => formatted.join("\n"),
Err(_) => error.to_string(), // Fall back to simple error message
};

Error::String(error_msg)
}
}

impl From<tauri::Error> for Error {
fn from(error: tauri::Error) -> Self {
Error::String(error.to_string())
Expand Down
Loading