APP下载

AI | Rust: Just Do It - 1

原创

RustAITauri

我不在乎你做什么项目,但我建议你做一些具体的事情,而不是瞄准太大胆的目标 —— Elad Gil(Investor of Silicon Valley)

Requirements Background

markitdown - github stars: 33k .

今天,看到有人将微软开源的markitdown Python库包装了一个桌面应用,使用的是Electron 技术栈,但是没开源。我就想自己试一试。

由于自己一直在学Rust,也在使用Tauri,但我自身是一名前端工程师,一直想打通各种语言的界限,特别是Node.js和Ptyhon。由于官网上只有集成Node.js的教程,就想试一试在Tauri.app 如何集成Python。

当然,为了提高效率,必须使用AI协助,下面是整个过程。


START - AI ASSISTANT

LLM: Claude 3.5 Sonnet

AI APP: Claude Desktop App


1. how to get started?

               
  • 1
  • 2
  • 3
  • 4
  • 5
I am using tauri(2.x) to develop one tool application, now I want to execute python codes inner the app. please providing one available solution and detailed implement steps. if there are anything unclear, please point it out. COPY

**Response**

Integrating Python Execution in Tauri 2.x

Prerequisites

  1. Tauri 2.x project set up
  2. Python installed on development machine
  3. pip package manager
  4. Rust toolchain

Implementation Steps

1. Project Structure

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
your-app/ ├── src/ │ ├── main.rs │ └── python/ │ ├── main.py │ └── requirements.txt ├── src-tauri/ │ ├── Cargo.toml │ └── tauri.conf.json └── package.json COPY

2. Python Script Setup

Create src/python/main.py:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
import sys import json def main(): # Get input from stdin input_data = sys.stdin.readline() # Parse the JSON input try: data = json.loads(input_data) # Example: Process the data result = { "status": "success", "result": f"Processed: {data['command']}" } except Exception as e: result = { "status": "error", "error": str(e) } # Send result back print(json.dumps(result)) if __name__ == "__main__": main() COPY

3. Rust Implementation

Update src-tauri/Cargo.toml:

               
  • 1
  • 2
  • 3
  • 4
  • 5
[dependencies] tauri = { version = "2.0.0-alpha" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1.0", features = ["full"] } COPY

Update src/main.rs:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
use std::process::{Command, Stdio}; use std::io::Write; use tauri::{command, State}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] struct PythonCommand { command: String, } #[derive(Debug, Serialize, Deserialize)] struct PythonResponse { status: String, result: Option<String>, error: Option<String>, } #[command] async fn execute_python(command: PythonCommand) -> Result<PythonResponse, String> { let python_executable = if cfg!(debug_assertions) { "python" // Use system Python in debug } else { "./resources/python/python" // Use bundled Python in release }; let mut child = Command::new(python_executable) .arg("./resources/python/main.py") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn() .map_err(|e| e.to_string())?; // Write command to Python process let stdin = child.stdin.as_mut().unwrap(); stdin.write_all(serde_json::to_string(&command).unwrap().as_bytes()) .map_err(|e| e.to_string())?; stdin.write_all(b"\n").map_err(|e| e.to_string())?; // Read response let output = child.wait_with_output().map_err(|e| e.to_string())?; let response: PythonResponse = serde_json::from_slice(&output.stdout) .map_err(|e| e.to_string())?; Ok(response) } fn main() { tauri::Builder::default() .invoke_handler(tauri::generate_handler![execute_python]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } COPY

4. Frontend Integration

Example usage in your frontend code:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
import { invoke } from '@tauri-apps/api'; async function runPythonCode() { try { const response = await invoke('execute_python', { command: { command: 'your_command_here' } }); console.log('Python response:', response); } catch (error) { console.error('Error executing Python:', error); } } COPY

5. Production Build Setup

  1. Install PyInstaller:

                   
    • 1
    pip install pyinstaller COPY
  2. Create a build script build-python.sh:

                   
    • 1
    • 2
    • 3
    • 4
    #!/bin/bash cd src/python pyinstaller --onefile main.py cp dist/main ./../../src-tauri/resources/python/ COPY
  3. Update tauri.conf.json:

                   
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    { "build": { "beforeBuildCommand": "bash build-python.sh", "resources": [ "resources/python/*" ] } } COPY

