tauri_plugin_pytauri/
lib.rs

1//! This crate is currently only used internally by pytauri to
2//! implement IPC communication between the frontend and Python.
3
4mod commands;
5mod gil_runtime;
6
7use std::error::Error;
8use std::fmt::Display;
9use std::ops::Deref;
10
11use pyo3::exceptions::PyRuntimeError;
12use pyo3::prelude::*;
13use pytauri_core::tauri_runtime::Runtime as PyTauriRuntime;
14use tauri::plugin::{Builder, TauriPlugin};
15use tauri::{Manager, Runtime};
16
17use crate::commands::invoke_handler;
18
19const PLUGIN_NAME: &str = "pytauri";
20
21type PyInvokeHandlerType = PyObject;
22
23struct PyInvokeHandler(PyInvokeHandlerType);
24
25impl Deref for PyInvokeHandler {
26    type Target = PyInvokeHandlerType;
27
28    fn deref(&self) -> &Self::Target {
29        &self.0
30    }
31}
32
33impl PyInvokeHandler {
34    fn new(handler: PyInvokeHandlerType) -> Self {
35        Self(handler)
36    }
37}
38
39/// Initializes the plugin.
40///
41/// The `py_invoke_handler` should have the following signature:
42///
43/// > def py_invoke_handler(invoke: [Invoke][pytauri_core::ext_mod::ipc::Invoke], /) -> None:
44/// >     ...
45///
46/// It will be stored in the tauri app state and used to handle ipc requests from the frontend.
47/// You can get its reference through [PyInvokeHandlerExt].
48///
49/// # NOTE:
50///
51/// - `py_invoke_handler` will be called in a tokio runtime, so it must not block for a long time.
52///     - `tokio runtime` means it is running on an external thread.
53/// - `py_invoke_handler` must not raise exceptions, otherwise it will result in logical undefined behavior.
54pub fn init(py_invoke_handler: PyInvokeHandlerType) -> TauriPlugin<PyTauriRuntime> {
55    Builder::<PyTauriRuntime>::new(PLUGIN_NAME)
56        .invoke_handler(invoke_handler)
57        .setup(|app_handle, _plugin_api| {
58            // if false, there has already state set for the app instance.
59            if !app_handle.manage(PyInvokeHandler::new(py_invoke_handler)) {
60                unreachable!(
61                    "`PyInvokeHandler` is private, so it is impossible for other crates to manage it"
62                )
63            }
64            Ok(())
65        })
66        .build()
67}
68
69mod sealed {
70    use super::*;
71
72    pub trait SealedTrait<R> {}
73
74    impl<R: Runtime, T: Manager<R>> SealedTrait<R> for T {}
75}
76
77/// This error indicates that the extension is not initialized
78#[derive(Debug)]
79pub struct PyInvokeHandlerStateError;
80
81impl Display for PyInvokeHandlerStateError {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        write!(
84            f,
85            "Failed to get `PyInvokeHandler` from state, maybe `{}` is not initialized",
86            env!("CARGO_PKG_NAME")
87        )
88    }
89}
90
91impl Error for PyInvokeHandlerStateError {}
92
93impl From<PyInvokeHandlerStateError> for PyErr {
94    fn from(value: PyInvokeHandlerStateError) -> Self {
95        PyRuntimeError::new_err(value.to_string())
96    }
97}
98
99pub type PyInvokeHandlerStateResult<T> = Result<T, PyInvokeHandlerStateError>;
100
101/// Gets the `py_invoke_handler` passed in when initializing the plugin.
102pub trait PyInvokeHandlerExt<R: Runtime>: Manager<R> + sealed::SealedTrait<R> {
103    fn try_py_invoke_handler(
104        &self,
105    ) -> PyInvokeHandlerStateResult<impl Deref<Target = PyInvokeHandlerType>> {
106        self.try_state::<PyInvokeHandler>()
107            .map(|state| state.inner().deref())
108            .ok_or(PyInvokeHandlerStateError)
109    }
110
111    /// # Panic
112    ///
113    /// If [PyInvokeHandlerExt::try_py_invoke_handler] returns an error.
114    fn py_invoke_handler(&self) -> impl Deref<Target = PyInvokeHandlerType> {
115        self.try_py_invoke_handler().unwrap()
116    }
117}
118
119impl<R: Runtime, T: Manager<R>> PyInvokeHandlerExt<R> for T {}