2025/07/07

TauriのsidecarでPythonを呼び出す

PythonTauri

pythonプログラムを用意する

簡単なpythonプログラムを用意します。

print("Hello World.")

実行可能なようにpyinstallerでビルドします。

pyinstaller -F ./src/python-script/main.py --distpath ./src-tauri/bin --clean -n main-x86_64-pc-windows-msvc

最後の「main-x86_64-pc-windows-msvc」はwindows用の設定です。

この部分は各OSに合わせた設定にしてください。

以下のコマンドで現在の環境に合ったサフィックスを確認できます。

rustc -vV
> host: x86_64-pc-windows-msvc <- この部分

次にTauriをビルドした時に作成したPythonプログラムが含まれるようにsrc-tauri/tauri.conf.jsonのbundle部分にexternalBinを追加します。

{
 ...
  "bundle": {
    "active": true,
    "targets": "all",
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.icns",
      "icons/icon.ico"
    ],
    "externalBin": [
      "bin/main"
    ]
  }
}

jsからsidecarを実行できるようにする

フロントのjavascriptからsidecarのイベントを受け付けるように既存のスクリプトを編集します。

import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";

let greetInputEl:HTMLInputElement | null;
let sidecarOutputEl:Element | null;

// サイドカーのイベントをListen
async function setupSidecarListeners() {
    // 標準出力のイベントをリッスン
    await listen('sidecar-output', (event) => {
        console.log('Received sidecar output:', event.payload);
        if (sidecarOutputEl) {
            sidecarOutputEl.textContent = event.payload as string;
        }
    });

    // エラー出力のイベントをリッスン
    await listen('sidecar-error', (event) => {
        console.error('Received sidecar error:', event.payload);
        if (sidecarOutputEl) {
            sidecarOutputEl.textContent = `Error: ${event.payload}`;
        }
    });

    // プロセス終了のイベントをリッスン
    await listen('sidecar-terminated', (event) => {
        console.log('Sidecar terminated:', event.payload);
    });
}

  

async function greet() {
    const name = greetInputEl.value;
    console.log('Running sidecar with name:', name);

    try {
        // サイドカーを実行
        await invoke("run_sidecar", { input: name });
    } catch (error) {
        console.error('Error running sidecar:', error);
    }
}

  

window.addEventListener("DOMContentLoaded", async () => {
    console.log('DOM Content Loaded');

    const greetFrom = document.querySelector("#greet-form");
    if( !greetFrom ) return;

    greetInputEl = document.querySelector("#greet-input");
    sidecarOutputEl = document.querySelector("#sidecar-output");

    // サイドカーのイベントリスナーをセットアップ
    await setupSidecarListeners();
  
    greetFrom.addEventListener("submit", (e) => {
        e.preventDefault();
        console.log('Form submitted');
        greet();
    });
});

次にrust側の実装を行います。

use tauri_plugin_shell::ShellExt;
use tauri_plugin_shell::process::CommandEvent;
use tauri::Emitter;

// サイドカーを実行する関数
#[tauri::command]
async fn run_sidecar(app: tauri::AppHandle, window: tauri::Window, input: Option<String>) -> Result<(), String> {
    println!("Starting sidecar execution...");
    let sidecar_command = match app.shell().sidecar("main") {
        Ok(command) => command,
        Err(e) => return Err(e.to_string()),
    };

    // 引数を設定
    let mut args = vec!["--app=tauri-app".to_string()];

    // 入力引数を準備
    if let Some(input_data) = input {
        println!("Adding input argument: {}", input_data);
        args.push(format!("--input={}", input_data));
    }

    println!("Executing sidecar with args: {:?}", args);

    // サイドカーを実行し、出力を取得
    let (mut rx, _child) = sidecar_command
        .args(args)
        .spawn()
        .map_err(|e| e.to_string())?;
  
    println!("Sidecar process started");

    // 非同期タスクとして実行
    tauri::async_runtime::spawn(async move {
        while let Some(event) = rx.recv().await {
            match event {
                CommandEvent::Stdout(line) => {
                    let line_str = String::from_utf8_lossy(&line);
                    window.emit("sidecar-output", Some(line_str.to_string()))
                        .expect("failed to emit event");
                }

                CommandEvent::Terminated(status) => {
                    println!("Process terminated with status: {:?}", status);
                    window.emit("sidecar-terminated", Some(format!("{:?}", status)))
                        .expect("failed to emit event");
                    break;
                }
                _ => {}
            }
        }
    });
    Ok(())
}

  

// 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)
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_shell::init())
        .plugin(tauri_plugin_opener::init())
        .invoke_handler(tauri::generate_handler![greet, run_sidecar])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

このまま実行してもパーミッションの関係で実行ができません。

src-tauri/capabilities/default.jsonのpermissionsに追加します。

{
	...
	"permissions": [
		"core:event:allow-listen" < 追加
	]
}

実行してみる

以下のコマンドで実行してみます。

bunでインストールしたのでbunコマンドで実行しています。

bun run tauri dev

するとアプリが立ち上がるので、入力フォームに「main」と入力してボタンを押してみます。

F12でコンソールを表示させると「Received sidecar output: Hello, World!」と表示されていて、pythonのプログラムからのレスポンスが返ってくることがわかります。

まとめ

今回はTauriでPythonのプログラムを実行する方法を学びました。

Tauriの日本語ドキュメントが少なく大変でしたが以下の参考記事のおかげで実装することができました。ありがとうございます!

参考にした記事

https://dev.classmethod.jp/articles/tauri-node-sidecar/

https://zenn.dev/wtshm/scraps/8d1f26e22cdf3a

最後に

株式会社Robbitsでは一緒に働く仲間を募集しています!
ご興味のある方は是非一度ホームページをご覧ください!

ホームページを見てみる