Important Notes

  1. Development vs Production:

    • In development, the code uses the system Python
    • In production, it uses the bundled Python executable created by PyInstaller
  2. Security Considerations:

    • Validate all input from the frontend before passing to Python
    • Consider implementing proper error handling and timeout mechanisms
    • Be cautious with file system operations in Python code
  3. Cross-Platform Considerations:

    • PyInstaller needs to create executables for each target platform
    • Path separators should be handled properly for cross-platform compatibility
    • Python executable name differs on Windows (python.exe)
  4. Performance:

    • Each Python execution creates a new process
    • For better performance with frequent calls, consider implementing a Python process pool
  5. Package Management:

    • All Python packages are installed in a virtual environment
    • The virtual environment is recreated during development setup
    • Production builds include all required packages in the PyInstaller bundle
  6. Cross-Platform Considerations:

    • Setup scripts are provided for both Windows (.bat) and Unix (.sh) systems
    • Path handling is normalized using Node.js path module
    • PyInstaller creates platform-specific executables
  7. Development Workflow:

    • Run npm run setup:python to initialize/update the Python environment
    • During development, the app uses the virtual environment
    • Production builds create a self-contained Python executable
  8. Adding New Python Libraries:

    1. Add the library to requirements.txt
    2. Create a wrapper in scripts/ directory
    3. Update main.py to handle new commands
    4. Add the library to hiddenimports in main.spec
    5. Run npm run setup:python to update the environment
  9. Security Notes:

    • The virtual environment isolates Python dependencies
    • Package versions should be pinned in requirements.txt
    • Input validation should be implemented for all Python commands

I'll help you integrate Python execution within your Tauri 2.x application. I'll provide a solution using pyinstaller to bundle Python with your app and communicate between Rust and Python.

A few points to clarify:

  • Are you planning to run the Python code in development mode only, or do you need it in the production build as well? This affects how we should bundle Python with your application.
  • What kind of Python operations do you need to perform? This could impact whether we need additional security measures or performance optimizations.
  • Which platforms (Windows/Mac/Linux) do you need to support? This affects the build process and path handling.

Let me know if you need clarification on any part of the implementation or have specific requirements I should address.

2. To clarify some key points based on AI's responses, as follows:

               
  • 1
  • 2
  • 3
  • 4
go on. I will clarify something: * I want to run the Python code in development and production mode. * I want to wrap some helpful Python libraries then execute them, such as the open source library of markitdown by microsoft. Of course, I want to install any Python libraries inner the application. * I want to support all platforms(Windows/Mac/Linux) COPY

**Response**

Thank you for the clarification. Let me provide a more specific solution with Python package management and multi-platform support.

Integrating Python Execution in Tauri 2.x

Prerequisites

  1. Tauri 2.x project set up
  2. Python installed on development machine (3.8+ recommended)
  3. pip package manager
  4. Rust toolchain
  5. virtualenv (pip install virtualenv)

Implementation Steps

1. Enhanced Project Structure

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
your-app/ ├── src/ │ ├── main.rs │ └── python/ │ ├── main.py │ ├── requirements.txt │ ├── venv/ # Virtual environment │ └── scripts/ │ ├── __init__.py │ └── markdown_helper.py # Example wrapper for markitdown ├── src-tauri/ │ ├── Cargo.toml │ ├── tauri.conf.json │ └── scripts/ │ ├── setup_python.sh # Unix setup script │ └── setup_python.bat # Windows setup script └── package.json COPY

2. Python Virtual Environment Setup

Create src-tauri/scripts/setup_python.sh:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
#!/bin/bash # Script directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PYTHON_DIR="$PROJECT_ROOT/src/python" # Create virtual environment if it doesn't exist if [ ! -d "$PYTHON_DIR/venv" ]; then python -m virtualenv "$PYTHON_DIR/venv" fi # Activate virtual environment and install packages source "$PYTHON_DIR/venv/bin/activate" pip install -r "$PYTHON_DIR/requirements.txt" # Create dist directory if it doesn't exist mkdir -p "$PROJECT_ROOT/src-tauri/resources/python" COPY

Create src-tauri/scripts/setup_python.bat:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
@echo off setlocal set SCRIPT_DIR=%~dp0 set PROJECT_ROOT=%SCRIPT_DIR%\..\.. set PYTHON_DIR=%PROJECT_ROOT%\src\python :: Create virtual environment if it doesn't exist if not exist "%PYTHON_DIR%\venv" ( python -m virtualenv "%PYTHON_DIR%\venv" ) :: Activate virtual environment and install packages call "%PYTHON_DIR%\venv\Scripts\activate.bat" pip install -r "%PYTHON_DIR%\requirements.txt" :: Create dist directory if it doesn't exist mkdir "%PROJECT_ROOT%\src-tauri\resources\python" 2>nul COPY

3. Python Package Management

Update src/python/requirements.txt:

               
  • 1
  • 2
  • 3
markitdown pyinstaller # Add other required packages here COPY

Create src/python/scripts/markdown_helper.py:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
from markitdown import markitdown import json class MarkdownHelper: @staticmethod def process_markdown(text): try: result = markitdown.to_html(text) return { "status": "success", "result": result } except Exception as e: return { "status": "error", "error": str(e) } COPY

Update src/python/main.py:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
import sys import json from scripts.markdown_helper import MarkdownHelper def main(): input_data = sys.stdin.readline() try: data = json.loads(input_data) command = data.get('command') if command == 'process_markdown': result = MarkdownHelper.process_markdown(data.get('text', '')) else: result = { "status": "error", "error": f"Unknown command: {command}" } except Exception as e: result = { "status": "error", "error": str(e) } print(json.dumps(result)) if __name__ == "__main__": main() COPY

4. Cross-Platform Build Setup

Update package.json:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
{ "scripts": { "setup:python": "node scripts/setup-python.js", "build": "npm run setup:python && tauri build" } } COPY

Create scripts/setup-python.js:

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
const { execSync } = require('child_process'); const path = require('path'); const os = require('os'); const isWindows = os.platform() === 'win32'; const setupScript = isWindows ? 'setup_python.bat' : 'setup_python.sh'; const scriptPath = path.join(__dirname, '../src-tauri/scripts', setupScript); try { execSync(isWindows ? scriptPath : `bash ${scriptPath}`, { stdio: 'inherit' }); } catch (error) { console.error('Failed to setup Python environment:', error); process.exit(1); } COPY

5. Updated Frontend Integration

               
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
import { invoke } from '@tauri-apps/api'; interface MarkdownResponse { status: string; result?: string; error?: string; } async function processMarkdown(text: string): Promise<MarkdownResponse> { try { const response = await invoke('execute_python', { command: { command: 'process_markdown', text: text } }); return response as MarkdownResponse; } catch (error) { console.error('Error processing markdown:', error); throw error; } } // Usage example const markdown = "# Hello World\nThis is a test"; const result = await processMarkdown(markdown); if (result.status === 'success') { console.log('HTML:', result.result); } else { console.error('Error:', result.error); } COPY

6. Production Build Process

  1. Update tauri.conf.json:

                   
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    { "build": { "beforeBuildCommand": "npm run setup:python", "resources": [ "resources/python/*" ], "windows": { "webviewInstallMode": { "silent": true } } } } COPY
  2. Create a PyInstaller spec file src/python/main.spec: ```python block_cipher = None

a = Analysis( ['main.py'], pathex=[], binaries=[], datas=[('scripts', 'scripts')], # Include scripts directory hiddenimports=['markitdown'], # Add required packages hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, )

pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)

exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name='main', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=True, disable_windowed_traceback=False, target_arch=None, codesign_identity=None, entitlements_file=None, )

```

Important Notes

  1. Package Management:

    • All Python packages are installed in a virtual environment
    • The virtual environment is recreated during development setup
    • Production builds include all required packages in the PyInstaller bundle
  2. Cross-Platform Considerations:

    • Setup scripts are provided for both Windows (.bat) and Unix (.sh) systems
    • Path handling is normalized using Node.js path module
    • PyInstaller creates platform-specific executables
  3. Development Workflow:

    • Run npm run setup:python to initialize/update the Python environment
    • During development, the app uses the virtual environment
    • Production builds create a self-contained Python executable
  4. Adding New Python Libraries:

    1. Add the library to requirements.txt
    2. Create a wrapper in scripts/ directory
    3. Update main.py to handle new commands
    4. Add the library to hiddenimports in main.spec
    5. Run npm run setup:python to update the environment
  5. Security Notes:

    • The virtual environment isolates Python dependencies
    • Package versions should be pinned in requirements.txt
    • Input validation should be implemented for all Python commands

**** END - AI ASSISTANT ****

评论区

写评论

登录

所以,就随便说点什么吧...

这里什么都没有,快来评论吧...